diff --git a/lib/ansible/modules/cloud/amazon/ec2_vol.py b/lib/ansible/modules/cloud/amazon/ec2_vol.py index 8850cb44a2e..8ccd3e2ef92 100644 --- a/lib/ansible/modules/cloud/amazon/ec2_vol.py +++ b/lib/ansible/modules/cloud/amazon/ec2_vol.py @@ -67,6 +67,12 @@ options: - device id to override device mapping. Assumes /dev/sdf for Linux/UNIX and /dev/xvdf for Windows. required: false default: null + delete_on_termination: + description: + - When set to "yes", the volume will be deleted upon instance termination. + required: false + default: "no" + choices: ["yes", "no"] zone: description: - zone in which to create the volume, if unset uses the zone the instance is in (if set) @@ -171,6 +177,56 @@ EXAMPLES = ''' volume_size: 50 volume_type: gp2 device_name: /dev/xvdf + +# Attach an existing volume to instance. The volume will be deleted upon instance termination. +- ec2_vol: + instance: XXXXXX + id: XXXXXX + device_name: /dev/sdf + delete_on_termination: yes +''' + +RETURN = ''' +device: + description: device name of attached volume + returned: when success + type: string + sample: "/def/sdf" +volume_id: + description: the id of volume + returned: when success + type: string + sample: "vol-35b333d9" +volume_type: + description: the volume type + returned: when success + type: string + sample: "standard" +volume: + description: a dictionary containing detailed attributes of the volume + returned: when success + type: string + sample: { + "attachment_set": { + "attach_time": "2015-10-23T00:22:29.000Z", + "deleteOnTermination": "false", + "device": "/dev/sdf", + "instance_id": "i-8356263c", + "status": "attached" + }, + "create_time": "2015-10-21T14:36:08.870Z", + "encrypted": false, + "id": "vol-35b333d9", + "iops": null, + "size": 1, + "snapshot_id": "", + "status": "in-use", + "tags": { + "env": "dev" + }, + "type": "standard", + "zone": "us-east-1b" + } ''' import time @@ -180,6 +236,7 @@ from distutils.version import LooseVersion try: import boto.ec2 from boto.exception import BotoServerError + from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping HAS_BOTO = True except ImportError: HAS_BOTO = False @@ -220,6 +277,7 @@ def get_volume(module, ec2): module.fail_json(msg="Found more than one volume in zone (if specified) with name: %s" % name) return vols[0] + def get_volumes(module, ec2): instance = module.params.get('instance') @@ -233,6 +291,7 @@ def get_volumes(module, ec2): module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) return vols + def delete_volume(module, ec2): volume_id = module.params['id'] try: @@ -243,6 +302,7 @@ def delete_volume(module, ec2): module.exit_json(changed=False) module.fail_json(msg=ec2_error.message) + def boto_supports_volume_encryption(): """ Check if Boto library supports encryption of EBS volumes (added in 2.29.0) @@ -290,6 +350,7 @@ def create_volume(module, ec2, zone): def attach_volume(module, ec2, volume, instance): device_name = module.params.get('device_name') + delete_on_termination = module.params.get('delete_on_termination') changed = False # If device_name isn't set, make a choice based on best practices here: @@ -313,6 +374,9 @@ def attach_volume(module, ec2, volume, instance): if adata.instance_id != instance.id: module.fail_json(msg = "Volume %s is already attached to another instance: %s" % (volume.id, adata.instance_id)) + else: + # Volume is already attached to right instance + changed = modify_dot_attribute(module, ec2, instance, device_name) else: try: volume.attach(instance.id, device_name) @@ -323,8 +387,41 @@ def attach_volume(module, ec2, volume, instance): except boto.exception.BotoServerError, e: module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + modify_dot_attribute(module, ec2, instance, device_name) + return volume, changed + +def modify_dot_attribute(module, ec2, instance, device_name): + """ Modify delete_on_termination attribute """ + + delete_on_termination = module.params.get('delete_on_termination') + changed = False + + try: + instance.update() + dot = instance.block_device_mapping[device_name].delete_on_termination + except boto.exception.BotoServerError, e: + module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + + if delete_on_termination != dot: + try: + bdt = BlockDeviceType(delete_on_termination=delete_on_termination) + bdm = BlockDeviceMapping() + bdm[device_name] = bdt + + ec2.modify_instance_attribute(instance_id=instance.id, attribute='blockDeviceMapping', value=bdm) + + while instance.block_device_mapping[device_name].delete_on_termination != delete_on_termination: + time.sleep(3) + instance.update() + changed = True + except boto.exception.BotoServerError, e: + module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + + return changed + + def detach_volume(module, ec2, volume): changed = False @@ -339,6 +436,7 @@ def detach_volume(module, ec2, volume): return volume, changed + def get_volume_info(volume, state): # If we're just listing volumes then do nothing, else get the latest update for the volume @@ -350,6 +448,7 @@ def get_volume_info(volume, state): volume_info = { 'create_time': volume.create_time, + 'encrypted': volume.encrypted, 'id': volume.id, 'iops': volume.iops, 'size': volume.size, @@ -365,9 +464,12 @@ def get_volume_info(volume, state): }, 'tags': volume.tags } + if hasattr(attachment, 'deleteOnTermination'): + volume_info['attachment_set']['deleteOnTermination'] = attachment.deleteOnTermination return volume_info + def main(): argument_spec = ec2_argument_spec() argument_spec.update(dict( @@ -379,6 +481,7 @@ def main(): iops = dict(), encrypted = dict(type='bool', default=False), device_name = dict(), + delete_on_termination = dict(type='bool', default=False), zone = dict(aliases=['availability_zone', 'aws_zone', 'ec2_zone']), snapshot = dict(), state = dict(choices=['absent', 'present', 'list'], default='present')