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)
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)
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)
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] return stack except ClientError as ce: if ce.response['Error']['Code'] == 'ValidationError': return None else: raise StackError(ce)
def wait_for_change_set(self): """ Wait for a ChangeSet to be created, printing the details when it is created. :return: True if ChangeSet created, False if there are no changes :raises StackError: If creation of ChangeSet fails for reason other than no changes """ try: self.client.get_waiter('change_set_create_complete').wait( ChangeSetName=self.change_set_name, StackName=self.config.stack_name, WaiterConfig={ 'Delay': 5, 'MaxAttempts': 120 }) except WaiterError as we: resp = we.last_response status = resp['Status'] reason = resp['StatusReason'] # See SAM CLI: https://github.com/awslabs/aws-sam-cli/blob/develop/samcli/lib/deploy/deployer.py#L272 if (status == 'FAILED' and "The submitted information didn't contain changes." in reason or 'No updates are to be performed' in reason): warn('No changes to Stack {}'.format(self.config.stack_name)) self.client.delete_change_set( ChangeSetName=self.change_set_name, StackName=self.config.stack_name) return False raise StackError( f'ChangeSet creation failed - Status: {status}, Reason: {reason}' ) describe_change_set_response = self.client.describe_change_set( ChangeSetName=self.change_set_name, StackName=self.config.stack_name) data = [[ change['ResourceChange']['Action'], change['ResourceChange']['LogicalResourceId'], change['ResourceChange']['ResourceType'], change['ResourceChange'].get('Replacement', '-') ] for change in describe_change_set_response['Changes']] table(data, ['Action', 'LogicalResourceId', 'ResourceType', 'Replacement']) return True