예제 #1
0
    def reject_change_set(self):
        """
        Delete the named ChangeSet, and if the stack is in the REVIEW_IN_PROGRESS status and there are
        no remaining ChangeSets, then delete the stack.
        """
        if not self.stack:
            raise ValidationError(f'Stack {self.config.stack_name} not found')

        self.print_stack_status()
        try:
            self.client.describe_change_set(ChangeSetName=self.change_set_name,
                                            StackName=self.config.stack_name)
        except self.client.exceptions.ChangeSetNotFoundException:
            raise ValidationError(
                f'ChangeSet {self.change_set_name} not found')

        info(
            f'\nDeleting ChangeSet {self.change_set_name} for {self.config.stack_name}'
        )

        self.client.delete_change_set(ChangeSetName=self.change_set_name,
                                      StackName=self.config.stack_name)

        if StackStatus.is_status(self.stack, StackStatus.REVIEW_IN_PROGRESS):
            change_sets = self.client.list_change_sets(
                StackName=self.config.stack_name)['Summaries']
            if not change_sets:
                info(
                    f'\nDeleting REVIEW_IN_PROGRESS Stack {self.config.stack_name} that has no remaining ChangeSets'
                )
                # Delete stack, no need to wait for deletion to complete
                self.client.delete_stack(StackName=self.config.stack_name)
예제 #2
0
def build_lambda(source_dir, output_dir, runtime, archive_name):
    """
    Build a Lambda Function zip using a builder from aws-lambda-builders
    :param source_dir: Source Directory
    :param output_dir: Output Directory
    :param runtime: Lambda Runtime
    :param archive_name: Archive name (optional)
    :return: Path to generated zip file
    """
    config = get_config(runtime, source_dir)
    builder = LambdaBuilder(config.language, config.dependency_manager, None)
    manifest_path = os.path.join(source_dir, config.manifest_name)
    archive_name = archive_name if archive_name else os.path.basename(os.path.normpath(source_dir))

    info(f'\nBuilding {runtime} Lambda function from {source_dir}\n')

    with tempfile.TemporaryDirectory() as artifacts_dir:
        with tempfile.TemporaryDirectory() as scratch_dir:
            try:
                builder.build(source_dir, artifacts_dir, scratch_dir, manifest_path, runtime)
                zip_file = make_archive(os.path.join(output_dir, archive_name), 'zip', artifacts_dir)
                info(f'\nBuilt Lambda Archive {zip_file}')
                return zip_file
            except LambdaBuilderError as e:
                raise PackagingError(e)
예제 #3
0
    def deploy(self):
        """
        Create a new Stack or update an existing Stack using a ChangeSet.
        This will wait for ChangeSet to be created and print the details,
        and if Auto-Approve is set, will then execute the ChangeSet and wait for that to complete.
        If there are no changes in the ChangeSet it will be automatically deleted.
        :raises StackError: If creating the ChangeSet or executing it fails
        """
        if not StackStatus.is_creatable(self.stack) and not StackStatus.is_updatable(self.stack):
            stack_status = StackStatus.get_status(self.stack)
            if stack_status == StackStatus.ROLLBACK_COMPLETE:
                warn(f'Deleting Stack {self.config.stack_name} in ROLLBACK_COMPLETE status before attempting to create')
                self.delete()
            else:
                raise ValidationError(f'Stack {self.config.stack_name} is not in a deployable status: '
                                      f'{stack_status.name}')

        info(f'\nCreating ChangeSet {self.change_set_name}\n')
        try:
            self.client.create_change_set(**self.build_change_set_args())
            if self.wait_for_change_set():
                if self.auto_approve:
                    self.execute_change_set()
                else:
                    self.pending_change_set()
        except ClientError as ce:
            raise StackError(ce)
예제 #4
0
    def delete(self, retain_resources=[]):
        """
        Delete a Stack, optionally retraining certain resources.
        Waits for Stack to delete and prints Events if deletion fails
        :param list retain_resources: List of LogicalIds to retain
        :raises StackError: if deletion fails
        """
        if not self.stack:
            raise ValidationError(f'Stack {self.config.stack_name} not found')
        if not StackStatus.is_deletable(self.stack):
            raise ValidationError(f'Stack {self.config.stack_name} is not in a deletable status: '
                                  f'{StackStatus.get_status(self.stack).name}')

        info(f'\nDeleting Stack {self.config.stack_name}')
        last_timestamp = self.get_last_timestamp()

        try:
            self.client.delete_stack(StackName=self.config.stack_name, RetainResources=retain_resources)

            self.client.get_waiter('stack_delete_complete').wait(
                StackName=self.config.stack_name,
                WaiterConfig={'Delay': 10, 'MaxAttempts': 360})

            info(f'\nDeletion of Stack {self.config.stack_name} successfully completed')
            self.stack = None
        except ClientError as ce:
            raise StackError(ce)
        except WaiterError as we:
            error(f'\nDeletion of Stack {self.config.stack_name} failed:\n')
            self.print_events(last_timestamp)
            raise StackError(we)
