1#!/usr/bin/python
2# Copyright: Ansible Project
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
5from __future__ import absolute_import, division, print_function
6__metaclass__ = type
7
8
9ANSIBLE_METADATA = {'metadata_version': '1.1',
10                    'status': ['preview'],
11                    'supported_by': 'community'}
12
13
14DOCUMENTATION = '''
15---
16module: efs
17short_description: create and maintain EFS file systems
18description:
19    - Module allows create, search and destroy Amazon EFS file systems
20version_added: "2.2"
21requirements: [ boto3 ]
22author:
23    - "Ryan Sydnor (@ryansydnor)"
24    - "Artem Kazakov (@akazakov)"
25options:
26    encrypt:
27        description:
28            - A boolean value that, if true, creates an encrypted file system. This can not be modified after the file
29              system is created.
30        type: bool
31        default: 'no'
32        version_added: 2.5
33    kms_key_id:
34        description:
35            - The id of the AWS KMS CMK that will be used to protect the encrypted file system. This parameter is only
36              required if you want to use a non-default CMK. If this parameter is not specified, the default CMK for
37              Amazon EFS is used. The key id can be Key ID, Key ID ARN, Key Alias or Key Alias ARN.
38        version_added: 2.5
39    purge_tags:
40        description:
41            - 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
42              is not set then tags will not be modified.
43        type: bool
44        default: 'yes'
45        version_added: 2.5
46    state:
47        description:
48            - Allows to create, search and destroy Amazon EFS file system
49        default: 'present'
50        choices: ['present', 'absent']
51    name:
52        description:
53            - Creation Token of Amazon EFS file system. Required for create and update. Either name or ID required for delete.
54    id:
55        description:
56            - ID of Amazon EFS. Either name or ID required for delete.
57    performance_mode:
58        description:
59            - File system's performance mode to use. Only takes effect during creation.
60        default: 'general_purpose'
61        choices: ['general_purpose', 'max_io']
62    tags:
63        description:
64            - "List of tags of Amazon EFS. Should be defined as dictionary
65              In case of 'present' state with list of tags and existing EFS (matched by 'name'), tags of EFS will be replaced with provided data."
66    targets:
67        description:
68            - "List of mounted targets. It should be a list of dictionaries, every dictionary should include next attributes:
69                   - subnet_id - Mandatory. The ID of the subnet to add the mount target in.
70                   - ip_address - Optional. A valid IPv4 address within the address range of the specified subnet.
71                   - security_groups - Optional. List of security group IDs, of the form 'sg-xxxxxxxx'. These must be for the same VPC as subnet specified
72               This data may be modified for existing EFS using state 'present' and new list of mount targets."
73    throughput_mode:
74        description:
75            - The throughput_mode for the file system to be created.
76            - Requires botocore >= 1.10.57
77        choices: ['bursting', 'provisioned']
78        version_added: 2.8
79    provisioned_throughput_in_mibps:
80        description:
81            - If the throughput_mode is provisioned, select the amount of throughput to provisioned in Mibps.
82            - Requires botocore >= 1.10.57
83        type: float
84        version_added: 2.8
85    wait:
86        description:
87            - "In case of 'present' state should wait for EFS 'available' life cycle state (of course, if current state not 'deleting' or 'deleted')
88               In case of 'absent' state should wait for EFS 'deleted' life cycle state"
89        type: bool
90        default: 'no'
91    wait_timeout:
92        description:
93            - How long the module should wait (in seconds) for desired state before returning. Zero means wait as long as necessary.
94        default: 0
95
96extends_documentation_fragment:
97    - aws
98    - ec2
99'''
100
101EXAMPLES = '''
102# EFS provisioning
103- efs:
104    state: present
105    name: myTestEFS
106    tags:
107        name: myTestNameTag
108        purpose: file-storage
109    targets:
110        - subnet_id: subnet-748c5d03
111          security_groups: [ "sg-1a2b3c4d" ]
112
113# Modifying EFS data
114- efs:
115    state: present
116    name: myTestEFS
117    tags:
118        name: myAnotherTestTag
119    targets:
120        - subnet_id: subnet-7654fdca
121          security_groups: [ "sg-4c5d6f7a" ]
122
123# Deleting EFS
124- efs:
125    state: absent
126    name: myTestEFS
127'''
128
129RETURN = '''
130creation_time:
131    description: timestamp of creation date
132    returned: always
133    type: str
134    sample: "2015-11-16 07:30:57-05:00"
135creation_token:
136    description: EFS creation token
137    returned: always
138    type: str
139    sample: "console-88609e04-9a0e-4a2e-912c-feaa99509961"
140file_system_id:
141    description: ID of the file system
142    returned: always
143    type: str
144    sample: "fs-xxxxxxxx"
145life_cycle_state:
146    description: state of the EFS file system
147    returned: always
148    type: str
149    sample: "creating, available, deleting, deleted"
150mount_point:
151    description: url of file system with leading dot from the time when AWS EFS required to add a region suffix to the address
152    returned: always
153    type: str
154    sample: ".fs-xxxxxxxx.efs.us-west-2.amazonaws.com:/"
155filesystem_address:
156    description: url of file system valid for use with mount
157    returned: always
158    type: str
159    sample: "fs-xxxxxxxx.efs.us-west-2.amazonaws.com:/"
160mount_targets:
161    description: list of mount targets
162    returned: always
163    type: list
164    sample:
165        [
166            {
167                "file_system_id": "fs-a7ad440e",
168                "ip_address": "172.31.17.173",
169                "life_cycle_state": "available",
170                "mount_target_id": "fsmt-d8907871",
171                "network_interface_id": "eni-6e387e26",
172                "owner_id": "740748460359",
173                "security_groups": [
174                    "sg-a30b22c6"
175                ],
176                "subnet_id": "subnet-e265c895"
177            },
178            ...
179        ]
180name:
181    description: name of the file system
182    returned: always
183    type: str
184    sample: "my-efs"
185number_of_mount_targets:
186    description: the number of targets mounted
187    returned: always
188    type: int
189    sample: 3
190owner_id:
191    description: AWS account ID of EFS owner
192    returned: always
193    type: str
194    sample: "XXXXXXXXXXXX"
195size_in_bytes:
196    description: size of the file system in bytes as of a timestamp
197    returned: always
198    type: dict
199    sample:
200        {
201            "timestamp": "2015-12-21 13:59:59-05:00",
202            "value": 12288
203        }
204performance_mode:
205    description: performance mode of the file system
206    returned: always
207    type: str
208    sample: "generalPurpose"
209tags:
210    description: tags on the efs instance
211    returned: always
212    type: dict
213    sample:
214        {
215            "name": "my-efs",
216            "key": "Value"
217        }
218
219'''
220
221from time import sleep
222from time import time as timestamp
223import traceback
224
225try:
226    from botocore.exceptions import ClientError, BotoCoreError
227except ImportError as e:
228    pass  # Taken care of by ec2.HAS_BOTO3
229
230from ansible.module_utils._text import to_native
231from ansible.module_utils.basic import AnsibleModule
232from ansible.module_utils.ec2 import (HAS_BOTO3, boto3_conn, camel_dict_to_snake_dict,
233                                      ec2_argument_spec, get_aws_connection_info, ansible_dict_to_boto3_tag_list,
234                                      compare_aws_tags, boto3_tag_list_to_ansible_dict)
235
236
237def _index_by_key(key, items):
238    return dict((item[key], item) for item in items)
239
240
241class EFSConnection(object):
242
243    DEFAULT_WAIT_TIMEOUT_SECONDS = 0
244
245    STATE_CREATING = 'creating'
246    STATE_AVAILABLE = 'available'
247    STATE_DELETING = 'deleting'
248    STATE_DELETED = 'deleted'
249
250    def __init__(self, module, region, **aws_connect_params):
251        self.connection = boto3_conn(module, conn_type='client',
252                                     resource='efs', region=region,
253                                     **aws_connect_params)
254
255        self.module = module
256        self.region = region
257        self.wait = module.params.get('wait')
258        self.wait_timeout = module.params.get('wait_timeout')
259
260    def get_file_systems(self, **kwargs):
261        """
262         Returns generator of file systems including all attributes of FS
263        """
264        items = iterate_all(
265            'FileSystems',
266            self.connection.describe_file_systems,
267            **kwargs
268        )
269        for item in items:
270            item['Name'] = item['CreationToken']
271            item['CreationTime'] = str(item['CreationTime'])
272            """
273            In the time when MountPoint was introduced there was a need to add a suffix of network path before one could use it
274            AWS updated it and now there is no need to add a suffix. MountPoint is left for back-compatibility purpose
275            And new FilesystemAddress variable is introduced for direct use with other modules (e.g. mount)
276            AWS documentation is available here:
277            https://docs.aws.amazon.com/efs/latest/ug/gs-step-three-connect-to-ec2-instance.html
278            """
279            item['MountPoint'] = '.%s.efs.%s.amazonaws.com:/' % (item['FileSystemId'], self.region)
280            item['FilesystemAddress'] = '%s.efs.%s.amazonaws.com:/' % (item['FileSystemId'], self.region)
281            if 'Timestamp' in item['SizeInBytes']:
282                item['SizeInBytes']['Timestamp'] = str(item['SizeInBytes']['Timestamp'])
283            if item['LifeCycleState'] == self.STATE_AVAILABLE:
284                item['Tags'] = self.get_tags(FileSystemId=item['FileSystemId'])
285                item['MountTargets'] = list(self.get_mount_targets(FileSystemId=item['FileSystemId']))
286            else:
287                item['Tags'] = {}
288                item['MountTargets'] = []
289            yield item
290
291    def get_tags(self, **kwargs):
292        """
293         Returns tag list for selected instance of EFS
294        """
295        tags = self.connection.describe_tags(**kwargs)['Tags']
296        return tags
297
298    def get_mount_targets(self, **kwargs):
299        """
300         Returns mount targets for selected instance of EFS
301        """
302        targets = iterate_all(
303            'MountTargets',
304            self.connection.describe_mount_targets,
305            **kwargs
306        )
307        for target in targets:
308            if target['LifeCycleState'] == self.STATE_AVAILABLE:
309                target['SecurityGroups'] = list(self.get_security_groups(
310                    MountTargetId=target['MountTargetId']
311                ))
312            else:
313                target['SecurityGroups'] = []
314            yield target
315
316    def get_security_groups(self, **kwargs):
317        """
318         Returns security groups for selected instance of EFS
319        """
320        return iterate_all(
321            'SecurityGroups',
322            self.connection.describe_mount_target_security_groups,
323            **kwargs
324        )
325
326    def get_file_system_id(self, name):
327        """
328         Returns ID of instance by instance name
329        """
330        info = first_or_default(iterate_all(
331            'FileSystems',
332            self.connection.describe_file_systems,
333            CreationToken=name
334        ))
335        return info and info['FileSystemId'] or None
336
337    def get_file_system_state(self, name, file_system_id=None):
338        """
339         Returns state of filesystem by EFS id/name
340        """
341        info = first_or_default(iterate_all(
342            'FileSystems',
343            self.connection.describe_file_systems,
344            CreationToken=name,
345            FileSystemId=file_system_id
346        ))
347        return info and info['LifeCycleState'] or self.STATE_DELETED
348
349    def get_mount_targets_in_state(self, file_system_id, states=None):
350        """
351         Returns states of mount targets of selected EFS with selected state(s) (optional)
352        """
353        targets = iterate_all(
354            'MountTargets',
355            self.connection.describe_mount_targets,
356            FileSystemId=file_system_id
357        )
358
359        if states:
360            if not isinstance(states, list):
361                states = [states]
362            targets = filter(lambda target: target['LifeCycleState'] in states, targets)
363
364        return list(targets)
365
366    def supports_provisioned_mode(self):
367        """
368        Ensure boto3 includes provisioned throughput mode feature
369        """
370        return hasattr(self.connection, 'update_file_system')
371
372    def get_throughput_mode(self, **kwargs):
373        """
374        Returns throughput mode for selected EFS instance
375        """
376        info = first_or_default(iterate_all(
377            'FileSystems',
378            self.connection.describe_file_systems,
379            **kwargs
380        ))
381
382        return info and info['ThroughputMode'] or None
383
384    def get_provisioned_throughput_in_mibps(self, **kwargs):
385        """
386        Returns throughput mode for selected EFS instance
387        """
388        info = first_or_default(iterate_all(
389            'FileSystems',
390            self.connection.describe_file_systems,
391            **kwargs
392        ))
393        return info.get('ProvisionedThroughputInMibps', None)
394
395    def create_file_system(self, name, performance_mode, encrypt, kms_key_id, throughput_mode, provisioned_throughput_in_mibps):
396        """
397         Creates new filesystem with selected name
398        """
399        changed = False
400        state = self.get_file_system_state(name)
401        params = {}
402        params['CreationToken'] = name
403        params['PerformanceMode'] = performance_mode
404        if encrypt:
405            params['Encrypted'] = encrypt
406        if kms_key_id is not None:
407            params['KmsKeyId'] = kms_key_id
408        if throughput_mode:
409            if self.supports_provisioned_mode():
410                params['ThroughputMode'] = throughput_mode
411            else:
412                self.module.fail_json(msg="throughput_mode parameter requires botocore >= 1.10.57")
413        if provisioned_throughput_in_mibps:
414            if self.supports_provisioned_mode():
415                params['ProvisionedThroughputInMibps'] = provisioned_throughput_in_mibps
416            else:
417                self.module.fail_json(msg="provisioned_throughput_in_mibps parameter requires botocore >= 1.10.57")
418
419        if state in [self.STATE_DELETING, self.STATE_DELETED]:
420            wait_for(
421                lambda: self.get_file_system_state(name),
422                self.STATE_DELETED
423            )
424            try:
425                self.connection.create_file_system(**params)
426                changed = True
427            except ClientError as e:
428                self.module.fail_json(msg="Unable to create file system: {0}".format(to_native(e)),
429                                      exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
430            except BotoCoreError as e:
431                self.module.fail_json(msg="Unable to create file system: {0}".format(to_native(e)),
432                                      exception=traceback.format_exc())
433
434        # we always wait for the state to be available when creating.
435        # if we try to take any actions on the file system before it's available
436        # we'll throw errors
437        wait_for(
438            lambda: self.get_file_system_state(name),
439            self.STATE_AVAILABLE,
440            self.wait_timeout
441        )
442
443        return changed
444
445    def update_file_system(self, name, throughput_mode, provisioned_throughput_in_mibps):
446        """
447        Update filesystem with new throughput settings
448        """
449        changed = False
450        state = self.get_file_system_state(name)
451        if state in [self.STATE_AVAILABLE, self.STATE_CREATING]:
452            fs_id = self.get_file_system_id(name)
453            current_mode = self.get_throughput_mode(FileSystemId=fs_id)
454            current_throughput = self.get_provisioned_throughput_in_mibps(FileSystemId=fs_id)
455            params = dict()
456            if throughput_mode and throughput_mode != current_mode:
457                params['ThroughputMode'] = throughput_mode
458            if provisioned_throughput_in_mibps and provisioned_throughput_in_mibps != current_throughput:
459                params['ProvisionedThroughputInMibps'] = provisioned_throughput_in_mibps
460            if len(params) > 0:
461                wait_for(
462                    lambda: self.get_file_system_state(name),
463                    self.STATE_AVAILABLE,
464                    self.wait_timeout
465                )
466                try:
467                    self.connection.update_file_system(FileSystemId=fs_id, **params)
468                    changed = True
469                except ClientError as e:
470                    self.module.fail_json(msg="Unable to update file system: {0}".format(to_native(e)),
471                                          exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
472                except BotoCoreError as e:
473                    self.module.fail_json(msg="Unable to update file system: {0}".format(to_native(e)),
474                                          exception=traceback.format_exc())
475        return changed
476
477    def converge_file_system(self, name, tags, purge_tags, targets, throughput_mode, provisioned_throughput_in_mibps):
478        """
479         Change attributes (mount targets and tags) of filesystem by name
480        """
481        result = False
482        fs_id = self.get_file_system_id(name)
483
484        if tags is not None:
485            tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(self.get_tags(FileSystemId=fs_id)), tags, purge_tags)
486
487            if tags_to_delete:
488                try:
489                    self.connection.delete_tags(
490                        FileSystemId=fs_id,
491                        TagKeys=tags_to_delete
492                    )
493                except ClientError as e:
494                    self.module.fail_json(msg="Unable to delete tags: {0}".format(to_native(e)),
495                                          exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
496                except BotoCoreError as e:
497                    self.module.fail_json(msg="Unable to delete tags: {0}".format(to_native(e)),
498                                          exception=traceback.format_exc())
499
500                result = True
501
502            if tags_need_modify:
503                try:
504                    self.connection.create_tags(
505                        FileSystemId=fs_id,
506                        Tags=ansible_dict_to_boto3_tag_list(tags_need_modify)
507                    )
508                except ClientError as e:
509                    self.module.fail_json(msg="Unable to create tags: {0}".format(to_native(e)),
510                                          exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
511                except BotoCoreError as e:
512                    self.module.fail_json(msg="Unable to create tags: {0}".format(to_native(e)),
513                                          exception=traceback.format_exc())
514
515                result = True
516
517        if targets is not None:
518            incomplete_states = [self.STATE_CREATING, self.STATE_DELETING]
519            wait_for(
520                lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)),
521                0
522            )
523            current_targets = _index_by_key('SubnetId', self.get_mount_targets(FileSystemId=fs_id))
524            targets = _index_by_key('SubnetId', targets)
525
526            targets_to_create, intersection, targets_to_delete = dict_diff(current_targets,
527                                                                           targets, True)
528
529            # To modify mount target it should be deleted and created again
530            changed = [sid for sid in intersection if not targets_equal(['SubnetId', 'IpAddress', 'NetworkInterfaceId'],
531                                                                        current_targets[sid], targets[sid])]
532            targets_to_delete = list(targets_to_delete) + changed
533            targets_to_create = list(targets_to_create) + changed
534
535            if targets_to_delete:
536                for sid in targets_to_delete:
537                    self.connection.delete_mount_target(
538                        MountTargetId=current_targets[sid]['MountTargetId']
539                    )
540                wait_for(
541                    lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)),
542                    0
543                )
544                result = True
545
546            if targets_to_create:
547                for sid in targets_to_create:
548                    self.connection.create_mount_target(
549                        FileSystemId=fs_id,
550                        **targets[sid]
551                    )
552                wait_for(
553                    lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)),
554                    0,
555                    self.wait_timeout
556                )
557                result = True
558
559            # If no security groups were passed into the module, then do not change it.
560            security_groups_to_update = [sid for sid in intersection if
561                                         'SecurityGroups' in targets[sid] and
562                                         current_targets[sid]['SecurityGroups'] != targets[sid]['SecurityGroups']]
563
564            if security_groups_to_update:
565                for sid in security_groups_to_update:
566                    self.connection.modify_mount_target_security_groups(
567                        MountTargetId=current_targets[sid]['MountTargetId'],
568                        SecurityGroups=targets[sid].get('SecurityGroups', None)
569                    )
570                result = True
571
572        return result
573
574    def delete_file_system(self, name, file_system_id=None):
575        """
576         Removes EFS instance by id/name
577        """
578        result = False
579        state = self.get_file_system_state(name, file_system_id)
580        if state in [self.STATE_CREATING, self.STATE_AVAILABLE]:
581            wait_for(
582                lambda: self.get_file_system_state(name),
583                self.STATE_AVAILABLE
584            )
585            if not file_system_id:
586                file_system_id = self.get_file_system_id(name)
587            self.delete_mount_targets(file_system_id)
588            self.connection.delete_file_system(FileSystemId=file_system_id)
589            result = True
590
591        if self.wait:
592            wait_for(
593                lambda: self.get_file_system_state(name),
594                self.STATE_DELETED,
595                self.wait_timeout
596            )
597
598        return result
599
600    def delete_mount_targets(self, file_system_id):
601        """
602         Removes mount targets by EFS id
603        """
604        wait_for(
605            lambda: len(self.get_mount_targets_in_state(file_system_id, self.STATE_CREATING)),
606            0
607        )
608
609        targets = self.get_mount_targets_in_state(file_system_id, self.STATE_AVAILABLE)
610        for target in targets:
611            self.connection.delete_mount_target(MountTargetId=target['MountTargetId'])
612
613        wait_for(
614            lambda: len(self.get_mount_targets_in_state(file_system_id, self.STATE_DELETING)),
615            0
616        )
617
618        return len(targets) > 0
619
620
621def iterate_all(attr, map_method, **kwargs):
622    """
623     Method creates iterator from result set
624    """
625    args = dict((key, value) for (key, value) in kwargs.items() if value is not None)
626    wait = 1
627    while True:
628        try:
629            data = map_method(**args)
630            for elm in data[attr]:
631                yield elm
632            if 'NextMarker' in data:
633                args['Marker'] = data['Nextmarker']
634                continue
635            break
636        except ClientError as e:
637            if e.response['Error']['Code'] == "ThrottlingException" and wait < 600:
638                sleep(wait)
639                wait = wait * 2
640                continue
641            else:
642                raise
643
644
645def targets_equal(keys, a, b):
646    """
647     Method compare two mount targets by specified attributes
648    """
649    for key in keys:
650        if key in b and a[key] != b[key]:
651            return False
652
653    return True
654
655
656def dict_diff(dict1, dict2, by_key=False):
657    """
658     Helper method to calculate difference of two dictionaries
659    """
660    keys1 = set(dict1.keys() if by_key else dict1.items())
661    keys2 = set(dict2.keys() if by_key else dict2.items())
662
663    intersection = keys1 & keys2
664
665    return keys2 ^ intersection, intersection, keys1 ^ intersection
666
667
668def first_or_default(items, default=None):
669    """
670     Helper method to fetch first element of list (if exists)
671    """
672    for item in items:
673        return item
674    return default
675
676
677def wait_for(callback, value, timeout=EFSConnection.DEFAULT_WAIT_TIMEOUT_SECONDS):
678    """
679     Helper method to wait for desired value returned by callback method
680    """
681    wait_start = timestamp()
682    while True:
683        if callback() != value:
684            if timeout != 0 and (timestamp() - wait_start > timeout):
685                raise RuntimeError('Wait timeout exceeded (' + str(timeout) + ' sec)')
686            else:
687                sleep(5)
688            continue
689        break
690
691
692def main():
693    """
694     Module action handler
695    """
696    argument_spec = ec2_argument_spec()
697    argument_spec.update(dict(
698        encrypt=dict(required=False, type="bool", default=False),
699        state=dict(required=False, type='str', choices=["present", "absent"], default="present"),
700        kms_key_id=dict(required=False, type='str', default=None),
701        purge_tags=dict(default=True, type='bool'),
702        id=dict(required=False, type='str', default=None),
703        name=dict(required=False, type='str', default=None),
704        tags=dict(required=False, type="dict", default={}),
705        targets=dict(required=False, type="list", default=[]),
706        performance_mode=dict(required=False, type='str', choices=["general_purpose", "max_io"], default="general_purpose"),
707        throughput_mode=dict(required=False, type='str', choices=["bursting", "provisioned"], default=None),
708        provisioned_throughput_in_mibps=dict(required=False, type='float'),
709        wait=dict(required=False, type="bool", default=False),
710        wait_timeout=dict(required=False, type="int", default=0)
711    ))
712
713    module = AnsibleModule(argument_spec=argument_spec)
714    if not HAS_BOTO3:
715        module.fail_json(msg='boto3 required for this module')
716
717    region, _, aws_connect_params = get_aws_connection_info(module, boto3=True)
718    connection = EFSConnection(module, region, **aws_connect_params)
719
720    name = module.params.get('name')
721    fs_id = module.params.get('id')
722    tags = module.params.get('tags')
723    target_translations = {
724        'ip_address': 'IpAddress',
725        'security_groups': 'SecurityGroups',
726        'subnet_id': 'SubnetId'
727    }
728    targets = [dict((target_translations[key], value) for (key, value) in x.items()) for x in module.params.get('targets')]
729    performance_mode_translations = {
730        'general_purpose': 'generalPurpose',
731        'max_io': 'maxIO'
732    }
733    encrypt = module.params.get('encrypt')
734    kms_key_id = module.params.get('kms_key_id')
735    performance_mode = performance_mode_translations[module.params.get('performance_mode')]
736    purge_tags = module.params.get('purge_tags')
737    throughput_mode = module.params.get('throughput_mode')
738    provisioned_throughput_in_mibps = module.params.get('provisioned_throughput_in_mibps')
739    state = str(module.params.get('state')).lower()
740    changed = False
741
742    if state == 'present':
743        if not name:
744            module.fail_json(msg='Name parameter is required for create')
745
746        changed = connection.create_file_system(name, performance_mode, encrypt, kms_key_id, throughput_mode, provisioned_throughput_in_mibps)
747        if connection.supports_provisioned_mode():
748            changed = connection.update_file_system(name, throughput_mode, provisioned_throughput_in_mibps) or changed
749        changed = connection.converge_file_system(name=name, tags=tags, purge_tags=purge_tags, targets=targets,
750                                                  throughput_mode=throughput_mode, provisioned_throughput_in_mibps=provisioned_throughput_in_mibps) or changed
751        result = first_or_default(connection.get_file_systems(CreationToken=name))
752
753    elif state == 'absent':
754        if not name and not fs_id:
755            module.fail_json(msg='Either name or id parameter is required for delete')
756
757        changed = connection.delete_file_system(name, fs_id)
758        result = None
759    if result:
760        result = camel_dict_to_snake_dict(result)
761    module.exit_json(changed=changed, efs=result)
762
763
764if __name__ == '__main__':
765    main()
766