diff --git a/library/cloud/ec2_vol b/library/cloud/ec2_vol index 176fb30ec2e..fb38852f429 100644 --- a/library/cloud/ec2_vol +++ b/library/cloud/ec2_vol @@ -55,7 +55,7 @@ options: version_added: "1.6" id: description: - - volume id if you wish to attach an existing volume (requires instance) + - volume id if you wish to attach an existing volume (requires instance) or remove an existing volume required: false default: null aliases: [] @@ -63,7 +63,7 @@ options: volume_size: description: - size of volume (in GB) to create. - required: true + required: false default: null aliases: [] iops: @@ -118,7 +118,13 @@ options: default: null aliases: [] version_added: "1.6" - + state: + description: + - whether to ensure the volume is present or absent + required: false + default: present + choices: ['absent', 'present'] + version_added: "1.6" requirements: [ "boto" ] author: Lester Wade ''' @@ -173,6 +179,7 @@ EXAMPLES = ''' wait: yes count: 1 register: ec2 + - local_action: module: ec2_vol instance: "{{ item.id }}" @@ -180,6 +187,12 @@ EXAMPLES = ''' device_name: /dev/xvdf with_items: ec2.instances register: ec2_vol + +# Remove a volume +- location: action + module: ec2_vol + id: vol-XXXXXXXX + state: absent ''' # Note: this module needs to be made idempotent. Possible solution is to use resource tags with the volumes. @@ -196,57 +209,50 @@ except ImportError: print "failed=True msg='boto required for this module'" sys.exit(1) -def main(): - argument_spec = ec2_argument_spec() - argument_spec.update(dict( - instance = dict(), - id = dict(), - name = dict(), - volume_size = dict(), - iops = dict(), - device_name = dict(), - zone = dict(aliases=['availability_zone', 'aws_zone', 'ec2_zone']), - snapshot = dict(), - ) - ) - module = AnsibleModule(argument_spec=argument_spec) - +def get_volume(module, ec2): + name = module.params.get('name') id = module.params.get('id') + zone = module.params.get('zone') + filters = {} + volume_ids = None + if zone: + filters['availability_zone'] = zone + if name: + filters = {'tag:Name': name} + if id: + volume_ids = [id] + try: + vols = ec2.get_all_volumes(volume_ids=volume_ids, filters=filters) + except boto.exception.BotoServerError, e: + module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + + if not vols: + module.fail_json(msg="Could not find volume in zone (if specified): %s" % name or id) + if len(vols) > 1: + module.fail_json(msg="Found more than one volume in zone (if specified) with name: %s" % name) + return vols[0] + + +def delete_volume(module, ec2): + vol = get_volume(module, ec2) + if not vol: + module.exit_json(changed=False) + else: + if vol.attachment_state() is not None: + adata = vol.attach_data + module.fail_json(msg="Volume %s is attached to an instance %s." % (vol.id, adata.instance_id)) + ec2.delete_volume(vol.id) + module.exit_json(changed=True) + + +def create_volume(module, ec2, zone): name = module.params.get('name') + id = module.params.get('id') instance = module.params.get('instance') - volume_size = module.params.get('volume_size') iops = module.params.get('iops') - device_name = module.params.get('device_name') - zone = module.params.get('zone') + volume_size = module.params.get('volume_size') snapshot = module.params.get('snapshot') - - ec2 = ec2_connect(module) - - if id and name: - module.fail_json(msg="Both id and name cannot be specified") - - if not (id or name or volume_size): - module.fail_json(msg="Cannot specify volume_size and either one of name or id") - - # Here we need to get the zone info for the instance. This covers situation where - # instance is specified but zone isn't. - # Useful for playbooks chaining instance launch with volume create + attach and where the - # zone doesn't matter to the user. - if instance: - reservation = ec2.get_all_instances(instance_ids=instance) - inst = reservation[0].instances[0] - zone = inst.placement - - # Check if there is a volume already mounted there. - if device_name: - if device_name in inst.block_device_mapping: - module.exit_json(msg="Volume mapping for %s already exists on instance %s" % (device_name, instance), - volume_id=inst.block_device_mapping[device_name].volume_id, - device=device_name, - changed=False) - # If custom iops is defined we use volume_type "io1" rather than the default of "standard" - if iops: volume_type = 'io1' else: @@ -259,26 +265,7 @@ def main(): if iops or volume_size: module.fail_json(msg = "Parameters are not compatible: [id or name] and [iops or volume_size]") - filters = {} - volume_ids = None - if zone: - filters['availability_zone'] = zone - if name: - filters = {'tag:Name': name} - if id: - volume_ids = [id] - try: - vols = ec2.get_all_volumes(volume_ids=volume_ids, filters=filters) - except boto.exception.BotoServerError, e: - module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) - - if not vols: - module.fail_json(msg = "Could not find volume in zone (if specified): %s" % name or id) - if len(vols) > 1: - module.fail_json(msg = - "Found more than one volume in zone (if specified) with name: %s" % name) - - volume = vols.pop() + volume = get_volume(module, ec2) if volume.attachment_state() is not None: adata = volume.attach_data if adata.instance_id != instance: @@ -298,12 +285,15 @@ def main(): volume.update() except boto.exception.BotoServerError, e: module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + return volume + - # Attach the created volume. +def attach_volume(module, ec2, volume, instance): + device_name = module.params.get('device_name') if device_name and instance: try: - attach = volume.attach(inst.id, device_name) + attach = volume.attach(instance.id, device_name) while volume.attachment_state() != 'attached': time.sleep(3) volume.update() @@ -334,11 +324,65 @@ def main(): except boto.exception.BotoServerError, e: module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) - print json.dumps({ - "volume_id": volume.id, - "device": device_name - }) - sys.exit(0) + +def main(): + argument_spec = ec2_argument_spec() + argument_spec.update(dict( + instance = dict(), + id = dict(), + name = dict(), + volume_size = dict(), + iops = dict(), + device_name = dict(), + zone = dict(aliases=['availability_zone', 'aws_zone', 'ec2_zone']), + snapshot = dict(), + state = dict(choices=['absent', 'present'], default='present') + ) + ) + module = AnsibleModule(argument_spec=argument_spec) + + id = module.params.get('id') + name = module.params.get('name') + instance = module.params.get('instance') + volume_size = module.params.get('volume_size') + iops = module.params.get('iops') + device_name = module.params.get('device_name') + zone = module.params.get('zone') + snapshot = module.params.get('snapshot') + state = module.params.get('state') + + ec2 = ec2_connect(module) + + if id and name: + module.fail_json(msg="Both id and name cannot be specified") + + if not (id or name or volume_size): + module.fail_json(msg="Cannot specify volume_size and either one of name or id") + + # Here we need to get the zone info for the instance. This covers situation where + # instance is specified but zone isn't. + # Useful for playbooks chaining instance launch with volume create + attach and where the + # zone doesn't matter to the user. + if instance: + reservation = ec2.get_all_instances(instance_ids=instance) + inst = reservation[0].instances[0] + zone = inst.placement + + # Check if there is a volume already mounted there. + if device_name: + if device_name in inst.block_device_mapping: + module.exit_json(msg="Volume mapping for %s already exists on instance %s" % (device_name, instance), + volume_id=inst.block_device_mapping[device_name].volume_id, + device=device_name, + changed=False) + + if state == 'absent': + delete_volume(module, ec2) + else: + volume = create_volume(module, ec2, zone) + if instance: + attach_volume(module, ec2, volume, inst) + module.exit_json(volume_id=volume.id, device=device_name) # import module snippets from ansible.module_utils.basic import *