ec2 11.7 KB
Newer Older
1
#!/usr/bin/python
Seth Vidal committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# 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
short_description: create an instance in ec2, return instanceid
description:
22
     - creates ec2 instances and optionally waits for it to be 'running'. This module has a dependency on python-boto.
Seth Vidal committed
23 24
version_added: "0.9"
options:
Tim Gerla committed
25
  key_name:
Seth Vidal committed
26
    description:
Jan-Piet Mens committed
27
      - key pair to use on the instance
Seth Vidal committed
28 29
    required: true
    default: null
Tim Gerla committed
30
    aliases: ['keypair']
31 32 33 34 35 36
  id:
    description:
      - identifier for this instance or set of instances, so that the module will be idempotent with respect to EC2 instances.
    required: false
    default: null
    aliases: []
Seth Vidal committed
37 38
  group:
    description:
39
      - security group to use with the instance
Seth Vidal committed
40
    required: false
41 42 43 44 45 46 47 48
    default: null 
    aliases: []
  group_id:
    version_added: "1.1"
    description:
      - security group id to use with the instance 
    required: false
    default: null
Seth Vidal committed
49 50 51 52 53 54 55 56 57
    aliases: []
  instance_type:
    description:
      - instance type to use for the instance
    required: true
    default: null
    aliases: []
  image:
    description:
Jan-Piet Mens committed
58
       - I(emi) (or I(ami)) to use for the instance
Seth Vidal committed
59 60 61 62 63
    required: true
    default: null
    aliases: []
  kernel:
    description:
Jan-Piet Mens committed
64
      - kernel I(eki) to use for the instance
Seth Vidal committed
65 66 67 68 69
    required: false
    default: null
    aliases: []
  ramdisk:
    description:
Jan-Piet Mens committed
70
      - ramdisk I(eri) to use for the instance
Seth Vidal committed
71 72 73 74 75 76
    required: false
    default: null
    aliases: []
  wait:
    description:
      - wait for the instance to be in state 'running' before returning
77
    required: false
78 79
    default: "no"
    choices: [ "yes", "no" ]
Seth Vidal committed
80
    aliases: []
milan committed
81
  wait_timeout:
milan committed
82
    description:
milan committed
83
      - how long before wait gives up, in seconds
milan committed
84 85
    default: 300
    aliases: []
Seth Vidal committed
86 87
  ec2_url:
    description:
88 89
      - url to use to connect to EC2 or your Eucalyptus cloud (by default the module will use EC2 endpoints)
    required: false
Seth Vidal committed
90 91 92 93 94
    default: null
    aliases: []
  ec2_secret_key:
    description:
      - ec2 secret key
95
    required: false
Seth Vidal committed
96 97 98 99 100
    default: null
    aliases: []
  ec2_access_key:
    description:
      - ec2 access key
101
    required: false
Seth Vidal committed
102 103
    default: null
    aliases: []
Lester Wade committed
104 105 106 107 108 109
  count:
    description:
      - number of instances to launch
    required: False
    default: 1
    aliases: []
110
  monitor:
111
    version_added: "1.1"
112 113
    description:
      - enable detailed monitoring (CloudWatch) for instance
114
    required: false
115 116
    default: null
    aliases: []
117 118 119 120
  user_data:
    version_added: "0.9"
    description:
      - opaque blob of data which is made available to the ec2 instance
121
    required: false
122 123
    default: null
    aliases: []
124 125 126
  instance_tags:
    version_added: "1.0"
    description:
127 128
      - a hash/dictionary of tags to add to the new instance; '{"key":"value"}' and '{"key":"value","key":"value"}'
    required: false
129 130
    default: null
    aliases: []
lwade committed
131
  vpc_subnet_id:
lwade committed
132 133 134 135 136 137
    version_added: "1.1"
    description:
      - the subnet ID in which to launch the instance (VPC)
    required: false
    default: null
    aliases: []
138
  private_ip:
139
    version_added: "1.2"
