2013-03-19 17:07:36 +01:00
#!/usr/bin/python
2012-11-09 06:15:12 +01:00
# 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
2014-12-01 20:46:07 +01:00
short_description : create , terminate , start or stop an instance in ec2
2012-11-09 06:15:12 +01:00
description :
2014-12-03 17:16:59 +01:00
- Creates or terminates ec2 instances .
version_added : " 0.9 "
2012-11-09 06:15:12 +01:00
options :
2013-01-22 21:09:31 +01:00
key_name :
2012-11-09 06:15:12 +01:00
description :
2012-11-21 18:49:30 +01:00
- key pair to use on the instance
2014-01-14 00:12:01 +01:00
required : false
2012-11-09 06:15:12 +01:00
default : null
2013-01-22 21:09:31 +01:00
aliases : [ ' keypair ' ]
2012-11-09 06:15:12 +01:00
group :
description :
2013-06-17 22:15:12 +02:00
- security group ( or list of groups ) to use with the instance
2012-11-09 06:15:12 +01:00
required : false
2013-06-21 02:00:52 +02:00
default : null
2013-06-20 04:10:48 +02:00
aliases : [ ' groups ' ]
2013-02-20 10:31:22 +01:00
group_id :
version_added : " 1.1 "
description :
2013-07-23 00:00:13 +02:00
- security group id ( or list of ids ) to use with the instance
2013-02-20 10:31:22 +01:00
required : false
default : null
2012-11-09 06:15:12 +01:00
aliases : [ ]
2013-04-23 01:23:30 +02:00
region :
version_added : " 1.2 "
description :
2015-05-18 22:30:18 +02:00
- The AWS region to use . Must be specified if ec2_url is not used . If not specified then the value of the EC2_REGION environment variable , if any , is used . See U ( http : / / docs . aws . amazon . com / general / latest / gr / rande . html #ec2_region)
2013-04-23 01:23:30 +02:00
required : false
default : null
2013-08-13 15:30:56 +02:00
aliases : [ ' aws_region ' , ' ec2_region ' ]
2013-04-08 11:42:34 +02:00
zone :
version_added : " 1.2 "
description :
2013-08-13 15:30:56 +02:00
- AWS availability zone in which to launch the instance
2013-04-08 11:42:34 +02:00
required : false
default : null
2013-08-13 15:30:56 +02:00
aliases : [ ' aws_zone ' , ' ec2_zone ' ]
2012-11-09 06:15:12 +01:00
instance_type :
description :
2015-05-18 22:30:18 +02:00
- instance type to use for the instance , see U ( http : / / docs . aws . amazon . com / AWSEC2 / latest / UserGuide / instance - types . html )
2012-11-09 06:15:12 +01:00
required : true
default : null
aliases : [ ]
2014-09-27 08:03:22 +02:00
tenancy :
2015-01-21 23:27:40 +01:00
version_added : " 1.9 "
2014-09-27 08:03:22 +02:00
description :
2015-05-18 22:30:18 +02:00
- An instance with a tenancy of " dedicated " runs on single - tenant hardware and can only be launched into a VPC . Note that to use dedicated tenancy you MUST specify a vpc_subnet_id as well . Dedicated tenancy is not available for EC2 " micro " instances .
2014-09-27 08:03:22 +02:00
required : false
default : default
2015-05-18 22:30:18 +02:00
choices : [ " default " , " dedicated " ]
2014-09-27 08:03:22 +02:00
aliases : [ ]
2013-12-13 19:43:30 +01:00
spot_price :
2013-12-13 21:01:58 +01:00
version_added : " 1.5 "
2013-12-13 19:43:30 +01:00
description :
- Maximum spot price to bid , If not set a regular on - demand instance is requested . A spot request is made with this maximum bid . When it is filled , the instance is started .
required : false
default : null
aliases : [ ]
2015-04-24 23:26:37 +02:00
spot_type :
2015-06-24 20:43:04 +02:00
version_added : " 2.0 "
2015-04-24 23:26:37 +02:00
description :
- Type of spot request ; one of " one-time " or " persistent " . Defaults to " one-time " if not supplied .
required : false
default : " one-time "
choices : [ " one-time " , " persistent " ]
aliases : [ ]
2012-11-09 06:15:12 +01:00
image :
description :
2014-12-01 20:46:07 +01:00
- I ( ami ) ID to use for the instance
2012-11-09 06:15:12 +01:00
required : true
default : null
aliases : [ ]
kernel :
description :
2012-11-21 18:49:30 +01:00
- kernel I ( eki ) to use for the instance
2012-11-09 06:15:12 +01:00
required : false
default : null
aliases : [ ]
ramdisk :
description :
2012-11-21 18:49:30 +01:00
- ramdisk I ( eri ) to use for the instance
2012-11-09 06:15:12 +01:00
required : false
default : null
aliases : [ ]
wait :
description :
2014-12-01 20:46:07 +01:00
- wait for the instance to be ' running ' before returning . Does not wait for SSH , see ' wait_for ' example for details .
2013-02-20 10:31:22 +01:00
required : false
2013-03-12 13:18:12 +01:00
default : " no "
choices : [ " yes " , " no " ]
2012-11-09 06:15:12 +01:00
aliases : [ ]
2013-03-07 18:01:55 +01:00
wait_timeout :
2013-03-07 23:37:25 +01:00
description :
2013-03-07 23:44:22 +01:00
- how long before wait gives up , in seconds
2013-03-07 23:37:25 +01:00
default : 300
aliases : [ ]
2013-12-13 19:43:30 +01:00
spot_wait_timeout :
2013-12-13 21:01:58 +01:00
version_added : " 1.5 "
2013-12-13 19:43:30 +01:00
description :
- how long to wait for the spot instance request to be fulfilled
default : 600
aliases : [ ]
2013-01-25 16:02:53 +01:00
count :
description :
- number of instances to launch
required : False
default : 1
aliases : [ ]
2013-10-11 19:32:23 +02:00
monitoring :
2013-02-20 10:31:22 +01:00
version_added : " 1.1 "
2013-02-04 19:03:47 +01:00
description :
- enable detailed monitoring ( CloudWatch ) for instance
2013-02-20 10:31:22 +01:00
required : false
2013-02-04 19:03:47 +01:00
default : null
2015-05-18 22:30:18 +02:00
choices : [ " yes " , " no " ]
2013-02-04 19:03:47 +01:00
aliases : [ ]
2012-11-17 16:02:29 +01:00
user_data :
version_added : " 0.9 "
description :
- opaque blob of data which is made available to the ec2 instance
2013-02-20 10:31:22 +01:00
required : false
2012-11-17 16:02:29 +01:00
default : null
aliases : [ ]
2013-02-09 17:42:14 +01:00
instance_tags :
version_added : " 1.0 "
description :
2015-09-08 18:16:53 +02:00
- a hash / dictionary of tags to add to the new instance or for starting / stopping instance by tag ; ' { " key " : " value " } ' and ' { " key " : " value " , " key " : " value " } '
2013-02-20 10:31:22 +01:00
required : false
2013-02-09 17:42:14 +01:00
default : null
aliases : [ ]
2013-06-17 15:35:53 +02:00
placement_group :
version_added : " 1.3 "
description :
- placement group for the instance when using EC2 Clustered Compute
required : false
default : null
aliases : [ ]
2013-02-23 18:46:35 +01:00
vpc_subnet_id :
2013-02-23 18:45:47 +01:00
version_added : " 1.1 "
description :
- the subnet ID in which to launch the instance ( VPC )
required : false
default : null
aliases : [ ]
2013-10-11 19:13:04 +02:00
assign_public_ip :
2014-03-20 15:21:10 +01:00
version_added : " 1.5 "
2013-10-11 19:13:04 +02:00
description :
- when provisioning within vpc , assign a public IP address . Boto library must be 2.13 .0 +
required : false
default : null
2015-05-18 15:21:49 +02:00
choices : [ " yes " , " no " ]
2013-10-11 19:13:04 +02:00
aliases : [ ]
2013-04-02 07:31:31 +02:00
private_ip :
2013-04-05 21:30:36 +02:00
version_added : " 1.2 "
2013-04-02 07:31:31 +02:00
description :
- the private ip address to assign the instance ( from the vpc subnet )
required : false
2014-07-31 23:29:52 +02:00
default : null
2013-04-02 07:31:31 +02:00
aliases : [ ]
2013-07-16 14:31:30 +02:00
instance_profile_name :
version_added : " 1.3 "
description :
2013-09-08 16:57:30 +02:00
- Name of the IAM instance profile to use . Boto library must be 2.5 .0 +
2013-07-16 14:31:30 +02:00
required : false
default : null
aliases : [ ]
2013-06-21 19:43:29 +02:00
instance_ids :
2013-06-21 02:00:52 +02:00
version_added : " 1.3 "
description :
2014-05-09 07:21:35 +02:00
- " list of instance ids, currently used for states: absent, running, stopped "
2013-06-21 02:00:52 +02:00
required : false
default : null
2014-11-16 19:48:39 +01:00
aliases : [ ' instance_id ' ]
2013-12-13 23:12:58 +01:00
source_dest_check :
2014-04-07 18:44:34 +02:00
version_added : " 1.6 "
2013-12-13 23:12:58 +01:00
description :
- Enable or Disable the Source / Destination checks ( for NAT instances and Virtual Routers )
required : false
2015-05-18 22:34:57 +02:00
default : yes
choices : [ " yes " , " no " ]
2015-06-23 07:14:30 +02:00
termination_protection :
version_added : " 2.0 "
description :
- Enable or Disable the Termination Protection
required : false
default : no
choices : [ " yes " , " no " ]
2013-06-21 19:43:29 +02:00
state :
version_added : " 1.3 "
description :
- create or terminate instances
required : false
default : ' present '
aliases : [ ]
2014-07-31 23:29:52 +02:00
choices : [ ' present ' , ' absent ' , ' running ' , ' stopped ' ]
2013-10-16 07:37:24 +02:00
volumes :
version_added : " 1.5 "
description :
2015-09-28 18:02:46 +02:00
- a list of hash / dictionaries of volumes to add to the new instance ; ' [ { " key " : " value " , " key " : " value " }] ' ; keys allowed are - device_name ( str ; required ) , delete_on_termination ( bool ; False ) , device_type ( deprecated ) , ephemeral ( str ) , encrypted ( bool ; False ) , snapshot ( str ) , volume_type ( str ) , iops ( int ) - device_type is deprecated use volume_type , iops must be set when volume_type = ' io1 ' , ephemeral and snapshot are mutually exclusive .
2013-10-16 07:37:24 +02:00
required : false
default : null
aliases : [ ]
2014-04-03 11:45:47 +02:00
ebs_optimized :
version_added : " 1.6 "
description :
- whether instance is using optimized EBS volumes , see U ( http : / / docs . aws . amazon . com / AWSEC2 / latest / UserGuide / EBSOptimized . html )
required : false
2015-10-21 22:43:50 +02:00
default : ' false '
2014-02-07 16:34:45 +01:00
exact_count :
version_added : " 1.5 "
description :
2015-07-09 23:55:56 +02:00
- An integer value which indicates how many instances that match the ' count_tag ' parameter should be running . Instances are either created or terminated based on this value .
2014-02-07 16:34:45 +01:00
required : false
default : null
aliases : [ ]
count_tag :
version_added : " 1.5 "
description :
2015-07-09 23:55:56 +02:00
- Used with ' exact_count ' to determine how many nodes based on a specific tag criteria should be running . This can be expressed in multiple ways and is shown in the EXAMPLES section . For instance , one can request 25 servers that are tagged with " class=webserver " .
2014-02-07 16:34:45 +01:00
required : false
default : null
aliases : [ ]
2015-09-05 01:44:35 +02:00
network_interfaces :
version_added : " 2.0 "
description :
- A list of existing network interfaces to attach to the instance at launch . When specifying existing network interfaces , none of the assign_public_ip , private_ip , vpc_subnet_id , group , or group_id parameters may be used . ( Those parameters are for creating a new network interface at launch . )
required : false
default : null
2015-09-06 19:53:28 +02:00
aliases : [ ' network_interface ' ]
2013-06-21 19:43:29 +02:00
2015-07-09 23:55:56 +02:00
author :
2015-06-15 20:41:22 +02:00
- " Tim Gerla (@tgerla) "
- " Lester Wade (@lwade) "
- " Seth Vidal "
2014-04-09 08:43:55 +02:00
extends_documentation_fragment : aws
2012-11-09 06:15:12 +01:00
'''
2013-04-05 21:30:36 +02:00
EXAMPLES = '''
2014-12-01 20:46:07 +01:00
# Note: These examples do not set authentication details, see the AWS Guide for details.
2013-08-13 15:30:56 +02:00
2013-04-05 21:30:36 +02:00
# Basic provisioning example
2014-12-01 20:46:07 +01:00
- ec2 :
2014-02-28 19:52:47 +01:00
key_name : mykey
2014-12-01 20:46:07 +01:00
instance_type : t2 . micro
image : ami - 123456
2013-06-21 02:00:52 +02:00
wait : yes
group : webserver
2013-04-05 21:30:36 +02:00
count : 3
2014-12-01 20:46:07 +01:00
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2013-04-05 21:30:36 +02:00
# Advanced example with tagging and CloudWatch
2014-12-01 20:46:07 +01:00
- ec2 :
2014-02-28 19:52:47 +01:00
key_name : mykey
2013-06-21 02:00:52 +02:00
group : databases
2014-12-01 20:46:07 +01:00
instance_type : t2 . micro
image : ami - 123456
2013-06-21 02:00:52 +02:00
wait : yes
wait_timeout : 500
count : 5
2015-07-09 23:55:56 +02:00
instance_tags :
2014-02-07 16:34:45 +01:00
db : postgres
monitoring : yes
2014-12-01 20:46:07 +01:00
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2013-04-05 21:30:36 +02:00
2014-03-18 21:49:27 +01:00
# Single instance with additional IOPS volume from snapshot and volume delete on termination
2014-12-01 20:46:07 +01:00
- ec2 :
2014-02-28 19:52:47 +01:00
key_name : mykey
2013-10-16 07:37:24 +02:00
group : webserver
2014-12-01 20:46:07 +01:00
instance_type : c3 . medium
image : ami - 123456
2013-10-16 07:37:24 +02:00
wait : yes
wait_timeout : 500
volumes :
2014-12-01 20:46:07 +01:00
- device_name : / dev / sdb
snapshot : snap - abcdef12
2015-09-28 18:02:46 +02:00
volume_type : io1
2014-12-01 20:46:07 +01:00
iops : 1000
volume_size : 100
delete_on_termination : true
2014-02-07 16:34:45 +01:00
monitoring : yes
2014-12-01 20:46:07 +01:00
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2013-10-16 07:37:24 +02:00
2013-06-17 22:15:12 +02:00
# Multiple groups example
2014-12-01 20:46:07 +01:00
- ec2 :
2014-02-28 19:52:47 +01:00
key_name : mykey
2013-06-20 04:10:48 +02:00
group : [ ' databases ' , ' internal-services ' , ' sshable ' , ' and-so-forth ' ]
2013-06-21 02:00:52 +02:00
instance_type : m1 . large
image : ami - 6e649707
wait : yes
wait_timeout : 500
count : 5
2015-07-09 23:55:56 +02:00
instance_tags :
2014-02-07 16:34:45 +01:00
db : postgres
monitoring : yes
2014-12-01 20:46:07 +01:00
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2013-06-17 22:15:12 +02:00
2013-10-16 07:37:24 +02:00
# Multiple instances with additional volume from snapshot
2014-12-01 20:46:07 +01:00
- ec2 :
2014-02-28 19:52:47 +01:00
key_name : mykey
2013-10-16 07:37:24 +02:00
group : webserver
instance_type : m1 . large
image : ami - 6e649707
wait : yes
wait_timeout : 500
count : 5
volumes :
- device_name : / dev / sdb
snapshot : snap - abcdef12
volume_size : 10
2014-02-07 16:34:45 +01:00
monitoring : yes
2013-07-23 00:00:13 +02:00
vpc_subnet_id : subnet - 29e63245
2013-10-11 19:13:04 +02:00
assign_public_ip : yes
2013-06-21 02:00:52 +02:00
2014-09-27 08:03:22 +02:00
# Dedicated tenancy example
- local_action :
module : ec2
assign_public_ip : yes
group_id : sg - 1 dc53f72
key_name : mykey
image : ami - 6e649707
instance_type : m1 . small
tenancy : dedicated
vpc_subnet_id : subnet - 29e63245
wait : yes
2013-12-13 19:43:30 +01:00
# Spot instance example
2014-12-01 20:46:07 +01:00
- ec2 :
2013-12-13 19:43:30 +01:00
spot_price : 0.24
spot_wait_timeout : 600
keypair : mykey
group_id : sg - 1 dc53f72
instance_type : m1 . small
image : ami - 6e649707
wait : yes
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2015-09-06 19:53:28 +02:00
# Examples using pre-existing network interfaces
- ec2 :
key_name : mykey
instance_type : t2 . small
image : ami - f005ba11
network_interface : eni - deadbeef
2015-09-06 19:18:20 +02:00
- ec2 :
key_name : mykey
instance_type : t2 . small
image : ami - f005ba11
network_interfaces : [ ' eni-deadbeef ' , ' eni-5ca1ab1e ' ]
2013-06-21 02:00:52 +02:00
# Launch instances, runs some tasks
# and then terminate them
- name : Create a sandbox instance
hosts : localhost
gather_facts : False
vars :
2014-02-28 19:52:47 +01:00
key_name : my_keypair
2013-06-21 02:00:52 +02:00
instance_type : m1 . small
security_group : my_securitygroup
image : my_ami_id
region : us - east - 1
tasks :
- name : Launch instance
2015-07-09 23:55:56 +02:00
ec2 :
2014-12-01 20:46:07 +01:00
key_name : " {{ keypair }} "
group : " {{ security_group }} "
instance_type : " {{ instance_type }} "
image : " {{ image }} "
wait : true
region : " {{ region }} "
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2013-06-21 02:00:52 +02:00
register : ec2
- name : Add new instance to host group
2014-12-01 20:46:07 +01:00
add_host : hostname = { { item . public_ip } } groupname = launched
2013-07-19 15:42:22 +02:00
with_items : ec2 . instances
2013-06-21 02:00:52 +02:00
- name : Wait for SSH to come up
2014-12-01 20:46:07 +01:00
wait_for : host = { { item . public_dns_name } } port = 22 delay = 60 timeout = 320 state = started
2013-07-19 15:42:22 +02:00
with_items : ec2 . instances
2013-06-21 02:00:52 +02:00
- name : Configure instance ( s )
hosts : launched
sudo : True
gather_facts : True
roles :
- my_awesome_role
- my_awesome_test
- name : Terminate instances
hosts : localhost
connection : local
tasks :
- name : Terminate instances that were previously launched
2014-12-01 20:46:07 +01:00
ec2 :
2013-06-21 19:43:29 +02:00
state : ' absent '
2014-02-05 16:12:41 +01:00
instance_ids : ' {{ ec2.instance_ids }} '
2013-06-21 02:00:52 +02:00
2013-12-12 23:16:59 +01:00
# Start a few existing instances, run some tasks
# and stop the instances
- name : Start sandbox instances
hosts : localhost
gather_facts : false
connection : local
vars :
instance_ids :
2014-02-05 16:14:44 +01:00
- ' i-xxxxxx '
- ' i-xxxxxx '
- ' i-xxxxxx '
2013-12-12 23:16:59 +01:00
region : us - east - 1
tasks :
- name : Start the sandbox instances
2014-12-01 20:46:07 +01:00
ec2 :
2014-02-05 16:14:44 +01:00
instance_ids : ' {{ instance_ids }} '
region : ' {{ region }} '
state : running
wait : True
2014-12-01 20:46:07 +01:00
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2013-12-12 23:16:59 +01:00
role :
- do_neat_stuff
- do_more_neat_stuff
- name : Stop sandbox instances
hosts : localhost
gather_facts : false
connection : local
vars :
instance_ids :
2014-02-05 16:14:44 +01:00
- ' i-xxxxxx '
- ' i-xxxxxx '
- ' i-xxxxxx '
2013-12-12 23:16:59 +01:00
region : us - east - 1
tasks :
2014-11-10 12:24:31 +01:00
- name : Stop the sandbox instances
2014-12-01 20:46:07 +01:00
ec2 :
instance_ids : ' {{ instance_ids }} '
region : ' {{ region }} '
state : stopped
wait : True
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2014-02-07 16:34:45 +01:00
2015-07-09 23:55:56 +02:00
#
# Start stopped instances specified by tag
#
- local_action :
module : ec2
instance_tags :
Name : ExtraPower
state : running
2014-02-07 16:34:45 +01:00
#
# Enforce that 5 instances with a tag "foo" are running
2014-12-01 20:46:07 +01:00
# (Highly recommended!)
2014-02-07 16:34:45 +01:00
#
2014-12-01 20:46:07 +01:00
- ec2 :
2015-10-07 13:49:05 +02:00
state : running
2014-02-28 19:52:47 +01:00
key_name : mykey
2014-02-07 16:34:45 +01:00
instance_type : c1 . medium
2014-12-01 20:46:07 +01:00
image : ami - 40603 AD1
2014-02-07 16:34:45 +01:00
wait : yes
group : webserver
instance_tags :
foo : bar
exact_count : 5
count_tag : foo
2014-12-01 20:46:07 +01:00
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2014-02-07 16:34:45 +01:00
#
2014-02-07 16:49:13 +01:00
# Enforce that 5 running instances named "database" with a "dbtype" of "postgres"
2014-02-07 16:34:45 +01:00
#
2014-12-01 20:46:07 +01:00
- ec2 :
2015-10-07 13:49:05 +02:00
state : running
2014-02-28 19:52:47 +01:00
key_name : mykey
2014-02-07 16:34:45 +01:00
instance_type : c1 . medium
2014-12-01 20:46:07 +01:00
image : ami - 40603 AD1
2014-02-07 16:34:45 +01:00
wait : yes
group : webserver
2015-07-09 23:55:56 +02:00
instance_tags :
2014-02-07 16:49:13 +01:00
Name : database
dbtype : postgres
2014-02-07 16:34:45 +01:00
exact_count : 5
2015-07-09 23:55:56 +02:00
count_tag :
2014-02-07 16:49:13 +01:00
Name : database
dbtype : postgres
2014-12-01 20:46:07 +01:00
vpc_subnet_id : subnet - 29e63245
assign_public_ip : yes
2014-02-07 16:34:45 +01:00
#
# count_tag complex argument examples
#
# instances with tag foo
count_tag :
foo :
# instances with tag foo=bar
count_tag :
foo : bar
# instances with tags foo=bar & baz
count_tag :
foo : bar
baz :
# instances with tags foo & bar & baz=bang
count_tag :
- foo
- bar
- baz : bang
2013-04-05 21:30:36 +02:00
'''
2012-11-09 06:15:12 +01:00
import time
2014-02-07 02:22:45 +01:00
from ast import literal_eval
2012-11-09 06:15:12 +01:00
2013-01-22 23:29:28 +01:00
try :
2013-04-23 01:23:30 +02:00
import boto . ec2
2013-10-16 07:37:24 +02:00
from boto . ec2 . blockdevicemapping import BlockDeviceType , BlockDeviceMapping
2013-06-21 02:00:52 +02:00
from boto . exception import EC2ResponseError
2014-12-04 21:12:26 +01:00
from boto . vpc import VPCConnection
2015-04-02 01:16:54 +02:00
HAS_BOTO = True
2013-01-22 23:29:28 +01:00
except ImportError :
2015-04-02 01:16:54 +02:00
HAS_BOTO = False
2013-01-22 23:29:28 +01:00
2014-09-17 22:20:05 +02:00
def find_running_instances_by_count_tag ( module , ec2 , count_tag , zone = None ) :
2014-02-07 02:22:45 +01:00
# get reservations for instances that match tag(s) and are running
2014-09-17 22:20:05 +02:00
reservations = get_reservations ( module , ec2 , tags = count_tag , state = " running " , zone = zone )
2014-02-07 02:22:45 +01:00
instances = [ ]
for res in reservations :
if hasattr ( res , ' instances ' ) :
for inst in res . instances :
2015-07-09 23:55:56 +02:00
instances . append ( inst )
2014-02-07 02:22:45 +01:00
return reservations , instances
def _set_none_to_blank ( dictionary ) :
result = dictionary
for k in result . iterkeys ( ) :
if type ( result [ k ] ) == dict :
2014-10-17 19:47:17 +02:00
result [ k ] = _set_none_to_blank ( result [ k ] )
2014-02-07 02:22:45 +01:00
elif not result [ k ] :
result [ k ] = " "
2015-07-09 23:55:56 +02:00
return result
2014-02-07 02:22:45 +01:00
2014-09-17 22:20:05 +02:00
def get_reservations ( module , ec2 , tags = None , state = None , zone = None ) :
2014-02-07 02:22:45 +01:00
# TODO: filters do not work with tags that have underscores
filters = dict ( )
if tags is not None :
if type ( tags ) is str :
try :
tags = literal_eval ( tags )
except :
pass
# if string, we only care that a tag of that name exists
if type ( tags ) is str :
filters . update ( { " tag-key " : tags } )
# if list, append each item to filters
if type ( tags ) is list :
for x in tags :
if type ( x ) is dict :
x = _set_none_to_blank ( x )
filters . update ( dict ( ( " tag: " + tn , tv ) for ( tn , tv ) in x . iteritems ( ) ) )
else :
filters . update ( { " tag-key " : x } )
# if dict, add the key and value to the filter
if type ( tags ) is dict :
tags = _set_none_to_blank ( tags )
filters . update ( dict ( ( " tag: " + tn , tv ) for ( tn , tv ) in tags . iteritems ( ) ) )
if state :
# http://stackoverflow.com/questions/437511/what-are-the-valid-instancestates-for-the-amazon-ec2-api
filters . update ( { ' instance-state-name ' : state } )
2014-09-17 22:20:05 +02:00
if zone :
filters . update ( { ' availability-zone ' : zone } )
2014-02-07 02:22:45 +01:00
results = ec2 . get_all_instances ( filters = filters )
return results
2013-06-21 02:00:52 +02:00
def get_instance_info ( inst ) :
"""
Retrieves instance information from an instance
ID and returns it as a dictionary
"""
2013-09-08 16:57:30 +02:00
instance_info = { ' id ' : inst . id ,
' ami_launch_index ' : inst . ami_launch_index ,
' private_ip ' : inst . private_ip_address ,
' private_dns_name ' : inst . private_dns_name ,
' public_ip ' : inst . ip_address ,
' 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 ,
' placement ' : inst . placement ,
2014-01-20 22:34:44 +01:00
' region ' : inst . placement [ : - 1 ] ,
2013-09-08 16:57:30 +02:00
' 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 ,
2015-02-04 21:20:11 +01:00
' hypervisor ' : inst . hypervisor ,
' tags ' : inst . tags ,
2015-03-12 03:07:55 +01:00
' groups ' : dict ( ( group . id , group . name ) for group in inst . groups ) ,
}
2013-09-08 16:57:30 +02:00
try :
instance_info [ ' virtualization_type ' ] = getattr ( inst , ' virtualization_type ' )
except AttributeError :
instance_info [ ' virtualization_type ' ] = None
2014-06-20 21:26:21 +02:00
try :
instance_info [ ' ebs_optimized ' ] = getattr ( inst , ' ebs_optimized ' )
except AttributeError :
instance_info [ ' ebs_optimized ' ] = False
2015-06-22 17:13:42 +02:00
try :
bdm_dict = { }
bdm = getattr ( inst , ' block_device_mapping ' )
for device_name in bdm . keys ( ) :
bdm_dict [ device_name ] = {
' status ' : bdm [ device_name ] . status ,
' volume_id ' : bdm [ device_name ] . volume_id ,
' delete_on_termination ' : bdm [ device_name ] . delete_on_termination
}
instance_info [ ' block_device_mapping ' ] = bdm_dict
except AttributeError :
instance_info [ ' block_device_mapping ' ] = False
2014-12-31 08:37:38 +01:00
try :
instance_info [ ' tenancy ' ] = getattr ( inst , ' placement_tenancy ' )
except AttributeError :
instance_info [ ' tenancy ' ] = ' default '
2013-09-08 16:57:30 +02:00
return instance_info
2013-10-11 19:13:04 +02:00
def boto_supports_associate_public_ip_address ( ec2 ) :
"""
Check if Boto library has associate_public_ip_address in the NetworkInterfaceSpecification
class . Added in Boto 2.13 .0
ec2 : authenticated ec2 connection object
Returns :
True if Boto library accepts associate_public_ip_address argument , else false
"""
try :
network_interface = boto . ec2 . networkinterface . NetworkInterfaceSpecification ( )
getattr ( network_interface , " associate_public_ip_address " )
return True
except AttributeError :
return False
2013-09-08 16:57:30 +02:00
def boto_supports_profile_name_arg ( ec2 ) :
"""
Check if Boto library has instance_profile_name argument . instance_profile_name has been added in Boto 2.5 .0
ec2 : authenticated ec2 connection object
2013-06-21 02:00:52 +02:00
2013-09-08 16:57:30 +02:00
Returns :
True if Boto library accept instance_profile_name argument , else false
"""
run_instances_method = getattr ( ec2 , ' run_instances ' )
return ' instance_profile_name ' in run_instances_method . func_code . co_varnames
2013-06-21 02:00:52 +02:00
2013-10-16 07:37:24 +02:00
def create_block_device ( module , ec2 , volume ) :
# Not aware of a way to determine this programatically
# http://aws.amazon.com/about-aws/whats-new/2013/10/09/ebs-provisioned-iops-maximum-iops-gb-ratio-increased-to-30-1/
MAX_IOPS_TO_SIZE_RATIO = 30
2015-09-28 18:02:46 +02:00
# device_type has been used historically to represent volume_type,
# however ec2_vol uses volume_type, as does the BlockDeviceType, so
# we add handling for either/or but not both
if all ( key in volume for key in [ ' device_type ' , ' volume_type ' ] ) :
module . fail_json ( msg = ' device_type is a deprecated name for volume_type. Do not use both device_type and volume_type ' )
# get whichever one is set, or NoneType if neither are set
volume_type = volume . get ( ' device_type ' ) or volume . get ( ' volume_type ' )
2013-10-16 07:37:24 +02:00
if ' snapshot ' not in volume and ' ephemeral ' not in volume :
2015-07-09 23:55:56 +02:00
if ' volume_size ' not in volume :
2013-10-16 07:37:24 +02:00
module . fail_json ( msg = ' Size must be specified when creating a new volume or modifying the root volume ' )
if ' snapshot ' in volume :
2015-09-28 18:02:46 +02:00
if volume_type == ' io1 ' and ' iops ' not in volume :
2013-10-16 07:37:24 +02:00
module . fail_json ( msg = ' io1 volumes must have an iops value set ' )
if ' iops ' in volume :
snapshot = ec2 . get_all_snapshots ( snapshot_ids = [ volume [ ' snapshot ' ] ] ) [ 0 ]
size = volume . get ( ' volume_size ' , snapshot . volume_size )
if int ( volume [ ' iops ' ] ) > MAX_IOPS_TO_SIZE_RATIO * size :
module . fail_json ( msg = ' IOPS must be at most %d times greater than size ' % MAX_IOPS_TO_SIZE_RATIO )
2015-07-09 17:12:54 +02:00
if ' encrypted ' in volume :
module . fail_json ( msg = ' You can not set encyrption when creating a volume from a snapshot ' )
2013-10-16 07:37:24 +02:00
if ' ephemeral ' in volume :
2015-07-09 23:55:56 +02:00
if ' snapshot ' in volume :
2013-10-16 07:37:24 +02:00
module . fail_json ( msg = ' Cannot set both ephemeral and snapshot ' )
return BlockDeviceType ( snapshot_id = volume . get ( ' snapshot ' ) ,
ephemeral_name = volume . get ( ' ephemeral ' ) ,
size = volume . get ( ' volume_size ' ) ,
2015-09-28 18:02:46 +02:00
volume_type = volume_type ,
2013-10-16 07:37:24 +02:00
delete_on_termination = volume . get ( ' delete_on_termination ' , False ) ,
2015-06-17 06:33:37 +02:00
iops = volume . get ( ' iops ' ) ,
2015-07-07 15:31:47 +02:00
encrypted = volume . get ( ' encrypted ' , None ) )
2015-09-28 18:02:46 +02:00
2013-12-20 00:16:56 +01:00
def boto_supports_param_in_spot_request ( ec2 , param ) :
"""
Check if Boto library has a < param > in its request_spot_instances ( ) method . For example , the placement_group parameter wasn ' t added until 2.3.0.
ec2 : authenticated ec2 connection object
Returns :
True if boto library has the named param as an argument on the request_spot_instances method , else False
"""
method = getattr ( ec2 , ' request_spot_instances ' )
return param in method . func_code . co_varnames
2014-02-07 02:22:45 +01:00
2014-12-04 21:12:26 +01:00
def enforce_count ( module , ec2 , vpc ) :
2014-02-07 02:22:45 +01:00
exact_count = module . params . get ( ' exact_count ' )
count_tag = module . params . get ( ' count_tag ' )
2014-09-17 22:20:05 +02:00
zone = module . params . get ( ' zone ' )
2014-02-07 02:22:45 +01:00
2014-11-03 16:43:26 +01:00
# fail here if the exact count was specified without filtering
# on a tag, as this may lead to a undesired removal of instances
if exact_count and count_tag is None :
module . fail_json ( msg = " you must use the ' count_tag ' option with exact_count " )
2014-09-17 22:20:05 +02:00
reservations , instances = find_running_instances_by_count_tag ( module , ec2 , count_tag , zone )
2014-02-07 02:22:45 +01:00
changed = None
checkmode = False
2014-08-27 11:07:19 +02:00
instance_dict_array = [ ]
2014-02-07 02:22:45 +01:00
changed_instance_ids = None
if len ( instances ) == exact_count :
changed = False
elif len ( instances ) < exact_count :
changed = True
to_create = exact_count - len ( instances )
if not checkmode :
( instance_dict_array , changed_instance_ids , changed ) \
2014-12-04 21:12:26 +01:00
= create_instances ( module , ec2 , vpc , override_count = to_create )
2014-02-07 19:06:57 +01:00
for inst in instance_dict_array :
instances . append ( inst )
2014-02-07 02:22:45 +01:00
elif len ( instances ) > exact_count :
changed = True
to_remove = len ( instances ) - exact_count
if not checkmode :
all_instance_ids = sorted ( [ x . id for x in instances ] )
remove_ids = all_instance_ids [ 0 : to_remove ]
2014-02-07 19:06:57 +01:00
instances = [ x for x in instances if x . id not in remove_ids ]
2014-02-07 02:22:45 +01:00
( changed , instance_dict_array , changed_instance_ids ) \
= terminate_instances ( module , ec2 , remove_ids )
terminated_list = [ ]
for inst in instance_dict_array :
inst [ ' state ' ] = " terminated "
terminated_list . append ( inst )
2015-07-09 23:55:56 +02:00
instance_dict_array = terminated_list
# ensure all instances are dictionaries
2014-02-07 19:06:57 +01:00
all_instances = [ ]
for inst in instances :
if type ( inst ) is not dict :
inst = get_instance_info ( inst )
2015-07-09 23:55:56 +02:00
all_instances . append ( inst )
2014-02-07 19:06:57 +01:00
return ( all_instances , instance_dict_array , changed_instance_ids , changed )
2015-07-09 23:55:56 +02:00
2014-12-04 21:12:26 +01:00
def create_instances ( module , ec2 , vpc , override_count = None ) :
2013-06-21 02:00:52 +02:00
"""
Creates new instances
2013-08-22 02:13:05 +02:00
module : AnsibleModule object
2013-06-21 02:00:52 +02:00
ec2 : authenticated ec2 connection object
Returns :
A list of dictionaries with instance information
about the instances that were launched
"""
2012-11-09 06:15:12 +01:00
2013-01-22 21:09:31 +01:00
key_name = module . params . get ( ' key_name ' )
2013-03-16 03:55:01 +01:00
id = module . params . get ( ' id ' )
2013-02-20 10:31:22 +01:00
group_name = module . params . get ( ' group ' )
group_id = module . params . get ( ' group_id ' )
2013-04-08 11:42:34 +02:00
zone = module . params . get ( ' zone ' )
2012-11-09 06:15:12 +01:00
instance_type = module . params . get ( ' instance_type ' )
2014-09-27 08:03:22 +02:00
tenancy = module . params . get ( ' tenancy ' )
2013-12-13 19:43:30 +01:00
spot_price = module . params . get ( ' spot_price ' )
2015-04-24 23:26:37 +02:00
spot_type = module . params . get ( ' spot_type ' )
2012-11-09 06:15:12 +01:00
image = module . params . get ( ' image ' )
2014-02-07 02:22:45 +01:00
if override_count :
count = override_count
else :
count = module . params . get ( ' count ' )
2013-02-04 19:03:47 +01:00
monitoring = module . params . get ( ' monitoring ' )
2012-11-09 06:15:12 +01:00
kernel = module . params . get ( ' kernel ' )
ramdisk = module . params . get ( ' ramdisk ' )
wait = module . params . get ( ' wait ' )
2013-03-07 18:01:55 +01:00
wait_timeout = int ( module . params . get ( ' wait_timeout ' ) )
2013-12-13 19:43:30 +01:00
spot_wait_timeout = int ( module . params . get ( ' spot_wait_timeout ' ) )
2013-06-17 15:35:53 +02:00
placement_group = module . params . get ( ' placement_group ' )
2012-11-17 16:02:29 +01:00
user_data = module . params . get ( ' user_data ' )
2013-02-09 17:42:14 +01:00
instance_tags = module . params . get ( ' instance_tags ' )
2013-02-23 18:45:47 +01:00
vpc_subnet_id = module . params . get ( ' vpc_subnet_id ' )
2013-10-11 19:13:04 +02:00
assign_public_ip = module . boolean ( module . params . get ( ' assign_public_ip ' ) )
2013-04-02 07:31:31 +02:00
private_ip = module . params . get ( ' private_ip ' )
2013-07-16 14:31:30 +02:00
instance_profile_name = module . params . get ( ' instance_profile_name ' )
2013-10-16 07:37:24 +02:00
volumes = module . params . get ( ' volumes ' )
2014-04-03 11:45:47 +02:00
ebs_optimized = module . params . get ( ' ebs_optimized ' )
2014-02-07 02:22:45 +01:00
exact_count = module . params . get ( ' exact_count ' )
count_tag = module . params . get ( ' count_tag ' )
2013-12-13 23:12:58 +01:00
source_dest_check = module . boolean ( module . params . get ( ' source_dest_check ' ) )
2015-06-23 07:14:30 +02:00
termination_protection = module . boolean ( module . params . get ( ' termination_protection ' ) )
2015-09-05 01:44:35 +02:00
network_interfaces = module . params . get ( ' network_interfaces ' )
2013-02-09 17:42:14 +01:00
2013-07-23 00:00:13 +02:00
# group_id and group_name are exclusive of each other
2013-04-05 15:21:55 +02:00
if group_id and group_name :
module . fail_json ( msg = str ( " Use only one type of parameter (group_name) or (group_id) " ) )
2013-06-21 02:00:52 +02:00
2015-03-12 02:39:24 +01:00
vpc_id = None
2014-12-04 21:12:26 +01:00
if vpc_subnet_id :
2015-08-20 23:32:05 +02:00
if not vpc :
module . fail_json ( msg = " region must be specified " )
else :
vpc_id = vpc . get_all_subnets ( subnet_ids = [ vpc_subnet_id ] ) [ 0 ] . vpc_id
2015-03-06 20:59:27 +01:00
else :
vpc_id = None
2014-12-04 21:12:26 +01:00
2013-02-20 10:31:22 +01:00
try :
2013-04-05 15:21:55 +02:00
# Here we try to lookup the group id from the security group name - if group is set.
if group_name :
2014-12-04 21:12:26 +01:00
if vpc_id :
grp_details = ec2 . get_all_security_groups ( filters = { ' vpc_id ' : vpc_id } )
else :
grp_details = ec2 . get_all_security_groups ( )
2015-03-03 21:07:42 +01:00
if isinstance ( group_name , basestring ) :
2013-06-17 22:15:12 +02:00
group_name = [ group_name ]
2015-03-03 21:07:42 +01:00
group_id = [ str ( grp . id ) for grp in grp_details if str ( grp . name ) in group_name ]
2013-04-05 15:21:55 +02:00
# Now we try to lookup the group id testing if group exists.
elif group_id :
2013-07-23 00:00:13 +02:00
#wrap the group_id in a list if it's not one already
2015-03-03 21:07:42 +01:00
if isinstance ( group_id , basestring ) :
2013-07-23 00:00:13 +02:00
group_id = [ group_id ]
2013-02-20 10:31:22 +01:00
grp_details = ec2 . get_all_security_groups ( group_ids = group_id )
2015-03-03 19:03:08 +01:00
group_name = [ grp_item . name for grp_item in grp_details ]
2013-02-20 10:31:22 +01:00
except boto . exception . NoAuthHandlerFound , e :
module . fail_json ( msg = str ( e ) )
2013-01-25 16:02:53 +01:00
2013-03-16 03:55:01 +01:00
# Lookup any instances that much our run id.
2013-06-21 02:00:52 +02:00
2013-03-16 03:55:01 +01:00
running_instances = [ ]
count_remaining = int ( count )
2013-06-21 02:00:52 +02:00
2013-03-16 03:55:01 +01:00
if id != None :
filter_dict = { ' client-token ' : id , ' instance-state-name ' : ' running ' }
2013-04-10 22:37:49 +02:00
previous_reservations = ec2 . get_all_instances ( None , filter_dict )
2013-03-16 03:55:01 +01:00
for res in previous_reservations :
for prev_instance in res . instances :
running_instances . append ( prev_instance )
2013-06-21 02:00:52 +02:00
count_remaining = count_remaining - len ( running_instances )
2013-01-26 12:26:51 +01:00
# 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.
2013-06-21 02:00:52 +02:00
2013-08-03 04:23:41 +02:00
if count_remaining == 0 :
changed = False
else :
changed = True
2013-03-16 03:55:01 +01:00
try :
2013-06-21 02:00:52 +02:00
params = { ' image_id ' : image ,
2013-04-05 15:21:55 +02:00
' key_name ' : key_name ,
' monitoring_enabled ' : monitoring ,
2013-06-17 15:35:53 +02:00
' placement ' : zone ,
2013-04-05 15:21:55 +02:00
' instance_type ' : instance_type ,
' kernel_id ' : kernel ,
' ramdisk_id ' : ramdisk ,
2013-09-08 16:57:30 +02:00
' user_data ' : user_data }
2014-04-07 00:34:44 +02:00
if ebs_optimized :
params [ ' ebs_optimized ' ] = ebs_optimized
2015-07-09 23:55:56 +02:00
2015-02-18 17:07:13 +01:00
# 'tenancy' always has a default value, but it is not a valid parameter for spot instance resquest
if not spot_price :
2014-09-27 08:03:22 +02:00
params [ ' tenancy ' ] = tenancy
2014-04-07 00:34:44 +02:00
2013-09-08 16:57:30 +02:00
if boto_supports_profile_name_arg ( ec2 ) :
params [ ' instance_profile_name ' ] = instance_profile_name
else :
if instance_profile_name is not None :
module . fail_json (
2013-09-12 22:14:27 +02:00
msg = " instance_profile_name parameter requires Boto version 2.5.0 or higher " )
2013-04-05 15:21:55 +02:00
2013-10-11 19:13:04 +02:00
if assign_public_ip :
if not boto_supports_associate_public_ip_address ( ec2 ) :
module . fail_json (
msg = " assign_public_ip parameter requires Boto version 2.13.0 or higher. " )
elif not vpc_subnet_id :
module . fail_json (
msg = " assign_public_ip only available with vpc_subnet_id " )
else :
2014-04-15 23:55:26 +02:00
if private_ip :
interface = boto . ec2 . networkinterface . NetworkInterfaceSpecification (
subnet_id = vpc_subnet_id ,
private_ip_address = private_ip ,
groups = group_id ,
associate_public_ip_address = assign_public_ip )
else :
interface = boto . ec2 . networkinterface . NetworkInterfaceSpecification (
subnet_id = vpc_subnet_id ,
groups = group_id ,
associate_public_ip_address = assign_public_ip )
2014-04-16 00:16:34 +02:00
interfaces = boto . ec2 . networkinterface . NetworkInterfaceCollection ( interface )
2015-07-09 23:55:56 +02:00
params [ ' network_interfaces ' ] = interfaces
2013-04-05 15:21:55 +02:00
else :
2015-09-05 01:44:35 +02:00
if network_interfaces :
2015-09-06 19:53:28 +02:00
if isinstance ( network_interfaces , basestring ) :
network_interfaces = [ network_interfaces ]
2015-09-05 01:44:35 +02:00
interfaces = [ ]
for i , network_interface_id in enumerate ( network_interfaces ) :
interface = boto . ec2 . networkinterface . NetworkInterfaceSpecification (
network_interface_id = network_interface_id ,
device_index = i )
interfaces . append ( interface )
params [ ' network_interfaces ' ] = \
boto . ec2 . networkinterface . NetworkInterfaceCollection ( * interfaces )
2013-10-11 19:13:04 +02:00
else :
2015-09-05 01:44:35 +02:00
params [ ' subnet_id ' ] = vpc_subnet_id
if vpc_subnet_id :
params [ ' security_group_ids ' ] = group_id
else :
params [ ' security_groups ' ] = group_name
2013-04-05 15:21:55 +02:00
2013-10-16 07:37:24 +02:00
if volumes :
bdm = BlockDeviceMapping ( )
2015-07-09 23:55:56 +02:00
for volume in volumes :
2013-10-16 07:37:24 +02:00
if ' device_name ' not in volume :
module . fail_json ( msg = ' Device name must be set for volume ' )
# Minimum volume size is 1GB. We'll use volume size explicitly set to 0
2015-07-09 23:55:56 +02:00
# to be a signal not to create this volume
2013-10-16 07:37:24 +02:00
if ' volume_size ' not in volume or int ( volume [ ' volume_size ' ] ) > 0 :
bdm [ volume [ ' device_name ' ] ] = create_block_device ( module , ec2 , volume )
params [ ' block_device_map ' ] = bdm
2014-02-26 20:57:07 +01:00
# check to see if we're using spot pricing first before starting instances
2013-12-13 19:43:30 +01:00
if not spot_price :
2014-04-15 23:55:26 +02:00
if assign_public_ip and private_ip :
params . update ( dict (
min_count = count_remaining ,
max_count = count_remaining ,
client_token = id ,
placement_group = placement_group ,
) )
else :
params . update ( dict (
min_count = count_remaining ,
max_count = count_remaining ,
client_token = id ,
placement_group = placement_group ,
private_ip_address = private_ip ,
) )
2013-12-13 19:43:30 +01:00
res = ec2 . run_instances ( * * params )
instids = [ i . id for i in res . instances ]
while True :
try :
ec2 . 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 ) )
2015-01-06 17:10:11 +01:00
# The instances returned through ec2.run_instances above can be in
# terminated state due to idempotency. See commit 7f11c3d for a complete
# explanation.
2015-01-06 17:34:57 +01:00
terminated_instances = [
str ( instance . id ) for instance in res . instances if instance . state == ' terminated '
]
2015-01-06 17:10:11 +01:00
if terminated_instances :
module . fail_json ( msg = " Instances with id(s) %s " % terminated_instances +
2015-01-06 17:34:57 +01:00
" were created previously but have since been terminated - " +
" use a (possibly different) ' instanceid ' parameter " )
2015-01-06 17:10:11 +01:00
2013-12-13 19:43:30 +01:00
else :
if private_ip :
module . fail_json (
msg = ' private_ip only available with on-demand (non-spot) instances ' )
2015-05-05 02:14:42 +02:00
if boto_supports_param_in_spot_request ( ec2 , ' placement_group ' ) :
2013-12-20 00:16:56 +01:00
params [ ' placement_group ' ] = placement_group
elif placement_group :
module . fail_json (
msg = " placement_group parameter requires Boto version 2.3.0 or higher. " )
2014-02-26 20:57:07 +01:00
params . update ( dict (
count = count_remaining ,
2015-04-24 23:26:37 +02:00
type = spot_type ,
2014-02-26 20:57:07 +01:00
) )
2013-12-13 19:43:30 +01:00
res = ec2 . request_spot_instances ( spot_price , * * params )
2014-02-26 20:57:07 +01:00
# Now we have to do the intermediate waiting
2013-12-13 19:43:30 +01:00
if wait :
spot_req_inst_ids = dict ( )
spot_wait_timeout = time . time ( ) + spot_wait_timeout
while spot_wait_timeout > time . time ( ) :
reqs = ec2 . get_all_spot_instance_requests ( )
for sirb in res :
if sirb . id in spot_req_inst_ids :
continue
for sir in reqs :
if sir . id == sirb . id and sir . instance_id is not None :
spot_req_inst_ids [ sirb . id ] = sir . instance_id
if len ( spot_req_inst_ids ) < count :
time . sleep ( 5 )
else :
break
if spot_wait_timeout < = time . time ( ) :
module . fail_json ( msg = " wait for spot requests timeout on %s " % time . asctime ( ) )
instids = spot_req_inst_ids . values ( )
2013-03-16 03:55:01 +01:00
except boto . exception . BotoServerError , e :
2014-02-17 04:05:28 +01:00
module . fail_json ( msg = " Instance creation failed => %s : %s " % ( e . error_code , e . error_message ) )
2012-11-09 06:15:12 +01:00
2013-03-16 03:55:01 +01:00
# wait here until the instances are up
num_running = 0
wait_timeout = time . time ( ) + wait_timeout
2013-09-25 23:12:38 +02:00
while wait_timeout > time . time ( ) and num_running < len ( instids ) :
2015-07-09 23:55:56 +02:00
try :
2014-06-23 22:58:07 +02:00
res_list = ec2 . get_all_instances ( instids )
2014-07-26 18:11:25 +02:00
except boto . exception . BotoServerError , e :
2014-06-23 22:58:07 +02:00
if e . error_code == ' InvalidInstanceID.NotFound ' :
time . sleep ( 1 )
continue
else :
raise
2013-12-13 19:43:30 +01:00
num_running = 0
for res in res_list :
num_running + = len ( [ i for i in res . instances if i . state == ' running ' ] )
if len ( res_list ) < = 0 :
2015-07-09 23:55:56 +02:00
# got a bad response of some sort, possibly due to
2013-09-25 23:12:38 +02:00
# stale/cached data. Wait a second and then try again
time . sleep ( 1 )
continue
if wait and num_running < len ( instids ) :
time . sleep ( 5 )
else :
break
2013-03-16 03:55:01 +01:00
if wait and wait_timeout < = time . time ( ) :
# waiting took too long
module . fail_json ( msg = " wait for instances running timeout on %s " % time . asctime ( ) )
2013-06-21 02:00:52 +02:00
2013-12-13 19:43:30 +01:00
#We do this after the loop ends so that we end up with one list
for res in res_list :
running_instances . extend ( res . instances )
2013-06-21 02:00:52 +02:00
2015-06-23 07:14:30 +02:00
# Enabled by default by AWS
if source_dest_check is False :
2013-12-13 23:12:58 +01:00
for inst in res . instances :
inst . modify_attribute ( ' sourceDestCheck ' , False )
2015-06-23 07:14:30 +02:00
# Disabled by default by AWS
if termination_protection is True :
for inst in res . instances :
inst . modify_attribute ( ' disableApiTermination ' , True )
2014-06-19 03:21:28 +02:00
# Leave this as late as possible to try and avoid InvalidInstanceID.NotFound
if instance_tags :
try :
ec2 . create_tags ( instids , instance_tags )
except boto . exception . EC2ResponseError , e :
module . fail_json ( msg = " Instance tagging failed => %s : %s " % ( e . error_code , e . error_message ) )
2013-03-16 03:55:01 +01:00
instance_dict_array = [ ]
2013-06-21 19:43:29 +02:00
created_instance_ids = [ ]
2013-03-16 03:55:01 +01:00
for inst in running_instances :
2015-05-25 03:25:11 +02:00
inst . update ( )
2013-06-21 02:00:52 +02:00
d = get_instance_info ( inst )
2013-06-21 19:43:29 +02:00
created_instance_ids . append ( inst . id )
2013-03-16 03:55:01 +01:00
instance_dict_array . append ( d )
2012-11-09 06:15:12 +01:00
2013-08-03 04:23:41 +02:00
return ( instance_dict_array , created_instance_ids , changed )
2013-06-21 02:00:52 +02:00
2013-06-21 19:43:29 +02:00
def terminate_instances ( module , ec2 , instance_ids ) :
2013-06-21 02:00:52 +02:00
"""
Terminates a list of instances
module : Ansible module object
ec2 : authenticated ec2 connection object
termination_list : a list of instances to terminate in the form of
[ { id : < inst - id > } , . . ]
Returns a dictionary of instance information
about the instances terminated .
If the instance to be terminated is running
" changed " will be set to False .
"""
2013-11-01 18:13:07 +01:00
# Whether to wait for termination to complete before returning
wait = module . params . get ( ' wait ' )
wait_timeout = int ( module . params . get ( ' wait_timeout ' ) )
2013-06-21 02:00:52 +02:00
changed = False
instance_dict_array = [ ]
2013-06-21 19:43:29 +02:00
if not isinstance ( instance_ids , list ) or len ( instance_ids ) < 1 :
module . fail_json ( msg = ' instance_ids should be a list of instances, aborting ' )
terminated_instance_ids = [ ]
2013-06-21 02:00:52 +02:00
for res in ec2 . get_all_instances ( instance_ids ) :
for inst in res . instances :
2014-05-21 16:18:30 +02:00
if inst . state == ' running ' or inst . state == ' stopped ' :
2013-06-21 19:43:29 +02:00
terminated_instance_ids . append ( inst . id )
2013-06-21 02:00:52 +02:00
instance_dict_array . append ( get_instance_info ( inst ) )
try :
ec2 . terminate_instances ( [ inst . id ] )
2014-02-02 18:33:27 +01:00
except EC2ResponseError , e :
2013-06-21 02:00:52 +02:00
module . fail_json ( msg = ' Unable to terminate instance {0} , error: {1} ' . format ( inst . id , e ) )
changed = True
2013-11-01 18:13:07 +01:00
# wait here until the instances are 'terminated'
if wait :
num_terminated = 0
wait_timeout = time . time ( ) + wait_timeout
while wait_timeout > time . time ( ) and num_terminated < len ( terminated_instance_ids ) :
response = ec2 . get_all_instances ( \
instance_ids = terminated_instance_ids , \
filters = { ' instance-state-name ' : ' terminated ' } )
try :
num_terminated = len ( response . pop ( ) . instances )
except Exception , e :
# got a bad response of some sort, possibly due to
# stale/cached data. Wait a second and then try again
time . sleep ( 1 )
continue
if num_terminated < len ( terminated_instance_ids ) :
time . sleep ( 5 )
# waiting took too long
if wait_timeout < time . time ( ) and num_terminated < len ( terminated_instance_ids ) :
module . fail_json ( msg = " wait for instance termination timeout on %s " % time . asctime ( ) )
2015-05-14 04:59:11 +02:00
#Lets get the current state of the instances after terminating - issue600
instance_dict_array = [ ]
for res in ec2 . get_all_instances ( instance_ids = terminated_instance_ids , \
filters = { ' instance-state-name ' : ' terminated ' } ) :
for inst in res . instances :
instance_dict_array . append ( get_instance_info ( inst ) )
2015-07-09 23:55:56 +02:00
2013-06-21 02:00:52 +02:00
2013-11-01 18:13:07 +01:00
return ( changed , instance_dict_array , terminated_instance_ids )
2013-06-21 02:00:52 +02:00
2015-07-09 23:55:56 +02:00
def startstop_instances ( module , ec2 , instance_ids , state , instance_tags ) :
2013-12-12 23:16:59 +01:00
"""
Starts or stops a list of existing instances
module : Ansible module object
ec2 : authenticated ec2 connection object
instance_ids : The list of instances to start in the form of
[ { id : < inst - id > } , . . ]
2015-07-09 23:55:56 +02:00
instance_tags : A dict of tag keys and values in the form of
{ key : value , . . . }
2014-04-07 09:06:01 +02:00
state : Intended state ( " running " or " stopped " )
2013-12-12 23:16:59 +01:00
Returns a dictionary of instance information
2014-04-07 09:06:01 +02:00
about the instances started / stopped .
2013-12-12 23:16:59 +01:00
If the instance was not able to change state ,
" changed " will be set to False .
2015-07-09 23:55:56 +02:00
Note that if instance_ids and instance_tags are both non - empty ,
this method will process the intersection of the two
2013-12-12 23:16:59 +01:00
"""
2015-07-09 23:55:56 +02:00
2013-12-12 23:16:59 +01:00
wait = module . params . get ( ' wait ' )
wait_timeout = int ( module . params . get ( ' wait_timeout ' ) )
2015-10-11 03:37:37 +02:00
source_dest_check = module . params . get ( ' source_dest_check ' )
termination_protection = module . params . get ( ' termination_protection ' )
2013-12-12 23:16:59 +01:00
changed = False
instance_dict_array = [ ]
2015-10-06 15:57:47 +02:00
source_dest_check = module . params . get ( ' source_dest_check ' )
termination_protection = module . params . get ( ' termination_protection ' )
2015-07-09 23:55:56 +02:00
2013-12-12 23:16:59 +01:00
if not isinstance ( instance_ids , list ) or len ( instance_ids ) < 1 :
2015-07-09 23:55:56 +02:00
# Fail unless the user defined instance tags
if not instance_tags :
module . fail_json ( msg = ' instance_ids should be a list of instances, aborting ' )
# To make an EC2 tag filter, we need to prepend 'tag:' to each key.
# An empty filter does no filtering, so it's safe to pass it to the
# get_all_instances method even if the user did not specify instance_tags
filters = { }
if instance_tags :
for key , value in instance_tags . items ( ) :
filters [ " tag: " + key ] = value
# Check that our instances are not in the state we want to take
2013-12-12 23:16:59 +01:00
2015-06-23 07:14:30 +02:00
# Check (and eventually change) instances attributes and instances state
2013-12-12 23:16:59 +01:00
running_instances_array = [ ]
2015-07-09 23:55:56 +02:00
for res in ec2 . get_all_instances ( instance_ids , filters = filters ) :
2013-12-12 23:16:59 +01:00
for inst in res . instances :
2015-06-23 07:14:30 +02:00
# Check "source_dest_check" attribute
if inst . get_attribute ( ' sourceDestCheck ' ) [ ' sourceDestCheck ' ] != source_dest_check :
inst . modify_attribute ( ' sourceDestCheck ' , source_dest_check )
changed = True
# Check "termination_protection" attribute
if inst . get_attribute ( ' disableApiTermination ' ) [ ' disableApiTermination ' ] != termination_protection :
inst . modify_attribute ( ' disableApiTermination ' , termination_protection )
changed = True
# Check instance state
if inst . state != state :
instance_dict_array . append ( get_instance_info ( inst ) )
try :
if state == ' running ' :
inst . start ( )
else :
inst . stop ( )
except EC2ResponseError , e :
module . fail_json ( msg = ' Unable to change state for instance {0} , error: {1} ' . format ( inst . id , e ) )
changed = True
2013-12-12 23:16:59 +01:00
## Wait for all the instances to finish starting or stopping
wait_timeout = time . time ( ) + wait_timeout
2014-04-07 09:06:01 +02:00
while wait and wait_timeout > time . time ( ) :
2014-07-30 15:13:42 +02:00
instance_dict_array = [ ]
2014-04-07 09:06:01 +02:00
matched_instances = [ ]
for res in ec2 . get_all_instances ( instance_ids ) :
for i in res . instances :
if i . state == state :
2014-07-30 15:13:42 +02:00
instance_dict_array . append ( get_instance_info ( i ) )
2014-04-07 09:06:01 +02:00
matched_instances . append ( i )
if len ( matched_instances ) < len ( instance_ids ) :
2013-12-12 23:16:59 +01:00
time . sleep ( 5 )
else :
break
if wait and wait_timeout < = time . time ( ) :
# waiting took too long
module . fail_json ( msg = " wait for instances running timeout on %s " % time . asctime ( ) )
2014-04-07 09:06:01 +02:00
return ( changed , instance_dict_array , instance_ids )
2013-12-12 23:16:59 +01:00
2013-06-21 02:00:52 +02:00
def main ( ) :
2014-02-05 12:11:06 +01:00
argument_spec = ec2_argument_spec ( )
argument_spec . update ( dict (
2013-06-21 02:00:52 +02:00
key_name = dict ( aliases = [ ' keypair ' ] ) ,
id = dict ( ) ,
2015-07-25 00:06:56 +02:00
group = dict ( type = ' list ' , aliases = [ ' groups ' ] ) ,
2013-10-07 21:34:35 +02:00
group_id = dict ( type = ' list ' ) ,
2013-08-13 15:30:56 +02:00
zone = dict ( aliases = [ ' aws_zone ' , ' ec2_zone ' ] ) ,
2013-06-21 02:00:52 +02:00
instance_type = dict ( aliases = [ ' type ' ] ) ,
2013-12-13 19:43:30 +01:00
spot_price = dict ( ) ,
2015-06-24 20:43:04 +02:00
spot_type = dict ( default = ' one-time ' , choices = [ " one-time " , " persistent " ] ) ,
2013-06-21 02:00:52 +02:00
image = dict ( ) ,
kernel = dict ( ) ,
2014-03-23 18:51:54 +01:00
count = dict ( type = ' int ' , default = ' 1 ' ) ,
2013-10-24 15:18:14 +02:00
monitoring = dict ( type = ' bool ' , default = False ) ,
2013-06-21 02:00:52 +02:00
ramdisk = dict ( ) ,
2013-10-24 15:18:14 +02:00
wait = dict ( type = ' bool ' , default = False ) ,
2013-06-21 02:00:52 +02:00
wait_timeout = dict ( default = 300 ) ,
2013-12-13 19:43:30 +01:00
spot_wait_timeout = dict ( default = 600 ) ,
2013-06-21 02:00:52 +02:00
placement_group = dict ( ) ,
user_data = dict ( ) ,
2013-10-31 23:45:46 +01:00
instance_tags = dict ( type = ' dict ' ) ,
2013-06-21 02:00:52 +02:00
vpc_subnet_id = dict ( ) ,
2013-10-11 19:13:04 +02:00
assign_public_ip = dict ( type = ' bool ' , default = False ) ,
2013-06-21 02:00:52 +02:00
private_ip = dict ( ) ,
2013-07-16 14:31:30 +02:00
instance_profile_name = dict ( ) ,
2014-11-16 20:00:18 +01:00
instance_ids = dict ( type = ' list ' , aliases = [ ' instance_id ' ] ) ,
2013-12-13 23:12:58 +01:00
source_dest_check = dict ( type = ' bool ' , default = True ) ,
2015-06-23 07:14:30 +02:00
termination_protection = dict ( type = ' bool ' , default = False ) ,
2015-04-25 02:33:00 +02:00
state = dict ( default = ' present ' , choices = [ ' present ' , ' absent ' , ' running ' , ' stopped ' ] ) ,
2014-02-19 22:29:15 +01:00
exact_count = dict ( type = ' int ' , default = None ) ,
2014-02-07 02:22:45 +01:00
count_tag = dict ( ) ,
2013-10-16 07:37:24 +02:00
volumes = dict ( type = ' list ' ) ,
2014-05-14 07:00:22 +02:00
ebs_optimized = dict ( type = ' bool ' , default = False ) ,
2014-09-27 08:03:22 +02:00
tenancy = dict ( default = ' default ' ) ,
2015-09-06 19:53:28 +02:00
network_interfaces = dict ( type = ' list ' , aliases = [ ' network_interface ' ] )
2013-06-21 02:00:52 +02:00
)
)
2014-02-07 19:42:43 +01:00
module = AnsibleModule (
argument_spec = argument_spec ,
mutually_exclusive = [
[ ' exact_count ' , ' count ' ] ,
[ ' exact_count ' , ' state ' ] ,
2015-09-06 20:00:35 +02:00
[ ' exact_count ' , ' instance_ids ' ] ,
[ ' network_interfaces ' , ' assign_public_ip ' ] ,
[ ' network_interfaces ' , ' group ' ] ,
[ ' network_interfaces ' , ' group_id ' ] ,
[ ' network_interfaces ' , ' private_ip ' ] ,
[ ' network_interfaces ' , ' vpc_subnet_id ' ] ,
2014-02-07 19:42:43 +01:00
] ,
)
2013-06-21 02:00:52 +02:00
2015-04-02 01:16:54 +02:00
if not HAS_BOTO :
module . fail_json ( msg = ' boto required for this module ' )
2013-12-17 03:04:12 +01:00
ec2 = ec2_connect ( module )
2013-06-21 02:00:52 +02:00
2015-04-26 15:50:07 +02:00
region , ec2_url , aws_connect_kwargs = get_aws_connection_info ( module )
2014-12-04 21:12:26 +01:00
if region :
try :
2015-04-26 15:50:07 +02:00
vpc = boto . vpc . connect_to_region ( region , * * aws_connect_kwargs )
2014-12-04 21:12:26 +01:00
except boto . exception . NoAuthHandlerFound , e :
module . fail_json ( msg = str ( e ) )
else :
2015-08-20 23:32:05 +02:00
vpc = None
2014-12-04 21:12:26 +01:00
2015-07-25 05:32:41 +02:00
tagged_instances = [ ]
2014-02-09 00:35:26 +01:00
2015-07-25 05:32:41 +02:00
state = module . params [ ' state ' ]
2014-04-07 09:06:01 +02:00
if state == ' absent ' :
2015-07-25 05:32:41 +02:00
instance_ids = module . params [ ' instance_ids ' ]
if not instance_ids :
module . fail_json ( msg = ' instance_ids list is required for absent state ' )
2013-06-21 02:00:52 +02:00
2013-06-21 19:43:29 +02:00
( changed , instance_dict_array , new_instance_ids ) = terminate_instances ( module , ec2 , instance_ids )
2014-04-07 09:06:01 +02:00
elif state in ( ' running ' , ' stopped ' ) :
2015-07-09 23:55:56 +02:00
instance_ids = module . params . get ( ' instance_ids ' )
instance_tags = module . params . get ( ' instance_tags ' )
if not ( isinstance ( instance_ids , list ) or isinstance ( instance_tags , dict ) ) :
module . fail_json ( msg = ' running list needs to be a list of instances or set of tags to run: %s ' % instance_ids )
2013-12-12 23:16:59 +01:00
2015-07-09 23:55:56 +02:00
( changed , instance_dict_array , new_instance_ids ) = startstop_instances ( module , ec2 , instance_ids , state , instance_tags )
2013-12-12 23:16:59 +01:00
2014-04-07 09:06:01 +02:00
elif state == ' present ' :
2013-06-21 02:00:52 +02:00
# Changed is always set to true when provisioning new instances
if not module . params . get ( ' image ' ) :
module . fail_json ( msg = ' image parameter is required for new instance ' )
2014-02-19 22:29:15 +01:00
if module . params . get ( ' exact_count ' ) is None :
2014-12-04 21:12:26 +01:00
( instance_dict_array , new_instance_ids , changed ) = create_instances ( module , ec2 , vpc )
2014-02-19 22:29:15 +01:00
else :
2014-12-04 21:12:26 +01:00
( tagged_instances , instance_dict_array , new_instance_ids , changed ) = enforce_count ( module , ec2 , vpc )
2013-06-21 02:00:52 +02:00
2014-02-07 19:06:57 +01:00
module . exit_json ( changed = changed , instance_ids = new_instance_ids , instances = instance_dict_array , tagged_instances = tagged_instances )
2012-11-09 06:15:12 +01:00
2013-11-01 16:59:24 +01:00
# import module snippets
from ansible . module_utils . basic import *
from ansible . module_utils . ec2 import *
2012-11-09 06:15:12 +01:00
main ( )