ec2_eip 9.79 KB
Newer Older
1 2 3 4
#!/usr/bin/python
DOCUMENTATION = '''
---
module: ec2_eip
James Tanner committed
5
short_description: associate an EC2 elastic IP with an instance.
6 7
description:
    - This module associates AWS EC2 elastic IP addresses with instances
James Cammarata committed
8
version_added: 1.4
9 10 11 12
options:
  instance_id:
    description:
      - The EC2 instance id
13
    required: false
14 15
  public_ip:
    description:
16 17 18
      - The elastic IP address to associate with the instance.
      - If absent, allocate a new address
    required: false
19 20 21 22 23 24 25 26 27 28 29 30 31
  state:
    description:
      - If present, associate the IP with the instance.
      - If absent, disassociate the IP with the instance.
    required: false
    choices: ['present', 'absent']
    default: present
  region:
    description:
      - the EC2 region to use
    required: false
    default: null
    aliases: [ ec2_region ]
32 33 34 35 36
  in_vpc:
    description:
      - allocate an EIP inside a VPC or not
    required: false
    default: false
37
    version_added: "1.4"
38
  reuse_existing_ip_allowed:
39 40 41 42
    description:
      - Reuse an EIP that is not associated to an instance (when available), instead of allocating a new one.
    required: false
    default: false
43
    version_added: "1.6"
44 45 46 47 48
  wait_timeout:
    description:
      - how long to wait in seconds for newly provisioned EIPs to become available
    default: 300
    version_added: "1.7"
49

50
extends_documentation_fragment: aws
51
author: Lorin Hochstein <lorin@nimbisservices.com>
52 53 54
notes:
   - This module will return C(public_ip) on success, which will contain the
     public IP address associated with the instance.
55 56 57 58
   - There may be a delay between the time the Elastic IP is assigned and when
     the cloud instance is reachable via the new address. Use wait_for and pause
     to delay further playbook execution until the instance is reachable, if
     necessary.
59 60 61
'''

EXAMPLES = '''
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
- name: associate an elastic IP with an instance
  ec2_eip: instance_id=i-1212f003 ip=93.184.216.119

- name: disassociate an elastic IP from an instance
  ec2_eip: instance_id=i-1212f003 ip=93.184.216.119 state=absent

- name: allocate a new elastic IP and associate it with an instance
  ec2_eip: instance_id=i-1212f003

- name: allocate a new elastic IP without associating it to anything
  ec2_eip:
  register: eip
- name: output the IP
  debug: msg="Allocated IP is {{ eip.public_ip }}"

- name: provision new instances with ec2
  ec2: keypair=mykey instance_type=c1.medium image=emi-40603AD1 wait=yes group=webserver count=3
  register: ec2
- name: associate new elastic IPs with each of the instances
  ec2_eip: "instance_id={{ item }}"
  with_items: ec2.instance_ids
83

84 85 86 87 88
- name: allocate a new elastic IP inside a VPC in us-west-2
  ec2_eip: region=us-west-2 in_vpc=yes
  register: eip
- name: output the IP
  debug: msg="Allocated IP inside a VPC is {{ eip.public_ip }}"
89 90 91 92 93 94 95 96 97 98
'''

try:
    import boto.ec2
except ImportError:
    boto_found = False
else:
    boto_found = True


99 100
wait_timeout = 0  

101 102 103
def associate_ip_and_instance(ec2, address, instance_id, module):
    if ip_is_associated_with_instance(ec2, address.public_ip, instance_id, module):
        module.exit_json(changed=False, public_ip=address.public_ip)
104 105 106 107 108

    # If we're in check mode, nothing else to do
    if module.check_mode:
        module.exit_json(changed=True)

109 110 111 112 113 114 115 116
    try:
        if address.domain == "vpc":
            res = ec2.associate_address(instance_id, allocation_id=address.allocation_id)
        else:
            res = ec2.associate_address(instance_id, public_ip=address.public_ip)
    except boto.exception.EC2ResponseError, e:
        module.fail_json(msg=str(e))
    
117
    if res:
118
        module.exit_json(changed=True, public_ip=address.public_ip)
119 120 121 122
    else:
        module.fail_json(msg="association failed")


123 124 125
def disassociate_ip_and_instance(ec2, address, instance_id, module):
    if not ip_is_associated_with_instance(ec2, address.public_ip, instance_id, module):
        module.exit_json(changed=False, public_ip=address.public_ip)
126 127 128 129 130

    # If we're in check mode, nothing else to do
    if module.check_mode:
        module.exit_json(changed=True)

131 132 133 134 135 136 137 138
    try:
        if address.domain == "vpc":
            res = ec2.disassociate_address(association_id=address.association_id)
        else:
            res = ec2.disassociate_address(public_ip=address.public_ip)
    except boto.exception.EC2ResponseError, e:
        module.fail_json(msg=str(e))

139 140 141 142 143 144
    if res:
        module.exit_json(changed=True)
    else:
        module.fail_json(msg="disassociation failed")


