def create_stack(self, context: RequestContext, request: CreateStackInput) -> CreateStackOutput: state = CloudFormationRegion.get() template_deployer.prepare_template_body(request) # TODO: avoid mutating request directly template = template_preparer.parse_template(request["TemplateBody"]) stack_name = template["StackName"] = request.get("StackName") stack = Stack(request, template) # find existing stack with same name, and remove it if this stack is in DELETED state existing = ([s for s in state.stacks.values() if s.stack_name == stack_name] or [None])[0] if existing: if "DELETE" not in existing.status: raise ValidationError( f'Stack named "{stack_name}" already exists with status "{existing.status}"' ) state.stacks.pop(existing.stack_id) state.stacks[stack.stack_id] = stack LOG.debug( 'Creating stack "%s" with %s resources ...', stack.stack_name, len(stack.template_resources), ) deployer = template_deployer.TemplateDeployer(stack) try: # TODO: create separate step to first resolve parameters deployer.deploy_stack() except Exception as e: stack.set_stack_status("CREATE_FAILED") msg = 'Unable to create stack "%s": %s' % (stack.stack_name, e) LOG.exception("%s") raise ValidationError(msg) from e return CreateStackOutput(StackId=stack.stack_id)
def create_change_set(req_params): stack_name = req_params.get('StackName') template_deployer.prepare_template_body(req_params) template = template_preparer.parse_template(req_params.pop('TemplateBody')) template['StackName'] = stack_name template['ChangeSetName'] = req_params.get('ChangeSetName') stack = existing = find_stack(stack_name) if not existing: # automatically create (empty) stack if none exists yet state = CloudFormationRegion.get() empty_stack_template = dict(template) empty_stack_template['Resources'] = {} req_params_copy = clone_stack_params(req_params) stack = Stack(req_params_copy, empty_stack_template) state.stacks[stack.stack_id] = stack stack.set_stack_status('CREATE_COMPLETE') change_set = StackChangeSet(req_params, template) deployer = template_deployer.TemplateDeployer(stack) deployer.construct_changes(stack, change_set, change_set_id=change_set.change_set_id, append_to_changeset=True) stack.change_sets.append(change_set) change_set.metadata['Status'] = 'CREATE_COMPLETE' change_set.metadata['ExecutionStatus'] = 'AVAILABLE' change_set.metadata['StatusReason'] = 'Changeset created' return {'StackId': change_set.stack_id, 'Id': change_set.change_set_id}
def execute_change_set( self, context: RequestContext, change_set_name: ChangeSetNameOrId, stack_name: StackNameOrId = None, client_request_token: ClientRequestToken = None, disable_rollback: DisableRollback = None, ) -> ExecuteChangeSetOutput: change_set = find_change_set(change_set_name, stack_name=stack_name) if not change_set: return not_found_error( f'Unable to find change set "{change_set_name}" for stack "{stack_name}"' ) if change_set.metadata.get("ExecutionStatus") != ExecutionStatus.AVAILABLE: LOG.debug("Change set %s not in execution status 'AVAILABLE'", change_set_name) raise InvalidChangeSetStatusException( f"ChangeSet [{change_set.metadata['ChangeSetId']}] cannot be executed in its current status of [{change_set.metadata.get('Status')}]" ) stack_name = change_set.stack.stack_name LOG.debug( 'Executing change set "%s" for stack "%s" with %s resources ...', change_set_name, stack_name, len(change_set.template_resources), ) deployer = template_deployer.TemplateDeployer(change_set.stack) try: deployer.apply_change_set(change_set) change_set.stack.metadata["ChangeSetId"] = change_set.change_set_id except NoStackUpdates: # TODO: parity-check if this exception should be re-raised or swallowed raise ValidationError("No updates to be performed for stack change set") return ExecuteChangeSetOutput()
def create_change_set(req_params): stack_name = req_params.get("StackName") template_deployer.prepare_template_body(req_params) template = template_preparer.parse_template(req_params.pop("TemplateBody")) template["StackName"] = stack_name template["ChangeSetName"] = req_params.get("ChangeSetName") stack = existing = find_stack(stack_name) if not existing: # automatically create (empty) stack if none exists yet state = CloudFormationRegion.get() empty_stack_template = dict(template) empty_stack_template["Resources"] = {} req_params_copy = clone_stack_params(req_params) stack = Stack(req_params_copy, empty_stack_template) state.stacks[stack.stack_id] = stack stack.set_stack_status("CREATE_COMPLETE") change_set = StackChangeSet(req_params, template) deployer = template_deployer.TemplateDeployer(stack) deployer.construct_changes( stack, change_set, change_set_id=change_set.change_set_id, append_to_changeset=True, ) stack.change_sets.append(change_set) change_set.metadata["Status"] = "CREATE_COMPLETE" change_set.metadata["ExecutionStatus"] = "AVAILABLE" change_set.metadata["StatusReason"] = "Changeset created" return {"StackId": change_set.stack_id, "Id": change_set.change_set_id}
def create_stack(req_params): state = CloudFormationRegion.get() template_deployer.prepare_template_body(req_params) template = template_preparer.parse_template(req_params['TemplateBody']) stack_name = template['StackName'] = req_params.get('StackName') stack = Stack(req_params, template) # find existing stack with same name, and remove it if this stack is in DELETED state existing = ([s for s in state.stacks.values() if s.stack_name == stack_name] or [None])[0] if existing: if 'DELETE' not in existing.status: return error_response('Stack named "%s" already exists with status "%s"' % ( stack_name, existing.status), code=400, code_string='ValidationError') state.stacks.pop(existing.stack_id) state.stacks[stack.stack_id] = stack LOG.debug('Creating stack "%s" with %s resources ...' % (stack.stack_name, len(stack.template_resources))) deployer = template_deployer.TemplateDeployer(stack) try: deployer.deploy_stack() except Exception as e: stack.set_stack_status('CREATE_FAILED') msg = 'Unable to create stack "%s": %s' % (stack.stack_name, e) LOG.debug('%s %s' % (msg, traceback.format_exc())) return error_response(msg, code=400, code_string='ValidationError') result = {'StackId': stack.stack_id} return result
def create_stack(req_params): state = CloudFormationRegion.get() template_deployer.prepare_template_body(req_params) # TODO: avoid mutating req_params directly template = template_preparer.parse_template(req_params["TemplateBody"]) stack_name = template["StackName"] = req_params.get("StackName") stack = Stack(req_params, template) # find existing stack with same name, and remove it if this stack is in DELETED state existing = ([s for s in state.stacks.values() if s.stack_name == stack_name] or [None])[0] if existing: if "DELETE" not in existing.status: return error_response( 'Stack named "%s" already exists with status "%s"' % (stack_name, existing.status), code=400, code_string="ValidationError", ) state.stacks.pop(existing.stack_id) state.stacks[stack.stack_id] = stack LOG.debug( 'Creating stack "%s" with %s resources ...' % (stack.stack_name, len(stack.template_resources)) ) deployer = template_deployer.TemplateDeployer(stack) try: # TODO: create separate step to first resolve parameters deployer.deploy_stack() except Exception as e: stack.set_stack_status("CREATE_FAILED") msg = 'Unable to create stack "%s": %s' % (stack.stack_name, e) LOG.debug("%s %s" % (msg, traceback.format_exc())) return error_response(msg, code=400, code_string="ValidationError") result = {"StackId": stack.stack_id} return result
def delete_stack(req_params): state = RegionState.get() stack_name = req_params.get('StackName') stack = find_stack(stack_name) deployer = template_deployer.TemplateDeployer(stack) deployer.delete_stack() state.stacks.pop(stack.stack_id) return {}
def delete_stack_set(req_params): state = CloudFormationRegion.get() set_name = req_params.get('StackSetName') stack_set = [sset for sset in state.stack_sets.values() if sset.stack_set_name == set_name] if not stack_set: return not_found_error('Stack set named "%s" does not exist' % set_name) for instance in stack_set[0].stack_instances: deployer = template_deployer.TemplateDeployer(instance.stack) deployer.delete_stack() return {}
def execute_change_set(req_params): stack_name = req_params.get('StackName') cs_name = req_params.get('ChangeSetName') change_set = find_change_set(cs_name, stack_name=stack_name) if not change_set: return error_response('Unable to find change set "%s" for stack "%s"' % (cs_name, stack_name)) deployer = template_deployer.TemplateDeployer(change_set.stack) deployer.apply_change_set(change_set) change_set.stack.metadata['ChangeSetId'] = change_set.change_set_id return {}
def delete_stack( self, context: RequestContext, stack_name: StackName, retain_resources: RetainResources = None, role_arn: RoleARN = None, client_request_token: ClientRequestToken = None, ) -> None: stack = find_stack(stack_name) deployer = template_deployer.TemplateDeployer(stack) deployer.delete_stack()
def execute_change_set(req_params): stack_name = req_params.get('StackName') cs_name = req_params.get('ChangeSetName') change_set = find_change_set(cs_name, stack_name=stack_name) if not change_set: return not_found_error('Unable to find change set "%s" for stack "%s"' % (cs_name, stack_name)) LOG.debug('Executing change set "%s" for stack "%s" with %s resources ...' % ( cs_name, stack_name, len(change_set.template_resources))) deployer = template_deployer.TemplateDeployer(change_set.stack) deployer.apply_change_set(change_set) change_set.stack.metadata['ChangeSetId'] = change_set.change_set_id return {}
def delete_stack_set( self, context: RequestContext, stack_set_name: StackSetName, call_as: CallAs = None ) -> DeleteStackSetOutput: state = CloudFormationRegion.get() stack_set = [ sset for sset in state.stack_sets.values() if sset.stack_set_name == stack_set_name ] if not stack_set: return not_found_error(f'Stack set named "{stack_set_name}" does not exist') for instance in stack_set[0].stack_instances: deployer = template_deployer.TemplateDeployer(instance.stack) deployer.delete_stack() return DeleteStackSetOutput()
def create_stack(req_params): state = RegionState.get() cloudformation_listener.prepare_template_body(req_params) template = template_deployer.parse_template(req_params['TemplateBody']) template['StackName'] = req_params.get('StackName') stack = Stack(req_params, template) state.stacks[stack.stack_id] = stack deployer = template_deployer.TemplateDeployer(stack) try: deployer.deploy_stack() except Exception as e: stack.set_stack_status('CREATE_FAILED') msg = 'Unable to create stack "%s": %s' % (stack.stack_name, e) LOG.debug('%s %s' % (msg, traceback.format_exc())) return error_response(msg, code=400, code_string='ValidationError') result = {'StackId': stack.stack_id} return result
def update_stack(req_params): stack_name = req_params.get('StackName') stack = find_stack(stack_name) if not stack: return not_found_error('Unable to update non-existing stack "%s"' % stack_name) template_preparer.prepare_template_body(req_params) template = template_preparer.parse_template(req_params['TemplateBody']) new_stack = Stack(req_params, template) deployer = template_deployer.TemplateDeployer(stack) try: deployer.update_stack(new_stack) except Exception as e: stack.set_stack_status('UPDATE_FAILED') msg = 'Unable to update stack "%s": %s' % (stack_name, e) LOG.debug('%s %s' % (msg, traceback.format_exc())) return error_response(msg, code=400, code_string='ValidationError') result = {'StackId': stack.stack_id} return result
def create_stack(req_params): state = CloudFormationRegion.get() template_deployer.prepare_template_body(req_params) template = template_preparer.parse_template(req_params['TemplateBody']) template['StackName'] = req_params.get('StackName') # print("##################333", req_params) stack = Stack(req_params, template) state.stacks[stack.stack_id] = stack LOG.debug('Creating stack "%s" with %s resources ...' % (stack.stack_name, len(stack.template_resources))) deployer = template_deployer.TemplateDeployer(stack) try: deployer.deploy_stack() except Exception as e: stack.set_stack_status('CREATE_FAILED') msg = 'Unable to create stack "%s": %s' % (stack.stack_name, e) LOG.debug('%s %s' % (msg, traceback.format_exc())) return error_response(msg, code=400, code_string='ValidationError') result = {'StackId': stack.stack_id} return result
def update_stack( self, context: RequestContext, request: UpdateStackInput, ) -> UpdateStackOutput: stack_name = request.get("StackName") stack = find_stack(stack_name) if not stack: return not_found_error(f'Unable to update non-existing stack "{stack_name}"') template_preparer.prepare_template_body(request) template = template_preparer.parse_template(request["TemplateBody"]) new_stack = Stack(request, template) deployer = template_deployer.TemplateDeployer(stack) try: deployer.update_stack(new_stack) except Exception as e: stack.set_stack_status("UPDATE_FAILED") msg = f'Unable to update stack "{stack_name}": {e}' LOG.exception("%s", msg) raise ValidationError(msg) from e return UpdateStackOutput(StackId=stack.stack_id)
def delete_stack(req_params): stack_name = req_params.get("StackName") stack = find_stack(stack_name) deployer = template_deployer.TemplateDeployer(stack) deployer.delete_stack() return {}
def create_change_set(req_params: Dict[str, Any]): change_set_type: ChangeSetTypes = req_params.get("ChangeSetType", "UPDATE") stack_name: Optional[str] = req_params.get("StackName") change_set_name: Optional[str] = req_params.get("ChangeSetName") template_body: Optional[str] = req_params.get("TemplateBody") # s3 or secretsmanager url template_url: Optional[str] = req_params.get( "TemplateUrl") or req_params.get("TemplateURL") if is_none_or_empty(change_set_name): return error_response("ChangeSetName required", 400, "ValidationError") # TODO: check proper message if is_none_or_empty(stack_name): return error_response("StackName required", 400, "ValidationError") # TODO: check proper message stack: Optional[Stack] = find_stack(stack_name) # validate and resolve template if template_body and template_url: return error_response( "Specify exactly one of 'TemplateBody' or 'TemplateUrl'", 400, "ValidationError") # TODO: check proper message if not template_body and not template_url: return error_response( "Specify exactly one of 'TemplateBody' or 'TemplateUrl'", 400, "ValidationError") # TODO: check proper message prepare_template_body( req_params) # TODO: function has too many unclear responsibilities template = template_preparer.parse_template(req_params["TemplateBody"]) del req_params["TemplateBody"] # TODO: stop mutating req_params template["StackName"] = stack_name template[ "ChangeSetName"] = change_set_name # TODO: validate with AWS what this is actually doing? if change_set_type == "UPDATE": # add changeset to existing stack if stack is None: return error_response( f"Stack '{stack_name}' does not exist.", 400, "ValidationError") # stack should exist already elif change_set_type == "CREATE": # create new (empty) stack if stack is not None: return error_response( f"Stack {stack_name} already exists", 400, "ValidationError" ) # stack should not exist yet (TODO: check proper message) state = CloudFormationRegion.get() empty_stack_template = dict(template) empty_stack_template["Resources"] = {} req_params_copy = clone_stack_params(req_params) stack = Stack(req_params_copy, empty_stack_template) state.stacks[stack.stack_id] = stack stack.set_stack_status("REVIEW_IN_PROGRESS") elif change_set_type == "IMPORT": raise NotImplementedError() # TODO: implement importing resources else: msg = f"1 validation error detected: Value '{change_set_type}' at 'changeSetType' failed to satisfy constraint: Member must satisfy enum value set: [IMPORT, UPDATE, CREATE]" return error_response(msg, code=400, code_string="ValidationError") change_set = StackChangeSet(req_params, template) # TODO: refactor the flow here deployer = template_deployer.TemplateDeployer(change_set) deployer.construct_changes( stack, change_set, change_set_id=change_set.change_set_id, append_to_changeset=True, ) # TODO: ignores return value (?) deployer.apply_parameter_changes( change_set, change_set) # TODO: bandaid to populate metadata stack.change_sets.append(change_set) change_set.metadata[ "Status"] = "CREATE_COMPLETE" # technically for some time this should first be CREATE_PENDING change_set.metadata[ "ExecutionStatus"] = "AVAILABLE" # technically for some time this should first be UNAVAILABLE return {"StackId": change_set.stack_id, "Id": change_set.change_set_id}
def create_change_set( self, context: RequestContext, request: CreateChangeSetInput ) -> CreateChangeSetOutput: req_params = request change_set_type = req_params.get("ChangeSetType", "UPDATE") stack_name = req_params.get("StackName") change_set_name = req_params.get("ChangeSetName") template_body = req_params.get("TemplateBody") # s3 or secretsmanager url template_url = req_params.get("TemplateURL") stack = find_stack(stack_name) # validate and resolve template if template_body and template_url: raise ValidationError( "Specify exactly one of 'TemplateBody' or 'TemplateUrl'" ) # TODO: check proper message if not template_body and not template_url: raise ValidationError( "Specify exactly one of 'TemplateBody' or 'TemplateUrl'" ) # TODO: check proper message prepare_template_body(req_params) # TODO: function has too many unclear responsibilities template = template_preparer.parse_template(req_params["TemplateBody"]) del req_params["TemplateBody"] # TODO: stop mutating req_params template["StackName"] = stack_name template[ "ChangeSetName" ] = change_set_name # TODO: validate with AWS what this is actually doing? if change_set_type == "UPDATE": # add changeset to existing stack if stack is None: raise ValidationError( f"Stack '{stack_name}' does not exist." ) # stack should exist already elif change_set_type == "CREATE": # create new (empty) stack if stack is not None: raise ValidationError( f"Stack {stack_name} already exists" ) # stack should not exist yet (TODO: check proper message) state = CloudFormationRegion.get() empty_stack_template = dict(template) empty_stack_template["Resources"] = {} req_params_copy = clone_stack_params(req_params) stack = Stack(req_params_copy, empty_stack_template) state.stacks[stack.stack_id] = stack stack.set_stack_status("REVIEW_IN_PROGRESS") elif change_set_type == "IMPORT": raise NotImplementedError() # TODO: implement importing resources else: msg = ( f"1 validation error detected: Value '{change_set_type}' at 'changeSetType' failed to satisfy " f"constraint: Member must satisfy enum value set: [IMPORT, UPDATE, CREATE] " ) raise ValidationError(msg) change_set = StackChangeSet(req_params, template) # TODO: refactor the flow here deployer = template_deployer.TemplateDeployer(change_set) changes = deployer.construct_changes( stack, change_set, change_set_id=change_set.change_set_id, append_to_changeset=True, filter_unchanged_resources=True, ) deployer.apply_parameter_changes( change_set, change_set ) # TODO: bandaid to populate metadata stack.change_sets.append(change_set) if not changes: change_set.metadata["Status"] = "FAILED" change_set.metadata["ExecutionStatus"] = "UNAVAILABLE" change_set.metadata[ "StatusReason" ] = "The submitted information didn't contain changes. Submit different information to create a change set." else: change_set.metadata[ "Status" ] = "CREATE_COMPLETE" # technically for some time this should first be CREATE_PENDING change_set.metadata[ "ExecutionStatus" ] = "AVAILABLE" # technically for some time this should first be UNAVAILABLE return CreateChangeSetOutput(StackId=change_set.stack_id, Id=change_set.change_set_id)