From 2616f9d713d5db987588337625470d42b8bc6850 Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 22 Dec 2017 05:51:46 +1100 Subject: [PATCH] [cloud] Add encryption support to efs module (#32815) * Add encryption support to efs module * Update the exception handling in AWS EFS module --- lib/ansible/modules/cloud/amazon/efs.py | 110 ++++++++++++++++++------ 1 file changed, 86 insertions(+), 24 deletions(-) diff --git a/lib/ansible/modules/cloud/amazon/efs.py b/lib/ansible/modules/cloud/amazon/efs.py index 1b4e118a5c0..e2a42f282cc 100644 --- a/lib/ansible/modules/cloud/amazon/efs.py +++ b/lib/ansible/modules/cloud/amazon/efs.py @@ -23,6 +23,29 @@ author: - "Ryan Sydnor (@ryansydnor)" - "Artem Kazakov (@akazakov)" options: + encrypt: + description: + - A boolean value that, if true, creates an encrypted file system. This can not be modfied after the file + system is created. + required: false + default: false + choices: ['yes', 'no'] + version_added: 2.5 + kms_key_id: + description: + - The id of the AWS KMS CMK that will be used to protect the encrypted file system. This parameter is only + required if you want to use a non-default CMK. If this parameter is not specified, the default CMK for + Amazon EFS is used. The key id can be Key ID, Key ID ARN, Key Alias or Key Alias ARN. + required: false + version_added: 2.5 + purge_tags: + description: + - If yes, existing tags will be purged from the resource to match exactly what is defined by I(tags) parameter. If the I(tags) parameter + is not set then tags will not be modified. + required: false + default: yes + choices: [ 'yes', 'no' ] + version_added: 2.5 state: description: - Allows to create, search and destroy Amazon EFS file system @@ -74,6 +97,7 @@ options: default: 0 extends_documentation_fragment: - aws + - ec2 ''' EXAMPLES = ''' @@ -193,15 +217,18 @@ tags: from time import sleep from time import time as timestamp +import traceback try: - from botocore.exceptions import ClientError + from botocore.exceptions import ClientError, BotoCoreError except ImportError as e: pass # Taken care of by ec2.HAS_BOTO3 +from ansible.module_utils._text import to_native from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.ec2 import (HAS_BOTO3, boto3_conn, camel_dict_to_snake_dict, - ec2_argument_spec, get_aws_connection_info) + ec2_argument_spec, get_aws_connection_info, ansible_dict_to_boto3_tag_list, + compare_aws_tags, boto3_tag_list_to_ansible_dict) def _index_by_key(key, items): @@ -221,6 +248,8 @@ class EFSConnection(object): self.connection = boto3_conn(module, conn_type='client', resource='efs', region=region, **aws_connect_params) + + self.module = module self.region = region self.wait = module.params.get('wait') self.wait_timeout = module.params.get('wait_timeout') @@ -256,12 +285,8 @@ class EFSConnection(object): """ Returns tag list for selected instance of EFS """ - tags = iterate_all( - 'Tags', - self.connection.describe_tags, - **kwargs - ) - return dict((tag['Key'], tag['Value']) for tag in tags) + tags = self.connection.describe_tags(**kwargs)['Tags'] + return tags def get_mount_targets(self, **kwargs): """ @@ -331,19 +356,34 @@ class EFSConnection(object): return list(targets) - def create_file_system(self, name, performance_mode): + def create_file_system(self, name, performance_mode, encrypt, kms_key_id): """ Creates new filesystem with selected name """ changed = False state = self.get_file_system_state(name) + params = {} + params['CreationToken'] = name + params['PerformanceMode'] = performance_mode + if encrypt: + params['Encrypted'] = encrypt + if kms_key_id is not None: + params['KmsKeyId'] = kms_key_id + if state in [self.STATE_DELETING, self.STATE_DELETED]: wait_for( lambda: self.get_file_system_state(name), self.STATE_DELETED ) - self.connection.create_file_system(CreationToken=name, PerformanceMode=performance_mode) - changed = True + try: + self.connection.create_file_system(**params) + changed = True + except ClientError as e: + self.module.fail_json(msg="Unable to create file system: {0}".format(to_native(e)), + exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) + except BotoCoreError as e: + self.module.fail_json(msg="Unable to create file system: {0}".format(to_native(e)), + exception=traceback.format_exc()) # we always wait for the state to be available when creating. # if we try to take any actions on the file system before it's available @@ -356,7 +396,7 @@ class EFSConnection(object): return changed - def converge_file_system(self, name, tags, targets): + def converge_file_system(self, name, tags, purge_tags, targets): """ Change attributes (mount targets and tags) of filesystem by name """ @@ -364,20 +404,36 @@ class EFSConnection(object): fs_id = self.get_file_system_id(name) if tags is not None: - tags_to_create, _, tags_to_delete = dict_diff(self.get_tags(FileSystemId=fs_id), tags) + tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(self.get_tags(FileSystemId=fs_id)), tags, purge_tags) if tags_to_delete: - self.connection.delete_tags( - FileSystemId=fs_id, - TagKeys=[item[0] for item in tags_to_delete] - ) + try: + self.connection.delete_tags( + FileSystemId=fs_id, + TagKeys=tags_to_delete + ) + except ClientError as e: + self.module.fail_json(msg="Unable to delete tags: {0}".format(to_native(e)), + exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) + except BotoCoreError as e: + self.module.fail_json(msg="Unable to delete tags: {0}".format(to_native(e)), + exception=traceback.format_exc()) + result = True - if tags_to_create: - self.connection.create_tags( - FileSystemId=fs_id, - Tags=[{'Key': item[0], 'Value': item[1]} for item in tags_to_create] - ) + if tags_need_modify: + try: + self.connection.create_tags( + FileSystemId=fs_id, + Tags=ansible_dict_to_boto3_tag_list(tags_need_modify) + ) + except ClientError as e: + self.module.fail_json(msg="Unable to create tags: {0}".format(to_native(e)), + exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) + except BotoCoreError as e: + self.module.fail_json(msg="Unable to create tags: {0}".format(to_native(e)), + exception=traceback.format_exc()) + result = True if targets is not None: @@ -561,7 +617,10 @@ def main(): """ argument_spec = ec2_argument_spec() argument_spec.update(dict( + encrypt=dict(required=False, type="bool", default=False), state=dict(required=False, type='str', choices=["present", "absent"], default="present"), + kms_key_id=dict(required=False, type='str', default=None), + purge_tags=dict(default=True, type='bool'), id=dict(required=False, type='str', default=None), name=dict(required=False, type='str', default=None), tags=dict(required=False, type="dict", default={}), @@ -592,7 +651,10 @@ def main(): 'general_purpose': 'generalPurpose', 'max_io': 'maxIO' } + encrypt = module.params.get('encrypt') + kms_key_id = module.params.get('kms_key_id') performance_mode = performance_mode_translations[module.params.get('performance_mode')] + purge_tags = module.params.get('purge_tags') changed = False state = str(module.params.get('state')).lower() @@ -601,8 +663,8 @@ def main(): if not name: module.fail_json(msg='Name parameter is required for create') - changed = connection.create_file_system(name, performance_mode) - changed = connection.converge_file_system(name=name, tags=tags, targets=targets) or changed + changed = connection.create_file_system(name, performance_mode, encrypt, kms_key_id) + changed = connection.converge_file_system(name=name, tags=tags, purge_tags=purge_tags, targets=targets) or changed result = first_or_default(connection.get_file_systems(CreationToken=name)) elif state == 'absent':