diff --git a/lib/ansible/modules/cloud/amazon/ec2_ami.py b/lib/ansible/modules/cloud/amazon/ec2_ami.py index 57770bf312d..2c5eff3feb9 100644 --- a/lib/ansible/modules/cloud/amazon/ec2_ami.py +++ b/lib/ansible/modules/cloud/amazon/ec2_ami.py @@ -78,12 +78,13 @@ options: default: null delete_snapshot: description: - - Whether or not to delete an AMI while deregistering it. + - Whether or not to delete snapshots when deregistering AMI. required: false - default: null + default: "no" + choices: [ "yes", "no" ] tags: description: - - a hash/dictionary of tags to add to the new image; '{"key":"value"}' and '{"key":"value","key":"value"}' + - a dictionary of tags to add to the new image; '{"key":"value"}' and '{"key":"value","key":"value"}' required: false default: null version_added: "2.0" @@ -92,10 +93,13 @@ options: - 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 be a list of account ids. group_name should be a list of groups, "all" is the only acceptable value currently. required: false default: null - aliases: [] - -author: "Evan Duffield (@scicoin-project) " -extends_documentation_fragment: aws + version_added: "2.0" +author: + - "Evan Duffield (@scicoin-project) " + - "Constantin Bugneac (@Constantin07) " +extends_documentation_fragment: + - aws + - ec2 ''' # Thank you to iAcquire for sponsoring development of this module. @@ -155,22 +159,22 @@ EXAMPLES = ''' no_device: yes register: instance -# Deregister/Delete AMI +# Deregister/Delete AMI (keep associated snapshots) - ec2_ami: aws_access_key: xxxxxxxxxxxxxxxxxxxxxxx aws_secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx region: xxxxxx image_id: "{{ instance.image_id }}" - delete_snapshot: True + delete_snapshot: False state: absent -# Deregister AMI +# Deregister AMI (delete associated snapshots too) - ec2_ami: aws_access_key: xxxxxxxxxxxxxxxxxxxxxxx aws_secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx region: xxxxxx image_id: "{{ instance.image_id }}" - delete_snapshot: False + delete_snapshot: True state: absent # Update AMI Launch Permissions, making it public @@ -194,6 +198,102 @@ EXAMPLES = ''' user_ids: ['123456789012'] ''' +RETURN = ''' +architecture: + description: architecture of image + returned: when AMI is created or already exists + type: string + sample: "x86_64" +block_device_mapping: + description: block device mapping associated with image + returned: when AMI is created or already exists + type: a dictionary of block devices + sample: { + "/dev/sda1": { + "delete_on_termination": true, + "encrypted": false, + "size": 10, + "snapshot_id": "snap-1a03b80e7", + "volume_type": "standard" + } +creationDate: + description: creation date of image + returned: when AMI is created or already exists + type: string + sample: "2015-10-15T22:43:44.000Z" +description: + description: description of image + returned: when AMI is created or already exists + type: string + sample: "nat-server" +hypervisor: + description: type of hypervisor + returned: when AMI is created or already exists + type: string + sample: "xen" +is_public: + description: whether image is public + returned: when AMI is created or already exists + type: bool + sample: false +location: + description: location of image + returned: when AMI is created or already exists + type: string + sample: "315210894379/nat-server" +name: + description: ami name of image + returned: when AMI is created or already exists + type: string + sample: "nat-server" +owner_id: + description: owner of image + returned: when AMI is created or already exists + type: string + sample: "435210894375" +platform: + description: plaform of image + returned: when AMI is created or already exists + type: string + sample: null +root_device_name: + description: root device name of image + returned: when AMI is created or already exists + type: string + sample: "/dev/sda1" +root_device_type: + description: root device type of image + returned: when AMI is created or already exists + type: string + sample: "ebs" +state: + description: state of image + returned: when AMI is created or already exists + type: string + sample: "available" +tags: + description: a dictionary of tags assigned to image + returned: when AMI is created or already exists + type: dictionary of tags + sample: { + "Env": "devel", + "Name": "nat-server" + } +virtualization_type: + description: image virtualization type + returned: when AMI is created or already exists + type: string + sample: "hvm" +snapshots_deleted: + description: a list of snapshot ids deleted after deregistering image + returned: after AMI is deregistered, if 'delete_snapshot' is set to 'yes' + type: list + sample: [ + "snap-fbcccb8f", + "snap-cfe7cdb4" + ] +''' + import sys import time @@ -205,6 +305,47 @@ except ImportError: HAS_BOTO = False +def get_block_device_mapping(image): + """ + Retrieves block device mapping from AMI + """ + + bdm_dict = dict() + + if image is not None and hasattr(image, 'block_device_mapping'): + bdm = getattr(image,'block_device_mapping') + for device_name in bdm.keys(): + bdm_dict[device_name] = { + 'size': bdm[device_name].size, + 'snapshot_id': bdm[device_name].snapshot_id, + 'volume_type': bdm[device_name].volume_type, + 'encrypted': bdm[device_name].encrypted, + 'delete_on_termination': bdm[device_name].delete_on_termination + } + + return bdm_dict + + +def get_ami_info(image): + + return dict( + image_id=image.id, + state=image.state, + architecture=image.architecture, + block_device_mapping=get_block_device_mapping(image), + creationDate=image.creationDate, + description=image.description, + hypervisor=image.hypervisor, + is_public=image.is_public, + location=image.location, + ownerId=image.ownerId, + root_device_name=image.root_device_name, + root_device_type=image.root_device_type, + tags=image.tags, + virtualization_type = image.virtualization_type + ) + + def create_image(module, ec2): """ Creates new AMI @@ -278,7 +419,7 @@ def create_image(module, ec2): except boto.exception.BotoServerError, e: module.fail_json(msg="%s: %s" % (e.error_code, e.error_message), image_id=image_id) - module.exit_json(msg="AMI creation operation complete", image_id=image_id, state=img.state, changed=True) + module.exit_json(msg="AMI creation operation complete", changed=True, **get_ami_info(img)) def deregister_image(module, ec2): @@ -295,13 +436,23 @@ def deregister_image(module, ec2): if img == None: module.fail_json(msg = "Image %s does not exist" % image_id, changed=False) - try: - params = {'image_id': image_id, - 'delete_snapshot': delete_snapshot} + # Get all associated snapshot ids before deregistering image otherwise this information becomes unavailable + snapshots = [] + if hasattr(img, 'block_device_mapping'): + for key in img.block_device_mapping: + snapshots.append(img.block_device_mapping[key].snapshot_id) - res = ec2.deregister_image(**params) - except boto.exception.BotoServerError, e: - module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + # When trying to re-delete already deleted image it doesn't raise an exception + # It just returns an object without image attributes + if hasattr(img, 'id'): + try: + params = {'image_id': image_id, + 'delete_snapshot': delete_snapshot} + res = ec2.deregister_image(**params) + except boto.exception.BotoServerError, e: + module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + else: + module.exit_json(msg = "Image %s has already been deleted" % image_id, changed=False) # wait here until the image is gone img = ec2.get_image(image_id) @@ -311,9 +462,21 @@ def deregister_image(module, ec2): time.sleep(3) if wait and wait_timeout <= time.time(): # waiting took too long - module.fail_json(msg = "timed out waiting for image to be reregistered/deleted") + module.fail_json(msg = "timed out waiting for image to be deregistered/deleted") - module.exit_json(msg="AMI deregister/delete operation complete", changed=True) + # Boto library has hardcoded the deletion of the snapshot for the root volume mounted as '/dev/sda1' only + # Make it possible to delete all snapshots which belong to image, including root block device mapped as '/dev/xvda' + if delete_snapshot: + try: + for snapshot_id in snapshots: + ec2.delete_snapshot(snapshot_id) + except boto.exception.BotoServerError, e: + if e.error_code == 'InvalidSnapshot.NotFound': + # Don't error out if root volume snapshot was already deleted as part of deregister_image + pass + module.exit_json(msg="AMI deregister/delete operation complete", changed=True, snapshots_deleted=snapshots) + else: + module.exit_json(msg="AMI deregister/delete operation complete", changed=True) def update_image(module, ec2): @@ -351,12 +514,12 @@ def main(): argument_spec.update(dict( instance_id = dict(), image_id = dict(), - delete_snapshot = dict(), + delete_snapshot = dict(default=False, type='bool'), name = dict(), - wait = dict(type="bool", default=False), + wait = dict(type='bool', default=False), wait_timeout = dict(default=900), description = dict(default=""), - no_reboot = dict(default=False, type="bool"), + no_reboot = dict(default=False, type='bool'), state = dict(default='present'), device_mapping = dict(type='list'), tags = dict(type='dict'), @@ -396,5 +559,5 @@ def main(): from ansible.module_utils.basic import * from ansible.module_utils.ec2 import * -main() - +if __name__ == '__main__': + main()