140 141 142 143 144
    description:
      - the private ip address to assign the instance (from the vpc subnet)
    required: false
    defualt: null
    aliases: []
Tim Gerla committed
145
requirements: [ "boto" ]
Lester Wade committed
146
author: Seth Vidal, Tim Gerla, Lester Wade
Seth Vidal committed
147 148
'''

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
EXAMPLES = '''
# Basic provisioning example
local_action: 
    module: ec2 
    keypair: mykey 
    instance_type: c1.medium 
    image: emi-40603AD1 
    wait: yes 
    group: webserver 
    count: 3

# Advanced example with tagging and CloudWatch
local_action: 
    module: ec2 
    keypair: mykey 
    group: databases 
    instance_type: m1.large 
    image: ami-6e649707 
    wait: yes 
    wait_timeout: 500 
    count: 5 
    instance_tags: '{"db":"postgres"}' monitoring=true'

# VPC example
local_action: 
    module: ec2 
    keypair: mykey 
    group_id: sg-1dc53f72 
    instance_type: m1.small 
    image: ami-6e649707 
    wait: yes 
    vpc_subnet_id: subnet-29e63245'
'''

183
import sys
Seth Vidal committed
184 185
import time

186
try:
milan committed
187
    import boto
188 189 190 191
except ImportError:
    print "failed=True msg='boto required for this module'"
    sys.exit(1)

Seth Vidal committed
192 193 194
def main():
    module = AnsibleModule(
        argument_spec = dict(
Tim Gerla committed
195
            key_name = dict(required=True, aliases = ['keypair']),
196
            id = dict(),
197 198
            group = dict(),
            group_id = dict(),
Seth Vidal committed
199 200 201
            instance_type = dict(aliases=['type']),
            image = dict(required=True),
            kernel = dict(),
Lester Wade committed
202
            count = dict(default='1'), 
203
            monitoring = dict(choices=BOOLEANS, default=False),
Seth Vidal committed
204 205
            ramdisk = dict(),
            wait = dict(choices=BOOLEANS, default=False),
milan committed
206
            wait_timeout = dict(default=300),
Seth Vidal committed
207
            ec2_url = dict(aliases=['EC2_URL']),
208
            ec2_secret_key = dict(aliases=['EC2_SECRET_KEY'], no_log=True),
Seth Vidal committed
209
            ec2_access_key = dict(aliases=['EC2_ACCESS_KEY']),
210
            user_data = dict(),
211
            instance_tags = dict(),
lwade committed
212
            vpc_subnet_id = dict(),
213
            private_ip = dict(),
Seth Vidal committed
214 215 216
        )
    )

Tim Gerla committed
217
    key_name = module.params.get('key_name')
218
    id = module.params.get('id')
219 220
    group_name = module.params.get('group')
    group_id = module.params.get('group_id')
Seth Vidal committed
221 222
    instance_type = module.params.get('instance_type')
    image = module.params.get('image')
Lester Wade committed
223
    count = module.params.get('count') 
224
    monitoring = module.params.get('monitoring')
Seth Vidal committed
225 226 227
    kernel = module.params.get('kernel')
    ramdisk = module.params.get('ramdisk')
    wait = module.params.get('wait')
milan committed
228
    wait_timeout = int(module.params.get('wait_timeout'))
Seth Vidal committed
229 230 231
    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')
232
    user_data = module.params.get('user_data')
233
    instance_tags = module.params.get('instance_tags')
lwade committed
234
    vpc_subnet_id = module.params.get('vpc_subnet_id')
235
    private_ip = module.params.get('private_ip')
236

Tim Gerla committed
237 238 239 240 241 242 243
    # 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']
Seth Vidal committed
244

245 246 247 248 249 250 251
    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))
252 253
    
    # Here we try to lookup the group name from the security group id - if group_id is set.
254

255 256 257 258 259 260 261
    try:
        if group_id:
            grp_details = ec2.get_all_security_groups(group_ids=group_id)
            grp_item = grp_details[0]
            group_name = grp_item.name
    except boto.exception.NoAuthHandlerFound, e:
            module.fail_json(msg = str(e))
Lester Wade committed
262

263 264 265 266 267 268 269
    # Lookup any instances that much our run id.
    
    running_instances = []
    count_remaining = int(count)
        
    if id != None:
        filter_dict = {'client-token':id, 'instance-state-name' : 'running'}
270
        previous_reservations = ec2.get_all_instances(None, filter_dict)
271 272 273 274 275
        for res in previous_reservations:
            for prev_instance in res.instances:
                running_instances.append(prev_instance)
        count_remaining = count_remaining - len(running_instances) 
    
Lester Wade committed
276
    # Both min_count and max_count equal count parameter. This means the launch request is explicit (we want count, or fail) in how many instances we want.
277

278 279 280 281 282 283 284
    
    if count_remaining > 0:
        try:
            res = ec2.run_instances(image, key_name = key_name,
                                client_token=id,
                                min_count = count_remaining, 
                                max_count = count_remaining,
285
                                monitoring_enabled = monitoring,
286
                                security_groups = [group_name],
Tim Gerla committed
287 288 289
                                instance_type = instance_type,
                                kernel_id = kernel,
                                ramdisk_id = ramdisk,
lwade committed
290
                                subnet_id = vpc_subnet_id,
291
                                private_ip_address = private_ip,
Tim Gerla committed
292
                                user_data = user_data)
293 294
        except boto.exception.BotoServerError, e:
            module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
Seth Vidal committed
295

296 297 298 299 300 301 302 303 304 305 306
        instids = [ i.id for i in res.instances ]
        while True:
            try:
                res.connection.get_all_instances(instids)
                break
            except boto.exception.EC2ResponseError as e:
                if "<Code>InvalidInstanceID.NotFound</Code>" in str(e):
                    # there's a race between start and get an instance
                    continue
                else:
                    module.fail_json(msg = str(e))
Seth Vidal committed
307

308 309 310 311 312
        if instance_tags:
            try:
                ec2.create_tags(instids, module.from_json(instance_tags))
            except boto.exception.EC2ResponseError as e:
                module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
313

314
        # wait here until the instances are up
milan committed
315 316
        res_list = res.connection.get_all_instances(instids)
        this_res = res_list[0]
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
        num_running = 0
        wait_timeout = time.time() + wait_timeout
        while wait and wait_timeout > time.time() and num_running < len(instids):
            res_list = res.connection.get_all_instances(instids)
            this_res = res_list[0]
            num_running = len([ i for i in this_res.instances if i.state=='running' ])
            time.sleep(5)
        if wait and wait_timeout <= time.time():
            # waiting took too long
            module.fail_json(msg = "wait for instances running timeout on %s" % time.asctime())
    
        for inst in this_res.instances:
            running_instances.append(inst)
        
    instance_dict_array = []
    for inst in running_instances:
Seth Vidal committed
333 334
        d = {
           'id': inst.id,
335 336 337
           'ami_launch_index': inst.ami_launch_index,
           'private_ip': inst.private_ip_address,
           'private_dns_name': inst.private_dns_name,
Seth Vidal committed
338
           'public_ip': inst.ip_address,
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
           'dns_name': inst.dns_name,
           'public_dns_name': inst.public_dns_name,
           'state_code': inst.state_code,
           'architecture': inst.architecture,
           'image_id': inst.image_id,
           'key_name': inst.key_name,
           'virtualization_type': inst.virtualization_type,
           'placement': inst.placement,
           'kernel': inst.kernel,
           'ramdisk': inst.ramdisk,
           'launch_time': inst.launch_time,
           'instance_type': inst.instance_type,
           'root_device_type': inst.root_device_type,
           'root_device_name': inst.root_device_name,
           'state': inst.state,
           'hypervisor': inst.hypervisor
Seth Vidal committed
355
            }
356
        instance_dict_array.append(d)
Seth Vidal committed
357

358
    module.exit_json(changed=True, instances=instance_dict_array)
Seth Vidal committed
359 360 361 362 363

# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>

main()