1#!/usr/local/bin/python3.8 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 9DOCUMENTATION = ''' 10--- 11module: ec2_ami 12version_added: 1.0.0 13short_description: Create or destroy an image (AMI) in ec2 14description: 15 - Registers or deregisters ec2 images. 16options: 17 instance_id: 18 description: 19 - Instance ID to create the AMI from. 20 type: str 21 name: 22 description: 23 - The name of the new AMI. 24 type: str 25 architecture: 26 description: 27 - The target architecture of the image to register 28 default: "x86_64" 29 type: str 30 kernel_id: 31 description: 32 - The target kernel id of the image to register. 33 type: str 34 virtualization_type: 35 description: 36 - The virtualization type of the image to register. 37 default: "hvm" 38 type: str 39 root_device_name: 40 description: 41 - The root device name of the image to register. 42 type: str 43 wait: 44 description: 45 - Wait for the AMI to be in state 'available' before returning. 46 default: false 47 type: bool 48 wait_timeout: 49 description: 50 - How long before wait gives up, in seconds. 51 default: 1200 52 type: int 53 state: 54 description: 55 - Register or deregister an AMI. 56 default: 'present' 57 choices: [ "absent", "present" ] 58 type: str 59 description: 60 description: 61 - Human-readable string describing the contents and purpose of the AMI. 62 type: str 63 no_reboot: 64 description: 65 - Flag indicating that the bundling process should not attempt to shutdown the instance before bundling. If this flag is True, the 66 responsibility of maintaining file system integrity is left to the owner of the instance. 67 default: false 68 type: bool 69 image_id: 70 description: 71 - Image ID to be deregistered. 72 type: str 73 device_mapping: 74 description: 75 - List of device hashes/dictionaries with custom configurations (same block-device-mapping parameters). 76 type: list 77 elements: dict 78 suboptions: 79 device_name: 80 type: str 81 description: 82 - The device name. For example C(/dev/sda). 83 required: yes 84 aliases: ['DeviceName'] 85 virtual_name: 86 type: str 87 description: 88 - The virtual name for the device. 89 - See the AWS documentation for more detail U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_BlockDeviceMapping.html). 90 - Alias C(VirtualName) has been deprecated and will be removed after 2022-06-01. 91 aliases: ['VirtualName'] 92 no_device: 93 type: bool 94 description: 95 - Suppresses the specified device included in the block device mapping of the AMI. 96 - Alias C(NoDevice) has been deprecated and will be removed after 2022-06-01. 97 aliases: ['NoDevice'] 98 volume_type: 99 type: str 100 description: The volume type. Defaults to C(gp2) when not set. 101 delete_on_termination: 102 type: bool 103 description: Whether the device should be automatically deleted when the Instance is terminated. 104 snapshot_id: 105 type: str 106 description: The ID of the Snapshot. 107 iops: 108 type: int 109 description: When using an C(io1) I(volume_type) this sets the number of IOPS provisioned for the volume 110 encrypted: 111 type: bool 112 description: Whether the volume should be encrypted. 113 volume_size: 114 aliases: ['size'] 115 type: int 116 description: The size of the volume (in GiB) 117 delete_snapshot: 118 description: 119 - Delete snapshots when deregistering the AMI. 120 default: false 121 type: bool 122 tags: 123 description: 124 - A dictionary of tags to add to the new image; '{"key":"value"}' and '{"key":"value","key":"value"}' 125 type: dict 126 purge_tags: 127 description: Whether to remove existing tags that aren't passed in the C(tags) parameter 128 default: false 129 type: bool 130 launch_permissions: 131 description: 132 - Users and groups that should be able to launch the AMI. Expects dictionary with a key of user_ids and/or group_names. user_ids should 133 be a list of account ids. group_name should be a list of groups, "all" is the only acceptable value currently. 134 - You must pass all desired launch permissions if you wish to modify existing launch permissions (passing just groups will remove all users) 135 type: dict 136 image_location: 137 description: 138 - The s3 location of an image to use for the AMI. 139 type: str 140 enhanced_networking: 141 description: 142 - A boolean representing whether enhanced networking with ENA is enabled or not. 143 type: bool 144 billing_products: 145 description: 146 - A list of valid billing codes. To be used with valid accounts by aws marketplace vendors. 147 type: list 148 elements: str 149 ramdisk_id: 150 description: 151 - The ID of the RAM disk. 152 type: str 153 sriov_net_support: 154 description: 155 - Set to simple to enable enhanced networking with the Intel 82599 Virtual Function interface for the AMI and any instances that you launch from the AMI. 156 type: str 157author: 158 - "Evan Duffield (@scicoin-project) <eduffield@iacquire.com>" 159 - "Constantin Bugneac (@Constantin07) <constantin.bugneac@endava.com>" 160 - "Ross Williams (@gunzy83) <gunzy83au@gmail.com>" 161 - "Willem van Ketwich (@wilvk) <willvk@gmail.com>" 162extends_documentation_fragment: 163- amazon.aws.aws 164- amazon.aws.ec2 165 166''' 167 168# Thank you to iAcquire for sponsoring development of this module. 169 170EXAMPLES = ''' 171# Note: These examples do not set authentication details, see the AWS Guide for details. 172 173- name: Basic AMI Creation 174 amazon.aws.ec2_ami: 175 instance_id: i-xxxxxx 176 wait: yes 177 name: newtest 178 tags: 179 Name: newtest 180 Service: TestService 181 182- name: Basic AMI Creation, without waiting 183 amazon.aws.ec2_ami: 184 instance_id: i-xxxxxx 185 wait: no 186 name: newtest 187 188- name: AMI Registration from EBS Snapshot 189 amazon.aws.ec2_ami: 190 name: newtest 191 state: present 192 architecture: x86_64 193 virtualization_type: hvm 194 root_device_name: /dev/xvda 195 device_mapping: 196 - device_name: /dev/xvda 197 volume_size: 8 198 snapshot_id: snap-xxxxxxxx 199 delete_on_termination: true 200 volume_type: gp2 201 202- name: AMI Creation, with a custom root-device size and another EBS attached 203 amazon.aws.ec2_ami: 204 instance_id: i-xxxxxx 205 name: newtest 206 device_mapping: 207 - device_name: /dev/sda1 208 size: XXX 209 delete_on_termination: true 210 volume_type: gp2 211 - device_name: /dev/sdb 212 size: YYY 213 delete_on_termination: false 214 volume_type: gp2 215 216- name: AMI Creation, excluding a volume attached at /dev/sdb 217 amazon.aws.ec2_ami: 218 instance_id: i-xxxxxx 219 name: newtest 220 device_mapping: 221 - device_name: /dev/sda1 222 size: XXX 223 delete_on_termination: true 224 volume_type: gp2 225 - device_name: /dev/sdb 226 no_device: yes 227 228- name: Deregister/Delete AMI (keep associated snapshots) 229 amazon.aws.ec2_ami: 230 image_id: "{{ instance.image_id }}" 231 delete_snapshot: False 232 state: absent 233 234- name: Deregister AMI (delete associated snapshots too) 235 amazon.aws.ec2_ami: 236 image_id: "{{ instance.image_id }}" 237 delete_snapshot: True 238 state: absent 239 240- name: Update AMI Launch Permissions, making it public 241 amazon.aws.ec2_ami: 242 image_id: "{{ instance.image_id }}" 243 state: present 244 launch_permissions: 245 group_names: ['all'] 246 247- name: Allow AMI to be launched by another account 248 amazon.aws.ec2_ami: 249 image_id: "{{ instance.image_id }}" 250 state: present 251 launch_permissions: 252 user_ids: ['123456789012'] 253''' 254 255RETURN = ''' 256architecture: 257 description: Architecture of image. 258 returned: when AMI is created or already exists 259 type: str 260 sample: "x86_64" 261block_device_mapping: 262 description: Block device mapping associated with image. 263 returned: when AMI is created or already exists 264 type: dict 265 sample: { 266 "/dev/sda1": { 267 "delete_on_termination": true, 268 "encrypted": false, 269 "size": 10, 270 "snapshot_id": "snap-1a03b80e7", 271 "volume_type": "standard" 272 } 273 } 274creationDate: 275 description: Creation date of image. 276 returned: when AMI is created or already exists 277 type: str 278 sample: "2015-10-15T22:43:44.000Z" 279description: 280 description: Description of image. 281 returned: when AMI is created or already exists 282 type: str 283 sample: "nat-server" 284hypervisor: 285 description: Type of hypervisor. 286 returned: when AMI is created or already exists 287 type: str 288 sample: "xen" 289image_id: 290 description: ID of the image. 291 returned: when AMI is created or already exists 292 type: str 293 sample: "ami-1234abcd" 294is_public: 295 description: Whether image is public. 296 returned: when AMI is created or already exists 297 type: bool 298 sample: false 299launch_permission: 300 description: Permissions allowing other accounts to access the AMI. 301 returned: when AMI is created or already exists 302 type: list 303 sample: 304 - group: "all" 305location: 306 description: Location of image. 307 returned: when AMI is created or already exists 308 type: str 309 sample: "315210894379/nat-server" 310name: 311 description: AMI name of image. 312 returned: when AMI is created or already exists 313 type: str 314 sample: "nat-server" 315ownerId: 316 description: Owner of image. 317 returned: when AMI is created or already exists 318 type: str 319 sample: "435210894375" 320platform: 321 description: Platform of image. 322 returned: when AMI is created or already exists 323 type: str 324 sample: null 325root_device_name: 326 description: Root device name of image. 327 returned: when AMI is created or already exists 328 type: str 329 sample: "/dev/sda1" 330root_device_type: 331 description: Root device type of image. 332 returned: when AMI is created or already exists 333 type: str 334 sample: "ebs" 335state: 336 description: State of image. 337 returned: when AMI is created or already exists 338 type: str 339 sample: "available" 340tags: 341 description: A dictionary of tags assigned to image. 342 returned: when AMI is created or already exists 343 type: dict 344 sample: { 345 "Env": "devel", 346 "Name": "nat-server" 347 } 348virtualization_type: 349 description: Image virtualization type. 350 returned: when AMI is created or already exists 351 type: str 352 sample: "hvm" 353snapshots_deleted: 354 description: A list of snapshot ids deleted after deregistering image. 355 returned: after AMI is deregistered, if I(delete_snapshot=true) 356 type: list 357 sample: [ 358 "snap-fbcccb8f", 359 "snap-cfe7cdb4" 360 ] 361''' 362 363import time 364 365try: 366 import botocore 367except ImportError: 368 pass # Handled by AnsibleAWSModule 369 370from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict 371 372from ..module_utils.core import AnsibleAWSModule 373from ..module_utils.core import is_boto3_error_code 374from ..module_utils.ec2 import AWSRetry 375from ..module_utils.ec2 import ansible_dict_to_boto3_tag_list 376from ..module_utils.ec2 import boto3_tag_list_to_ansible_dict 377from ..module_utils.ec2 import compare_aws_tags 378from ..module_utils.waiters import get_waiter 379 380 381def get_block_device_mapping(image): 382 bdm_dict = dict() 383 if image is not None and image.get('block_device_mappings') is not None: 384 bdm = image.get('block_device_mappings') 385 for device in bdm: 386 device_name = device.get('device_name') 387 if 'ebs' in device: 388 ebs = device.get("ebs") 389 bdm_dict_item = { 390 'size': ebs.get("volume_size"), 391 'snapshot_id': ebs.get("snapshot_id"), 392 'volume_type': ebs.get("volume_type"), 393 'encrypted': ebs.get("encrypted"), 394 'delete_on_termination': ebs.get("delete_on_termination") 395 } 396 elif 'virtual_name' in device: 397 bdm_dict_item = dict(virtual_name=device['virtual_name']) 398 bdm_dict[device_name] = bdm_dict_item 399 return bdm_dict 400 401 402def get_ami_info(camel_image): 403 image = camel_dict_to_snake_dict(camel_image) 404 return dict( 405 image_id=image.get("image_id"), 406 state=image.get("state"), 407 architecture=image.get("architecture"), 408 block_device_mapping=get_block_device_mapping(image), 409 creationDate=image.get("creation_date"), 410 description=image.get("description"), 411 hypervisor=image.get("hypervisor"), 412 is_public=image.get("public"), 413 location=image.get("image_location"), 414 ownerId=image.get("owner_id"), 415 root_device_name=image.get("root_device_name"), 416 root_device_type=image.get("root_device_type"), 417 virtualization_type=image.get("virtualization_type"), 418 name=image.get("name"), 419 tags=boto3_tag_list_to_ansible_dict(image.get('tags')), 420 platform=image.get("platform"), 421 enhanced_networking=image.get("ena_support"), 422 image_owner_alias=image.get("image_owner_alias"), 423 image_type=image.get("image_type"), 424 kernel_id=image.get("kernel_id"), 425 product_codes=image.get("product_codes"), 426 ramdisk_id=image.get("ramdisk_id"), 427 sriov_net_support=image.get("sriov_net_support"), 428 state_reason=image.get("state_reason"), 429 launch_permissions=image.get('launch_permissions') 430 ) 431 432 433def create_image(module, connection): 434 instance_id = module.params.get('instance_id') 435 name = module.params.get('name') 436 wait = module.params.get('wait') 437 wait_timeout = module.params.get('wait_timeout') 438 description = module.params.get('description') 439 architecture = module.params.get('architecture') 440 kernel_id = module.params.get('kernel_id') 441 root_device_name = module.params.get('root_device_name') 442 virtualization_type = module.params.get('virtualization_type') 443 no_reboot = module.params.get('no_reboot') 444 device_mapping = module.params.get('device_mapping') 445 tags = module.params.get('tags') 446 launch_permissions = module.params.get('launch_permissions') 447 image_location = module.params.get('image_location') 448 enhanced_networking = module.params.get('enhanced_networking') 449 billing_products = module.params.get('billing_products') 450 ramdisk_id = module.params.get('ramdisk_id') 451 sriov_net_support = module.params.get('sriov_net_support') 452 453 try: 454 params = { 455 'Name': name, 456 'Description': description 457 } 458 459 block_device_mapping = None 460 461 # Remove empty values injected by using options 462 if device_mapping: 463 block_device_mapping = [] 464 for device in device_mapping: 465 device = dict((k, v) for k, v in device.items() if v is not None) 466 device['Ebs'] = {} 467 device = rename_item_if_exists(device, 'device_name', 'DeviceName') 468 device = rename_item_if_exists(device, 'virtual_name', 'VirtualName') 469 device = rename_item_if_exists(device, 'no_device', 'NoDevice') 470 device = rename_item_if_exists(device, 'volume_type', 'VolumeType', 'Ebs') 471 device = rename_item_if_exists(device, 'snapshot_id', 'SnapshotId', 'Ebs') 472 device = rename_item_if_exists(device, 'delete_on_termination', 'DeleteOnTermination', 'Ebs') 473 device = rename_item_if_exists(device, 'size', 'VolumeSize', 'Ebs', attribute_type=int) 474 device = rename_item_if_exists(device, 'volume_size', 'VolumeSize', 'Ebs', attribute_type=int) 475 device = rename_item_if_exists(device, 'iops', 'Iops', 'Ebs') 476 device = rename_item_if_exists(device, 'encrypted', 'Encrypted', 'Ebs') 477 block_device_mapping.append(device) 478 if block_device_mapping: 479 params['BlockDeviceMappings'] = block_device_mapping 480 if instance_id: 481 params['InstanceId'] = instance_id 482 params['NoReboot'] = no_reboot 483 image_id = connection.create_image(aws_retry=True, **params).get('ImageId') 484 else: 485 if architecture: 486 params['Architecture'] = architecture 487 if virtualization_type: 488 params['VirtualizationType'] = virtualization_type 489 if image_location: 490 params['ImageLocation'] = image_location 491 if enhanced_networking: 492 params['EnaSupport'] = enhanced_networking 493 if billing_products: 494 params['BillingProducts'] = billing_products 495 if ramdisk_id: 496 params['RamdiskId'] = ramdisk_id 497 if sriov_net_support: 498 params['SriovNetSupport'] = sriov_net_support 499 if kernel_id: 500 params['KernelId'] = kernel_id 501 if root_device_name: 502 params['RootDeviceName'] = root_device_name 503 image_id = connection.register_image(aws_retry=True, **params).get('ImageId') 504 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 505 module.fail_json_aws(e, msg="Error registering image") 506 507 if wait: 508 delay = 15 509 max_attempts = wait_timeout // delay 510 waiter = get_waiter(connection, 'image_available') 511 waiter.wait(ImageIds=[image_id], WaiterConfig=dict(Delay=delay, MaxAttempts=max_attempts)) 512 513 if tags: 514 try: 515 connection.create_tags(aws_retry=True, Resources=[image_id], Tags=ansible_dict_to_boto3_tag_list(tags)) 516 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 517 module.fail_json_aws(e, msg="Error tagging image") 518 519 if launch_permissions: 520 try: 521 params = dict(Attribute='LaunchPermission', ImageId=image_id, LaunchPermission=dict(Add=list())) 522 for group_name in launch_permissions.get('group_names', []): 523 params['LaunchPermission']['Add'].append(dict(Group=group_name)) 524 for user_id in launch_permissions.get('user_ids', []): 525 params['LaunchPermission']['Add'].append(dict(UserId=str(user_id))) 526 if params['LaunchPermission']['Add']: 527 connection.modify_image_attribute(aws_retry=True, **params) 528 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 529 module.fail_json_aws(e, msg="Error setting launch permissions for image %s" % image_id) 530 531 module.exit_json(msg="AMI creation operation complete.", changed=True, 532 **get_ami_info(get_image_by_id(module, connection, image_id))) 533 534 535def deregister_image(module, connection): 536 image_id = module.params.get('image_id') 537 delete_snapshot = module.params.get('delete_snapshot') 538 wait = module.params.get('wait') 539 wait_timeout = module.params.get('wait_timeout') 540 image = get_image_by_id(module, connection, image_id) 541 542 if image is None: 543 module.exit_json(changed=False) 544 545 # Get all associated snapshot ids before deregistering image otherwise this information becomes unavailable. 546 snapshots = [] 547 if 'BlockDeviceMappings' in image: 548 for mapping in image.get('BlockDeviceMappings'): 549 snapshot_id = mapping.get('Ebs', {}).get('SnapshotId') 550 if snapshot_id is not None: 551 snapshots.append(snapshot_id) 552 553 # When trying to re-deregister an already deregistered image it doesn't raise an exception, it just returns an object without image attributes. 554 if 'ImageId' in image: 555 try: 556 connection.deregister_image(aws_retry=True, ImageId=image_id) 557 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 558 module.fail_json_aws(e, msg="Error deregistering image") 559 else: 560 module.exit_json(msg="Image %s has already been deregistered." % image_id, changed=False) 561 562 image = get_image_by_id(module, connection, image_id) 563 wait_timeout = time.time() + wait_timeout 564 565 while wait and wait_timeout > time.time() and image is not None: 566 image = get_image_by_id(module, connection, image_id) 567 time.sleep(3) 568 569 if wait and wait_timeout <= time.time(): 570 module.fail_json(msg="Timed out waiting for image to be deregistered.") 571 572 exit_params = {'msg': "AMI deregister operation complete.", 'changed': True} 573 574 if delete_snapshot: 575 for snapshot_id in snapshots: 576 try: 577 connection.delete_snapshot(aws_retry=True, SnapshotId=snapshot_id) 578 # Don't error out if root volume snapshot was already deregistered as part of deregister_image 579 except is_boto3_error_code('InvalidSnapshot.NotFound'): 580 pass 581 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except 582 module.fail_json_aws(e, msg='Failed to delete snapshot.') 583 exit_params['snapshots_deleted'] = snapshots 584 585 module.exit_json(**exit_params) 586 587 588def update_image(module, connection, image_id): 589 launch_permissions = module.params.get('launch_permissions') 590 image = get_image_by_id(module, connection, image_id) 591 if image is None: 592 module.fail_json(msg="Image %s does not exist" % image_id, changed=False) 593 changed = False 594 595 if launch_permissions is not None: 596 current_permissions = image['LaunchPermissions'] 597 598 current_users = set(permission['UserId'] for permission in current_permissions if 'UserId' in permission) 599 desired_users = set(str(user_id) for user_id in launch_permissions.get('user_ids', [])) 600 current_groups = set(permission['Group'] for permission in current_permissions if 'Group' in permission) 601 desired_groups = set(launch_permissions.get('group_names', [])) 602 603 to_add_users = desired_users - current_users 604 to_remove_users = current_users - desired_users 605 to_add_groups = desired_groups - current_groups 606 to_remove_groups = current_groups - desired_groups 607 608 to_add = [dict(Group=group) for group in to_add_groups] + [dict(UserId=user_id) for user_id in to_add_users] 609 to_remove = [dict(Group=group) for group in to_remove_groups] + [dict(UserId=user_id) for user_id in to_remove_users] 610 611 if to_add or to_remove: 612 try: 613 connection.modify_image_attribute(aws_retry=True, 614 ImageId=image_id, Attribute='launchPermission', 615 LaunchPermission=dict(Add=to_add, Remove=to_remove)) 616 changed = True 617 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 618 module.fail_json_aws(e, msg="Error updating launch permissions of image %s" % image_id) 619 620 desired_tags = module.params.get('tags') 621 if desired_tags is not None: 622 current_tags = boto3_tag_list_to_ansible_dict(image.get('Tags')) 623 tags_to_add, tags_to_remove = compare_aws_tags(current_tags, desired_tags, purge_tags=module.params.get('purge_tags')) 624 625 if tags_to_remove: 626 try: 627 connection.delete_tags(aws_retry=True, Resources=[image_id], Tags=[dict(Key=tagkey) for tagkey in tags_to_remove]) 628 changed = True 629 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 630 module.fail_json_aws(e, msg="Error updating tags") 631 632 if tags_to_add: 633 try: 634 connection.create_tags(aws_retry=True, Resources=[image_id], Tags=ansible_dict_to_boto3_tag_list(tags_to_add)) 635 changed = True 636 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 637 module.fail_json_aws(e, msg="Error updating tags") 638 639 description = module.params.get('description') 640 if description and description != image['Description']: 641 try: 642 connection.modify_image_attribute(aws_retry=True, Attribute='Description ', ImageId=image_id, Description=dict(Value=description)) 643 changed = True 644 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 645 module.fail_json_aws(e, msg="Error setting description for image %s" % image_id) 646 647 if changed: 648 module.exit_json(msg="AMI updated.", changed=True, 649 **get_ami_info(get_image_by_id(module, connection, image_id))) 650 else: 651 module.exit_json(msg="AMI not updated.", changed=False, 652 **get_ami_info(get_image_by_id(module, connection, image_id))) 653 654 655def get_image_by_id(module, connection, image_id): 656 try: 657 try: 658 images_response = connection.describe_images(aws_retry=True, ImageIds=[image_id]) 659 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 660 module.fail_json_aws(e, msg="Error retrieving image %s" % image_id) 661 images = images_response.get('Images') 662 no_images = len(images) 663 if no_images == 0: 664 return None 665 if no_images == 1: 666 result = images[0] 667 try: 668 result['LaunchPermissions'] = connection.describe_image_attribute(aws_retry=True, Attribute='launchPermission', 669 ImageId=image_id)['LaunchPermissions'] 670 result['ProductCodes'] = connection.describe_image_attribute(aws_retry=True, Attribute='productCodes', 671 ImageId=image_id)['ProductCodes'] 672 except is_boto3_error_code('InvalidAMIID.Unavailable'): 673 pass 674 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: # pylint: disable=duplicate-except 675 module.fail_json_aws(e, msg="Error retrieving image attributes for image %s" % image_id) 676 return result 677 module.fail_json(msg="Invalid number of instances (%s) found for image_id: %s." % (str(len(images)), image_id)) 678 except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: 679 module.fail_json_aws(e, msg="Error retrieving image by image_id") 680 681 682def rename_item_if_exists(dict_object, attribute, new_attribute, child_node=None, attribute_type=None): 683 new_item = dict_object.get(attribute) 684 if new_item is not None: 685 if attribute_type is not None: 686 new_item = attribute_type(new_item) 687 if child_node is None: 688 dict_object[new_attribute] = new_item 689 else: 690 dict_object[child_node][new_attribute] = new_item 691 dict_object.pop(attribute) 692 return dict_object 693 694 695def main(): 696 mapping_options = dict( 697 device_name=dict(type='str', aliases=['DeviceName'], required=True), 698 virtual_name=dict( 699 type='str', aliases=['VirtualName'], 700 deprecated_aliases=[dict(name='VirtualName', date='2022-06-01', collection_name='amazon.aws')]), 701 no_device=dict( 702 type='bool', aliases=['NoDevice'], 703 deprecated_aliases=[dict(name='NoDevice', date='2022-06-01', collection_name='amazon.aws')]), 704 volume_type=dict(type='str'), 705 delete_on_termination=dict(type='bool'), 706 snapshot_id=dict(type='str'), 707 iops=dict(type='int'), 708 encrypted=dict(type='bool'), 709 volume_size=dict(type='int', aliases=['size']), 710 ) 711 argument_spec = dict( 712 instance_id=dict(), 713 image_id=dict(), 714 architecture=dict(default='x86_64'), 715 kernel_id=dict(), 716 virtualization_type=dict(default='hvm'), 717 root_device_name=dict(), 718 delete_snapshot=dict(default=False, type='bool'), 719 name=dict(), 720 wait=dict(type='bool', default=False), 721 wait_timeout=dict(default=1200, type='int'), 722 description=dict(default=''), 723 no_reboot=dict(default=False, type='bool'), 724 state=dict(default='present', choices=['present', 'absent']), 725 device_mapping=dict(type='list', elements='dict', options=mapping_options), 726 tags=dict(type='dict'), 727 launch_permissions=dict(type='dict'), 728 image_location=dict(), 729 enhanced_networking=dict(type='bool'), 730 billing_products=dict(type='list', elements='str',), 731 ramdisk_id=dict(), 732 sriov_net_support=dict(), 733 purge_tags=dict(type='bool', default=False) 734 ) 735 736 module = AnsibleAWSModule( 737 argument_spec=argument_spec, 738 required_if=[ 739 ['state', 'absent', ['image_id']], 740 ] 741 ) 742 743 # Using a required_one_of=[['name', 'image_id']] overrides the message that should be provided by 744 # the required_if for state=absent, so check manually instead 745 if not any([module.params['image_id'], module.params['name']]): 746 module.fail_json(msg="one of the following is required: name, image_id") 747 748 connection = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) 749 750 if module.params.get('state') == 'absent': 751 deregister_image(module, connection) 752 elif module.params.get('state') == 'present': 753 if module.params.get('image_id'): 754 update_image(module, connection, module.params.get('image_id')) 755 if not module.params.get('instance_id') and not module.params.get('device_mapping'): 756 module.fail_json(msg="The parameters instance_id or device_mapping (register from EBS snapshot) are required for a new image.") 757 create_image(module, connection) 758 759 760if __name__ == '__main__': 761 main() 762