def update_stack(context, args): deployment_name = args.deployment resource_group_name = args.resource_group # Use default deployment if necessary if deployment_name is None: if context.config.default_deployment is None: raise HandledError('No default deployment has been set. Provide the --deployment parameter or use the default-deployment command to set a default deployment.') deployment_name = context.config.default_deployment # Get needed data, verifies the resource group stack exists resource_group = context.resource_groups.get(resource_group_name) resource_group_stack_id = resource_group.get_stack_id(deployment_name) pending_resource_status = resource_group.get_pending_resource_status(deployment_name) # Is it ok to do this? capabilities = context.stack.confirm_stack_operation( resource_group_stack_id, 'deployment {} resource group {}'.format(deployment_name, resource_group_name), args, pending_resource_status ) # Update the stack... project_uploader = ProjectUploader(context) deployment_uploader = project_uploader.get_deployment_uploader(deployment_name) resource_group_uploader, resource_group_template_url = before_update( deployment_uploader, resource_group_name ) parameters = resource_group.get_stack_parameters( deployment_name, uploader = resource_group_uploader ) # wait a bit for S3 to help insure that templates can be read by cloud formation time.sleep(constant.STACK_UPDATE_DELAY_TIME) context.stack.update( resource_group_stack_id, resource_group_template_url, parameters = parameters, pending_resource_status = pending_resource_status, capabilities = capabilities ) after_update(deployment_uploader, resource_group_name) # Deprecated in 1.9 - TODO remove context.hooks.call_module_handlers('cli-plugin-code/resource_group_hooks.py', 'on_post_update', args=[deployment_name, resource_group_name], deprecated=True )
def _update_access_stack(context, args, deployment_name): # Get the data we need... deployment_stack_id = context.config.get_deployment_stack_id( deployment_name) deployment_access_stack_id = context.config.get_deployment_access_stack_id( deployment_name) pending_resource_status = __get_pending_access_resource_status( context, deployment_name) # Is it ok to do this? capabilities = context.stack.confirm_stack_operation( deployment_access_stack_id, 'deployment {} access'.format(deployment_name), args, pending_resource_status) # Do the update... project_uploader = ProjectUploader(context) deployment_uploader = project_uploader.get_deployment_uploader( deployment_name) context.view.processing_template('{} deployment'.format(deployment_name)) access_template_url = deployment_uploader.upload_content( constant.DEPLOYMENT_ACCESS_TEMPLATE_FILENAME, json.dumps(context.config.deployment_access_template_aggregator. effective_template, indent=4, sort_keys=True), 'Configured Deployment Access Template') parameters = __get_access_stack_parameters( context, deployment_name, deployment_stack_id=deployment_stack_id, uploader=deployment_uploader) # wait a bit for S3 to help insure that templates can be read by cloud formation time.sleep(constant.STACK_UPDATE_DELAY_TIME) context.stack.update(deployment_access_stack_id, access_template_url, parameters, pending_resource_status=pending_resource_status, capabilities=capabilities)
def update_stack(context, args): # Use default deployment if necessary if args.deployment is None: if context.config.default_deployment is None: raise HandledError('No default deployment has been set. Provide the --deployment parameter or use the default-deployment command to set a default deployment.') args.deployment = context.config.default_deployment # Does deployment-template.json include resource group from a gem which isn't enabled for the project? for resource_group_name in context.resource_groups.keys(): __check_resource_group_gem_status(context, resource_group_name) # Resource group (and other) file write checks create_and_validate_writable_list(context) # Get necessary data, verifies project has been initialized and that the stack exists. deployment_stack_id = context.config.get_deployment_stack_id(args.deployment) pending_resource_status = __get_pending_deployment_resource_status(context, args.deployment) # Is it ok to do this? capabilities = context.stack.confirm_stack_operation( deployment_stack_id, 'deployment {}'.format(args.deployment), args, pending_resource_status, ignore_resource_types = [ 'Custom::EmptyDeployment' ] ) # Do the upload ... project_uploader = ProjectUploader(context) deployment_uploader = project_uploader.get_deployment_uploader(args.deployment) deployment_template_url = before_update(context, deployment_uploader) parameters = __get_deployment_stack_parameters(context, args.deployment, uploader = deployment_uploader) # wait a bit for S3 to help insure that templates can be read by cloud formation time.sleep(constant.STACK_UPDATE_DELAY_TIME) context.stack.update( deployment_stack_id, deployment_template_url, parameters, pending_resource_status = pending_resource_status, capabilities = capabilities ) after_update(context, deployment_uploader) # Update mappings... if args.deployment == context.config.default_deployment: mappings.update(context, util.Args()) if args.deployment == context.config.release_deployment: temp_args = util.Args() temp_args.release = True mappings.update(context, temp_args)
def create_stack(context, args): # Has the project been initialized? if not context.config.project_initialized: raise HandledError('The project has not been initialized.') # Does a deployment with that name already exist? if context.config.deployment_stack_exists(args.deployment): raise HandledError('The project already has a {} deployment.'.format(args.deployment)) # Does deployment-template.json include resource group from a gem which isn't enabled for the project? for resource_group_name in context.resource_groups.keys(): __check_resource_group_gem_status(context, resource_group_name) # Is the project settings file writable? context.config.validate_writable(context.config.local_project_settings_path) # Is the deployment name valid? util.validate_stack_name(args.deployment) # If there is no project default deployment, make this the project default deployment if context.config.project_default_deployment is None: args.make_project_default = True # If there is no release deployment, make this the release deployment if context.config.release_deployment is None: args.make_release_deployment = True # Need to handle situations where the deployment and/or access stack were # not successfully created on previous attempts. pending_deployment_stack_id = context.config.get_pending_deployment_stack_id(args.deployment) pending_deployment_access_stack_id = context.config.get_pending_deployment_access_stack_id(args.deployment) pending_deployment_stack_status = context.stack.get_stack_status(pending_deployment_stack_id) pending_deployment_access_stack_status = context.stack.get_stack_status(pending_deployment_access_stack_id) # Does a stack with the name already exist? It's ok if a previous attempt # at creation left a stack with this name behind, we'll deal with that later. deployment_stack_name = args.stack_name or context.config.get_default_deployment_stack_name(args.deployment) deployment_region = util.get_region_from_arn(context.config.project_stack_id) if pending_deployment_stack_id is None or deployment_stack_name != util.get_stack_name_from_arn(pending_deployment_stack_id): if context.stack.name_exists(deployment_stack_name, deployment_region): raise HandledError('An AWS Cloud Formation stack with the name {} already exists in region {}. Use the --stack-name option to provide a different name.'.format(deployment_stack_name, deployment_region)) # Resource group (and other) file write checks create_and_validate_writable_list(context) # Is it ok to use AWS? pending_resource_status = __get_pending_combined_resource_status(context, args.deployment) capabilities = context.stack.confirm_stack_operation( None, # stack id 'deployment {}'.format(args.deployment), args, pending_resource_status, ignore_resource_types = [ 'Custom::EmptyDeployment' ] ) # We have the following scenerios to deal with: # # 1) This is the first attempt to create the deployment, or previous attempts didn't # get as far as creating any stacks. # # 2) The previous attempt failed to create or update the deployment stack, which was # left in a ROLLBACK_COMPLETED, UPDATE_ROLLBACK_FAILED, or ROLLBACK_FAILED state. This # stack must be deleted and a new one created. # # 3) The previous attempt created the deployment stack but failed to create the access # stack, leaving it in the ROLLBACK_COMPLETED state. In this case we update the deployment # stack (to make sure it reflects any changes that may have been made), delete the access # stack and attempt to create a new one. # # 4) Both the deployment and access stacks were created successfully, but the pending # stack id properites in the config were not replaced with the non-pending properties # (this could happen if someone kills the client during the access stack creation # process, which then runs to a successful completion). In this case we update both # stacks to make sure they reflect any changes, then replace the "pending" stack id # properties. project_uploader = ProjectUploader(context) deployment_uploader = project_uploader.get_deployment_uploader(args.deployment) template_url = before_update(context, deployment_uploader) deployment_stack_parameters = __get_deployment_stack_parameters(context, args.deployment, uploader = deployment_uploader) # wait a bit for S3 to help insure that templates can be read by cloud formation time.sleep(constant.STACK_UPDATE_DELAY_TIME) try: if pending_deployment_stack_status not in [None, context.stack.STATUS_ROLLBACK_COMPLETE, context.stack.STATUS_DELETE_COMPLETE, context.stack.STATUS_UPDATE_ROLLBACK_FAILED, context.stack.STATUS_ROLLBACK_FAILED]: # case 3 or 4 - deployment stack was previously created successfully, update it context.stack.update( pending_deployment_stack_id, template_url, deployment_stack_parameters, capabilities = capabilities ) deployment_stack_id = pending_deployment_stack_id else: if pending_deployment_stack_status in [context.stack.STATUS_ROLLBACK_COMPLETE, context.stack.STATUS_ROLLBACK_FAILED, context.stack.STATUS_UPDATE_ROLLBACK_FAILED]: # case 2 - deployment stack failed to create previously, delete it context.stack.delete(pending_deployment_stack_id) # case 1 and 2 - deployment stack wasn't creatred previously or was just # deleted, attempt to create it deployment_stack_id = context.stack.create_using_url( deployment_stack_name, template_url, deployment_region, deployment_stack_parameters, created_callback=lambda id: context.config.set_pending_deployment_stack_id(args.deployment, id), capabilities = capabilities) # Now create or update the access stack... context.view.processing_template('{} deployment'.format(args.deployment)) access_template_url = deployment_uploader.upload_content( constant.DEPLOYMENT_ACCESS_TEMPLATE_FILENAME, json.dumps(context.config.deployment_access_template_aggregator.effective_template, indent=4, sort_keys=True), 'processed deployment access temmplate') access_stack_parameters = __get_access_stack_parameters( context, args.deployment, deployment_stack_id = deployment_stack_id, uploader = deployment_uploader ) if pending_deployment_access_stack_status not in [None, context.stack.STATUS_ROLLBACK_COMPLETE, context.stack.STATUS_DELETE_COMPLETE]: # case 4 - access stack was previously created successfully but the pending # stack id properties were not replaced. Update the stack. context.stack.update( pending_deployment_access_stack_id, access_template_url, deployment_stack_parameters, capabilities = capabilities ) deployment_access_stack_id = pending_deployment_access_stack_id else: if pending_deployment_access_stack_status == context.stack.STATUS_ROLLBACK_COMPLETE: # case 3 - access stack failed to create previously, delete it context.stack.delete(pending_deployment_access_stack_id) # case 1 or 3 - access stack wasn't created before, or was just deleted. Attempt # to create. deployment_access_stack_name = deployment_stack_name + '-Access' deployment_access_stack_id = context.stack.create_using_url( deployment_access_stack_name, access_template_url, deployment_region, parameters = access_stack_parameters, created_callback=lambda id: context.config.set_pending_deployment_access_stack_id(args.deployment, id), capabilities = capabilities) except: context.config.force_gui_refresh() raise context.config.force_gui_refresh() context.config.finalize_deployment_stack_ids(args.deployment) context.view.deployment_stack_created(args.deployment, deployment_stack_id, deployment_access_stack_id) # Should the new deployment become the project default deployment or the release deployment? if args.make_project_default: context.config.set_project_default_deployment(args.deployment) mappings.update(context, util.Args()) context.view.default_deployment(context.config.user_default_deployment, context.config.project_default_deployment) if args.make_release_deployment: context.config.set_release_deployment(args.deployment) temp_args = util.Args() temp_args.release = True mappings.update(context, temp_args) context.view.release_deployment(context.config.release_deployment) after_update(context, deployment_uploader)
def delete_stack(context, args): resource_group_stack_id = context.config.get_resource_group_stack_id(args.deployment, args.resource_group) pending_resource_status = context.stack.get_pending_resource_status( resource_group_stack_id, new_template = {} ) # Is it ok to do this? capabilities = context.stack.confirm_stack_operation( None, # stack id 'deployment {} resource group {}'.format(args.deployment, args.resource_group), args, pending_resource_status ) # Does a "safe" delete of a resource group stack. The existing deployment # template is modified to remove the stack and config resources and used # to update the deployment. This prevents unexpected changes to other resource # groups as a side effect of the deployment update. project_uploader = ProjectUploader(context) deployment_uploader = project_uploader.get_deployment_uploader(args.deployment) context.view.processing_template('{} deployment'.format(args.deployment)) deployment_stack_id = context.config.get_deployment_stack_id(args.deployment) deployment_template = context.stack.get_current_template(deployment_stack_id) deployment_parameters = context.stack.get_current_parameters(deployment_stack_id) deployment_resources = deployment_template.get('Resources', {}) resource_group_stack_resource = deployment_resources.get(args.resource_group, None) if resource_group_stack_resource is not None: del deployment_resources[args.resource_group] resource_group_config_resource = deployment_resources.get(args.resource_group + 'Configuration', None) if resource_group_config_resource is not None: del deployment_resources[args.resource_group + 'Configuration'] if resource_group_stack_resource is None and resource_group_config_resource is None: raise HandledError('Definitions for {} resource group related resources where not found in the current {} deployment template.'.format(args.resource_group, args.deployment)) if not deployment_resources: deployment_resources['EmptyDeployment'] = { "Type": "Custom::EmptyDeployment", "Properties": { "ServiceToken": { "Ref": "ProjectResourceHandler" } } } deployment_template_url = deployment_uploader.upload_content(constant.DEPLOYMENT_TEMPLATE_FILENAME, json.dumps(deployment_template), 'deployment template without resource group definitions') resource_group_stack_id = context.stack.get_physical_resource_id(deployment_stack_id, args.resource_group) # wait a bit for S3 to help insure that templates can be read by cloud formation time.sleep(constant.STACK_UPDATE_DELAY_TIME) # Tell stack.update that a child stack is being deleted so that it # cleans up any resources that stack contains. pending_resource_status = { args.resource_group: { 'OldDefinition': { 'Type': 'AWS::CloudFormation::Stack' }, 'PendingAction': context.stack.PENDING_DELETE } } try: context.stack.update( deployment_stack_id, deployment_template_url, deployment_parameters, pending_resource_status = pending_resource_status, capabilities = capabilities ) except: context.config.force_gui_refresh() raise context.config.force_gui_refresh() context.view.resource_group_stack_deleted(args.deployment, args.resource_group)
def create_stack(context, args): # Does a "safe" create of a resource group stack. The existing deployment # template is modified to add the stack and config resources and used # to update the deployment stack. This prevents unexpected changes to other # resource groups as a side effect of the deployment update. resource_group = context.resource_groups.get(args.resource_group) pending_resource_status = resource_group.get_pending_resource_status(args.deployment) # Is it ok to do this? capabilities = context.stack.confirm_stack_operation( None, # stack id 'deployment {} resource group {}'.format(args.deployment, args.resource_group), args, pending_resource_status ) # Do the create... project_uploader = ProjectUploader(context) deployment_uploader = project_uploader.get_deployment_uploader(args.deployment) before_update( deployment_uploader, args.resource_group ) context.view.processing_template('{} deployment'.format(args.deployment)) deployment_stack_id = context.config.get_deployment_stack_id(args.deployment) deployment_template = context.stack.get_current_template(deployment_stack_id) deployment_parameters = context.stack.get_current_parameters(deployment_stack_id) deployment_resources = deployment_template.get('Resources', {}) effective_deployment_resources = context.config.deployment_template_aggregator.effective_template.get('Resources',{}) resource_group_stack_resource = deployment_resources.get(args.resource_group, None) if resource_group_stack_resource is None: resource_group_stack_resource = copy.deepcopy(effective_deployment_resources.get(args.resource_group, {})) deployment_resources[args.resource_group] = resource_group_stack_resource resource_group_config_name = args.resource_group + 'Configuration' resource_group_config_resource = deployment_resources.get(resource_group_config_name, None) if resource_group_config_resource is None: resource_group_config_resource = copy.deepcopy(effective_deployment_resources.get(resource_group_config_name, {})) resource_group_config_resource.get('Properties', {})['ConfigurationKey'] = deployment_uploader.key deployment_resources[resource_group_config_name] = resource_group_config_resource if 'EmptyDeployment' in deployment_resources: del deployment_resources['EmptyDeployment'] deployment_template_url = deployment_uploader.upload_content(constant.DEPLOYMENT_TEMPLATE_FILENAME, json.dumps(deployment_template), 'deployment template with resource group definitions') # wait a bit for S3 to help insure that templates can be read by cloud formation time.sleep(constant.STACK_UPDATE_DELAY_TIME) try: context.stack.update( deployment_stack_id, deployment_template_url, deployment_parameters, pending_resource_status = __nest_pending_resource_status(args.deployment, pending_resource_status), capabilities = capabilities ) except: context.config.force_gui_refresh() raise context.config.force_gui_refresh() context.view.resource_group_stack_created(args.deployment, args.resource_group) after_update(deployment_uploader, args.resource_group) # Deprecated in 1.9 - TODO remove context.hooks.call_module_handlers('cli-plugin-code/resource_group_hooks.py', 'on_post_update', args=[args.deployment, args.resource_group], deprecated=True )