145
def find_address(ec2, public_ip, module):
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    """ Find an existing Elastic IP address """  
    if wait_timeout != 0:
        timeout = time.time() + wait_timeout
        while timeout > time.time():
            try:
                addresses = ec2.get_all_addresses([public_ip])
                break
            except boto.exception.EC2ResponseError, e:
                if "Address '%s' not found." % public_ip in e.message :
                    pass
                else:
                    module.fail_json(msg=str(e.message))
            time.sleep(5)
        
        if timeout <= time.time():
            module.fail_json(msg = "wait for EIPs timeout on %s" % time.asctime())    
    else:
James Martin committed
163
        try:
164
            addresses = ec2.get_all_addresses([public_ip])
James Martin committed
165
        except boto.exception.EC2ResponseError, e:
166
            module.fail_json(msg=str(e.message))
James Martin committed
167

168 169 170 171
    return addresses[0]


def ip_is_associated_with_instance(ec2, public_ip, instance_id, module):
172
    """ Check if the elastic IP is currently associated with the instance """
173 174 175
    address = find_address(ec2, public_ip, module)
    if address:
        return address.instance_id == instance_id
176 177 178
    else:
        return False

179

180
def allocate_address(ec2, domain, module, reuse_existing_ip_allowed):
181
    """ Allocate a new elastic IP address (when needed) and return it """
182 183 184 185
    # If we're in check mode, nothing else to do
    if module.check_mode:
        module.exit_json(change=True)

186
    if reuse_existing_ip_allowed:
187 188 189 190 191 192
      if domain:
        domain_filter = { 'domain' : domain }
      else:
        domain_filter = { 'domain' : 'standard' }
      all_addresses = ec2.get_all_addresses(filters=domain_filter)

Alexey Wasilyev committed
193
      unassociated_addresses = filter(lambda a: a.instance_id == "", all_addresses)
194 195 196 197 198 199 200
      if unassociated_addresses:
        address = unassociated_addresses[0];
      else:
        address = ec2.allocate_address(domain=domain)
    else:
      address = ec2.allocate_address(domain=domain)

201
    return address
202 203


204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
def release_address(ec2, public_ip, module):
    """ Release a previously allocated elastic IP address """
    
    address = find_address(ec2, public_ip, module)
    
    # If we're in check mode, nothing else to do
    if module.check_mode:
        module.exit_json(change=True)
    
    res = address.release()    
    if res:
        module.exit_json(changed=True)
    else:
        module.fail_json(msg="release failed")


def find_instance(ec2, instance_id, module):
    """ Attempt to find the EC2 instance and return it """
    
    try:
        reservations = ec2.get_all_reservations(instance_ids=[instance_id])
    except boto.exception.EC2ResponseError, e:
        module.fail_json(msg=str(e))
    
    if len(reservations) == 1:
        instances = reservations[0].instances
        if len(instances) == 1:
            return instances[0]
    
    module.fail_json(msg="could not find instance" + instance_id)
    

236
def main():
237 238
    argument_spec = ec2_argument_spec()
    argument_spec.update(dict(
239
            instance_id = dict(required=False),
240
            public_ip = dict(required=False, aliases= ['ip']),
241 242
            state = dict(required=False, default='present',
                         choices=['present', 'absent']),
243
            in_vpc = dict(required=False, type='bool', default=False),
244
            reuse_existing_ip_allowed = dict(required=False, type='bool', default=False),
245
            wait_timeout = dict(default=300),
246 247 248 249 250
        )
    )

    module = AnsibleModule(
        argument_spec=argument_spec,
251 252 253 254 255 256
        supports_check_mode=True
    )

    if not boto_found:
        module.fail_json(msg="boto is required")

257
    ec2 = ec2_connect(module)
258 259 260 261

    instance_id = module.params.get('instance_id')
    public_ip = module.params.get('public_ip')
    state = module.params.get('state')
262 263
    in_vpc = module.params.get('in_vpc')
    domain  = "vpc" if in_vpc else None
264 265
    reuse_existing_ip_allowed = module.params.get('reuse_existing_ip_allowed')
    new_eip_timeout = int(module.params.get('wait_timeout'))
266 267

    if state == 'present':
268 269 270 271 272 273 274
        # Allocate an EIP and exit
        if not instance_id and not public_ip:     
            address = allocate_address(ec2, domain, module, reuse_existing_ip_allowed)
            module.exit_json(changed=True, public_ip=address.public_ip)

        # Return the EIP object since we've been given a public IP
        if public_ip:
275
            address = find_address(ec2, public_ip, module)
276 277 278 279 280 281 282 283 284 285 286 287

        # Allocate an IP for instance since no public_ip was provided
        if  instance_id and not public_ip: 
            instance = find_instance(ec2, instance_id, module)
            if instance.vpc_id:
                domain = "vpc"
            address = allocate_address(ec2, domain, module, reuse_existing_ip_allowed)
            # overriding the timeout since this is a a newly provisioned ip
            global wait_timeout
            wait_timeout = new_eip_timeout       

        # Associate address object (provided or allocated) with instance           
288
        associate_ip_and_instance(ec2, address, instance_id, module)
289

290
    else:
291 292
        #disassociating address from instance
        if instance_id:
293 294
            address = find_address(ec2, public_ip, module)
            disassociate_ip_and_instance(ec2, address, instance_id, module)
295 296 297
        #releasing address
        else:
            release_address(ec2, public_ip, module)
298 299


300 301 302
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import *
303 304 305

if __name__ == '__main__':
    main()