def run(self): logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: " f"starting deploy try {self.try_count} of {self.retry_count}") all_params = {} logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: collecting ssm params" ) for ssm_param_name, ssm_param in self.input().get('ssm_params', {}).items(): all_params[ssm_param_name] = ssm_param.read() logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: collecting manifest params" ) for parameter in self.parameters: all_params[parameter.get('name')] = parameter.get('value') role = f"arn:aws:iam::{self.account_id}:role/servicecatalog-puppet/PuppetRole" with betterboto_client.CrossAccountClientContextManager( 'servicecatalog', role, f'sc-{self.region}-{self.account_id}', region_name=self.region) as service_catalog: logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: looking for previous failures" ) provisioned_product_id, provisioning_artifact_id = aws.terminate_if_status_is_not_available( service_catalog, self.launch_name, self.product_id, self.account_id, self.region) logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: " f"provisioned_product_id: {provisioned_product_id}, " f"provisioning_artifact_id : {provisioning_artifact_id}") with betterboto_client.CrossAccountClientContextManager( 'cloudformation', role, f'cfn-{self.region}-{self.account_id}', region_name=self.region) as cloudformation: need_to_provision = True if provisioned_product_id: default_cfn_params = aws.get_default_parameters_for_stack( cloudformation, f"SC-{self.account_id}-{provisioned_product_id}") else: default_cfn_params = {} for default_cfn_param_name in default_cfn_params.keys(): if all_params.get(default_cfn_param_name) is None: all_params[ default_cfn_param_name] = default_cfn_params[ default_cfn_param_name] if provisioning_artifact_id == self.version_id: logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: found previous good provision" ) if provisioned_product_id: logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: checking params for diffs" ) provisioned_parameters = aws.get_parameters_for_stack( cloudformation, f"SC-{self.account_id}-{provisioned_product_id}") logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: " f"current params: {provisioned_parameters}") logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: " f"new params: {all_params}") if provisioned_parameters == all_params: logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: params unchanged" ) need_to_provision = False else: logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: params changed" ) if need_to_provision: logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: about to provision with " f"params: {json.dumps(all_params)}") if provisioned_product_id: with betterboto_client.CrossAccountClientContextManager( 'cloudformation', role, f'cfn-{self.region}-{self.account_id}', region_name=self.region) as cloudformation: stack = aws.get_stack_output_for( cloudformation, f"SC-{self.account_id}-{provisioned_product_id}" ) stack_status = stack.get('StackStatus') logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: current cfn stack_status is " f"{stack_status}") if stack_status not in [ "UPDATE_COMPLETE", "CREATE_COMPLETE" ]: raise Exception( f"[{self.launch_name}] {self.account_id}:{self.region} :: current cfn stack_status is " f"{stack_status}") provisioned_product_id = aws.provision_product( service_catalog, self.launch_name, self.account_id, self.region, self.product_id, self.version_id, self.puppet_account_id, aws.get_path_for_product(service_catalog, self.product_id), all_params, self.version, ) f = self.output().open('w') with betterboto_client.CrossAccountClientContextManager( 'cloudformation', role, f'cfn-{self.region}-{self.account_id}', region_name=self.region) as cloudformation: f.write( json.dumps( aws.get_stack_output_for( cloudformation, f"SC-{self.account_id}-{provisioned_product_id}" ), indent=4, default=str, )) f.close() logger.info( f"[{self.launch_name}] {self.account_id}:{self.region} :: finished provisioning" )
def run(self): logger.info( f"[{self.uid}] starting deploy try {self.try_count} of {self.retry_count}" ) product_id, version_id = self.get_product_and_version_ids() all_params = self.get_all_params() role = f"arn:aws:iam::{self.account_id}:role/servicecatalog-puppet/PuppetRole" with betterboto_client.CrossAccountClientContextManager( 'servicecatalog', role, f'sc-{self.region}-{self.account_id}', region_name=self.region) as service_catalog: logger.info(f"[{self.uid}] looking for previous failures") path_id = aws.get_path_for_product(service_catalog, product_id, self.portfolio) provisioned_product_id, provisioning_artifact_id = aws.terminate_if_status_is_not_available( service_catalog, self.launch_name, product_id, self.account_id, self.region) logger.info( f"[{self.uid}] pp_id: {provisioned_product_id}, paid : {provisioning_artifact_id}" ) with betterboto_client.CrossAccountClientContextManager( 'cloudformation', role, f'cfn-{self.region}-{self.account_id}', region_name=self.region) as cloudformation: need_to_provision = True logging.info( f"running as {role},checking {product_id} {version_id} {path_id} in {self.account_id} {self.region}" ) with self.input().get('provisioning_artifact_parameters').open( 'r') as f: provisioning_artifact_parameters = json.loads(f.read()) params_to_use = {} for p in provisioning_artifact_parameters: param_name = p.get('ParameterKey') params_to_use[param_name] = all_params.get( param_name, p.get('DefaultValue')) if provisioning_artifact_id == version_id: logger.info(f"[{self.uid}] found previous good provision") if provisioned_product_id: logger.info(f"[{self.uid}] checking params for diffs") provisioned_parameters = aws.get_parameters_for_stack( cloudformation, f"SC-{self.account_id}-{provisioned_product_id}") logger.info( f"[{self.uid}] current params: {provisioned_parameters}" ) logger.info( f"[{self.uid}] new params: {params_to_use}") if provisioned_parameters == params_to_use: logger.info(f"[{self.uid}] params unchanged") need_to_provision = False else: logger.info(f"[{self.uid}] params changed") if need_to_provision: logger.info( f"[{self.uid}] about to provision with params: {json.dumps(params_to_use)}" ) if provisioned_product_id: with betterboto_client.CrossAccountClientContextManager( 'cloudformation', role, f'cfn-{self.region}-{self.account_id}', region_name=self.region) as cloudformation: stack = aws.get_stack_output_for( cloudformation, f"SC-{self.account_id}-{provisioned_product_id}" ) stack_status = stack.get('StackStatus') logger.info( f"[{self.uid}] current cfn stack_status is {stack_status}" ) if stack_status not in [ "UPDATE_COMPLETE", "CREATE_COMPLETE", "UPDATE_ROLLBACK_COMPLETE" ]: raise Exception( f"[{self.uid}] current cfn stack_status is {stack_status}" ) if stack_status == "UPDATE_ROLLBACK_COMPLETE": logger.warning( f"[{self.uid}] SC-{self.account_id}-{provisioned_product_id} has a status of " f"{stack_status}. This may need manual resolution." ) if provisioned_product_id: if self.should_use_product_plans: provisioned_product_id = aws.provision_product_with_plan( service_catalog, self.launch_name, self.account_id, self.region, product_id, version_id, self.puppet_account_id, path_id, params_to_use, self.version, self.should_use_sns, ) else: provisioned_product_id = aws.update_provisioned_product( service_catalog, self.launch_name, self.account_id, self.region, product_id, version_id, self.puppet_account_id, path_id, params_to_use, self.version, ) else: provisioned_product_id = aws.provision_product( service_catalog, self.launch_name, self.account_id, self.region, product_id, version_id, self.puppet_account_id, path_id, params_to_use, self.version, self.should_use_sns, ) with betterboto_client.CrossAccountClientContextManager( 'cloudformation', role, f'cfn-{self.region}-{self.account_id}', region_name=self.region) as spoke_cloudformation: stack_details = aws.get_stack_output_for( spoke_cloudformation, f"SC-{self.account_id}-{provisioned_product_id}") for ssm_param_output in self.ssm_param_outputs: logger.info( f"[{self.uid}] writing SSM Param: {ssm_param_output.get('stack_output')}" ) with betterboto_client.ClientContextManager('ssm') as ssm: found_match = False for output in stack_details.get('Outputs', []): if output.get('OutputKey') == ssm_param_output.get( 'stack_output'): found_match = True logger.info(f"[{self.uid}] found value") ssm.put_parameter( Name=ssm_param_output.get('param_name'), Value=output.get('OutputValue'), Type=ssm_param_output.get( 'param_type', 'String'), Overwrite=True, ) if not found_match: raise Exception( f"[{self.uid}] Could not find match for {ssm_param_output.get('stack_output')}" ) for p in self.post_actions: yield portfoliomanagement.ProvisionActionTask(**p) with self.output().open('w') as f: f.write(json.dumps( stack_details, indent=4, default=str, )) logger.info(f"[{self.uid}] finished provisioning")
def run(self): details = self.load_from_input("details") product_id = details.get("product_details").get("ProductId") version_id = details.get("version_details").get("Id") task_output = dict( **self.params_for_results_display(), account_parameters=tasks.unwrap(self.account_parameters), launch_parameters=tasks.unwrap(self.launch_parameters), manifest_parameters=tasks.unwrap(self.manifest_parameters), ) all_params = self.get_parameter_values() with self.spoke_regional_client("servicecatalog") as service_catalog: path_name = self.portfolio ( provisioned_product_id, provisioning_artifact_id, provisioned_product_status, ) = aws.terminate_if_status_is_not_available( service_catalog, self.launch_name, product_id, self.account_id, self.region, self.should_delete_rollback_complete_stacks, ) self.info( f"pp_id: {provisioned_product_id}, paid : {provisioning_artifact_id}" ) with self.spoke_regional_client("cloudformation") as cloudformation: need_to_provision = True self.info( f"running ,checking {product_id} {version_id} {path_name} in {self.account_id} {self.region}" ) with self.input().get("provisioning_artifact_parameters").open( "r" ) as f: provisioning_artifact_parameters = json.loads(f.read()) params_to_use = {} for p in provisioning_artifact_parameters: param_name = p.get("ParameterKey") params_to_use[param_name] = all_params.get( param_name, p.get("DefaultValue") ) if provisioning_artifact_id == version_id: self.info(f"found previous good provision") if provisioned_product_id: self.info(f"checking params for diffs") pp_stack_name = aws.get_stack_name_for_pp_id( service_catalog, provisioned_product_id ) provisioned_parameters = aws.get_parameters_for_stack( cloudformation, pp_stack_name, ) self.info(f"current params: {provisioned_parameters}") self.info(f"new params: {params_to_use}") if provisioned_parameters == params_to_use: self.info(f"params unchanged") need_to_provision = False else: self.info(f"params changed") if provisioned_product_status == "TAINTED": need_to_provision = True if need_to_provision: self.info( f"about to provision with params: {json.dumps(tasks.unwrap(params_to_use))}" ) if provisioned_product_id: pp_stack_name = aws.get_stack_name_for_pp_id( service_catalog, provisioned_product_id ) stack = aws.get_stack_output_for(cloudformation, pp_stack_name,) stack_status = stack.get("StackStatus") self.info(f"current cfn stack_status is {stack_status}") if stack_status not in [ "UPDATE_COMPLETE", "CREATE_COMPLETE", "UPDATE_ROLLBACK_COMPLETE", ]: raise Exception( f"[{self.uid}] current cfn stack_status is {stack_status}" ) if stack_status == "UPDATE_ROLLBACK_COMPLETE": self.warning( f"[{self.uid}] {pp_stack_name} has a status of " f"{stack_status}. This may need manual resolution." ) if provisioned_product_id: if self.should_use_product_plans: path_id = aws.get_path_for_product( service_catalog, product_id, self.portfolio ) provisioned_product_id = aws.provision_product_with_plan( service_catalog, self.launch_name, self.account_id, self.region, product_id, version_id, self.puppet_account_id, path_id, params_to_use, self.version, self.should_use_sns, ) else: provisioned_product_id = aws.update_provisioned_product( service_catalog, self.launch_name, self.account_id, self.region, product_id, version_id, self.puppet_account_id, path_name, params_to_use, self.version, self.execution, ) else: provisioned_product_id = aws.provision_product( service_catalog, self.launch_name, self.account_id, self.region, product_id, version_id, self.puppet_account_id, path_name, params_to_use, self.version, self.should_use_sns, self.execution, ) self.info(f"self.execution is {self.execution}") if self.execution in [ constants.EXECUTION_MODE_HUB, constants.EXECUTION_MODE_SPOKE, ]: self.info( f"Running in execution mode: {self.execution}, checking for SSM outputs" ) outputs = service_catalog.get_provisioned_product_outputs( ProvisionedProductId=provisioned_product_id ).get("Outputs", []) for ssm_param_output in self.ssm_param_outputs: self.info( f"writing SSM Param: {ssm_param_output.get('stack_output')}" ) with self.hub_client("ssm") as ssm: found_match = False # TODO push into another task for output in outputs: if output.get("OutputKey") == ssm_param_output.get( "stack_output" ): ssm_parameter_name = ssm_param_output.get( "param_name" ) ssm_parameter_name = ssm_parameter_name.replace( "${AWS::Region}", self.region ) ssm_parameter_name = ssm_parameter_name.replace( "${AWS::AccountId}", self.account_id ) found_match = True self.info(f"found value") ssm.put_parameter_and_wait( Name=ssm_parameter_name, Value=output.get("OutputValue"), Type=ssm_param_output.get( "param_type", "String" ), Overwrite=True, ) if not found_match: raise Exception( f"[{self.uid}] Could not find match for {ssm_param_output.get('stack_output')}" ) self.write_output(task_output) else: self.write_output(task_output) self.info("finished")