def delete_group(module=None, iam=None, name=None): changed = False try: iam.delete_group(name) except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if ('must delete policies first') in error_msg: for policy in iam.get_all_group_policies( name).list_group_policies_result.policy_names: iam.delete_group_policy(name, policy) try: iam.delete_group(name) except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if ('must delete policies first') in error_msg: module.fail_json( changed=changed, msg= "All inline policies have been removed. Though it appears" "that %s has Managed Polices. This is not " "currently supported by boto. Please detach the policies " "through the console and try again." % name) else: module.fail_json(changed=changed, msg=str(error_msg)) else: changed = True else: module.fail_json(changed=changed, msg=str(error_msg)) else: changed = True return changed, name
def delete_role(module, iam, name, role_list, prof_list): changed = False iam_role_result = None instance_profile_result = None try: if name in role_list: cur_ins_prof = [ rp['instance_profile_name'] for rp in iam.list_instance_profiles_for_role(name). list_instance_profiles_for_role_result.instance_profiles ] for profile in cur_ins_prof: iam.remove_role_from_instance_profile(profile, name) try: iam.delete_role(name) except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if ('must detach all policies first') in error_msg: for policy in iam.list_role_policies( name).list_role_policies_result.policy_names: iam.delete_role_policy(name, policy) try: iam_role_result = iam.delete_role(name) except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if ('must detach all policies first') in error_msg: module.fail_json( changed=changed, msg= "All inline policies have been removed. Though it appears" "that %s has Managed Polices. This is not " "currently supported by boto. Please detach the policies " "through the console and try again." % name) else: module.fail_json(changed=changed, msg=str(err)) else: changed = True else: changed = True for prof in prof_list: if name == prof: instance_profile_result = iam.delete_instance_profile(name) except boto.exception.BotoServerError as err: module.fail_json(changed=changed, msg=str(err)) else: updated_role_list = list_all_roles(iam) return changed, updated_role_list, iam_role_result, instance_profile_result
def get_stack_events(cfn, stack_name, events_limit, token_filter=None): '''This event data was never correct, it worked as a side effect. So the v2.3 format is different.''' ret = {'events': [], 'log': []} try: pg = cfn.get_paginator('describe_stack_events').paginate( StackName=stack_name, PaginationConfig={'MaxItems': events_limit}) if token_filter is not None: events = list( pg.search("StackEvents[?ClientRequestToken == '{0}']".format( token_filter))) else: events = list(pg.search("StackEvents[*]")) except (botocore.exceptions.ValidationError, botocore.exceptions.ClientError) as err: error_msg = boto_exception(err) if 'does not exist' in error_msg: # missing stack, don't bail. ret['log'].append('Stack does not exist.') return ret ret['log'].append('Unknown error: ' + str(error_msg)) return ret for e in events: eventline = 'StackEvent {ResourceType} {LogicalResourceId} {ResourceStatus}'.format( **e) ret['events'].append(eventline) if e['ResourceStatus'].endswith('FAILED'): failline = '{ResourceType} {LogicalResourceId} {ResourceStatus}: {ResourceStatusReason}'.format( **e) ret['log'].append(failline) return ret
def main(): argument_spec = ec2_argument_spec() argument_spec.update(dict( mode=dict(choices=['push'], default='push'), file_change_strategy=dict(choices=['force', 'date_size', 'checksum'], default='date_size'), bucket=dict(required=True), key_prefix=dict(required=False, default=''), file_root=dict(required=True, type='path'), permission=dict(required=False, choices=['private', 'public-read', 'public-read-write', 'authenticated-read', 'aws-exec-read', 'bucket-owner-read', 'bucket-owner-full-control']), retries=dict(required=False, removed_in_version='2.14'), mime_map=dict(required=False, type='dict'), exclude=dict(required=False, default=".*"), include=dict(required=False, default="*"), cache_control=dict(required=False, default=''), delete=dict(required=False, type='bool', default=False), # future options: encoding, metadata, storage_class, retries ) ) module = AnsibleModule( argument_spec=argument_spec, ) if not HAS_DATEUTIL: module.fail_json(msg='dateutil required for this module') if not HAS_BOTO3: module.fail_json(msg='boto3 required for this module') result = {} mode = module.params['mode'] region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True) if not region: module.fail_json(msg="Region must be specified") s3 = boto3_conn(module, conn_type='client', resource='s3', region=region, endpoint=ec2_url, **aws_connect_kwargs) if mode == 'push': try: result['filelist_initial'] = gather_files(module.params['file_root'], exclude=module.params['exclude'], include=module.params['include']) result['filelist_typed'] = determine_mimetypes(result['filelist_initial'], module.params.get('mime_map')) result['filelist_s3'] = calculate_s3_path(result['filelist_typed'], module.params['key_prefix']) result['filelist_local_etag'] = calculate_local_etag(result['filelist_s3']) result['filelist_actionable'] = filter_list(s3, module.params['bucket'], result['filelist_local_etag'], module.params['file_change_strategy']) result['uploads'] = upload_files(s3, module.params['bucket'], result['filelist_actionable'], module.params) if module.params['delete']: result['removed'] = remove_files(s3, result['filelist_local_etag'], module.params) # mark changed if we actually upload something. if result.get('uploads') or result.get('removed'): result['changed'] = True # result.update(filelist=actionable_filelist) except botocore.exceptions.ClientError as err: error_msg = boto_exception(err) module.fail_json(msg=error_msg, exception=traceback.format_exc(), **camel_dict_to_snake_dict(err.response)) module.exit_json(**result)
def create_stack(module, stack_params, cfn, events_limit): if 'TemplateBody' not in stack_params and 'TemplateURL' not in stack_params: module.fail_json( msg= "Either 'template', 'template_body' or 'template_url' is required when the stack does not exist." ) # 'DisableRollback', 'TimeoutInMinutes', 'EnableTerminationProtection' and # 'OnFailure' only apply on creation, not update. # # 'OnFailure' and 'DisableRollback' are incompatible with each other, so # throw error if both are defined if module.params.get('on_create_failure') is None: stack_params['DisableRollback'] = module.params['disable_rollback'] else: if module.params['disable_rollback']: module.fail_json( msg= "You can specify either 'on_create_failure' or 'disable_rollback', but not both." ) stack_params['OnFailure'] = module.params['on_create_failure'] if module.params.get('create_timeout') is not None: stack_params['TimeoutInMinutes'] = module.params['create_timeout'] if module.params.get('termination_protection') is not None: if boto_supports_termination_protection(cfn): stack_params['EnableTerminationProtection'] = bool( module.params.get('termination_protection')) else: module.fail_json( msg= "termination_protection parameter requires botocore >= 1.7.18") try: response = cfn.create_stack(**stack_params) # Use stack ID to follow stack state in case of on_create_failure = DELETE result = stack_operation(cfn, response['StackId'], 'CREATE', events_limit, stack_params.get('ClientRequestToken', None)) except Exception as err: error_msg = boto_exception(err) module.fail_json(msg="Failed to create stack {0}: {1}.".format( stack_params.get('StackName'), error_msg), exception=traceback.format_exc()) if not result: module.fail_json(msg="empty result") return result
def update_termination_protection(module, cfn, stack_name, desired_termination_protection_state): '''updates termination protection of a stack''' if not boto_supports_termination_protection(cfn): module.fail_json( msg="termination_protection parameter requires botocore >= 1.7.18") stack = get_stack_facts(cfn, stack_name) if stack: if stack[ 'EnableTerminationProtection'] is not desired_termination_protection_state: try: cfn.update_termination_protection( EnableTerminationProtection= desired_termination_protection_state, StackName=stack_name) except botocore.exceptions.ClientError as e: module.fail_json(msg=boto_exception(e), exception=traceback.format_exc())
def set_users_groups(module, iam, name, groups, updated=None, new_name=None): """ Sets groups for a user, will purge groups not explicitly passed, while retaining pre-existing groups that also are in the new list. """ changed = False if updated: name = new_name try: orig_users_groups = [ og['group_name'] for og in iam.get_groups_for_user( name).list_groups_for_user_result.groups ] remove_groups = [ rg for rg in frozenset(orig_users_groups).difference(groups) ] new_groups = [ ng for ng in frozenset(groups).difference(orig_users_groups) ] except boto.exception.BotoServerError as err: module.fail_json(changed=changed, msg=str(err)) else: if len(orig_users_groups) > 0: for new in new_groups: iam.add_user_to_group(new, name) for rm in remove_groups: iam.remove_user_from_group(rm, name) else: for group in groups: try: iam.add_user_to_group(group, name) except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if ('The group with name %s cannot be found.' % group) in error_msg: module.fail_json(changed=False, msg="Group %s doesn't exist" % group) if len(remove_groups) > 0 or len(new_groups) > 0: changed = True return (groups, changed)
def get_stack_facts(cfn, stack_name): try: stack_response = cfn.describe_stacks(StackName=stack_name) stack_info = stack_response['Stacks'][0] except (botocore.exceptions.ValidationError, botocore.exceptions.ClientError) as err: error_msg = boto_exception(err) if 'does not exist' in error_msg: # missing stack, don't bail. return None # other error, bail. raise err if stack_response and stack_response.get('Stacks', None): stacks = stack_response['Stacks'] if len(stacks): stack_info = stacks[0] return stack_info
def update_stack(module, stack_params, cfn, events_limit): if 'TemplateBody' not in stack_params and 'TemplateURL' not in stack_params: stack_params['UsePreviousTemplate'] = True # if the state is present and the stack already exists, we try to update it. # AWS will tell us if the stack template and parameters are the same and # don't need to be updated. try: cfn.update_stack(**stack_params) result = stack_operation(cfn, stack_params['StackName'], 'UPDATE', events_limit, stack_params.get('ClientRequestToken', None)) except Exception as err: error_msg = boto_exception(err) if 'No updates are to be performed.' in error_msg: result = dict(changed=False, output='Stack is already up-to-date.') else: module.fail_json(msg="Failed to update stack {0}: {1}".format( stack_params.get('StackName'), error_msg), exception=traceback.format_exc()) if not result: module.fail_json(msg="empty result") return result
def check_mode_changeset(module, stack_params, cfn): """Create a change set, describe it and delete it before returning check mode outputs.""" stack_params['ChangeSetName'] = build_changeset_name(stack_params) # changesets don't accept ClientRequestToken parameters stack_params.pop('ClientRequestToken', None) try: change_set = cfn.create_change_set(**stack_params) for i in range(60): # total time 5 min description = cfn.describe_change_set( ChangeSetName=change_set['Id']) if description['Status'] in ('CREATE_COMPLETE', 'FAILED'): break time.sleep(5) else: # if the changeset doesn't finish in 5 mins, this `else` will trigger and fail module.fail_json(msg="Failed to create change set %s" % stack_params['ChangeSetName']) cfn.delete_change_set(ChangeSetName=change_set['Id']) reason = description.get('StatusReason') if description[ 'Status'] == 'FAILED' and "didn't contain changes" in description[ 'StatusReason']: return { 'changed': False, 'msg': reason, 'meta': description['StatusReason'] } return {'changed': True, 'msg': reason, 'meta': description['Changes']} except (botocore.exceptions.ValidationError, botocore.exceptions.ClientError) as err: error_msg = boto_exception(err) module.fail_json(msg=error_msg, exception=traceback.format_exc())
def update_user(module, iam, name, new_name, new_path, key_state, key_count, keys, pwd, updated): changed = False name_change = False if updated and new_name: name = new_name try: current_keys = [ ck['access_key_id'] for ck in iam.get_all_access_keys( name).list_access_keys_result.access_key_metadata ] status = [ ck['status'] for ck in iam.get_all_access_keys( name).list_access_keys_result.access_key_metadata ] key_qty = len(current_keys) except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if 'cannot be found' in error_msg and updated: current_keys = [ ck['access_key_id'] for ck in iam.get_all_access_keys( new_name).list_access_keys_result.access_key_metadata ] status = [ ck['status'] for ck in iam.get_all_access_keys( new_name).list_access_keys_result.access_key_metadata ] name = new_name else: module.fail_json(changed=False, msg=str(err)) updated_key_list = {} if new_name or new_path: c_path = iam.get_user(name).get_user_result.user['path'] if (name != new_name) or (c_path != new_path): changed = True try: if not updated: user = iam.update_user( name, new_user_name=new_name, new_path=new_path ).update_user_response.response_metadata else: user = iam.update_user( name, new_path=new_path ).update_user_response.response_metadata user['updates'] = dict(old_username=name, new_username=new_name, old_path=c_path, new_path=new_path) except boto.exception.BotoServerError as err: error_msg = boto_exception(err) module.fail_json(changed=False, msg=str(err)) else: if not updated: name_change = True if pwd: try: iam.update_login_profile(name, pwd) changed = True except boto.exception.BotoServerError: try: iam.create_login_profile(name, pwd) changed = True except boto.exception.BotoServerError as err: error_msg = boto_exception(str(err)) if 'Password does not conform to the account password policy' in error_msg: module.fail_json(changed=False, msg="Password doesn't conform to policy") else: module.fail_json(msg=error_msg) try: current_keys = [ ck['access_key_id'] for ck in iam.get_all_access_keys( name).list_access_keys_result.access_key_metadata ] status = [ ck['status'] for ck in iam.get_all_access_keys( name).list_access_keys_result.access_key_metadata ] key_qty = len(current_keys) except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if 'cannot be found' in error_msg and updated: current_keys = [ ck['access_key_id'] for ck in iam.get_all_access_keys( new_name).list_access_keys_result.access_key_metadata ] status = [ ck['status'] for ck in iam.get_all_access_keys( new_name).list_access_keys_result.access_key_metadata ] name = new_name else: module.fail_json(changed=False, msg=str(err)) new_keys = [] if key_state == 'create': try: while key_count > key_qty: new_keys.append( iam.create_access_key( user_name=name).create_access_key_response. create_access_key_result.access_key) key_qty += 1 changed = True except boto.exception.BotoServerError as err: module.fail_json(changed=False, msg=str(err)) if keys and key_state: for access_key in keys: if key_state in ('active', 'inactive'): if access_key in current_keys: for current_key, current_key_state in zip( current_keys, status): if key_state != current_key_state.lower(): try: iam.update_access_key(access_key, key_state.capitalize(), user_name=name) changed = True except boto.exception.BotoServerError as err: module.fail_json(changed=False, msg=str(err)) else: module.fail_json(msg="Supplied keys not found for %s. " "Current keys: %s. " "Supplied key(s): %s" % (name, current_keys, keys)) if key_state == 'remove': if access_key in current_keys: try: iam.delete_access_key(access_key, user_name=name) except boto.exception.BotoServerError as err: module.fail_json(changed=False, msg=str(err)) else: changed = True try: final_keys, final_key_status = \ [ck['access_key_id'] for ck in iam.get_all_access_keys(name). list_access_keys_result. access_key_metadata],\ [ck['status'] for ck in iam.get_all_access_keys(name). list_access_keys_result. access_key_metadata] except boto.exception.BotoServerError as err: module.fail_json(changed=changed, msg=str(err)) for fk, fks in zip(final_keys, final_key_status): updated_key_list.update({fk: fks}) return name_change, updated_key_list, changed, new_keys
def delete_dependencies_first(module, iam, name): changed = False # try to delete any keys try: current_keys = [ ck['access_key_id'] for ck in iam.get_all_access_keys( name).list_access_keys_result.access_key_metadata ] for key in current_keys: iam.delete_access_key(key, name) changed = True except boto.exception.BotoServerError as err: module.fail_json(changed=changed, msg="Failed to delete keys: %s" % err, exception=traceback.format_exc()) # try to delete login profiles try: login_profile = iam.get_login_profiles(name).get_login_profile_response iam.delete_login_profile(name) changed = True except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if 'Login Profile for User ' + name + ' cannot be found.' not in error_msg: module.fail_json(changed=changed, msg="Failed to delete login profile: %s" % err, exception=traceback.format_exc()) # try to detach policies try: for policy in iam.get_all_user_policies( name).list_user_policies_result.policy_names: iam.delete_user_policy(name, policy) changed = True except boto.exception.BotoServerError as err: error_msg = boto_exception(err) if 'must detach all policies first' in error_msg: module.fail_json( changed=changed, msg="All inline policies have been removed. Though it appears" "that %s has Managed Polices. This is not " "currently supported by boto. Please detach the policies " "through the console and try again." % name) module.fail_json(changed=changed, msg="Failed to delete policies: %s" % err, exception=traceback.format_exc()) # try to deactivate associated MFA devices try: mfa_devices = iam.get_all_mfa_devices(name).get( 'list_mfa_devices_response', {}).get('list_mfa_devices_result', {}).get('mfa_devices', []) for device in mfa_devices: iam.deactivate_mfa_device(name, device['serial_number']) changed = True except boto.exception.BotoServerError as err: module.fail_json( changed=changed, msg="Failed to deactivate associated MFA devices: %s" % err, exception=traceback.format_exc()) return changed
def main(): argument_spec = ec2_argument_spec() argument_spec.update( dict(stack_name=dict(required=True), template_parameters=dict(required=False, type='dict', default={}), state=dict(default='present', choices=['present', 'absent']), template=dict(default=None, required=False, type='path'), notification_arns=dict(default=None, required=False), stack_policy=dict(default=None, required=False), disable_rollback=dict(default=False, type='bool'), on_create_failure=dict( default=None, required=False, choices=['DO_NOTHING', 'ROLLBACK', 'DELETE']), create_timeout=dict(default=None, type='int'), template_url=dict(default=None, required=False), template_body=dict(default=None, required=False), template_format=dict(removed_in_version='2.14'), create_changeset=dict(default=False, type='bool'), changeset_name=dict(default=None, required=False), role_arn=dict(default=None, required=False), tags=dict(default=None, type='dict'), termination_protection=dict(default=None, type='bool'), events_limit=dict(default=200, type='int'), backoff_retries=dict(type='int', default=10, required=False), backoff_delay=dict(type='int', default=3, required=False), backoff_max_delay=dict(type='int', default=30, required=False), capabilities=dict( type='list', default=['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']))) module = AnsibleModule( argument_spec=argument_spec, mutually_exclusive=[['template_url', 'template', 'template_body']], supports_check_mode=True) if not HAS_BOTO3: module.fail_json(msg='boto3 and botocore are required for this module') invalid_capabilities = [] user_capabilities = module.params.get('capabilities') for user_cap in user_capabilities: if user_cap not in [ 'CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND' ]: invalid_capabilities.append(user_cap) if invalid_capabilities: module.fail_json(msg="Specified capabilities are invalid : %r," " please check documentation for valid capabilities" % invalid_capabilities) # collect the parameters that are passed to boto3. Keeps us from having so many scalars floating around. stack_params = { 'Capabilities': user_capabilities, 'ClientRequestToken': to_native(uuid.uuid4()), } state = module.params['state'] stack_params['StackName'] = module.params['stack_name'] if module.params['template'] is not None: with open(module.params['template'], 'r') as template_fh: stack_params['TemplateBody'] = template_fh.read() elif module.params['template_body'] is not None: stack_params['TemplateBody'] = module.params['template_body'] elif module.params['template_url'] is not None: stack_params['TemplateURL'] = module.params['template_url'] if module.params.get('notification_arns'): stack_params['NotificationARNs'] = module.params[ 'notification_arns'].split(',') else: stack_params['NotificationARNs'] = [] # can't check the policy when verifying. if module.params[ 'stack_policy'] is not None and not module.check_mode and not module.params[ 'create_changeset']: with open(module.params['stack_policy'], 'r') as stack_policy_fh: stack_params['StackPolicyBody'] = stack_policy_fh.read() template_parameters = module.params['template_parameters'] stack_params['Parameters'] = [] for k, v in template_parameters.items(): if isinstance(v, dict): # set parameter based on a dict to allow additional CFN Parameter Attributes param = dict(ParameterKey=k) if 'value' in v: param['ParameterValue'] = str(v['value']) if 'use_previous_value' in v and bool(v['use_previous_value']): param['UsePreviousValue'] = True param.pop('ParameterValue', None) stack_params['Parameters'].append(param) else: # allow default k/v configuration to set a template parameter stack_params['Parameters'].append({ 'ParameterKey': k, 'ParameterValue': str(v) }) if isinstance(module.params.get('tags'), dict): stack_params['Tags'] = ansible_dict_to_boto3_tag_list( module.params['tags']) if module.params.get('role_arn'): stack_params['RoleARN'] = module.params['role_arn'] result = {} try: region, ec2_url, aws_connect_kwargs = get_aws_connection_info( module, boto3=True) cfn = boto3_conn(module, conn_type='client', resource='cloudformation', region=region, endpoint=ec2_url, **aws_connect_kwargs) except botocore.exceptions.NoCredentialsError as e: module.fail_json(msg=boto_exception(e)) # Wrap the cloudformation client methods that this module uses with # automatic backoff / retry for throttling error codes backoff_wrapper = AWSRetry.jittered_backoff( retries=module.params.get('backoff_retries'), delay=module.params.get('backoff_delay'), max_delay=module.params.get('backoff_max_delay')) cfn.describe_stack_events = backoff_wrapper(cfn.describe_stack_events) cfn.create_stack = backoff_wrapper(cfn.create_stack) cfn.list_change_sets = backoff_wrapper(cfn.list_change_sets) cfn.create_change_set = backoff_wrapper(cfn.create_change_set) cfn.update_stack = backoff_wrapper(cfn.update_stack) cfn.describe_stacks = backoff_wrapper(cfn.describe_stacks) cfn.list_stack_resources = backoff_wrapper(cfn.list_stack_resources) cfn.delete_stack = backoff_wrapper(cfn.delete_stack) if boto_supports_termination_protection(cfn): cfn.update_termination_protection = backoff_wrapper( cfn.update_termination_protection) stack_info = get_stack_facts(cfn, stack_params['StackName']) if module.check_mode: if state == 'absent' and stack_info: module.exit_json(changed=True, msg='Stack would be deleted', meta=[]) elif state == 'absent' and not stack_info: module.exit_json(changed=False, msg='Stack doesn\'t exist', meta=[]) elif state == 'present' and not stack_info: module.exit_json(changed=True, msg='New stack would be created', meta=[]) else: module.exit_json(**check_mode_changeset(module, stack_params, cfn)) if state == 'present': if not stack_info: result = create_stack(module, stack_params, cfn, module.params.get('events_limit')) elif module.params.get('create_changeset'): result = create_changeset(module, stack_params, cfn, module.params.get('events_limit')) else: if module.params.get('termination_protection') is not None: update_termination_protection( module, cfn, stack_params['StackName'], bool(module.params.get('termination_protection'))) result = update_stack(module, stack_params, cfn, module.params.get('events_limit')) # format the stack output stack = get_stack_facts(cfn, stack_params['StackName']) if stack is not None: if result.get('stack_outputs') is None: # always define stack_outputs, but it may be empty result['stack_outputs'] = {} for output in stack.get('Outputs', []): result['stack_outputs'][ output['OutputKey']] = output['OutputValue'] stack_resources = [] reslist = cfn.list_stack_resources( StackName=stack_params['StackName']) for res in reslist.get('StackResourceSummaries', []): stack_resources.append({ "logical_resource_id": res['LogicalResourceId'], "physical_resource_id": res.get('PhysicalResourceId', ''), "resource_type": res['ResourceType'], "last_updated_time": res['LastUpdatedTimestamp'], "status": res['ResourceStatus'], "status_reason": res.get('ResourceStatusReason') # can be blank, apparently }) result['stack_resources'] = stack_resources elif state == 'absent': # absent state is different because of the way delete_stack works. # problem is it it doesn't give an error if stack isn't found # so must describe the stack first try: stack = get_stack_facts(cfn, stack_params['StackName']) if not stack: result = {'changed': False, 'output': 'Stack not found.'} else: if stack_params.get('RoleARN') is None: cfn.delete_stack(StackName=stack_params['StackName']) else: cfn.delete_stack(StackName=stack_params['StackName'], RoleARN=stack_params['RoleARN']) result = stack_operation( cfn, stack_params['StackName'], 'DELETE', module.params.get('events_limit'), stack_params.get('ClientRequestToken', None)) except Exception as err: module.fail_json(msg=boto_exception(err), exception=traceback.format_exc()) module.exit_json(**result)
def create_changeset(module, stack_params, cfn, events_limit): if 'TemplateBody' not in stack_params and 'TemplateURL' not in stack_params: module.fail_json( msg="Either 'template' or 'template_url' is required.") if module.params['changeset_name'] is not None: stack_params['ChangeSetName'] = module.params['changeset_name'] # changesets don't accept ClientRequestToken parameters stack_params.pop('ClientRequestToken', None) try: changeset_name = build_changeset_name(stack_params) stack_params['ChangeSetName'] = changeset_name # Determine if this changeset already exists pending_changesets = list_changesets(cfn, stack_params['StackName']) if changeset_name in pending_changesets: warning = 'WARNING: %d pending changeset(s) exist(s) for this stack!' % len( pending_changesets) result = dict(changed=False, output='ChangeSet %s already exists.' % changeset_name, warnings=[warning]) else: cs = cfn.create_change_set(**stack_params) # Make sure we don't enter an infinite loop time_end = time.time() + 600 while time.time() < time_end: try: newcs = cfn.describe_change_set(ChangeSetName=cs['Id']) except botocore.exceptions.BotoCoreError as err: error_msg = boto_exception(err) module.fail_json(msg=error_msg) if newcs['Status'] == 'CREATE_PENDING' or newcs[ 'Status'] == 'CREATE_IN_PROGRESS': time.sleep(1) elif newcs[ 'Status'] == 'FAILED' and "The submitted information didn't contain changes" in newcs[ 'StatusReason']: cfn.delete_change_set(ChangeSetName=cs['Id']) result = dict( changed=False, output= 'The created Change Set did not contain any changes to this stack and was deleted.' ) # a failed change set does not trigger any stack events so we just want to # skip any further processing of result and just return it directly return result else: break # Lets not hog the cpu/spam the AWS API time.sleep(1) result = stack_operation(cfn, stack_params['StackName'], 'CREATE_CHANGESET', events_limit) result['change_set_id'] = cs['Id'] result['warnings'] = [ 'Created changeset named %s for stack %s' % (changeset_name, stack_params['StackName']), 'You can execute it using: aws cloudformation execute-change-set --change-set-name %s' % cs['Id'], 'NOTE that dependencies on this stack might fail due to pending changes!' ] except Exception as err: error_msg = boto_exception(err) if 'No updates are to be performed.' in error_msg: result = dict(changed=False, output='Stack is already up-to-date.') else: module.fail_json( msg="Failed to create change set: {0}".format(error_msg), exception=traceback.format_exc()) if not result: module.fail_json(msg="empty result") return result
def run(ecr, params): # type: (EcsEcr, dict, int) -> Tuple[bool, dict] result = {} try: name = params['name'] state = params['state'] policy_text = params['policy'] purge_policy = params['purge_policy'] registry_id = params['registry_id'] force_set_policy = params['force_set_policy'] image_tag_mutability = params['image_tag_mutability'].upper() lifecycle_policy_text = params['lifecycle_policy'] purge_lifecycle_policy = params['purge_lifecycle_policy'] # Parse policies, if they are given try: policy = policy_text and json.loads(policy_text) except ValueError: result['policy'] = policy_text result['msg'] = 'Could not parse policy' return False, result try: lifecycle_policy = \ lifecycle_policy_text and json.loads(lifecycle_policy_text) except ValueError: result['lifecycle_policy'] = lifecycle_policy_text result['msg'] = 'Could not parse lifecycle_policy' return False, result result['state'] = state result['created'] = False repo = ecr.get_repository(registry_id, name) if state == 'present': result['created'] = False if not repo: repo = ecr.create_repository(registry_id, name, image_tag_mutability) result['changed'] = True result['created'] = True else: repo = ecr.put_image_tag_mutability(registry_id, name, image_tag_mutability) result['repository'] = repo if purge_lifecycle_policy: original_lifecycle_policy = \ ecr.get_lifecycle_policy(registry_id, name) result['lifecycle_policy'] = None if original_lifecycle_policy: ecr.purge_lifecycle_policy(registry_id, name) result['changed'] = True elif lifecycle_policy_text is not None: try: lifecycle_policy = sort_json_policy_dict(lifecycle_policy) result['lifecycle_policy'] = lifecycle_policy original_lifecycle_policy = ecr.get_lifecycle_policy( registry_id, name) if original_lifecycle_policy: original_lifecycle_policy = sort_json_policy_dict( original_lifecycle_policy) if original_lifecycle_policy != lifecycle_policy: ecr.put_lifecycle_policy(registry_id, name, lifecycle_policy_text) result['changed'] = True except Exception: # Some failure w/ the policy. It's helpful to know what the # policy is. result['lifecycle_policy'] = lifecycle_policy_text raise if purge_policy: original_policy = ecr.get_repository_policy(registry_id, name) result['policy'] = None if original_policy: ecr.delete_repository_policy(registry_id, name) result['changed'] = True elif policy_text is not None: try: # Sort any lists containing only string types policy = sort_lists_of_strings(policy) result['policy'] = policy original_policy = ecr.get_repository_policy( registry_id, name) if original_policy: original_policy = sort_lists_of_strings( original_policy) if compare_policies(original_policy, policy): ecr.set_repository_policy(registry_id, name, policy_text, force_set_policy) result['changed'] = True except Exception: # Some failure w/ the policy. It's helpful to know what the # policy is. result['policy'] = policy_text raise elif state == 'absent': result['name'] = name if repo: ecr.delete_repository(registry_id, name) result['changed'] = True except Exception as err: msg = str(err) if isinstance(err, ClientError): msg = boto_exception(err) result['msg'] = msg result['exception'] = traceback.format_exc() return False, result if ecr.skipped: result['skipped'] = True if ecr.changed: result['changed'] = True return True, result