diff --git a/cloud/amazon/ec2_ami.py b/cloud/amazon/ec2_ami.py index 2a205d62cc7..594aebeaa94 100644 --- a/cloud/amazon/ec2_ami.py +++ b/cloud/amazon/ec2_ami.py @@ -72,12 +72,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" @@ -87,7 +88,9 @@ options: required: false default: null version_added: "2.0" -author: "Evan Duffield (@scicoin-project) " +author: + - "Evan Duffield (@scicoin-project) " + - "Constantin Bugneac (@Constantin07) " extends_documentation_fragment: - aws - ec2 @@ -150,16 +153,7 @@ EXAMPLES = ''' no_device: yes register: instance -# Deregister/Delete AMI -- ec2_ami: - aws_access_key: xxxxxxxxxxxxxxxxxxxxxxx - aws_secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - region: xxxxxx - image_id: "{{ instance.image_id }}" - delete_snapshot: True - state: absent - -# Deregister AMI +# Deregister/Delete AMI (keep associated snapshots) - ec2_ami: aws_access_key: xxxxxxxxxxxxxxxxxxxxxxx aws_secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @@ -168,6 +162,15 @@ EXAMPLES = ''' delete_snapshot: False state: absent +# 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: True + state: absent + # Update AMI Launch Permissions, making it public - ec2_ami: aws_access_key: xxxxxxxxxxxxxxxxxxxxxxx @@ -189,6 +192,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 @@ -201,6 +300,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 @@ -275,7 +415,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): @@ -292,13 +432,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) @@ -308,9 +458,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): @@ -348,12 +510,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'), @@ -393,4 +555,5 @@ def main(): from ansible.module_utils.basic import * from ansible.module_utils.ec2 import * -main() +if __name__ == '__main__': + main()