#!/usr/bin/python # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. DOCUMENTATION = ''' --- module: ec2_vol short_description: create and attach a volume, return volume id and device map description: - creates an EBS volume and optionally attaches it to an instance. If both an instance ID and a device name is given and the instance has a device at the device name, then no volume is created and no attachment is made. This module has a dependency on python-boto. version_added: "1.1" options: instance: description: - instance ID if you wish to attach the volume. required: false default: null aliases: [] volume_size: description: - size of volume (in GB) to create. required: true default: null aliases: [] device_name: description: - device id to override device mapping. Assumes /dev/sdf for Linux/UNIX and /dev/xvdf for Windows. required: false default: null aliases: [] region: description: - region in which to create the volume required: false default: null aliases: [] zone: description: - zone in which to create the volume, if unset uses the zone the instance is in (if set) required: false default: null aliases: [] examples: - code: 'local_action: ec2_vol instance=XXXXXX volume_size=5 device_name=sdd' description: "Simple playbook example" - code: | - name: Launch instances local_action: ec2 keypair=$keypair image=$image wait=yes count=3 register: ec2 - name: Create volumes and attach local_action: ec2_vol instance=${item.id} volume_size=5 with_items: ${ec2.instances} register: ec2_vol description: "Advanced - attaching multiple volumes to multiple instances" requirements: [ "boto" ] author: Lester Wade ''' # Note: this module needs to be made idempotent. Possible solution is to use resource tags with the volumes. # if state=present and it doesn't exist, create, tag and attach. # Check for state by looking for volume attachment with tag (and against block device mapping?). # Would personally like to revisit this in May when Eucalyptus also has tagging support (3.3). import sys import time try: import boto.ec2 except ImportError: print "failed=True msg='boto required for this module'" sys.exit(1) def main(): module = AnsibleModule( argument_spec = dict( instance = dict(), volume_size = dict(required=True), device_name = dict(), region = dict(), zone = dict(), ec2_url = dict(aliases=['EC2_URL']), ec2_secret_key = dict(aliases=['EC2_SECRET_KEY']), ec2_access_key = dict(aliases=['EC2_ACCESS_KEY']), ) ) instance = module.params.get('instance') volume_size = module.params.get('volume_size') device_name = module.params.get('device_name') region = module.params.get('region') zone = module.params.get('zone') ec2_url = module.params.get('ec2_url') ec2_secret_key = module.params.get('ec2_secret_key') ec2_access_key = module.params.get('ec2_access_key') # allow eucarc environment variables to be used if ansible vars aren't set if not ec2_url and 'EC2_URL' in os.environ: ec2_url = os.environ['EC2_URL'] if not ec2_secret_key and 'EC2_SECRET_KEY' in os.environ: ec2_secret_key = os.environ['EC2_SECRET_KEY'] if not ec2_access_key and 'EC2_ACCESS_KEY' in os.environ: ec2_access_key = os.environ['EC2_ACCESS_KEY'] # If we have a region specified, connect to its endpoint. if region: try: ec2 = boto.ec2.connect_to_region(region, aws_access_key_id=ec2_access_key, aws_secret_access_key=ec2_secret_key) except boto.exception.NoAuthHandlerFound, e: module.fail_json(msg = str(e)) # Otherwise, no region so we fallback to the old connection method else: try: if ec2_url: # if we have an URL set, connect to the specified endpoint ec2 = boto.connect_ec2_endpoint(ec2_url, ec2_access_key, ec2_secret_key) else: # otherwise it's Amazon. ec2 = boto.connect_ec2(ec2_access_key, ec2_secret_key) except boto.exception.NoAuthHandlerFound, e: module.fail_json(msg = str(e)) # 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), changed=False) # If no instance supplied, try volume creation based on module parameters. try: volume = ec2.create_volume(volume_size, zone) while volume.status != 'available': time.sleep(3) volume.update() except boto.exception.BotoServerError, e: module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) # Attach the created volume. if device_name and instance: try: attach = volume.attach(inst.id, device_name) while volume.attachment_state() != 'attached': time.sleep(3) volume.update() except boto.exception.BotoServerError, e: module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) # If device_name isn't set, make a choice based on best practices here: # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html # In future this needs to be more dynamic but combining block device mapping best practices # (bounds for devices, as above) with instance.block_device_mapping data would be tricky. For me ;) # Use password data attribute to tell whether the instance is Windows or Linux if device_name is None and instance: try: if inst.get_password_data == '': device_name = '/dev/sdf' attach = volume.attach(inst.id, device_name) while volume.attachment_state() != 'attached': time.sleep(3) volume.update() else: device_name = '/dev/xvdf' attach = volume.attach(inst.id, device_name) while volume.attachment_state() != 'attached': time.sleep(3) volume.update() 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) # this is magic, see lib/ansible/module_common.py #<<INCLUDE_ANSIBLE_MODULE_COMMON>> main()