Esempio n. 1
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)
Esempio n. 2
0
def test_is_status():
    assert StackStatus.is_status(
        {STACK_STATUS: StackStatus.CREATE_COMPLETE.name},
        StackStatus.CREATE_COMPLETE)
    assert not StackStatus.is_status(
        {STACK_STATUS: StackStatus.CREATE_COMPLETE.name},
        StackStatus.CREATE_FAILED)
Esempio n. 3
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)
Esempio n. 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)
Esempio n. 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)
Esempio n. 6
0
    def build_change_set_args(self):
        """
        Build dictionary of arguments for creating a ChangeSet
        :return: Dictionary of arguments based upon Config
        """
        args = {
            'StackName': self.config.stack_name,
            'ChangeSetName': self.change_set_name,
            'ChangeSetType': 'CREATE' if StackStatus.is_creatable(self.stack) else 'UPDATE',
            'Parameters': self.build_parameters(),
            'Tags': self.build_tags()
        }
        if self.config.capabilities:
            args['Capabilities'] = self.config.capabilities

        if Config.is_template_url(self.config.template):
            args['TemplateURL'] = self.config.template
        else:
            with open(self.config.template) as t:
                args['TemplateBody'] = t.read()

        return args
Esempio n. 7
0
def test_is_creatable():
    assert StackStatus.is_creatable(None)
    assert StackStatus.is_creatable(
        {STACK_STATUS: StackStatus.REVIEW_IN_PROGRESS.name})
    assert not StackStatus.is_creatable(
        {STACK_STATUS: StackStatus.CREATE_COMPLETE.name})
Esempio n. 8
0
def test_is_status_invalid_status():
    with pytest.raises(ValueError, match='Unknown StackStatus UNKNOWN'):
        StackStatus.is_status({STACK_STATUS: 'UNKNOWN'},
                              StackStatus.CREATE_COMPLETE)
Esempio n. 9
0
def test_is_status_missing_status():
    with pytest.raises(ValueError, match='No StackStatus available'):
        StackStatus.is_status({}, StackStatus.CREATE_COMPLETE)