def get_template_summary(req_params): stack_name = req_params.get('StackName') stack = None if stack_name: stack = find_stack(stack_name) if not stack: return stack_not_found_error(stack_name) template_deployer.prepare_template_body(req_params) template = template_preparer.parse_template(req_params['TemplateBody']) req_params['StackName'] = 'tmp-stack' stack = Stack(req_params, template) result = stack.describe_details() id_summaries = {} for resource_id, resource in stack.template_resources.items(): res_type = resource['Type'] id_summaries[res_type] = id_summaries.get(res_type) or [] id_summaries[res_type].append(resource_id) result['ResourceTypes'] = list(id_summaries.keys()) result['ResourceIdentifierSummaries'] = [{ 'ResourceType': key, 'LogicalResourceIds': { 'member': values } } for key, values in id_summaries.items()] 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']) 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_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(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 get_template_summary( self, context: RequestContext, request: GetTemplateSummaryInput, ) -> GetTemplateSummaryOutput: stack_name = request.get("StackName") if stack_name: stack = find_stack(stack_name) if not stack: return stack_not_found_error(stack_name) else: template_deployer.prepare_template_body(request) template = template_preparer.parse_template(request["TemplateBody"]) request["StackName"] = "tmp-stack" stack = Stack(request, template) result: GetTemplateSummaryOutput = stack.describe_details() id_summaries = {} for resource_id, resource in stack.template_resources.items(): res_type = resource["Type"] id_summaries[res_type] = id_summaries.get(res_type) or [] id_summaries[res_type].append(resource_id) result["ResourceTypes"] = list(id_summaries.keys()) result["ResourceIdentifierSummaries"] = [ {"ResourceType": key, "LogicalResourceIds": values} for key, values in id_summaries.items() ] return result
def get_template_summary(req_params): stack_name = req_params.get("StackName") stack = None if stack_name: stack = find_stack(stack_name) if not stack: return stack_not_found_error(stack_name) else: template_deployer.prepare_template_body(req_params) template = template_preparer.parse_template(req_params["TemplateBody"]) req_params["StackName"] = "tmp-stack" stack = Stack(req_params, template) result = stack.describe_details() id_summaries = {} for resource_id, resource in stack.template_resources.items(): res_type = resource["Type"] id_summaries[res_type] = id_summaries.get(res_type) or [] id_summaries[res_type].append(resource_id) result["ResourceTypes"] = list(id_summaries.keys()) result["ResourceIdentifierSummaries"] = [{ "ResourceType": key, "LogicalResourceIds": { "member": values } } for key, values in id_summaries.items()] return result
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) # 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 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') 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(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_change_set(req_params): stack_name = req_params.get('StackName') template_deployer.prepare_template_body(req_params) template = template_preparer.parse_template(req_params['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'] = {} stack = Stack(clone(req_params), empty_stack_template) state.stacks[stack.stack_id] = stack stack.set_stack_status('CREATE_COMPLETE') change_set = StackChangeSet(req_params, template) stack.change_sets.append(change_set) change_set.metadata['Status'] = 'CREATE_COMPLETE' return {'StackId': change_set.stack_id, 'Id': change_set.change_set_id}
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 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)