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