예제 #5
0
    def execute_change_set(self):
        """
        Execute a ChangeSet, waiting for execution to complete and printing the details of Stack Events
        caused by this ChangeSet
        :raises StackError: If there is an error executing the ChangeSet
        """
        last_timestamp = self.get_last_timestamp()

        try:
            info(f'\nExecuting ChangeSet {self.change_set_name} for {self.config.stack_name}')

            self.client.execute_change_set(ChangeSetName=self.change_set_name, StackName=self.config.stack_name)

            waiter_name = 'stack_create_complete' if StackStatus.is_creatable(self.stack) else 'stack_update_complete'
            self.client.get_waiter(waiter_name).wait(StackName=self.config.stack_name,
                                                     WaiterConfig={'Delay': 10, 'MaxAttempts': 360})

            info(f'\nChangeSet {self.change_set_name} for {self.config.stack_name} successfully completed:\n')
            self.print_events(last_timestamp)

        except ClientError as ce:
            raise StackError(ce)
        except WaiterError as we:
            error(f'\nChangeSet {self.change_set_name} for {self.config.stack_name} failed:\n')
            self.print_events(last_timestamp)
            raise StackError(we)
예제 #6
0
    def update_termination_protection(self, deleting=False):
        protect = False if deleting else self.config.termination_protection
        current = False if not self.stack else self.stack.get(
            'EnableTerminationProtection', False)

        if protect is not current:
            self.client.update_termination_protection(
                StackName=self.config.stack_name,
                EnableTerminationProtection=protect)
            info(
                f'\n{"Enabled" if protect else "Disabled"} Termination Protection'
            )
예제 #7
0
 def print_stack_status(self):
     """
     Print the status of the stack if it exists or does not, with last update (or creation) timestamp.
     """
     if self.stack:
         stack_timestamp = self.stack['LastUpdatedTime'] if 'LastUpdatedTime' in self.stack \
             else self.stack['CreationTime']
         info(
             f'\nStack: {self.config.stack_name}, Status: {StackStatus.get_status(self.stack).name} '
             f'({self.format_timestamp(stack_timestamp)})')
     else:
         info(f'\nStack: {self.config.stack_name}, Status: does not exist')
예제 #8
0
    def status(self, event_days):
        """
        Output the current status of the stack showing pending ChangeSets and recent events.
        :param int event_days: Number of days to show events for
        """
        self.print_stack_status()
        self.check_change_sets()

        if self.stack:
            last_timestamp = arrow.utcnow().shift(days=-event_days)
            info(f'\nEvents since {self.format_timestamp(last_timestamp)}:\n')
            self.print_events(last_timestamp.datetime)
            self.print_outputs()
예제 #9
0
 def load_stack(self):
     """
     Load Description of stack to determine if it exists and the current status
     :return: Matching stack of None
     """
     try:
         stacks = self.client.describe_stacks(StackName=self.config.stack_name)['Stacks']
         stack = stacks[0]
         info(f'\nStack: {self.config.stack_name}, Status: {StackStatus.get_status(stack).name}')
         return stack
     except ClientError:
         info(f'\nStack: {self.config.stack_name}, Status: does not exist')
         return None
예제 #10
0
 def upload(self, filename, bucket, key, acl='bucket-owner-full-control'):
     """
     Uploads a local file to S3.
     :param filename: Name of local file
     :param bucket: S3 Bucket Name
     :param key: S3 Key
     :param acl: Object ACL, defaults to bucket-owner-full-control
     :raises ValidationError: If local file does not exist
     :raises TransferError: If there is an error uploading the file
     """
     try:
         self._client.upload_file(Filename=filename,
                                  Bucket=bucket,
                                  Key=key,
                                  ExtraArgs={'ACL': acl})
         info(f'\nUploaded {filename} to s3://{bucket}/{key}')
     except FileNotFoundError:
         raise ValidationError(f'File {filename} not found')
     except (Boto3Error, ClientError) as e:
         raise TransferError(e)
예제 #11
0
 def print_outputs(self):
     if self.stack and 'Outputs' in self.stack:
         info('\nOutputs:\n')
         data = [[o['OutputKey'], o['OutputValue']]
                 for o in self.stack['Outputs']]
         table(data, ['Key', 'Value'])
예제 #12
0
 def pending_change_set(self, change_set_id):
     """Subclasses can override this to export the change set information in another format"""
     info(f'\nChangeSet {self.change_set_name} is ready to run')