def _set_topic_attrs(self): changed = False try: topic_attributes = self.connection.get_topic_attributes( TopicArn=self.topic_arn)['Attributes'] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self.module.fail_json_aws( e, msg="Couldn't get topic attributes for topic %s" % self.topic_arn) if self.display_name and self.display_name != topic_attributes[ 'DisplayName']: changed = True self.attributes_set.append('display_name') if not self.check_mode: try: self.connection.set_topic_attributes( TopicArn=self.topic_arn, AttributeName='DisplayName', AttributeValue=self.display_name) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self.module.fail_json_aws(e, msg="Couldn't set display name") if self.policy and compare_policies( self.policy, json.loads(topic_attributes['Policy'])): changed = True self.attributes_set.append('policy') if not self.check_mode: try: self.connection.set_topic_attributes( TopicArn=self.topic_arn, AttributeName='Policy', AttributeValue=json.dumps(self.policy)) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self.module.fail_json_aws(e, msg="Couldn't set topic policy") if self.delivery_policy and ( 'DeliveryPolicy' not in topic_attributes or compare_policies( self.delivery_policy, json.loads(topic_attributes['DeliveryPolicy']))): changed = True self.attributes_set.append('delivery_policy') if not self.check_mode: try: self.connection.set_topic_attributes( TopicArn=self.topic_arn, AttributeName='DeliveryPolicy', AttributeValue=json.dumps(self.delivery_policy)) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: self.module.fail_json_aws( e, msg="Couldn't set topic delivery policy") return changed
def test_compare_small_policies_without_differences(self): """ Testing two small policies which are identical except for: * The contents of the statement are in different orders * The second policy contains a list of length one whereas in the first it is a string """ self.assertFalse( compare_policies(self.small_policy_one, self.small_policy_two))
def create_or_update_bucket_cors(connection, module): name = module.params.get("name") rules = module.params.get("rules", []) changed = False try: current_camel_rules = connection.get_bucket_cors( Bucket=name)['CORSRules'] except ClientError: current_camel_rules = [] new_camel_rules = snake_dict_to_camel_dict(rules, capitalize_first=True) # compare_policies() takes two dicts and makes them hashable for comparison if compare_policies(new_camel_rules, current_camel_rules): changed = True if changed: try: cors = connection.put_bucket_cors( Bucket=name, CORSConfiguration={'CORSRules': new_camel_rules}) except ClientError as e: module.fail_json( msg="Unable to update CORS for bucket {0}: {1}".format( name, to_native(e)), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) except BotoCoreError as e: module.fail_json(msg=to_native(e), exception=traceback.format_exc()) module.exit_json(changed=changed, name=name, rules=rules)
def test_compare_large_policies_without_differences(self): """ Testing two larger policies which are identical except for: * The statements are in different orders * The contents of the statements are also in different orders * The second contains a list of length one for the Principal whereas in the first it is a string """ self.assertFalse( compare_policies(self.larger_policy_one, self.larger_policy_two))
def main(): argument_spec = dict(name=dict(required=True, type='str'), role_arn=dict(required=True, type='str'), artifact_store=dict(required=True, type='dict'), stages=dict(required=True, type='list'), version=dict(type='int'), state=dict(choices=['present', 'absent'], default='present')) module = AnsibleAWSModule(argument_spec=argument_spec) client_conn = module.client('codepipeline') state = module.params.get('state') changed = False # Determine if the CodePipeline exists found_code_pipeline = describe_pipeline(client=client_conn, name=module.params['name'], version=module.params['version'], module=module) pipeline_result = {} if state == 'present': if 'pipeline' in found_code_pipeline: pipeline_dict = copy.deepcopy(found_code_pipeline['pipeline']) # Update dictionary with provided module params: pipeline_dict['roleArn'] = module.params['role_arn'] pipeline_dict['artifactStore'] = module.params['artifact_store'] pipeline_dict['stages'] = module.params['stages'] if module.params['version'] is not None: pipeline_dict['version'] = module.params['version'] pipeline_result = update_pipeline(client=client_conn, pipeline_dict=pipeline_dict, module=module) if compare_policies(found_code_pipeline['pipeline'], pipeline_result['pipeline']): changed = True else: pipeline_result = create_pipeline( client=client_conn, name=module.params['name'], role_arn=module.params['role_arn'], artifact_store=module.params['artifact_store'], stages=module.params['stages'], version=module.params['version'], module=module) changed = True elif state == 'absent': if found_code_pipeline: pipeline_result = delete_pipeline(client=client_conn, name=module.params['name'], module=module) changed = True module.exit_json(changed=changed, **camel_dict_to_snake_dict(pipeline_result))
def create(self): matching_policies = [] policy_doc = self.get_policy_text() policy_match = False for pol in self.list(): if not compare_policies(self.get(pol), policy_doc): matching_policies.append(pol) policy_match = True if (self.policy_name not in matching_policies) and not (self.skip_duplicates and policy_match): self.put(policy_doc)
def find_missing(self, update, current_condition): missing = [] for desired in update['Updates']: found = False desired_condition = desired[self.conditiontuple] current_conditions = current_condition[self.conditiontuples] for condition in current_conditions: if not compare_policies(condition, desired_condition): found = True if not found: missing.append(desired) return missing
def get_or_create_policy_version(module, iam, policy, policy_document): try: versions = iam.list_policy_versions( PolicyArn=policy['Arn'])['Versions'] except botocore.exceptions.ClientError as e: module.fail_json(msg="Couldn't list policy versions: %s" % str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) for v in versions: try: document = iam.get_policy_version( PolicyArn=policy['Arn'], VersionId=v['VersionId'])['PolicyVersion']['Document'] except botocore.exceptions.ClientError as e: module.fail_json(msg="Couldn't get policy version %s: %s" % (v['VersionId'], str(e)), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # If the current policy matches the existing one if not compare_policies(document, json.loads( to_native(policy_document))): return v, False # No existing version so create one # There is a service limit (typically 5) of policy versions. # # Rather than assume that it is 5, we'll try to create the policy # and if that doesn't work, delete the oldest non default policy version # and try again. try: version = iam.create_policy_version( PolicyArn=policy['Arn'], PolicyDocument=policy_document)['PolicyVersion'] return version, True except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == 'LimitExceeded': delete_oldest_non_default_version(module, iam, policy) try: version = iam.create_policy_version( PolicyArn=policy['Arn'], PolicyDocument=policy_document)['PolicyVersion'] return version, True except botocore.exceptions.ClientError as second_e: e = second_e # Handle both when the exception isn't LimitExceeded or # the second attempt still failed module.fail_json(msg="Couldn't create policy version: %s" % str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
def create_or_update_identity_policy(connection, module): identity = module.params.get('identity') policy_name = module.params.get('policy_name') required_policy = module.params.get('policy') required_policy_dict = json.loads(required_policy) changed = False policy = get_identity_policy(connection, module, identity, policy_name) policy_dict = json.loads(policy) if policy else None if compare_policies(policy_dict, required_policy_dict): changed = True try: if not module.check_mode: connection.put_identity_policy(Identity=identity, PolicyName=policy_name, Policy=required_policy, aws_retry=True) except (BotoCoreError, ClientError) as e: module.fail_json_aws( e, msg='Failed to put identity policy {policy}'.format( policy=policy_name)) # Load the list of applied policies to include in the response. # In principle we should be able to just return the response, but given # eventual consistency behaviours in AWS it's plausible that we could # end up with a list that doesn't contain the policy we just added. # So out of paranoia check for this case and if we're missing the policy # just make sure it's present. # # As a nice side benefit this also means the return is correct in check mode try: policies_present = connection.list_identity_policies( Identity=identity, aws_retry=True)['PolicyNames'] except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg='Failed to list identity policies') if policy_name is not None and policy_name not in policies_present: policies_present = list(policies_present) policies_present.append(policy_name) module.exit_json( changed=changed, policies=policies_present, )
def wait_policy_is_applied(module, s3_client, bucket_name, expected_policy, should_fail=True): for dummy in range(0, 12): try: current_policy = get_bucket_policy(s3_client, bucket_name) except (ClientError, BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to get bucket policy") if compare_policies(current_policy, expected_policy): time.sleep(5) else: return current_policy if should_fail: module.fail_json( msg="Bucket policy failed to apply in the expected time") else: return None
def test_compare_larger_policies_with_difference(self): """ Testing two larger policies which are identical except for: * one different principal """ self.assertTrue( compare_policies(self.larger_policy_two, self.larger_policy_three))
def update_key(connection, module, key): changed = False alias = module.params.get('alias') key_id = key['key_arn'] if alias: changed = update_alias(connection, module, key_id, alias) or changed if key['key_state'] == 'PendingDeletion': try: connection.cancel_key_deletion(KeyId=key_id) # key is disabled after deletion cancellation # set this so that ensure_enabled_disabled works correctly key['key_state'] = 'Disabled' changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to cancel key deletion") changed = ensure_enabled_disabled(connection, module, key) or changed description = module.params.get('description') # don't update description if description is not set # (means you can't remove a description completely) if description and key['description'] != description: try: connection.update_key_description(KeyId=key_id, Description=description) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to update key description") desired_tags = module.params.get('tags') to_add, to_remove = compare_aws_tags(key['tags'], desired_tags, module.params.get('purge_tags')) if to_remove: try: connection.untag_resource(KeyId=key_id, TagKeys=to_remove) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to remove or update tag") if to_add: try: connection.tag_resource(KeyId=key_id, Tags=[{ 'TagKey': tag_key, 'TagValue': desired_tags[tag_key] } for tag_key in to_add]) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to add tag to key") # Update existing policy before trying to tweak grants if module.params.get('policy'): policy = module.params.get('policy') try: keyret = connection.get_key_policy(KeyId=key_id, PolicyName='default') except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # If we can't fetch the current policy assume we're making a change # Could occur if we have PutKeyPolicy without GetKeyPolicy original_policy = {} original_policy = json.loads(keyret['Policy']) try: new_policy = json.loads(policy) except ValueError as e: module.fail_json_aws(e, msg="Unable to parse new policy as JSON") if compare_policies(original_policy, new_policy): changed = True if not module.check_mode: try: connection.put_key_policy(KeyId=key_id, PolicyName='default', Policy=policy) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to update key policy") desired_grants = module.params.get('grants') existing_grants = key['grants'] to_add, to_remove = compare_grants(existing_grants, desired_grants, module.params.get('purge_grants')) if to_remove: for grant in to_remove: try: connection.retire_grant(KeyId=key_id, GrantId=grant['grant_id']) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to retire grant") if to_add: for grant in to_add: grant_params = convert_grant_params(grant, key) try: connection.create_grant(**grant_params) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to create grant") # make results consistent with kms_facts before returning result = get_key_details(connection, module, key_id) module.exit_json(changed=changed, **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
def test_compare_numeric_policy_number_and_string_are_equal(self): """ Testing two policies one using a quoted number, the other an int """ self.assertFalse( compare_policies(self.numeric_policy_string, self.numeric_policy_number))
def test_compare_boolean_policy_bool_and_string_are_equal(self): """ Testing two policies one using a quoted boolean, the other a bool """ self.assertFalse( compare_policies(self.bool_policy_string, self.bool_policy_bool))
def test_compare_smaller_policy_with_larger(self): """ Testing two policies of different sizes """ self.assertTrue( compare_policies(self.larger_policy_one, self.small_policy_one))
def create_or_update_bucket(s3_client, module, location): policy = module.params.get("policy") name = module.params.get("name") requester_pays = module.params.get("requester_pays") tags = module.params.get("tags") purge_tags = module.params.get("purge_tags") versioning = module.params.get("versioning") encryption = module.params.get("encryption") encryption_key_id = module.params.get("encryption_key_id") changed = False result = {} try: bucket_is_present = bucket_exists(s3_client, name) except EndpointConnectionError as e: module.fail_json_aws(e, msg="Invalid endpoint provided: %s" % to_text(e)) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Failed to check bucket presence") if not bucket_is_present: try: bucket_changed = create_bucket(s3_client, name, location) s3_client.get_waiter('bucket_exists').wait(Bucket=name) changed = changed or bucket_changed except WaiterError as e: module.fail_json_aws( e, msg= 'An error occurred waiting for the bucket to become available') except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Failed while creating bucket") # Versioning try: versioning_status = get_bucket_versioning(s3_client, name) except BotoCoreError as exp: module.fail_json_aws(exp, msg="Failed to get bucket versioning") except ClientError as exp: if exp.response['Error'][ 'Code'] != 'NotImplemented' or versioning is not None: module.fail_json_aws(exp, msg="Failed to get bucket versioning") else: if versioning is not None: required_versioning = None if versioning and versioning_status.get('Status') != "Enabled": required_versioning = 'Enabled' elif not versioning and versioning_status.get( 'Status') == "Enabled": required_versioning = 'Suspended' if required_versioning: try: put_bucket_versioning(s3_client, name, required_versioning) changed = True except (BotoCoreError, ClientError) as e: module.fail_json_aws( e, msg="Failed to update bucket versioning") versioning_status = wait_versioning_is_applied( module, s3_client, name, required_versioning) # This output format is there to ensure compatibility with previous versions of the module result['versioning'] = { 'Versioning': versioning_status.get('Status', 'Disabled'), 'MfaDelete': versioning_status.get('MFADelete', 'Disabled'), } # Requester pays try: requester_pays_status = get_bucket_request_payment(s3_client, name) except BotoCoreError as exp: module.fail_json_aws(exp, msg="Failed to get bucket request payment") except ClientError as exp: if exp.response['Error'][ 'Code'] != 'NotImplemented' or requester_pays is not None: module.fail_json_aws(exp, msg="Failed to get bucket request payment") else: if requester_pays: payer = 'Requester' if requester_pays else 'BucketOwner' if requester_pays_status != payer: put_bucket_request_payment(s3_client, name, payer) requester_pays_status = wait_payer_is_applied( module, s3_client, name, payer, should_fail=False) if requester_pays_status is None: # We have seen that it happens quite a lot of times that the put request was not taken into # account, so we retry one more time put_bucket_request_payment(s3_client, name, payer) requester_pays_status = wait_payer_is_applied( module, s3_client, name, payer, should_fail=True) changed = True result['requester_pays'] = requester_pays # Policy try: current_policy = get_bucket_policy(s3_client, name) except BotoCoreError as exp: module.fail_json_aws(exp, msg="Failed to get bucket policy") except ClientError as exp: if exp.response['Error'][ 'Code'] != 'NotImplemented' or policy is not None: module.fail_json_aws(exp, msg="Failed to get bucket policy") else: if policy is not None: if isinstance(policy, string_types): policy = json.loads(policy) if not policy and current_policy: try: delete_bucket_policy(s3_client, name) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Failed to delete bucket policy") current_policy = wait_policy_is_applied( module, s3_client, name, policy) changed = True elif compare_policies(current_policy, policy): try: put_bucket_policy(s3_client, name, policy) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Failed to update bucket policy") current_policy = wait_policy_is_applied(module, s3_client, name, policy, should_fail=False) if current_policy is None: # As for request payement, it happens quite a lot of times that the put request was not taken into # account, so we retry one more time put_bucket_policy(s3_client, name, policy) current_policy = wait_policy_is_applied(module, s3_client, name, policy, should_fail=True) changed = True result['policy'] = current_policy # Tags try: current_tags_dict = get_current_bucket_tags_dict(s3_client, name) except BotoCoreError as exp: module.fail_json_aws(exp, msg="Failed to get bucket tags") except ClientError as exp: if exp.response['Error'][ 'Code'] != 'NotImplemented' or tags is not None: module.fail_json_aws(exp, msg="Failed to get bucket tags") else: if tags is not None: # Tags are always returned as text tags = dict((to_text(k), to_text(v)) for k, v in tags.items()) if not purge_tags: # Ensure existing tags that aren't updated by desired tags remain current_copy = current_tags_dict.copy() current_copy.update(tags) tags = current_copy if current_tags_dict != tags: if tags: try: put_bucket_tagging(s3_client, name, tags) except (BotoCoreError, ClientError) as e: module.fail_json_aws( e, msg="Failed to update bucket tags") else: if purge_tags: try: delete_bucket_tagging(s3_client, name) except (BotoCoreError, ClientError) as e: module.fail_json_aws( e, msg="Failed to delete bucket tags") current_tags_dict = wait_tags_are_applied( module, s3_client, name, tags) changed = True result['tags'] = current_tags_dict # Encryption if hasattr(s3_client, "get_bucket_encryption"): try: current_encryption = get_bucket_encryption(s3_client, name) except (ClientError, BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to get bucket encryption") elif encryption is not None: module.fail_json( msg="Using bucket encryption requires botocore version >= 1.7.41") if encryption is not None: current_encryption_algorithm = current_encryption.get( 'SSEAlgorithm') if current_encryption else None current_encryption_key = current_encryption.get( 'KMSMasterKeyID') if current_encryption else None if encryption == 'none' and current_encryption_algorithm is not None: try: delete_bucket_encryption(s3_client, name) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Failed to delete bucket encryption") current_encryption = wait_encryption_is_applied( module, s3_client, name, None) changed = True elif encryption != 'none' and ( encryption != current_encryption_algorithm) or ( encryption == 'aws:kms' and current_encryption_key != encryption_key_id): expected_encryption = {'SSEAlgorithm': encryption} if encryption == 'aws:kms' and encryption_key_id is not None: expected_encryption.update( {'KMSMasterKeyID': encryption_key_id}) try: put_bucket_encryption(s3_client, name, expected_encryption) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e, msg="Failed to set bucket encryption") current_encryption = wait_encryption_is_applied( module, s3_client, name, expected_encryption) changed = True result['encryption'] = current_encryption module.exit_json(changed=changed, name=name, **result)