Add intentional coverage for incidental_ec2_instance (#72028)

* Add a test suite for module_utils.common.dict_transformations

* ci_complete

ci_coverage

Add a wait_for test using delegate_to

* Remove incidental_ec2_instance

* Remove unused test support modules

* Requested changes

ci_complete

ci_coverage

* Oops, put everything back to test coverage again

ci_complete

ci_coverage

* Remove incidental_ec2_instance tests and supporting modules
pull/72138/head
Sloane Hertel 4 years ago committed by GitHub
parent 9ffa84cc1c
commit 960e4c0809
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,48 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: convert_camelCase
short_description: test converting data to camelCase
description: test converting data to camelCase
options:
data:
description: Data to modify
type: dict
required: True
capitalize_first:
description: Whether to capitalize the first character
default: False
type: bool
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict
def main():
module = AnsibleModule(
argument_spec=dict(
data=dict(type='dict', required=True),
capitalize_first=dict(type='bool', default=False),
),
)
result = snake_dict_to_camel_dict(
module.params['data'],
module.params['capitalize_first']
)
module.exit_json(data=result)
if __name__ == '__main__':
main()

@ -0,0 +1,55 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: convert_snake_case
short_description: test converting data to snake_case
description: test converting data to snake_case
options:
data:
description: Data to modify
type: dict
required: True
reversible:
description:
- Make the snake_case conversion in a way that can be converted back to the original value
- For example, convert IAMUser to i_a_m_user instead of iam_user
default: False
ignore_list:
description: list of top level keys that should not have their contents converted
type: list
default: []
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
def main():
module = AnsibleModule(
argument_spec=dict(
data=dict(type='dict', required=True),
reversible=dict(type='bool', default=False),
ignore_list=dict(type='list', default=[]),
),
)
result = camel_dict_to_snake_dict(
module.params['data'],
module.params['reversible'],
module.params['ignore_list']
)
module.exit_json(data=result)
if __name__ == '__main__':
main()

@ -0,0 +1,3 @@
- include_tasks: test_convert_snake_case.yml
- include_tasks: test_convert_camelCase.yml

@ -0,0 +1,33 @@
- convert_camelCase:
data: {'top_level_key': {'nested_key': 'do_not_convert'}}
register: result
- assert:
that:
- "result.data == {'topLevelKey': {'nestedKey': 'do_not_convert'}}"
- convert_camelCase:
data: {'t_o_p_level_key': {'n_e_s_t_e_d_key': 'do_not_convert'}}
register: result
- assert:
that:
- "result.data == {'tOPLevelKey': {'nESTEDKey': 'do_not_convert'}}"
- convert_camelCase:
data: {'t_o_p_level_key': {'n_e_s_t_e_d_key': 'do_not_convert'}}
capitalize_first: True
register: result
- assert:
that:
- "result.data == {'TOPLevelKey': {'NESTEDKey': 'do_not_convert'}}"
- convert_camelCase:
data: {'results': [{'i_a_m_user': 'user_name', 'tags': {'do_convert': 'do_not_convert'}}]}
capitalize_first: True
register: result
- assert:
that:
- "result.data == {'Results': [{'IAMUser': 'user_name', 'Tags': {'DoConvert': 'do_not_convert'}}]}"

@ -0,0 +1,26 @@
- convert_snake_case:
data: {'TOPLevelKey': {'NESTEDKey': 'DoNotConvert'}}
register: result
- assert:
that:
- "result.data == {'top_level_key': {'nested_key': 'DoNotConvert'}}"
- convert_snake_case:
data: {'TOPLevelKey': {'NESTEDKey': 'DoNotConvert'}}
reversible: True
register: result
- assert:
that:
- "result.data == {'t_o_p_level_key': {'n_e_s_t_e_d_key': 'DoNotConvert'}}"
- convert_snake_case:
data: {'Results': [{'IAMUser': 'UserName', 'Tags': {'DoConvert': 'DoNotConvert'}}], 'Tags': {'DoNotConvert': 'DoNotConvert'}}
reversible: True
ignore_list: ['Tags'] # Ignore top level 'Tags' key if found
register: result
- assert:
that:
- "result.data == {'results': [{'i_a_m_user': 'UserName', 'tags': {'do_convert': 'DoNotConvert'}}], 'tags': {'DoNotConvert': 'DoNotConvert'}}"

@ -1,2 +0,0 @@
cloud/aws
shippable/aws/incidental

@ -1,17 +0,0 @@
[tests]
# Sorted fastest to slowest
version_fail_wrapper
ebs_optimized
block_devices
cpu_options
default_vpc_tests
external_resource_attach
instance_no_wait
iam_instance_role
termination_protection
tags_and_vpc_settings
checkmode_tests
[all:vars]
ansible_connection=local
ansible_python_interpreter="{{ ansible_playbook_python }}"

@ -1,43 +0,0 @@
---
# Beware: most of our tests here are run in parallel.
# To add new tests you'll need to add a new host to the inventory and a matching
# '{{ inventory_hostname }}'.yml file in roles/ec2_instance/tasks/
# Prepare the VPC and figure out which AMI to use
- hosts: all
gather_facts: no
tasks:
- module_defaults:
ec2_ami_info:
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
security_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region }}"
vars:
# We can't just use "run_once" because the facts don't propagate when
# running an 'include' that was run_once
setup_run_once: yes
block:
- include_role:
name: 'ec2_instance'
tasks_from: find_ami.yml
- include_role:
name: 'ec2_instance'
tasks_from: env_setup.yml
rescue:
- include_role:
name: 'ec2_instance'
tasks_from: env_cleanup.yml
run_once: yes
- fail:
msg: 'Environment preparation failed'
run_once: yes
# VPC should get cleaned up once all hosts have run
- hosts: all
gather_facts: no
strategy: free
#serial: 10
roles:
- ec2_instance

@ -1,3 +0,0 @@
dependencies:
- incidental_setup_ec2
- setup_remote_tmp_dir

@ -1,14 +0,0 @@
---
# defaults file for ec2_instance
ec2_instance_owner: 'integration-run-{{ resource_prefix }}'
ec2_instance_type: 't3.micro'
ec2_instance_tag_TestId: '{{ resource_prefix }}-{{ inventory_hostname }}'
ec2_ami_name: 'amzn2-ami-hvm-2.*-x86_64-gp2'
vpc_name: '{{ resource_prefix }}-vpc'
vpc_seed: '{{ resource_prefix }}'
vpc_cidr: '10.{{ 256 | random(seed=vpc_seed) }}.0.0/16'
subnet_a_cidr: '10.{{ 256 | random(seed=vpc_seed) }}.32.0/24'
subnet_a_startswith: '10.{{ 256 | random(seed=vpc_seed) }}.32.'
subnet_b_cidr: '10.{{ 256 | random(seed=vpc_seed) }}.33.0/24'
subnet_b_startswith: '10.{{ 256 | random(seed=vpc_seed) }}.33.'

@ -1,13 +0,0 @@
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}

@ -1,82 +0,0 @@
- block:
- name: "New instance with an extra block device"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-ebs-vols"
image_id: "{{ ec2_ami_image }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
volumes:
- device_name: /dev/sdb
ebs:
volume_size: 20
delete_on_termination: true
volume_type: standard
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
instance_type: "{{ ec2_instance_type }}"
wait: true
register: block_device_instances
- name: "Gather instance info"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-ebs-vols"
register: block_device_instances_info
- assert:
that:
- block_device_instances is not failed
- block_device_instances is changed
- block_device_instances_info.instances[0].block_device_mappings[0]
- block_device_instances_info.instances[0].block_device_mappings[1]
- block_device_instances_info.instances[0].block_device_mappings[1].device_name == '/dev/sdb'
- name: "New instance with an extra block device (check mode)"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-ebs-vols-checkmode"
image_id: "{{ ec2_ami_image }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
volumes:
- device_name: /dev/sdb
ebs:
volume_size: 20
delete_on_termination: true
volume_type: standard
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
instance_type: "{{ ec2_instance_type }}"
check_mode: yes
- name: "fact presented ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-ebs-vols"
"instance-state-name": "running"
register: presented_instance_fact
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-ebs-vols-checkmode"
register: checkmode_instance_fact
- name: "Confirm whether the check mode is working normally."
assert:
that:
- "{{ presented_instance_fact.instances | length }} > 0"
- "{{ checkmode_instance_fact.instances | length }} == 0"
- name: "Terminate instances"
ec2_instance:
state: absent
instance_ids: "{{ block_device_instances.instance_ids }}"
always:
- name: "Terminate block_devices instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,172 +0,0 @@
- block:
- name: "Make basic instance"
ec2_instance:
state: present
name: "{{ resource_prefix }}-checkmode-comparison"
image_id: "{{ ec2_ami_image }}"
security_groups: "{{ sg.group_id }}"
instance_type: "{{ ec2_instance_type }}"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
wait: false
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
register: basic_instance
- name: "Make basic instance (check mode)"
ec2_instance:
state: present
name: "{{ resource_prefix }}-checkmode-comparison-checkmode"
image_id: "{{ ec2_ami_image }}"
security_groups: "{{ sg.group_id }}"
instance_type: "{{ ec2_instance_type }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
check_mode: yes
- name: "fact presented ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-checkmode-comparison"
register: presented_instance_fact
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-checkmode-comparison-checkmode"
register: checkmode_instance_fact
- name: "Confirm whether the check mode is working normally."
assert:
that:
- "{{ presented_instance_fact.instances | length }} > 0"
- "{{ checkmode_instance_fact.instances | length }} == 0"
- name: "Stop instance (check mode)"
ec2_instance:
state: stopped
name: "{{ resource_prefix }}-checkmode-comparison"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
check_mode: yes
- name: "fact ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-checkmode-comparison"
register: confirm_checkmode_stopinstance_fact
- name: "Verify that it was not stopped."
assert:
that:
- '"{{ confirm_checkmode_stopinstance_fact.instances[0].state.name }}" != "stopped"'
- name: "Stop instance."
ec2_instance:
state: stopped
name: "{{ resource_prefix }}-checkmode-comparison"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
register: instance_stop
until: not instance_stop.failed
retries: 10
- name: "fact stopped ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-checkmode-comparison"
register: confirm_stopinstance_fact
- name: "Verify that it was stopped."
assert:
that:
- '"{{ confirm_stopinstance_fact.instances[0].state.name }}" in ["stopped", "stopping"]'
- name: "Running instance in check mode."
ec2_instance:
state: running
name: "{{ resource_prefix }}-checkmode-comparison"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
check_mode: yes
- name: "fact ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-checkmode-comparison"
register: confirm_checkmode_runninginstance_fact
- name: "Verify that it was not running."
assert:
that:
- '"{{ confirm_checkmode_runninginstance_fact.instances[0].state.name }}" != "running"'
- name: "Running instance."
ec2_instance:
state: running
name: "{{ resource_prefix }}-checkmode-comparison"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
- name: "fact ec2 instance."
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-checkmode-comparison"
register: confirm_runninginstance_fact
- name: "Verify that it was running."
assert:
that:
- '"{{ confirm_runninginstance_fact.instances[0].state.name }}" == "running"'
- name: "Terminate instance in check mode."
ec2_instance:
state: absent
name: "{{ resource_prefix }}-checkmode-comparison"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
check_mode: yes
- name: "fact ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-checkmode-comparison"
register: confirm_checkmode_terminatedinstance_fact
- name: "Verify that it was not terminated,"
assert:
that:
- '"{{ confirm_checkmode_terminatedinstance_fact.instances[0].state.name }}" != "terminated"'
- name: "Terminate instance."
ec2_instance:
state: absent
name: "{{ resource_prefix }}-checkmode-comparison"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
- name: "fact ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-checkmode-comparison"
register: confirm_terminatedinstance_fact
- name: "Verify that it was terminated,"
assert:
that:
- '"{{ confirm_terminatedinstance_fact.instances[0].state.name }}" == "terminated"'
always:
- name: "Terminate checkmode instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,86 +0,0 @@
- block:
- name: "create t3.nano instance with cpu_options"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-t3nano-1-threads-per-core"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
instance_type: t3.nano
cpu_options:
core_count: 1
threads_per_core: 1
wait: false
register: instance_creation
- name: "instance with cpu_options created with the right options"
assert:
that:
- instance_creation is success
- instance_creation is changed
- name: "modify cpu_options on existing instance (warning displayed)"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-t3nano-1-threads-per-core"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
instance_type: t3.nano
cpu_options:
core_count: 1
threads_per_core: 2
wait: false
register: cpu_options_update
ignore_errors: yes
- name: "fact presented ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-t3nano-1-threads-per-core"
register: presented_instance_fact
- name: "modify cpu_options has no effect on existing instance"
assert:
that:
- cpu_options_update is success
- cpu_options_update is not changed
- "{{ presented_instance_fact.instances | length }} > 0"
- "'{{ presented_instance_fact.instances.0.state.name }}' in ['running','pending']"
- "{{ presented_instance_fact.instances.0.cpu_options.core_count }} == 1"
- "{{ presented_instance_fact.instances.0.cpu_options.threads_per_core }} == 1"
- name: "create t3.nano instance with cpu_options(check mode)"
ec2_instance:
name: "{{ resource_prefix }}-test-t3nano-1-threads-per-core-checkmode"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
instance_type: t3.nano
cpu_options:
core_count: 1
threads_per_core: 1
check_mode: yes
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-t3nano-1-threads-per-core-checkmode"
register: checkmode_instance_fact
- name: "Confirm existence of instance id."
assert:
that:
- "{{ checkmode_instance_fact.instances | length }} == 0"
always:
- name: "Terminate cpu_options instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,57 +0,0 @@
- block:
- name: "Make instance in a default subnet of the VPC"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-default-vpc"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
security_group: "default"
instance_type: "{{ ec2_instance_type }}"
wait: false
register: in_default_vpc
- name: "Make instance in a default subnet of the VPC(check mode)"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-default-vpc-checkmode"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
security_group: "default"
instance_type: "{{ ec2_instance_type }}"
check_mode: yes
- name: "fact presented ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-default-vpc"
register: presented_instance_fact
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-default-vpc-checkmode"
register: checkmode_instance_fact
- name: "Confirm whether the check mode is working normally."
assert:
that:
- "{{ presented_instance_fact.instances | length }} > 0"
- "{{ checkmode_instance_fact.instances | length }} == 0"
- name: "Terminate instances"
ec2_instance:
state: absent
instance_ids: "{{ in_default_vpc.instance_ids }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
always:
- name: "Terminate vpc_tests instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,41 +0,0 @@
- block:
- name: "Make EBS optimized instance in the testing subnet of the test VPC"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-ebs-optimized-instance-in-vpc"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
security_groups: "{{ sg.group_id }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
ebs_optimized: true
instance_type: t3.nano
wait: false
register: ebs_opt_in_vpc
- name: "Get ec2 instance info"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-ebs-optimized-instance-in-vpc"
register: ebs_opt_instance_info
- name: "Assert instance is ebs_optimized"
assert:
that:
- "{{ ebs_opt_instance_info.instances.0.ebs_optimized }}"
- name: "Terminate instances"
ec2_instance:
state: absent
instance_ids: "{{ ebs_opt_in_vpc.instance_ids }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
always:
- name: "Terminate ebs_optimzed instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,111 +0,0 @@
- name: set up aws connection info
set_fact:
aws_connection_info: &aws_connection_info
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
security_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region }}"
no_log: yes
- name: "remove Instances"
ec2_instance:
state: absent
filters:
vpc-id: "{{ testing_vpc.vpc.id }}"
wait: yes
<<: *aws_connection_info
ignore_errors: yes
retries: 10
- name: "remove ENIs"
ec2_eni_info:
filters:
vpc-id: "{{ testing_vpc.vpc.id }}"
<<: *aws_connection_info
register: enis
- name: "delete all ENIs"
ec2_eni:
state: absent
eni_id: "{{ item.id }}"
<<: *aws_connection_info
until: removed is not failed
with_items: "{{ enis.network_interfaces }}"
ignore_errors: yes
retries: 10
- name: "remove the security group"
ec2_group:
state: absent
name: "{{ resource_prefix }}-sg"
description: a security group for ansible tests
vpc_id: "{{ testing_vpc.vpc.id }}"
<<: *aws_connection_info
register: removed
until: removed is not failed
ignore_errors: yes
retries: 10
- name: "remove routing rules"
ec2_vpc_route_table:
state: absent
vpc_id: "{{ testing_vpc.vpc.id }}"
tags:
created: "{{ resource_prefix }}-route"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ igw.gateway_id }}"
subnets:
- "{{ testing_subnet_a.subnet.id }}"
- "{{ testing_subnet_b.subnet.id }}"
<<: *aws_connection_info
register: removed
until: removed is not failed
ignore_errors: yes
retries: 10
- name: "remove internet gateway"
ec2_vpc_igw:
state: absent
vpc_id: "{{ testing_vpc.vpc.id }}"
<<: *aws_connection_info
register: removed
until: removed is not failed
ignore_errors: yes
retries: 10
- name: "remove subnet A"
ec2_vpc_subnet:
state: absent
vpc_id: "{{ testing_vpc.vpc.id }}"
cidr: "{{ subnet_a_cidr }}"
<<: *aws_connection_info
register: removed
until: removed is not failed
ignore_errors: yes
retries: 10
- name: "remove subnet B"
ec2_vpc_subnet:
state: absent
vpc_id: "{{ testing_vpc.vpc.id }}"
cidr: "{{ subnet_b_cidr }}"
<<: *aws_connection_info
register: removed
until: removed is not failed
ignore_errors: yes
retries: 10
- name: "remove the VPC"
ec2_vpc_net:
state: absent
name: "{{ vpc_name }}"
cidr_block: "{{ vpc_cidr }}"
tags:
Name: Ansible Testing VPC
tenancy: default
<<: *aws_connection_info
register: removed
until: removed is not failed
ignore_errors: yes
retries: 10

@ -1,95 +0,0 @@
- name: set up aws connection info
set_fact:
aws_connection_info: &aws_connection_info
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
security_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region }}"
no_log: yes
- run_once: '{{ setup_run_once | default("no") | bool }}'
block:
- name: "fetch AZ availability"
aws_az_info:
<<: *aws_connection_info
register: az_info
- name: "Assert that we have multiple AZs available to us"
assert:
that: az_info.availability_zones | length >= 2
- name: "pick AZs"
set_fact:
subnet_a_az: '{{ az_info.availability_zones[0].zone_name }}'
subnet_b_az: '{{ az_info.availability_zones[1].zone_name }}'
- name: "Create VPC for use in testing"
ec2_vpc_net:
state: present
name: "{{ vpc_name }}"
cidr_block: "{{ vpc_cidr }}"
tags:
Name: Ansible ec2_instance Testing VPC
tenancy: default
<<: *aws_connection_info
register: testing_vpc
- name: "Create internet gateway for use in testing"
ec2_vpc_igw:
state: present
vpc_id: "{{ testing_vpc.vpc.id }}"
<<: *aws_connection_info
register: igw
- name: "Create default subnet in zone A"
ec2_vpc_subnet:
state: present
vpc_id: "{{ testing_vpc.vpc.id }}"
cidr: "{{ subnet_a_cidr }}"
az: "{{ subnet_a_az }}"
resource_tags:
Name: "{{ resource_prefix }}-subnet-a"
<<: *aws_connection_info
register: testing_subnet_a
- name: "Create secondary subnet in zone B"
ec2_vpc_subnet:
state: present
vpc_id: "{{ testing_vpc.vpc.id }}"
cidr: "{{ subnet_b_cidr }}"
az: "{{ subnet_b_az }}"
resource_tags:
Name: "{{ resource_prefix }}-subnet-b"
<<: *aws_connection_info
register: testing_subnet_b
- name: "create routing rules"
ec2_vpc_route_table:
state: present
vpc_id: "{{ testing_vpc.vpc.id }}"
tags:
created: "{{ resource_prefix }}-route"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ igw.gateway_id }}"
subnets:
- "{{ testing_subnet_a.subnet.id }}"
- "{{ testing_subnet_b.subnet.id }}"
<<: *aws_connection_info
- name: "create a security group with the vpc"
ec2_group:
state: present
name: "{{ resource_prefix }}-sg"
description: a security group for ansible tests
vpc_id: "{{ testing_vpc.vpc.id }}"
rules:
- proto: tcp
from_port: 22
to_port: 22
cidr_ip: 0.0.0.0/0
- proto: tcp
from_port: 80
to_port: 80
cidr_ip: 0.0.0.0/0
<<: *aws_connection_info
register: sg

@ -1,129 +0,0 @@
- block:
# Make custom ENIs and attach via the `network` parameter
- ec2_eni:
state: present
delete_on_termination: true
subnet_id: "{{ testing_subnet_b.subnet.id }}"
security_groups:
- "{{ sg.group_id }}"
register: eni_a
- ec2_eni:
state: present
delete_on_termination: true
subnet_id: "{{ testing_subnet_b.subnet.id }}"
security_groups:
- "{{ sg.group_id }}"
register: eni_b
- ec2_eni:
state: present
delete_on_termination: true
subnet_id: "{{ testing_subnet_b.subnet.id }}"
security_groups:
- "{{ sg.group_id }}"
register: eni_c
- ec2_key:
name: "{{ resource_prefix }}_test_key"
- name: "Make instance in the testing subnet created in the test VPC"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-eni-vpc"
key_name: "{{ resource_prefix }}_test_key"
network:
interfaces:
- id: "{{ eni_a.interface.id }}"
image_id: "{{ ec2_ami_image }}"
availability_zone: '{{ subnet_b_az }}'
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
instance_type: "{{ ec2_instance_type }}"
wait: false
register: in_test_vpc
- name: "Gather {{ resource_prefix }}-test-eni-vpc info"
ec2_instance_info:
filters:
"tag:Name": '{{ resource_prefix }}-test-eni-vpc'
register: in_test_vpc_instance
- assert:
that:
- 'in_test_vpc_instance.instances.0.key_name == "{{ resource_prefix }}_test_key"'
- '(in_test_vpc_instance.instances.0.network_interfaces | length) == 1'
- name: "Add a second interface"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-eni-vpc"
network:
interfaces:
- id: "{{ eni_a.interface.id }}"
- id: "{{ eni_b.interface.id }}"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
instance_type: "{{ ec2_instance_type }}"
wait: false
register: add_interface
until: add_interface is not failed
ignore_errors: yes
retries: 10
- name: "Make instance in the testing subnet created in the test VPC(check mode)"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-eni-vpc-checkmode"
key_name: "{{ resource_prefix }}_test_key"
network:
interfaces:
- id: "{{ eni_c.interface.id }}"
image_id: "{{ ec2_ami_image }}"
availability_zone: '{{ subnet_b_az }}'
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
instance_type: "{{ ec2_instance_type }}"
check_mode: yes
- name: "fact presented ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-eni-vpc"
register: presented_instance_fact
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-eni-vpc-checkmode"
register: checkmode_instance_fact
- name: "Confirm existence of instance id."
assert:
that:
- "{{ presented_instance_fact.instances | length }} > 0"
- "{{ checkmode_instance_fact.instances | length }} == 0"
always:
- name: "Terminate external_resource_attach instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes
- ec2_key:
state: absent
name: "{{ resource_prefix }}_test_key"
ignore_errors: yes
- ec2_eni:
state: absent
eni_id: '{{ item.interface.id }}'
ignore_errors: yes
with_items:
- '{{ eni_a }}'
- '{{ eni_b }}'
- '{{ eni_c }}'

@ -1,15 +0,0 @@
- run_once: '{{ setup_run_once | default("no") | bool }}'
block:
- name: "Find AMI to use"
run_once: yes
ec2_ami_info:
owners: 'amazon'
filters:
name: '{{ ec2_ami_name }}'
register: ec2_amis
- name: "Set fact with latest AMI"
run_once: yes
vars:
latest_ami: '{{ ec2_amis.images | sort(attribute="creation_date") | last }}'
set_fact:
ec2_ami_image: '{{ latest_ami.image_id }}'

@ -1,127 +0,0 @@
- block:
- name: "Create IAM role for test"
iam_role:
state: present
name: "ansible-test-sts-{{ resource_prefix }}-test-policy"
assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
create_instance_profile: yes
managed_policy:
- AmazonEC2ContainerServiceRole
register: iam_role
- name: "Create second IAM role for test"
iam_role:
state: present
name: "ansible-test-sts-{{ resource_prefix }}-test-policy-2"
assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
create_instance_profile: yes
managed_policy:
- AmazonEC2ContainerServiceRole
register: iam_role_2
- name: "wait 10 seconds for roles to become available"
wait_for:
timeout: 10
delegate_to: localhost
- name: "Make instance with an instance_role"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-instance-role"
image_id: "{{ ec2_ami_image }}"
security_groups: "{{ sg.group_id }}"
instance_type: "{{ ec2_instance_type }}"
instance_role: "ansible-test-sts-{{ resource_prefix }}-test-policy"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
register: instance_with_role
- assert:
that:
- 'instance_with_role.instances[0].iam_instance_profile.arn == iam_role.arn.replace(":role/", ":instance-profile/")'
- name: "Make instance with an instance_role(check mode)"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-instance-role-checkmode"
image_id: "{{ ec2_ami_image }}"
security_groups: "{{ sg.group_id }}"
instance_type: "{{ ec2_instance_type }}"
instance_role: "{{ iam_role.arn.replace(':role/', ':instance-profile/') }}"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
check_mode: yes
- name: "fact presented ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-instance-role"
register: presented_instance_fact
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-instance-role-checkmode"
register: checkmode_instance_fact
- name: "Confirm whether the check mode is working normally."
assert:
that:
- "{{ presented_instance_fact.instances | length }} > 0"
- "{{ checkmode_instance_fact.instances | length }} == 0"
- name: "Update instance with new instance_role"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-instance-role"
image_id: "{{ ec2_ami_image }}"
security_groups: "{{ sg.group_id }}"
instance_type: "{{ ec2_instance_type }}"
instance_role: "{{ iam_role_2.arn.replace(':role/', ':instance-profile/') }}"
vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
register: instance_with_updated_role
- name: "wait 10 seconds for role update to complete"
wait_for:
timeout: 10
delegate_to: localhost
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-instance-role"
register: updates_instance_info
- assert:
that:
- 'updates_instance_info.instances[0].iam_instance_profile.arn == iam_role_2.arn.replace(":role/", ":instance-profile/")'
- 'updates_instance_info.instances[0].instance_id == instance_with_role.instances[0].instance_id'
always:
- name: "Terminate iam_instance_role instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes
- name: "Delete IAM role for test"
iam_role:
state: absent
name: "{{ item }}"
assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
create_instance_profile: yes
managed_policy:
- AmazonEC2ContainerServiceRole
loop:
- "ansible-test-sts-{{ resource_prefix }}-test-policy"
- "ansible-test-sts-{{ resource_prefix }}-test-policy-2"
register: removed
until: removed is not failed
ignore_errors: yes
retries: 10

@ -1,68 +0,0 @@
- block:
- name: "New instance and don't wait for it to complete"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-no-wait"
image_id: "{{ ec2_ami_image }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
wait: false
instance_type: "{{ ec2_instance_type }}"
register: in_test_vpc
- assert:
that:
- in_test_vpc is not failed
- in_test_vpc is changed
- in_test_vpc.instances is not defined
- in_test_vpc.instance_ids is defined
- in_test_vpc.instance_ids | length > 0
- name: "New instance and don't wait for it to complete ( check mode )"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-no-wait-checkmode"
image_id: "{{ ec2_ami_image }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
wait: false
instance_type: "{{ ec2_instance_type }}"
check_mode: yes
- name: "Facts for ec2 test instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-no-wait"
register: real_instance_fact
until: real_instance_fact.instances | length > 0
retries: 10
- name: "Facts for checkmode ec2 test instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-no-wait-checkmode"
register: checkmode_instance_fact
- name: "Confirm whether the check mode is working normally."
assert:
that:
- "{{ real_instance_fact.instances | length }} > 0"
- "{{ checkmode_instance_fact.instances | length }} == 0"
- name: "Terminate instances"
ec2_instance:
state: absent
instance_ids: "{{ in_test_vpc.instance_ids }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
always:
- name: "Terminate instance_no_wait instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,62 +0,0 @@
---
# Beware: most of our tests here are run in parallel.
# To add new tests you'll need to add a new host to the inventory and a matching
# '{{ inventory_hostname }}'.yml file in roles/ec2_instance/tasks/
#
# Please make sure you tag your instances with
# tags:
# "tag:TestId": "{{ ec2_instance_tag_TestId }}"
# And delete them based off that tag at the end of your specific set of tests
#
# ###############################################################################
#
# A Note about ec2 environment variable name preference:
# - EC2_URL -> AWS_URL
# - EC2_ACCESS_KEY -> AWS_ACCESS_KEY_ID -> AWS_ACCESS_KEY
# - EC2_SECRET_KEY -> AWS_SECRET_ACCESS_KEY -> AWX_SECRET_KEY
# - EC2_REGION -> AWS_REGION
#
- name: set up aws connection info
set_fact:
aws_connection_info: &aws_connection_info
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
security_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region }}"
no_log: yes
- name: "Wrap up all tests and setup AWS credentials"
module_defaults:
ec2_instance:
<<: *aws_connection_info
ec2_instance_info:
<<: *aws_connection_info
ec2_key:
<<: *aws_connection_info
ec2_eni:
<<: *aws_connection_info
iam_role:
<<: *aws_connection_info
block:
- debug:
msg: "{{ inventory_hostname }} start: {{ lookup('pipe','date') }}"
- include_tasks: '{{ inventory_hostname }}.yml'
- debug:
msg: "{{ inventory_hostname }} finish: {{ lookup('pipe','date') }}"
always:
- set_fact:
_role_complete: True
- vars:
completed_hosts: '{{ ansible_play_hosts_all | map("extract", hostvars, "_role_complete") | list | select("defined") | list | length }}'
hosts_in_play: '{{ ansible_play_hosts_all | length }}'
debug:
msg: "{{ completed_hosts }} of {{ hosts_in_play }} complete"
- include_tasks: env_cleanup.yml
vars:
completed_hosts: '{{ ansible_play_hosts_all | map("extract", hostvars, "_role_complete") | list | select("defined") | list | length }}'
hosts_in_play: '{{ ansible_play_hosts_all | length }}'
when:
- aws_cleanup
- completed_hosts == hosts_in_play

@ -1,158 +0,0 @@
- block:
- name: "Make instance in the testing subnet created in the test VPC"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-basic-vpc-create"
image_id: "{{ ec2_ami_image }}"
user_data: |
#cloud-config
package_upgrade: true
package_update: true
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
Something: else
security_groups: "{{ sg.group_id }}"
network:
source_dest_check: false
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
instance_type: "{{ ec2_instance_type }}"
wait: false
register: in_test_vpc
- name: "Make instance in the testing subnet created in the test VPC(check mode)"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-basic-vpc-create-checkmode"
image_id: "{{ ec2_ami_image }}"
user_data: |
#cloud-config
package_upgrade: true
package_update: true
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
Something: else
security_groups: "{{ sg.group_id }}"
network:
source_dest_check: false
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
instance_type: "{{ ec2_instance_type }}"
check_mode: yes
- name: "Try to re-make the instance, hopefully this shows changed=False"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-basic-vpc-create"
image_id: "{{ ec2_ami_image }}"
user_data: |
#cloud-config
package_upgrade: true
package_update: true
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
Something: else
security_groups: "{{ sg.group_id }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
instance_type: "{{ ec2_instance_type }}"
register: remake_in_test_vpc
- name: "Remaking the same instance resulted in no changes"
assert:
that: not remake_in_test_vpc.changed
- name: "check that instance IDs match anyway"
assert:
that: 'remake_in_test_vpc.instance_ids[0] == in_test_vpc.instance_ids[0]'
- name: "check that source_dest_check was set to false"
assert:
that: 'not remake_in_test_vpc.instances[0].source_dest_check'
- name: "fact presented ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-basic-vpc-create"
register: presented_instance_fact
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-basic-vpc-create-checkmode"
register: checkmode_instance_fact
- name: "Confirm whether the check mode is working normally."
assert:
that:
- "{{ presented_instance_fact.instances | length }} > 0"
- "{{ checkmode_instance_fact.instances | length }} == 0"
- name: "Alter it by adding tags"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-basic-vpc-create"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
Another: thing
security_groups: "{{ sg.group_id }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
instance_type: "{{ ec2_instance_type }}"
register: add_another_tag
- ec2_instance_info:
instance_ids: "{{ add_another_tag.instance_ids }}"
register: check_tags
- name: "Remaking the same instance resulted in no changes"
assert:
that:
- check_tags.instances[0].tags.Another == 'thing'
- check_tags.instances[0].tags.Something == 'else'
- name: "Purge a tag"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-basic-vpc-create"
image_id: "{{ ec2_ami_image }}"
purge_tags: true
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
Another: thing
security_groups: "{{ sg.group_id }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
instance_type: "{{ ec2_instance_type }}"
- ec2_instance_info:
instance_ids: "{{ add_another_tag.instance_ids }}"
register: check_tags
- name: "Remaking the same instance resulted in no changes"
assert:
that:
- "'Something' not in check_tags.instances[0].tags"
- name: "check that subnet-default public IP rule was followed"
assert:
that:
- check_tags.instances[0].public_dns_name == ""
- check_tags.instances[0].private_ip_address.startswith(subnet_b_startswith)
- check_tags.instances[0].subnet_id == testing_subnet_b.subnet.id
- name: "check that tags were applied"
assert:
that:
- check_tags.instances[0].tags.Name.startswith(resource_prefix)
- "'{{ check_tags.instances[0].state.name }}' in ['pending', 'running']"
- name: "Terminate instance"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: false
register: result
- assert:
that: result.changed
always:
- name: "Terminate tags_and_vpc_settings instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,101 +0,0 @@
- block:
- name: "Make termination-protected instance in the testing subnet created in the test VPC"
ec2_instance:
state: running
name: "{{ resource_prefix }}-test-protected-instance-in-vpc"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
security_groups: "{{ sg.group_id }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
termination_protection: true
instance_type: "{{ ec2_instance_type }}"
wait: yes
register: in_test_vpc
- name: "Make termination-protected instance in the testing subnet created in the test VPC(check mode)"
ec2_instance:
state: running
name: "{{ resource_prefix }}-test-protected-instance-in-vpc-checkmode"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
security_groups: "{{ sg.group_id }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
termination_protection: true
instance_type: "{{ ec2_instance_type }}"
check_mode: yes
- name: "fact presented ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-protected-instance-in-vpc"
"instance-state-name": "running"
register: presented_instance_fact
- name: "fact checkmode ec2 instance"
ec2_instance_info:
filters:
"tag:Name": "{{ resource_prefix }}-test-protected-instance-in-vpc-checkmode"
register: checkmode_instance_fact
- name: "Confirm whether the check mode is working normally."
assert:
that:
- "{{ presented_instance_fact.instances | length }} > 0"
- "'{{ presented_instance_fact.instances.0.state.name }}' in ['running', 'pending']"
- "{{ checkmode_instance_fact.instances | length }} == 0"
- name: "Try to terminate the instance"
ec2_instance:
state: absent
name: "{{ resource_prefix }}-test-protected-instance-in-vpc"
image_id: "{{ ec2_ami_image }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
security_groups: "{{ sg.group_id }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
termination_protection: true
instance_type: "{{ ec2_instance_type }}"
register: bad_terminate
ignore_errors: yes
- name: "Cannot terminate protected instance"
assert:
that:
- bad_terminate is failed
- name: "Alter termination protection setting"
ec2_instance:
state: present
name: "{{ resource_prefix }}-test-protected-instance-in-vpc"
image_id: "{{ ec2_ami_image }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
termination_protection: false
instance_type: "{{ ec2_instance_type }}"
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
- name: "Try to terminate the instance again (should work)"
ec2_instance:
state: absent
name: "{{ resource_prefix }}-test-protected-instance-in-vpc"
image_id: "{{ ec2_ami_image }}"
vpc_subnet_id: "{{ testing_subnet_b.subnet.id }}"
instance_type: "{{ ec2_instance_type }}"
wait: false
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
register: terminate_results
- assert:
that: terminate_results is not failed
always:
- name: "Terminate termination_protection instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,29 +0,0 @@
- block:
- name: "create t3.nano with cpu options (fails gracefully)"
ec2_instance:
state: present
name: "ansible-test-{{ resource_prefix | regex_search('([0-9]+)$') }}-ec2"
image_id: "{{ ec2_ami_image }}"
instance_type: "t3.nano"
cpu_options:
core_count: 1
threads_per_core: 1
tags:
TestId: "{{ ec2_instance_tag_TestId }}"
register: ec2_instance_cpu_options_creation
ignore_errors: yes
- name: "check that graceful error message is returned when creation with cpu_options and old botocore"
assert:
that:
- ec2_instance_cpu_options_creation.failed
- 'ec2_instance_cpu_options_creation.msg == "cpu_options is only supported with botocore >= 1.10.16"'
always:
- name: "Terminate version_fail instances"
ec2_instance:
state: absent
filters:
"tag:TestId": "{{ ec2_instance_tag_TestId }}"
wait: yes
ignore_errors: yes

@ -1,30 +0,0 @@
---
- include_role:
name: 'setup_remote_tmp_dir'
- set_fact:
virtualenv: "{{ remote_tmp_dir }}/virtualenv"
virtualenv_command: "{{ ansible_python_interpreter }} -m virtualenv"
- set_fact:
virtualenv_interpreter: "{{ virtualenv }}/bin/python"
- pip:
name: "virtualenv"
- pip:
name:
- 'botocore<1.10.16'
- boto3
- coverage<5
virtualenv: "{{ virtualenv }}"
virtualenv_command: "{{ virtualenv_command }}"
virtualenv_site_packages: no
- include_tasks: version_fail.yml
vars:
ansible_python_interpreter: "{{ virtualenv_interpreter }}"
- file:
state: absent
path: "{{ virtualenv }}"

@ -1,12 +0,0 @@
#!/usr/bin/env bash
#
# Beware: most of our tests here are run in parallel.
# To add new tests you'll need to add a new host to the inventory and a matching
# '{{ inventory_hostname }}'.yml file in roles/ec2_instance/tasks/
set -eux
export ANSIBLE_ROLES_PATH=../
ansible-playbook main.yml -i inventory "$@"

@ -1,4 +1,15 @@
---
- name: test wait_for with delegate_to
wait_for:
timeout: 2
delegate_to: localhost
register: waitfor
- assert:
that:
- waitfor is successful
- waitfor.elapsed >= 2
- name: setup create a directory to serve files from
file:
dest: "{{ files_dir }}"

@ -1,633 +0,0 @@
#!/usr/bin/python
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ec2_eni
short_description: Create and optionally attach an Elastic Network Interface (ENI) to an instance
description:
- Create and optionally attach an Elastic Network Interface (ENI) to an instance. If an ENI ID or private_ip is
provided, the existing ENI (if any) will be modified. The 'attached' parameter controls the attachment status
of the network interface.
version_added: "2.0"
author: "Rob White (@wimnat)"
options:
eni_id:
description:
- The ID of the ENI (to modify).
- If I(eni_id=None) and I(state=present), a new eni will be created.
type: str
instance_id:
description:
- Instance ID that you wish to attach ENI to.
- Since version 2.2, use the I(attached) parameter to attach or detach an ENI. Prior to 2.2, to detach an ENI from an instance, use C(None).
type: str
private_ip_address:
description:
- Private IP address.
type: str
subnet_id:
description:
- ID of subnet in which to create the ENI.
type: str
description:
description:
- Optional description of the ENI.
type: str
security_groups:
description:
- List of security groups associated with the interface. Only used when I(state=present).
- Since version 2.2, you can specify security groups by ID or by name or a combination of both. Prior to 2.2, you can specify only by ID.
type: list
elements: str
state:
description:
- Create or delete ENI.
default: present
choices: [ 'present', 'absent' ]
type: str
device_index:
description:
- The index of the device for the network interface attachment on the instance.
default: 0
type: int
attached:
description:
- Specifies if network interface should be attached or detached from instance. If omitted, attachment status
won't change
version_added: 2.2
type: bool
force_detach:
description:
- Force detachment of the interface. This applies either when explicitly detaching the interface by setting I(instance_id=None)
or when deleting an interface with I(state=absent).
default: false
type: bool
delete_on_termination:
description:
- Delete the interface when the instance it is attached to is terminated. You can only specify this flag when the
interface is being modified, not on creation.
required: false
type: bool
source_dest_check:
description:
- By default, interfaces perform source/destination checks. NAT instances however need this check to be disabled.
You can only specify this flag when the interface is being modified, not on creation.
required: false
type: bool
secondary_private_ip_addresses:
description:
- A list of IP addresses to assign as secondary IP addresses to the network interface.
This option is mutually exclusive of I(secondary_private_ip_address_count)
required: false
version_added: 2.2
type: list
elements: str
purge_secondary_private_ip_addresses:
description:
- To be used with I(secondary_private_ip_addresses) to determine whether or not to remove any secondary IP addresses other than those specified.
- Set I(secondary_private_ip_addresses=[]) to purge all secondary addresses.
default: false
type: bool
version_added: 2.5
secondary_private_ip_address_count:
description:
- The number of secondary IP addresses to assign to the network interface. This option is mutually exclusive of I(secondary_private_ip_addresses)
required: false
version_added: 2.2
type: int
allow_reassignment:
description:
- Indicates whether to allow an IP address that is already assigned to another network interface or instance
to be reassigned to the specified network interface.
required: false
default: false
type: bool
version_added: 2.7
extends_documentation_fragment:
- aws
- ec2
notes:
- This module identifies and ENI based on either the I(eni_id), a combination of I(private_ip_address) and I(subnet_id),
or a combination of I(instance_id) and I(device_id). Any of these options will let you specify a particular ENI.
'''
EXAMPLES = '''
# Note: These examples do not set authentication details, see the AWS Guide for details.
# Create an ENI. As no security group is defined, ENI will be created in default security group
- ec2_eni:
private_ip_address: 172.31.0.20
subnet_id: subnet-xxxxxxxx
state: present
# Create an ENI and attach it to an instance
- ec2_eni:
instance_id: i-xxxxxxx
device_index: 1
private_ip_address: 172.31.0.20
subnet_id: subnet-xxxxxxxx
state: present
# Create an ENI with two secondary addresses
- ec2_eni:
subnet_id: subnet-xxxxxxxx
state: present
secondary_private_ip_address_count: 2
# Assign a secondary IP address to an existing ENI
# This will purge any existing IPs
- ec2_eni:
subnet_id: subnet-xxxxxxxx
eni_id: eni-yyyyyyyy
state: present
secondary_private_ip_addresses:
- 172.16.1.1
# Remove any secondary IP addresses from an existing ENI
- ec2_eni:
subnet_id: subnet-xxxxxxxx
eni_id: eni-yyyyyyyy
state: present
secondary_private_ip_address_count: 0
# Destroy an ENI, detaching it from any instance if necessary
- ec2_eni:
eni_id: eni-xxxxxxx
force_detach: true
state: absent
# Update an ENI
- ec2_eni:
eni_id: eni-xxxxxxx
description: "My new description"
state: present
# Update an ENI identifying it by private_ip_address and subnet_id
- ec2_eni:
subnet_id: subnet-xxxxxxx
private_ip_address: 172.16.1.1
description: "My new description"
# Detach an ENI from an instance
- ec2_eni:
eni_id: eni-xxxxxxx
instance_id: None
state: present
### Delete an interface on termination
# First create the interface
- ec2_eni:
instance_id: i-xxxxxxx
device_index: 1
private_ip_address: 172.31.0.20
subnet_id: subnet-xxxxxxxx
state: present
register: eni
# Modify the interface to enable the delete_on_terminaton flag
- ec2_eni:
eni_id: "{{ eni.interface.id }}"
delete_on_termination: true
'''
RETURN = '''
interface:
description: Network interface attributes
returned: when state != absent
type: complex
contains:
description:
description: interface description
type: str
sample: Firewall network interface
groups:
description: list of security groups
type: list
elements: dict
sample: [ { "sg-f8a8a9da": "default" } ]
id:
description: network interface id
type: str
sample: "eni-1d889198"
mac_address:
description: interface's physical address
type: str
sample: "00:00:5E:00:53:23"
owner_id:
description: aws account id
type: str
sample: 812381371
private_ip_address:
description: primary ip address of this interface
type: str
sample: 10.20.30.40
private_ip_addresses:
description: list of all private ip addresses associated to this interface
type: list
elements: dict
sample: [ { "primary_address": true, "private_ip_address": "10.20.30.40" } ]
source_dest_check:
description: value of source/dest check flag
type: bool
sample: True
status:
description: network interface status
type: str
sample: "pending"
subnet_id:
description: which vpc subnet the interface is bound
type: str
sample: subnet-b0a0393c
vpc_id:
description: which vpc this network interface is bound
type: str
sample: vpc-9a9a9da
'''
import time
import re
try:
import boto.ec2
import boto.vpc
from boto.exception import BotoServerError
HAS_BOTO = True
except ImportError:
HAS_BOTO = False
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ec2 import (AnsibleAWSError, connect_to_aws,
ec2_argument_spec, get_aws_connection_info,
get_ec2_security_group_ids_from_names)
def get_eni_info(interface):
# Private addresses
private_addresses = []
for ip in interface.private_ip_addresses:
private_addresses.append({'private_ip_address': ip.private_ip_address, 'primary_address': ip.primary})
interface_info = {'id': interface.id,
'subnet_id': interface.subnet_id,
'vpc_id': interface.vpc_id,
'description': interface.description,
'owner_id': interface.owner_id,
'status': interface.status,
'mac_address': interface.mac_address,
'private_ip_address': interface.private_ip_address,
'source_dest_check': interface.source_dest_check,
'groups': dict((group.id, group.name) for group in interface.groups),
'private_ip_addresses': private_addresses
}
if interface.attachment is not None:
interface_info['attachment'] = {'attachment_id': interface.attachment.id,
'instance_id': interface.attachment.instance_id,
'device_index': interface.attachment.device_index,
'status': interface.attachment.status,
'attach_time': interface.attachment.attach_time,
'delete_on_termination': interface.attachment.delete_on_termination,
}
return interface_info
def wait_for_eni(eni, status):
while True:
time.sleep(3)
eni.update()
# If the status is detached we just need attachment to disappear
if eni.attachment is None:
if status == "detached":
break
else:
if status == "attached" and eni.attachment.status == "attached":
break
def create_eni(connection, vpc_id, module):
instance_id = module.params.get("instance_id")
attached = module.params.get("attached")
if instance_id == 'None':
instance_id = None
device_index = module.params.get("device_index")
subnet_id = module.params.get('subnet_id')
private_ip_address = module.params.get('private_ip_address')
description = module.params.get('description')
security_groups = get_ec2_security_group_ids_from_names(module.params.get('security_groups'), connection, vpc_id=vpc_id, boto3=False)
secondary_private_ip_addresses = module.params.get("secondary_private_ip_addresses")
secondary_private_ip_address_count = module.params.get("secondary_private_ip_address_count")
changed = False
try:
eni = connection.create_network_interface(subnet_id, private_ip_address, description, security_groups)
if attached and instance_id is not None:
try:
eni.attach(instance_id, device_index)
except BotoServerError:
eni.delete()
raise
# Wait to allow creation / attachment to finish
wait_for_eni(eni, "attached")
eni.update()
if secondary_private_ip_address_count is not None:
try:
connection.assign_private_ip_addresses(network_interface_id=eni.id, secondary_private_ip_address_count=secondary_private_ip_address_count)
except BotoServerError:
eni.delete()
raise
if secondary_private_ip_addresses is not None:
try:
connection.assign_private_ip_addresses(network_interface_id=eni.id, private_ip_addresses=secondary_private_ip_addresses)
except BotoServerError:
eni.delete()
raise
changed = True
except BotoServerError as e:
module.fail_json(msg=e.message)
module.exit_json(changed=changed, interface=get_eni_info(eni))
def modify_eni(connection, vpc_id, module, eni):
instance_id = module.params.get("instance_id")
attached = module.params.get("attached")
do_detach = module.params.get('state') == 'detached'
device_index = module.params.get("device_index")
description = module.params.get('description')
security_groups = module.params.get('security_groups')
force_detach = module.params.get("force_detach")
source_dest_check = module.params.get("source_dest_check")
delete_on_termination = module.params.get("delete_on_termination")
secondary_private_ip_addresses = module.params.get("secondary_private_ip_addresses")
purge_secondary_private_ip_addresses = module.params.get("purge_secondary_private_ip_addresses")
secondary_private_ip_address_count = module.params.get("secondary_private_ip_address_count")
allow_reassignment = module.params.get("allow_reassignment")
changed = False
try:
if description is not None:
if eni.description != description:
connection.modify_network_interface_attribute(eni.id, "description", description)
changed = True
if len(security_groups) > 0:
groups = get_ec2_security_group_ids_from_names(security_groups, connection, vpc_id=vpc_id, boto3=False)
if sorted(get_sec_group_list(eni.groups)) != sorted(groups):
connection.modify_network_interface_attribute(eni.id, "groupSet", groups)
changed = True
if source_dest_check is not None:
if eni.source_dest_check != source_dest_check:
connection.modify_network_interface_attribute(eni.id, "sourceDestCheck", source_dest_check)
changed = True
if delete_on_termination is not None and eni.attachment is not None:
if eni.attachment.delete_on_termination is not delete_on_termination:
connection.modify_network_interface_attribute(eni.id, "deleteOnTermination", delete_on_termination, eni.attachment.id)
changed = True
current_secondary_addresses = [i.private_ip_address for i in eni.private_ip_addresses if not i.primary]
if secondary_private_ip_addresses is not None:
secondary_addresses_to_remove = list(set(current_secondary_addresses) - set(secondary_private_ip_addresses))
if secondary_addresses_to_remove and purge_secondary_private_ip_addresses:
connection.unassign_private_ip_addresses(network_interface_id=eni.id,
private_ip_addresses=list(set(current_secondary_addresses) -
set(secondary_private_ip_addresses)),
dry_run=False)
changed = True
secondary_addresses_to_add = list(set(secondary_private_ip_addresses) - set(current_secondary_addresses))
if secondary_addresses_to_add:
connection.assign_private_ip_addresses(network_interface_id=eni.id,
private_ip_addresses=secondary_addresses_to_add,
secondary_private_ip_address_count=None,
allow_reassignment=allow_reassignment, dry_run=False)
changed = True
if secondary_private_ip_address_count is not None:
current_secondary_address_count = len(current_secondary_addresses)
if secondary_private_ip_address_count > current_secondary_address_count:
connection.assign_private_ip_addresses(network_interface_id=eni.id,
private_ip_addresses=None,
secondary_private_ip_address_count=(secondary_private_ip_address_count -
current_secondary_address_count),
allow_reassignment=allow_reassignment, dry_run=False)
changed = True
elif secondary_private_ip_address_count < current_secondary_address_count:
# How many of these addresses do we want to remove
secondary_addresses_to_remove_count = current_secondary_address_count - secondary_private_ip_address_count
connection.unassign_private_ip_addresses(network_interface_id=eni.id,
private_ip_addresses=current_secondary_addresses[:secondary_addresses_to_remove_count],
dry_run=False)
if attached is True:
if eni.attachment and eni.attachment.instance_id != instance_id:
detach_eni(eni, module)
eni.attach(instance_id, device_index)
wait_for_eni(eni, "attached")
changed = True
if eni.attachment is None:
eni.attach(instance_id, device_index)
wait_for_eni(eni, "attached")
changed = True
elif attached is False:
detach_eni(eni, module)
except BotoServerError as e:
module.fail_json(msg=e.message)
eni.update()
module.exit_json(changed=changed, interface=get_eni_info(eni))
def delete_eni(connection, module):
eni_id = module.params.get("eni_id")
force_detach = module.params.get("force_detach")
try:
eni_result_set = connection.get_all_network_interfaces(eni_id)
eni = eni_result_set[0]
if force_detach is True:
if eni.attachment is not None:
eni.detach(force_detach)
# Wait to allow detachment to finish
wait_for_eni(eni, "detached")
eni.update()
eni.delete()
changed = True
else:
eni.delete()
changed = True
module.exit_json(changed=changed)
except BotoServerError as e:
regex = re.compile('The networkInterface ID \'.*\' does not exist')
if regex.search(e.message) is not None:
module.exit_json(changed=False)
else:
module.fail_json(msg=e.message)
def detach_eni(eni, module):
attached = module.params.get("attached")
force_detach = module.params.get("force_detach")
if eni.attachment is not None:
eni.detach(force_detach)
wait_for_eni(eni, "detached")
if attached:
return
eni.update()
module.exit_json(changed=True, interface=get_eni_info(eni))
else:
module.exit_json(changed=False, interface=get_eni_info(eni))
def uniquely_find_eni(connection, module):
eni_id = module.params.get("eni_id")
private_ip_address = module.params.get('private_ip_address')
subnet_id = module.params.get('subnet_id')
instance_id = module.params.get('instance_id')
device_index = module.params.get('device_index')
attached = module.params.get('attached')
try:
filters = {}
# proceed only if we're univocally specifying an ENI
if eni_id is None and private_ip_address is None and (instance_id is None and device_index is None):
return None
if private_ip_address and subnet_id:
filters['private-ip-address'] = private_ip_address
filters['subnet-id'] = subnet_id
if not attached and instance_id and device_index:
filters['attachment.instance-id'] = instance_id
filters['attachment.device-index'] = device_index
if eni_id is None and len(filters) == 0:
return None
eni_result = connection.get_all_network_interfaces(eni_id, filters=filters)
if len(eni_result) == 1:
return eni_result[0]
else:
return None
except BotoServerError as e:
module.fail_json(msg=e.message)
return None
def get_sec_group_list(groups):
# Build list of remote security groups
remote_security_groups = []
for group in groups:
remote_security_groups.append(group.id.encode())
return remote_security_groups
def _get_vpc_id(connection, module, subnet_id):
try:
return connection.get_all_subnets(subnet_ids=[subnet_id])[0].vpc_id
except BotoServerError as e:
module.fail_json(msg=e.message)
def main():
argument_spec = ec2_argument_spec()
argument_spec.update(
dict(
eni_id=dict(default=None, type='str'),
instance_id=dict(default=None, type='str'),
private_ip_address=dict(type='str'),
subnet_id=dict(type='str'),
description=dict(type='str'),
security_groups=dict(default=[], type='list'),
device_index=dict(default=0, type='int'),
state=dict(default='present', choices=['present', 'absent']),
force_detach=dict(default='no', type='bool'),
source_dest_check=dict(default=None, type='bool'),
delete_on_termination=dict(default=None, type='bool'),
secondary_private_ip_addresses=dict(default=None, type='list'),
purge_secondary_private_ip_addresses=dict(default=False, type='bool'),
secondary_private_ip_address_count=dict(default=None, type='int'),
allow_reassignment=dict(default=False, type='bool'),
attached=dict(default=None, type='bool')
)
)
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[
['secondary_private_ip_addresses', 'secondary_private_ip_address_count']
],
required_if=([
('state', 'absent', ['eni_id']),
('attached', True, ['instance_id']),
('purge_secondary_private_ip_addresses', True, ['secondary_private_ip_addresses'])
])
)
if not HAS_BOTO:
module.fail_json(msg='boto required for this module')
region, ec2_url, aws_connect_params = get_aws_connection_info(module)
if region:
try:
connection = connect_to_aws(boto.ec2, region, **aws_connect_params)
vpc_connection = connect_to_aws(boto.vpc, region, **aws_connect_params)
except (boto.exception.NoAuthHandlerFound, AnsibleAWSError) as e:
module.fail_json(msg=str(e))
else:
module.fail_json(msg="region must be specified")
state = module.params.get("state")
if state == 'present':
eni = uniquely_find_eni(connection, module)
if eni is None:
subnet_id = module.params.get("subnet_id")
if subnet_id is None:
module.fail_json(msg="subnet_id is required when creating a new ENI")
vpc_id = _get_vpc_id(vpc_connection, module, subnet_id)
create_eni(connection, vpc_id, module)
else:
vpc_id = eni.vpc_id
modify_eni(connection, vpc_id, module, eni)
elif state == 'absent':
delete_eni(connection, module)
if __name__ == '__main__':
main()

@ -1,276 +0,0 @@
#!/usr/bin/python
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ec2_eni_info
short_description: Gather information about ec2 ENI interfaces in AWS
description:
- Gather information about ec2 ENI interfaces in AWS.
- This module was called C(ec2_eni_facts) before Ansible 2.9. The usage did not change.
version_added: "2.0"
author: "Rob White (@wimnat)"
requirements: [ boto3 ]
options:
filters:
description:
- A dict of filters to apply. Each dict item consists of a filter key and a filter value.
See U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeNetworkInterfaces.html) for possible filters.
type: dict
extends_documentation_fragment:
- aws
- ec2
'''
EXAMPLES = '''
# Note: These examples do not set authentication details, see the AWS Guide for details.
# Gather information about all ENIs
- ec2_eni_info:
# Gather information about a particular ENI
- ec2_eni_info:
filters:
network-interface-id: eni-xxxxxxx
'''
RETURN = '''
network_interfaces:
description: List of matching elastic network interfaces
returned: always
type: complex
contains:
association:
description: Info of associated elastic IP (EIP)
returned: always, empty dict if no association exists
type: dict
sample: {
allocation_id: "eipalloc-5sdf123",
association_id: "eipassoc-8sdf123",
ip_owner_id: "4415120123456",
public_dns_name: "ec2-52-1-0-63.compute-1.amazonaws.com",
public_ip: "52.1.0.63"
}
attachment:
description: Info about attached ec2 instance
returned: always, empty dict if ENI is not attached
type: dict
sample: {
attach_time: "2017-08-05T15:25:47+00:00",
attachment_id: "eni-attach-149d21234",
delete_on_termination: false,
device_index: 1,
instance_id: "i-15b8d3cadbafa1234",
instance_owner_id: "4415120123456",
status: "attached"
}
availability_zone:
description: Availability zone of ENI
returned: always
type: str
sample: "us-east-1b"
description:
description: Description text for ENI
returned: always
type: str
sample: "My favourite network interface"
groups:
description: List of attached security groups
returned: always
type: list
sample: [
{
group_id: "sg-26d0f1234",
group_name: "my_ec2_security_group"
}
]
id:
description: The id of the ENI (alias for network_interface_id)
returned: always
type: str
sample: "eni-392fsdf"
interface_type:
description: Type of the network interface
returned: always
type: str
sample: "interface"
ipv6_addresses:
description: List of IPv6 addresses for this interface
returned: always
type: list
sample: []
mac_address:
description: MAC address of the network interface
returned: always
type: str
sample: "0a:f8:10:2f:ab:a1"
network_interface_id:
description: The id of the ENI
returned: always
type: str
sample: "eni-392fsdf"
owner_id:
description: AWS account id of the owner of the ENI
returned: always
type: str
sample: "4415120123456"
private_dns_name:
description: Private DNS name for the ENI
returned: always
type: str
sample: "ip-172-16-1-180.ec2.internal"
private_ip_address:
description: Private IP address for the ENI
returned: always
type: str
sample: "172.16.1.180"
private_ip_addresses:
description: List of private IP addresses attached to the ENI
returned: always
type: list
sample: []
requester_id:
description: The ID of the entity that launched the ENI
returned: always
type: str
sample: "AIDAIONYVJQNIAZFT3ABC"
requester_managed:
description: Indicates whether the network interface is being managed by an AWS service.
returned: always
type: bool
sample: false
source_dest_check:
description: Indicates whether the network interface performs source/destination checking.
returned: always
type: bool
sample: false
status:
description: Indicates if the network interface is attached to an instance or not
returned: always
type: str
sample: "in-use"
subnet_id:
description: Subnet ID the ENI is in
returned: always
type: str
sample: "subnet-7bbf01234"
tag_set:
description: Dictionary of tags added to the ENI
returned: always
type: dict
sample: {}
vpc_id:
description: ID of the VPC the network interface it part of
returned: always
type: str
sample: "vpc-b3f1f123"
'''
try:
from botocore.exceptions import ClientError, NoCredentialsError
HAS_BOTO3 = True
except ImportError:
HAS_BOTO3 = False
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ec2 import ansible_dict_to_boto3_filter_list, boto3_conn
from ansible.module_utils.ec2 import boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict
from ansible.module_utils.ec2 import ec2_argument_spec, get_aws_connection_info
def list_eni(connection, module):
if module.params.get("filters") is None:
filters = []
else:
filters = ansible_dict_to_boto3_filter_list(module.params.get("filters"))
try:
network_interfaces_result = connection.describe_network_interfaces(Filters=filters)['NetworkInterfaces']
except (ClientError, NoCredentialsError) as e:
module.fail_json(msg=e.message)
# Modify boto3 tags list to be ansible friendly dict and then camel_case
camel_network_interfaces = []
for network_interface in network_interfaces_result:
network_interface['TagSet'] = boto3_tag_list_to_ansible_dict(network_interface['TagSet'])
# Added id to interface info to be compatible with return values of ec2_eni module:
network_interface['Id'] = network_interface['NetworkInterfaceId']
camel_network_interfaces.append(camel_dict_to_snake_dict(network_interface))
module.exit_json(network_interfaces=camel_network_interfaces)
def get_eni_info(interface):
# Private addresses
private_addresses = []
for ip in interface.private_ip_addresses:
private_addresses.append({'private_ip_address': ip.private_ip_address, 'primary_address': ip.primary})
interface_info = {'id': interface.id,
'subnet_id': interface.subnet_id,
'vpc_id': interface.vpc_id,
'description': interface.description,
'owner_id': interface.owner_id,
'status': interface.status,
'mac_address': interface.mac_address,
'private_ip_address': interface.private_ip_address,
'source_dest_check': interface.source_dest_check,
'groups': dict((group.id, group.name) for group in interface.groups),
'private_ip_addresses': private_addresses
}
if hasattr(interface, 'publicDnsName'):
interface_info['association'] = {'public_ip_address': interface.publicIp,
'public_dns_name': interface.publicDnsName,
'ip_owner_id': interface.ipOwnerId
}
if interface.attachment is not None:
interface_info['attachment'] = {'attachment_id': interface.attachment.id,
'instance_id': interface.attachment.instance_id,
'device_index': interface.attachment.device_index,
'status': interface.attachment.status,
'attach_time': interface.attachment.attach_time,
'delete_on_termination': interface.attachment.delete_on_termination,
}
return interface_info
def main():
argument_spec = ec2_argument_spec()
argument_spec.update(
dict(
filters=dict(default=None, type='dict')
)
)
module = AnsibleModule(argument_spec=argument_spec)
if module._name == 'ec2_eni_facts':
module.deprecate("The 'ec2_eni_facts' module has been renamed to 'ec2_eni_info'",
version='2.13', collection_name='ansible.builtin')
if not HAS_BOTO3:
module.fail_json(msg='boto3 required for this module')
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
list_eni(connection, module)
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

@ -1,572 +0,0 @@
#!/usr/bin/python
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ec2_instance_info
short_description: Gather information about ec2 instances in AWS
description:
- Gather information about ec2 instances in AWS
- This module was called C(ec2_instance_facts) before Ansible 2.9. The usage did not change.
version_added: "2.4"
author:
- Michael Schuett (@michaeljs1990)
- Rob White (@wimnat)
requirements: [ "boto3", "botocore" ]
options:
instance_ids:
description:
- If you specify one or more instance IDs, only instances that have the specified IDs are returned.
required: false
version_added: 2.4
type: list
filters:
description:
- A dict of filters to apply. Each dict item consists of a filter key and a filter value. See
U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html) for possible filters. Filter
names and values are case sensitive.
required: false
default: {}
type: dict
extends_documentation_fragment:
- aws
- ec2
'''
EXAMPLES = '''
# Note: These examples do not set authentication details, see the AWS Guide for details.
# Gather information about all instances
- ec2_instance_info:
# Gather information about all instances in AZ ap-southeast-2a
- ec2_instance_info:
filters:
availability-zone: ap-southeast-2a
# Gather information about a particular instance using ID
- ec2_instance_info:
instance_ids:
- i-12345678
# Gather information about any instance with a tag key Name and value Example
- ec2_instance_info:
filters:
"tag:Name": Example
# Gather information about any instance in states "shutting-down", "stopping", "stopped"
- ec2_instance_info:
filters:
instance-state-name: [ "shutting-down", "stopping", "stopped" ]
'''
RETURN = '''
instances:
description: a list of ec2 instances
returned: always
type: complex
contains:
ami_launch_index:
description: The AMI launch index, which can be used to find this instance in the launch group.
returned: always
type: int
sample: 0
architecture:
description: The architecture of the image
returned: always
type: str
sample: x86_64
block_device_mappings:
description: Any block device mapping entries for the instance.
returned: always
type: complex
contains:
device_name:
description: The device name exposed to the instance (for example, /dev/sdh or xvdh).
returned: always
type: str
sample: /dev/sdh
ebs:
description: Parameters used to automatically set up EBS volumes when the instance is launched.
returned: always
type: complex
contains:
attach_time:
description: The time stamp when the attachment initiated.
returned: always
type: str
sample: "2017-03-23T22:51:24+00:00"
delete_on_termination:
description: Indicates whether the volume is deleted on instance termination.
returned: always
type: bool
sample: true
status:
description: The attachment state.
returned: always
type: str
sample: attached
volume_id:
description: The ID of the EBS volume
returned: always
type: str
sample: vol-12345678
cpu_options:
description: The CPU options set for the instance.
returned: always if botocore version >= 1.10.16
type: complex
contains:
core_count:
description: The number of CPU cores for the instance.
returned: always
type: int
sample: 1
threads_per_core:
description: The number of threads per CPU core. On supported instance, a value of 1 means Intel Hyper-Threading Technology is disabled.
returned: always
type: int
sample: 1
client_token:
description: The idempotency token you provided when you launched the instance, if applicable.
returned: always
type: str
sample: mytoken
ebs_optimized:
description: Indicates whether the instance is optimized for EBS I/O.
returned: always
type: bool
sample: false
hypervisor:
description: The hypervisor type of the instance.
returned: always
type: str
sample: xen
iam_instance_profile:
description: The IAM instance profile associated with the instance, if applicable.
returned: always
type: complex
contains:
arn:
description: The Amazon Resource Name (ARN) of the instance profile.
returned: always
type: str
sample: "arn:aws:iam::000012345678:instance-profile/myprofile"
id:
description: The ID of the instance profile
returned: always
type: str
sample: JFJ397FDG400FG9FD1N
image_id:
description: The ID of the AMI used to launch the instance.
returned: always
type: str
sample: ami-0011223344
instance_id:
description: The ID of the instance.
returned: always
type: str
sample: i-012345678
instance_type:
description: The instance type size of the running instance.
returned: always
type: str
sample: t2.micro
key_name:
description: The name of the key pair, if this instance was launched with an associated key pair.
returned: always
type: str
sample: my-key
launch_time:
description: The time the instance was launched.
returned: always
type: str
sample: "2017-03-23T22:51:24+00:00"
monitoring:
description: The monitoring for the instance.
returned: always
type: complex
contains:
state:
description: Indicates whether detailed monitoring is enabled. Otherwise, basic monitoring is enabled.
returned: always
type: str
sample: disabled
network_interfaces:
description: One or more network interfaces for the instance.
returned: always
type: complex
contains:
association:
description: The association information for an Elastic IPv4 associated with the network interface.
returned: always
type: complex
contains:
ip_owner_id:
description: The ID of the owner of the Elastic IP address.
returned: always
type: str
sample: amazon
public_dns_name:
description: The public DNS name.
returned: always
type: str
sample: ""
public_ip:
description: The public IP address or Elastic IP address bound to the network interface.
returned: always
type: str
sample: 1.2.3.4
attachment:
description: The network interface attachment.
returned: always
type: complex
contains:
attach_time:
description: The time stamp when the attachment initiated.
returned: always
type: str
sample: "2017-03-23T22:51:24+00:00"
attachment_id:
description: The ID of the network interface attachment.
returned: always
type: str
sample: eni-attach-3aff3f
delete_on_termination:
description: Indicates whether the network interface is deleted when the instance is terminated.
returned: always
type: bool
sample: true
device_index:
description: The index of the device on the instance for the network interface attachment.
returned: always
type: int
sample: 0
status:
description: The attachment state.
returned: always
type: str
sample: attached
description:
description: The description.
returned: always
type: str
sample: My interface
groups:
description: One or more security groups.
returned: always
type: list
elements: dict
contains:
group_id:
description: The ID of the security group.
returned: always
type: str
sample: sg-abcdef12
group_name:
description: The name of the security group.
returned: always
type: str
sample: mygroup
ipv6_addresses:
description: One or more IPv6 addresses associated with the network interface.
returned: always
type: list
elements: dict
contains:
ipv6_address:
description: The IPv6 address.
returned: always
type: str
sample: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
mac_address:
description: The MAC address.
returned: always
type: str
sample: "00:11:22:33:44:55"
network_interface_id:
description: The ID of the network interface.
returned: always
type: str
sample: eni-01234567
owner_id:
description: The AWS account ID of the owner of the network interface.
returned: always
type: str
sample: 01234567890
private_ip_address:
description: The IPv4 address of the network interface within the subnet.
returned: always
type: str
sample: 10.0.0.1
private_ip_addresses:
description: The private IPv4 addresses associated with the network interface.
returned: always
type: list
elements: dict
contains:
association:
description: The association information for an Elastic IP address (IPv4) associated with the network interface.
returned: always
type: complex
contains:
ip_owner_id:
description: The ID of the owner of the Elastic IP address.
returned: always
type: str
sample: amazon
public_dns_name:
description: The public DNS name.
returned: always
type: str
sample: ""
public_ip:
description: The public IP address or Elastic IP address bound to the network interface.
returned: always
type: str
sample: 1.2.3.4
primary:
description: Indicates whether this IPv4 address is the primary private IP address of the network interface.
returned: always
type: bool
sample: true
private_ip_address:
description: The private IPv4 address of the network interface.
returned: always
type: str
sample: 10.0.0.1
source_dest_check:
description: Indicates whether source/destination checking is enabled.
returned: always
type: bool
sample: true
status:
description: The status of the network interface.
returned: always
type: str
sample: in-use
subnet_id:
description: The ID of the subnet for the network interface.
returned: always
type: str
sample: subnet-0123456
vpc_id:
description: The ID of the VPC for the network interface.
returned: always
type: str
sample: vpc-0123456
placement:
description: The location where the instance launched, if applicable.
returned: always
type: complex
contains:
availability_zone:
description: The Availability Zone of the instance.
returned: always
type: str
sample: ap-southeast-2a
group_name:
description: The name of the placement group the instance is in (for cluster compute instances).
returned: always
type: str
sample: ""
tenancy:
description: The tenancy of the instance (if the instance is running in a VPC).
returned: always
type: str
sample: default
private_dns_name:
description: The private DNS name.
returned: always
type: str
sample: ip-10-0-0-1.ap-southeast-2.compute.internal
private_ip_address:
description: The IPv4 address of the network interface within the subnet.
returned: always
type: str
sample: 10.0.0.1
product_codes:
description: One or more product codes.
returned: always
type: list
elements: dict
contains:
product_code_id:
description: The product code.
returned: always
type: str
sample: aw0evgkw8ef3n2498gndfgasdfsd5cce
product_code_type:
description: The type of product code.
returned: always
type: str
sample: marketplace
public_dns_name:
description: The public DNS name assigned to the instance.
returned: always
type: str
sample:
public_ip_address:
description: The public IPv4 address assigned to the instance
returned: always
type: str
sample: 52.0.0.1
root_device_name:
description: The device name of the root device
returned: always
type: str
sample: /dev/sda1
root_device_type:
description: The type of root device used by the AMI.
returned: always
type: str
sample: ebs
security_groups:
description: One or more security groups for the instance.
returned: always
type: list
elements: dict
contains:
group_id:
description: The ID of the security group.
returned: always
type: str
sample: sg-0123456
group_name:
description: The name of the security group.
returned: always
type: str
sample: my-security-group
source_dest_check:
description: Indicates whether source/destination checking is enabled.
returned: always
type: bool
sample: true
state:
description: The current state of the instance.
returned: always
type: complex
contains:
code:
description: The low byte represents the state.
returned: always
type: int
sample: 16
name:
description: The name of the state.
returned: always
type: str
sample: running
state_transition_reason:
description: The reason for the most recent state transition.
returned: always
type: str
sample:
subnet_id:
description: The ID of the subnet in which the instance is running.
returned: always
type: str
sample: subnet-00abcdef
tags:
description: Any tags assigned to the instance.
returned: always
type: dict
sample:
virtualization_type:
description: The type of virtualization of the AMI.
returned: always
type: str
sample: hvm
vpc_id:
description: The ID of the VPC the instance is in.
returned: always
type: dict
sample: vpc-0011223344
'''
import traceback
try:
import boto3
from botocore.exceptions import ClientError
HAS_BOTO3 = True
except ImportError:
HAS_BOTO3 = False
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ec2 import (ansible_dict_to_boto3_filter_list,
boto3_conn, boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict,
ec2_argument_spec, get_aws_connection_info)
def list_ec2_instances(connection, module):
instance_ids = module.params.get("instance_ids")
filters = ansible_dict_to_boto3_filter_list(module.params.get("filters"))
try:
reservations_paginator = connection.get_paginator('describe_instances')
reservations = reservations_paginator.paginate(InstanceIds=instance_ids, Filters=filters).build_full_result()
except ClientError as e:
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
# Get instances from reservations
instances = []
for reservation in reservations['Reservations']:
instances = instances + reservation['Instances']
# Turn the boto3 result in to ansible_friendly_snaked_names
snaked_instances = [camel_dict_to_snake_dict(instance) for instance in instances]
# Turn the boto3 result in to ansible friendly tag dictionary
for instance in snaked_instances:
instance['tags'] = boto3_tag_list_to_ansible_dict(instance.get('tags', []), 'key', 'value')
module.exit_json(instances=snaked_instances)
def main():
argument_spec = ec2_argument_spec()
argument_spec.update(
dict(
instance_ids=dict(default=[], type='list'),
filters=dict(default={}, type='dict')
)
)
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=[
['instance_ids', 'filters']
],
supports_check_mode=True
)
if module._name == 'ec2_instance_facts':
module.deprecate("The 'ec2_instance_facts' module has been renamed to 'ec2_instance_info'",
version='2.13', collection_name='ansible.builtin')
if not HAS_BOTO3:
module.fail_json(msg='boto3 required for this module')
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
if region:
connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
else:
module.fail_json(msg="region must be specified")
list_ec2_instances(connection, module)
if __name__ == '__main__':
main()

@ -1,271 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ec2_key
version_added: "1.5"
short_description: create or delete an ec2 key pair
description:
- create or delete an ec2 key pair.
options:
name:
description:
- Name of the key pair.
required: true
type: str
key_material:
description:
- Public key material.
required: false
type: str
force:
description:
- Force overwrite of already existing key pair if key has changed.
required: false
default: true
type: bool
version_added: "2.3"
state:
description:
- create or delete keypair
required: false
choices: [ present, absent ]
default: 'present'
type: str
wait:
description:
- This option has no effect since version 2.5 and will be removed in 2.14.
version_added: "1.6"
type: bool
wait_timeout:
description:
- This option has no effect since version 2.5 and will be removed in 2.14.
version_added: "1.6"
type: int
required: false
extends_documentation_fragment:
- aws
- ec2
requirements: [ boto3 ]
author:
- "Vincent Viallet (@zbal)"
- "Prasad Katti (@prasadkatti)"
'''
EXAMPLES = '''
# Note: These examples do not set authentication details, see the AWS Guide for details.
- name: create a new ec2 key pair, returns generated private key
ec2_key:
name: my_keypair
- name: create key pair using provided key_material
ec2_key:
name: my_keypair
key_material: 'ssh-rsa AAAAxyz...== me@example.com'
- name: create key pair using key_material obtained using 'file' lookup plugin
ec2_key:
name: my_keypair
key_material: "{{ lookup('file', '/path/to/public_key/id_rsa.pub') }}"
# try creating a key pair with the name of an already existing keypair
# but don't overwrite it even if the key is different (force=false)
- name: try creating a key pair with name of an already existing keypair
ec2_key:
name: my_existing_keypair
key_material: 'ssh-rsa AAAAxyz...== me@example.com'
force: false
- name: remove key pair by name
ec2_key:
name: my_keypair
state: absent
'''
RETURN = '''
changed:
description: whether a keypair was created/deleted
returned: always
type: bool
sample: true
msg:
description: short message describing the action taken
returned: always
type: str
sample: key pair created
key:
description: details of the keypair (this is set to null when state is absent)
returned: always
type: complex
contains:
fingerprint:
description: fingerprint of the key
returned: when state is present
type: str
sample: 'b0:22:49:61:d9:44:9d:0c:7e:ac:8a:32:93:21:6c:e8:fb:59:62:43'
name:
description: name of the keypair
returned: when state is present
type: str
sample: my_keypair
private_key:
description: private key of a newly created keypair
returned: when a new keypair is created by AWS (key_material is not provided)
type: str
sample: '-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKC...
-----END RSA PRIVATE KEY-----'
'''
import uuid
from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils._text import to_bytes
try:
from botocore.exceptions import ClientError
except ImportError:
pass # caught by AnsibleAWSModule
def extract_key_data(key):
data = {
'name': key['KeyName'],
'fingerprint': key['KeyFingerprint']
}
if 'KeyMaterial' in key:
data['private_key'] = key['KeyMaterial']
return data
def get_key_fingerprint(module, ec2_client, key_material):
'''
EC2's fingerprints are non-trivial to generate, so push this key
to a temporary name and make ec2 calculate the fingerprint for us.
http://blog.jbrowne.com/?p=23
https://forums.aws.amazon.com/thread.jspa?messageID=352828
'''
# find an unused name
name_in_use = True
while name_in_use:
random_name = "ansible-" + str(uuid.uuid4())
name_in_use = find_key_pair(module, ec2_client, random_name)
temp_key = import_key_pair(module, ec2_client, random_name, key_material)
delete_key_pair(module, ec2_client, random_name, finish_task=False)
return temp_key['KeyFingerprint']
def find_key_pair(module, ec2_client, name):
try:
key = ec2_client.describe_key_pairs(KeyNames=[name])['KeyPairs'][0]
except ClientError as err:
if err.response['Error']['Code'] == "InvalidKeyPair.NotFound":
return None
module.fail_json_aws(err, msg="error finding keypair")
except IndexError:
key = None
return key
def create_key_pair(module, ec2_client, name, key_material, force):
key = find_key_pair(module, ec2_client, name)
if key:
if key_material and force:
if not module.check_mode:
new_fingerprint = get_key_fingerprint(module, ec2_client, key_material)
if key['KeyFingerprint'] != new_fingerprint:
delete_key_pair(module, ec2_client, name, finish_task=False)
key = import_key_pair(module, ec2_client, name, key_material)
key_data = extract_key_data(key)
module.exit_json(changed=True, key=key_data, msg="key pair updated")
else:
# Assume a change will be made in check mode since a comparison can't be done
module.exit_json(changed=True, key=extract_key_data(key), msg="key pair updated")
key_data = extract_key_data(key)
module.exit_json(changed=False, key=key_data, msg="key pair already exists")
else:
# key doesn't exist, create it now
key_data = None
if not module.check_mode:
if key_material:
key = import_key_pair(module, ec2_client, name, key_material)
else:
try:
key = ec2_client.create_key_pair(KeyName=name)
except ClientError as err:
module.fail_json_aws(err, msg="error creating key")
key_data = extract_key_data(key)
module.exit_json(changed=True, key=key_data, msg="key pair created")
def import_key_pair(module, ec2_client, name, key_material):
try:
key = ec2_client.import_key_pair(KeyName=name, PublicKeyMaterial=to_bytes(key_material))
except ClientError as err:
module.fail_json_aws(err, msg="error importing key")
return key
def delete_key_pair(module, ec2_client, name, finish_task=True):
key = find_key_pair(module, ec2_client, name)
if key:
if not module.check_mode:
try:
ec2_client.delete_key_pair(KeyName=name)
except ClientError as err:
module.fail_json_aws(err, msg="error deleting key")
if not finish_task:
return
module.exit_json(changed=True, key=None, msg="key deleted")
module.exit_json(key=None, msg="key did not exist")
def main():
argument_spec = dict(
name=dict(required=True),
key_material=dict(),
force=dict(type='bool', default=True),
state=dict(default='present', choices=['present', 'absent']),
wait=dict(type='bool', removed_in_version='2.14'),
wait_timeout=dict(type='int', removed_in_version='2.14')
)
module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True)
ec2_client = module.client('ec2')
name = module.params['name']
state = module.params.get('state')
key_material = module.params.get('key_material')
force = module.params.get('force')
if state == 'absent':
delete_key_pair(module, ec2_client, name)
elif state == 'present':
create_key_pair(module, ec2_client, name, key_material, force)
if __name__ == '__main__':
main()

@ -1,283 +0,0 @@
#!/usr/bin/python
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ec2_vpc_igw
short_description: Manage an AWS VPC Internet gateway
description:
- Manage an AWS VPC Internet gateway
version_added: "2.0"
author: Robert Estelle (@erydo)
options:
vpc_id:
description:
- The VPC ID for the VPC in which to manage the Internet Gateway.
required: true
type: str
tags:
description:
- "A dict of tags to apply to the internet gateway. Any tags currently applied to the internet gateway and not present here will be removed."
aliases: [ 'resource_tags' ]
version_added: "2.4"
type: dict
state:
description:
- Create or terminate the IGW
default: present
choices: [ 'present', 'absent' ]
type: str
extends_documentation_fragment:
- aws
- ec2
requirements:
- botocore
- boto3
'''
EXAMPLES = '''
# Note: These examples do not set authentication details, see the AWS Guide for details.
# Ensure that the VPC has an Internet Gateway.
# The Internet Gateway ID is can be accessed via {{igw.gateway_id}} for use in setting up NATs etc.
ec2_vpc_igw:
vpc_id: vpc-abcdefgh
state: present
register: igw
'''
RETURN = '''
changed:
description: If any changes have been made to the Internet Gateway.
type: bool
returned: always
sample:
changed: false
gateway_id:
description: The unique identifier for the Internet Gateway.
type: str
returned: I(state=present)
sample:
gateway_id: "igw-XXXXXXXX"
tags:
description: The tags associated the Internet Gateway.
type: dict
returned: I(state=present)
sample:
tags:
"Ansible": "Test"
vpc_id:
description: The VPC ID associated with the Internet Gateway.
type: str
returned: I(state=present)
sample:
vpc_id: "vpc-XXXXXXXX"
'''
try:
import botocore
except ImportError:
pass # caught by AnsibleAWSModule
from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils.aws.waiters import get_waiter
from ansible.module_utils.ec2 import (
AWSRetry,
camel_dict_to_snake_dict,
boto3_tag_list_to_ansible_dict,
ansible_dict_to_boto3_filter_list,
ansible_dict_to_boto3_tag_list,
compare_aws_tags
)
from ansible.module_utils.six import string_types
class AnsibleEc2Igw(object):
def __init__(self, module, results):
self._module = module
self._results = results
self._connection = self._module.client('ec2')
self._check_mode = self._module.check_mode
def process(self):
vpc_id = self._module.params.get('vpc_id')
state = self._module.params.get('state', 'present')
tags = self._module.params.get('tags')
if state == 'present':
self.ensure_igw_present(vpc_id, tags)
elif state == 'absent':
self.ensure_igw_absent(vpc_id)
def get_matching_igw(self, vpc_id):
filters = ansible_dict_to_boto3_filter_list({'attachment.vpc-id': vpc_id})
igws = []
try:
response = self._connection.describe_internet_gateways(Filters=filters)
igws = response.get('InternetGateways', [])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e)
igw = None
if len(igws) > 1:
self._module.fail_json(
msg='EC2 returned more than one Internet Gateway for VPC {0}, aborting'.format(vpc_id))
elif igws:
igw = camel_dict_to_snake_dict(igws[0])
return igw
def check_input_tags(self, tags):
nonstring_tags = [k for k, v in tags.items() if not isinstance(v, string_types)]
if nonstring_tags:
self._module.fail_json(msg='One or more tags contain non-string values: {0}'.format(nonstring_tags))
def ensure_tags(self, igw_id, tags, add_only):
final_tags = []
filters = ansible_dict_to_boto3_filter_list({'resource-id': igw_id, 'resource-type': 'internet-gateway'})
cur_tags = None
try:
cur_tags = self._connection.describe_tags(Filters=filters)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e, msg="Couldn't describe tags")
purge_tags = bool(not add_only)
to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')), tags, purge_tags)
final_tags = boto3_tag_list_to_ansible_dict(cur_tags.get('Tags'))
if to_update:
try:
if self._check_mode:
# update tags
final_tags.update(to_update)
else:
AWSRetry.exponential_backoff()(self._connection.create_tags)(
Resources=[igw_id],
Tags=ansible_dict_to_boto3_tag_list(to_update)
)
self._results['changed'] = True
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e, msg="Couldn't create tags")
if to_delete:
try:
if self._check_mode:
# update tags
for key in to_delete:
del final_tags[key]
else:
tags_list = []
for key in to_delete:
tags_list.append({'Key': key})
AWSRetry.exponential_backoff()(self._connection.delete_tags)(Resources=[igw_id], Tags=tags_list)
self._results['changed'] = True
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e, msg="Couldn't delete tags")
if not self._check_mode and (to_update or to_delete):
try:
response = self._connection.describe_tags(Filters=filters)
final_tags = boto3_tag_list_to_ansible_dict(response.get('Tags'))
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e, msg="Couldn't describe tags")
return final_tags
@staticmethod
def get_igw_info(igw):
return {
'gateway_id': igw['internet_gateway_id'],
'tags': igw['tags'],
'vpc_id': igw['vpc_id']
}
def ensure_igw_absent(self, vpc_id):
igw = self.get_matching_igw(vpc_id)
if igw is None:
return self._results
if self._check_mode:
self._results['changed'] = True
return self._results
try:
self._results['changed'] = True
self._connection.detach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id)
self._connection.delete_internet_gateway(InternetGatewayId=igw['internet_gateway_id'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e, msg="Unable to delete Internet Gateway")
return self._results
def ensure_igw_present(self, vpc_id, tags):
self.check_input_tags(tags)
igw = self.get_matching_igw(vpc_id)
if igw is None:
if self._check_mode:
self._results['changed'] = True
self._results['gateway_id'] = None
return self._results
try:
response = self._connection.create_internet_gateway()
# Ensure the gateway exists before trying to attach it or add tags
waiter = get_waiter(self._connection, 'internet_gateway_exists')
waiter.wait(InternetGatewayIds=[response['InternetGateway']['InternetGatewayId']])
igw = camel_dict_to_snake_dict(response['InternetGateway'])
self._connection.attach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id)
self._results['changed'] = True
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e, msg='Unable to create Internet Gateway')
igw['vpc_id'] = vpc_id
igw['tags'] = self.ensure_tags(igw_id=igw['internet_gateway_id'], tags=tags, add_only=False)
igw_info = self.get_igw_info(igw)
self._results.update(igw_info)
return self._results
def main():
argument_spec = dict(
vpc_id=dict(required=True),
state=dict(default='present', choices=['present', 'absent']),
tags=dict(default=dict(), required=False, type='dict', aliases=['resource_tags'])
)
module = AnsibleAWSModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
results = dict(
changed=False
)
igw_manager = AnsibleEc2Igw(module=module, results=results)
igw_manager.process()
module.exit_json(**results)
if __name__ == '__main__':
main()

@ -1,750 +0,0 @@
#!/usr/bin/python
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: ec2_vpc_route_table
short_description: Manage route tables for AWS virtual private clouds
description:
- Manage route tables for AWS virtual private clouds
version_added: "2.0"
author:
- Robert Estelle (@erydo)
- Rob White (@wimnat)
- Will Thames (@willthames)
options:
lookup:
description: Look up route table by either tags or by route table ID. Non-unique tag lookup will fail.
If no tags are specified then no lookup for an existing route table is performed and a new
route table will be created. To change tags of a route table you must look up by id.
default: tag
choices: [ 'tag', 'id' ]
type: str
propagating_vgw_ids:
description: Enable route propagation from virtual gateways specified by ID.
type: list
elements: str
purge_routes:
version_added: "2.3"
description: Purge existing routes that are not found in routes.
type: bool
default: 'yes'
purge_subnets:
version_added: "2.3"
description: Purge existing subnets that are not found in subnets. Ignored unless the subnets option is supplied.
default: 'true'
type: bool
purge_tags:
version_added: "2.5"
description: Purge existing tags that are not found in route table.
type: bool
default: 'no'
route_table_id:
description:
- The ID of the route table to update or delete.
- Required when I(lookup=id).
type: str
routes:
description: List of routes in the route table.
Routes are specified as dicts containing the keys 'dest' and one of 'gateway_id',
'instance_id', 'network_interface_id', or 'vpc_peering_connection_id'.
If 'gateway_id' is specified, you can refer to the VPC's IGW by using the value 'igw'.
Routes are required for present states.
type: list
elements: dict
state:
description: Create or destroy the VPC route table.
default: present
choices: [ 'present', 'absent' ]
type: str
subnets:
description: An array of subnets to add to this route table. Subnets may be specified
by either subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'.
type: list
elements: str
tags:
description: >
A dictionary of resource tags of the form: C({ tag1: value1, tag2: value2 }). Tags are
used to uniquely identify route tables within a VPC when the route_table_id is not supplied.
aliases: [ "resource_tags" ]
type: dict
vpc_id:
description:
- VPC ID of the VPC in which to create the route table.
- Required when I(state=present) or I(lookup=tag).
type: str
extends_documentation_fragment:
- aws
- ec2
'''
EXAMPLES = '''
# Note: These examples do not set authentication details, see the AWS Guide for details.
# Basic creation example:
- name: Set up public subnet route table
ec2_vpc_route_table:
vpc_id: vpc-1245678
region: us-west-1
tags:
Name: Public
subnets:
- "{{ jumpbox_subnet.subnet.id }}"
- "{{ frontend_subnet.subnet.id }}"
- "{{ vpn_subnet.subnet_id }}"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ igw.gateway_id }}"
register: public_route_table
- name: Set up NAT-protected route table
ec2_vpc_route_table:
vpc_id: vpc-1245678
region: us-west-1
tags:
Name: Internal
subnets:
- "{{ application_subnet.subnet.id }}"
- 'Database Subnet'
- '10.0.0.0/8'
routes:
- dest: 0.0.0.0/0
instance_id: "{{ nat.instance_id }}"
register: nat_route_table
- name: delete route table
ec2_vpc_route_table:
vpc_id: vpc-1245678
region: us-west-1
route_table_id: "{{ route_table.id }}"
lookup: id
state: absent
'''
RETURN = '''
route_table:
description: Route Table result
returned: always
type: complex
contains:
associations:
description: List of subnets associated with the route table
returned: always
type: complex
contains:
main:
description: Whether this is the main route table
returned: always
type: bool
sample: false
route_table_association_id:
description: ID of association between route table and subnet
returned: always
type: str
sample: rtbassoc-ab47cfc3
route_table_id:
description: ID of the route table
returned: always
type: str
sample: rtb-bf779ed7
subnet_id:
description: ID of the subnet
returned: always
type: str
sample: subnet-82055af9
id:
description: ID of the route table (same as route_table_id for backwards compatibility)
returned: always
type: str
sample: rtb-bf779ed7
propagating_vgws:
description: List of Virtual Private Gateways propagating routes
returned: always
type: list
sample: []
route_table_id:
description: ID of the route table
returned: always
type: str
sample: rtb-bf779ed7
routes:
description: List of routes in the route table
returned: always
type: complex
contains:
destination_cidr_block:
description: CIDR block of destination
returned: always
type: str
sample: 10.228.228.0/22
gateway_id:
description: ID of the gateway
returned: when gateway is local or internet gateway
type: str
sample: local
instance_id:
description: ID of a NAT instance
returned: when the route is via an EC2 instance
type: str
sample: i-abcd123456789
instance_owner_id:
description: AWS account owning the NAT instance
returned: when the route is via an EC2 instance
type: str
sample: 123456789012
nat_gateway_id:
description: ID of the NAT gateway
returned: when the route is via a NAT gateway
type: str
sample: local
origin:
description: mechanism through which the route is in the table
returned: always
type: str
sample: CreateRouteTable
state:
description: state of the route
returned: always
type: str
sample: active
tags:
description: Tags applied to the route table
returned: always
type: dict
sample:
Name: Public route table
Public: 'true'
vpc_id:
description: ID for the VPC in which the route lives
returned: always
type: str
sample: vpc-6e2d2407
'''
import re
from time import sleep
from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils.aws.waiters import get_waiter
from ansible.module_utils.ec2 import ansible_dict_to_boto3_filter_list
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, snake_dict_to_camel_dict
from ansible.module_utils.ec2 import ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict
from ansible.module_utils.ec2 import compare_aws_tags, AWSRetry
try:
import botocore
except ImportError:
pass # caught by AnsibleAWSModule
CIDR_RE = re.compile(r'^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$')
SUBNET_RE = re.compile(r'^subnet-[A-z0-9]+$')
ROUTE_TABLE_RE = re.compile(r'^rtb-[A-z0-9]+$')
@AWSRetry.exponential_backoff()
def describe_subnets_with_backoff(connection, **params):
return connection.describe_subnets(**params)['Subnets']
def find_subnets(connection, module, vpc_id, identified_subnets):
"""
Finds a list of subnets, each identified either by a raw ID, a unique
'Name' tag, or a CIDR such as 10.0.0.0/8.
Note that this function is duplicated in other ec2 modules, and should
potentially be moved into a shared module_utils
"""
subnet_ids = []
subnet_names = []
subnet_cidrs = []
for subnet in (identified_subnets or []):
if re.match(SUBNET_RE, subnet):
subnet_ids.append(subnet)
elif re.match(CIDR_RE, subnet):
subnet_cidrs.append(subnet)
else:
subnet_names.append(subnet)
subnets_by_id = []
if subnet_ids:
filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id})
try:
subnets_by_id = describe_subnets_with_backoff(connection, SubnetIds=subnet_ids, Filters=filters)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't find subnet with id %s" % subnet_ids)
subnets_by_cidr = []
if subnet_cidrs:
filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id, 'cidr': subnet_cidrs})
try:
subnets_by_cidr = describe_subnets_with_backoff(connection, Filters=filters)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't find subnet with cidr %s" % subnet_cidrs)
subnets_by_name = []
if subnet_names:
filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id, 'tag:Name': subnet_names})
try:
subnets_by_name = describe_subnets_with_backoff(connection, Filters=filters)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't find subnet with names %s" % subnet_names)
for name in subnet_names:
matching_count = len([1 for s in subnets_by_name for t in s.get('Tags', []) if t['Key'] == 'Name' and t['Value'] == name])
if matching_count == 0:
module.fail_json(msg='Subnet named "{0}" does not exist'.format(name))
elif matching_count > 1:
module.fail_json(msg='Multiple subnets named "{0}"'.format(name))
return subnets_by_id + subnets_by_cidr + subnets_by_name
def find_igw(connection, module, vpc_id):
"""
Finds the Internet gateway for the given VPC ID.
"""
filters = ansible_dict_to_boto3_filter_list({'attachment.vpc-id': vpc_id})
try:
igw = connection.describe_internet_gateways(Filters=filters)['InternetGateways']
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg='No IGW found for VPC {0}'.format(vpc_id))
if len(igw) == 1:
return igw[0]['InternetGatewayId']
elif len(igw) == 0:
module.fail_json(msg='No IGWs found for VPC {0}'.format(vpc_id))
else:
module.fail_json(msg='Multiple IGWs found for VPC {0}'.format(vpc_id))
@AWSRetry.exponential_backoff()
def describe_tags_with_backoff(connection, resource_id):
filters = ansible_dict_to_boto3_filter_list({'resource-id': resource_id})
paginator = connection.get_paginator('describe_tags')
tags = paginator.paginate(Filters=filters).build_full_result()['Tags']
return boto3_tag_list_to_ansible_dict(tags)
def tags_match(match_tags, candidate_tags):
return all((k in candidate_tags and candidate_tags[k] == v
for k, v in match_tags.items()))
def ensure_tags(connection=None, module=None, resource_id=None, tags=None, purge_tags=None, check_mode=None):
try:
cur_tags = describe_tags_with_backoff(connection, resource_id)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg='Unable to list tags for VPC')
to_add, to_delete = compare_aws_tags(cur_tags, tags, purge_tags)
if not to_add and not to_delete:
return {'changed': False, 'tags': cur_tags}
if check_mode:
if not purge_tags:
tags = cur_tags.update(tags)
return {'changed': True, 'tags': tags}
if to_delete:
try:
connection.delete_tags(Resources=[resource_id], Tags=[{'Key': k} for k in to_delete])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't delete tags")
if to_add:
try:
connection.create_tags(Resources=[resource_id], Tags=ansible_dict_to_boto3_tag_list(to_add))
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't create tags")
try:
latest_tags = describe_tags_with_backoff(connection, resource_id)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg='Unable to list tags for VPC')
return {'changed': True, 'tags': latest_tags}
@AWSRetry.exponential_backoff()
def describe_route_tables_with_backoff(connection, **params):
try:
return connection.describe_route_tables(**params)['RouteTables']
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == 'InvalidRouteTableID.NotFound':
return None
else:
raise
def get_route_table_by_id(connection, module, route_table_id):
route_table = None
try:
route_tables = describe_route_tables_with_backoff(connection, RouteTableIds=[route_table_id])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't get route table")
if route_tables:
route_table = route_tables[0]
return route_table
def get_route_table_by_tags(connection, module, vpc_id, tags):
count = 0
route_table = None
filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id})
try:
route_tables = describe_route_tables_with_backoff(connection, Filters=filters)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't get route table")
for table in route_tables:
this_tags = describe_tags_with_backoff(connection, table['RouteTableId'])
if tags_match(tags, this_tags):
route_table = table
count += 1
if count > 1:
module.fail_json(msg="Tags provided do not identify a unique route table")
else:
return route_table
def route_spec_matches_route(route_spec, route):
if route_spec.get('GatewayId') and 'nat-' in route_spec['GatewayId']:
route_spec['NatGatewayId'] = route_spec.pop('GatewayId')
if route_spec.get('GatewayId') and 'vpce-' in route_spec['GatewayId']:
if route_spec.get('DestinationCidrBlock', '').startswith('pl-'):
route_spec['DestinationPrefixListId'] = route_spec.pop('DestinationCidrBlock')
return set(route_spec.items()).issubset(route.items())
def route_spec_matches_route_cidr(route_spec, route):
return route_spec['DestinationCidrBlock'] == route.get('DestinationCidrBlock')
def rename_key(d, old_key, new_key):
d[new_key] = d.pop(old_key)
def index_of_matching_route(route_spec, routes_to_match):
for i, route in enumerate(routes_to_match):
if route_spec_matches_route(route_spec, route):
return "exact", i
elif 'Origin' in route_spec and route_spec['Origin'] != 'EnableVgwRoutePropagation':
if route_spec_matches_route_cidr(route_spec, route):
return "replace", i
def ensure_routes(connection=None, module=None, route_table=None, route_specs=None,
propagating_vgw_ids=None, check_mode=None, purge_routes=None):
routes_to_match = [route for route in route_table['Routes']]
route_specs_to_create = []
route_specs_to_recreate = []
for route_spec in route_specs:
match = index_of_matching_route(route_spec, routes_to_match)
if match is None:
if route_spec.get('DestinationCidrBlock'):
route_specs_to_create.append(route_spec)
else:
module.warn("Skipping creating {0} because it has no destination cidr block. "
"To add VPC endpoints to route tables use the ec2_vpc_endpoint module.".format(route_spec))
else:
if match[0] == "replace":
if route_spec.get('DestinationCidrBlock'):
route_specs_to_recreate.append(route_spec)
else:
module.warn("Skipping recreating route {0} because it has no destination cidr block.".format(route_spec))
del routes_to_match[match[1]]
routes_to_delete = []
if purge_routes:
for r in routes_to_match:
if not r.get('DestinationCidrBlock'):
module.warn("Skipping purging route {0} because it has no destination cidr block. "
"To remove VPC endpoints from route tables use the ec2_vpc_endpoint module.".format(r))
continue
if r['Origin'] == 'CreateRoute':
routes_to_delete.append(r)
changed = bool(routes_to_delete or route_specs_to_create or route_specs_to_recreate)
if changed and not check_mode:
for route in routes_to_delete:
try:
connection.delete_route(RouteTableId=route_table['RouteTableId'], DestinationCidrBlock=route['DestinationCidrBlock'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't delete route")
for route_spec in route_specs_to_recreate:
try:
connection.replace_route(RouteTableId=route_table['RouteTableId'],
**route_spec)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't recreate route")
for route_spec in route_specs_to_create:
try:
connection.create_route(RouteTableId=route_table['RouteTableId'],
**route_spec)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't create route")
return {'changed': bool(changed)}
def ensure_subnet_association(connection=None, module=None, vpc_id=None, route_table_id=None, subnet_id=None,
check_mode=None):
filters = ansible_dict_to_boto3_filter_list({'association.subnet-id': subnet_id, 'vpc-id': vpc_id})
try:
route_tables = describe_route_tables_with_backoff(connection, Filters=filters)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't get route tables")
for route_table in route_tables:
if route_table['RouteTableId'] is None:
continue
for a in route_table['Associations']:
if a['Main']:
continue
if a['SubnetId'] == subnet_id:
if route_table['RouteTableId'] == route_table_id:
return {'changed': False, 'association_id': a['RouteTableAssociationId']}
else:
if check_mode:
return {'changed': True}
try:
connection.disassociate_route_table(AssociationId=a['RouteTableAssociationId'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't disassociate subnet from route table")
try:
association_id = connection.associate_route_table(RouteTableId=route_table_id, SubnetId=subnet_id)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't associate subnet with route table")
return {'changed': True, 'association_id': association_id}
def ensure_subnet_associations(connection=None, module=None, route_table=None, subnets=None,
check_mode=None, purge_subnets=None):
current_association_ids = [a['RouteTableAssociationId'] for a in route_table['Associations'] if not a['Main']]
new_association_ids = []
changed = False
for subnet in subnets:
result = ensure_subnet_association(connection=connection, module=module, vpc_id=route_table['VpcId'],
route_table_id=route_table['RouteTableId'], subnet_id=subnet['SubnetId'], check_mode=check_mode)
changed = changed or result['changed']
if changed and check_mode:
return {'changed': True}
new_association_ids.append(result['association_id'])
if purge_subnets:
to_delete = [a_id for a_id in current_association_ids
if a_id not in new_association_ids]
for a_id in to_delete:
changed = True
if not check_mode:
try:
connection.disassociate_route_table(AssociationId=a_id)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't disassociate subnet from route table")
return {'changed': changed}
def ensure_propagation(connection=None, module=None, route_table=None, propagating_vgw_ids=None,
check_mode=None):
changed = False
gateways = [gateway['GatewayId'] for gateway in route_table['PropagatingVgws']]
to_add = set(propagating_vgw_ids) - set(gateways)
if to_add:
changed = True
if not check_mode:
for vgw_id in to_add:
try:
connection.enable_vgw_route_propagation(RouteTableId=route_table['RouteTableId'],
GatewayId=vgw_id)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't enable route propagation")
return {'changed': changed}
def ensure_route_table_absent(connection, module):
lookup = module.params.get('lookup')
route_table_id = module.params.get('route_table_id')
tags = module.params.get('tags')
vpc_id = module.params.get('vpc_id')
purge_subnets = module.params.get('purge_subnets')
if lookup == 'tag':
if tags is not None:
route_table = get_route_table_by_tags(connection, module, vpc_id, tags)
else:
route_table = None
elif lookup == 'id':
route_table = get_route_table_by_id(connection, module, route_table_id)
if route_table is None:
return {'changed': False}
# disassociate subnets before deleting route table
if not module.check_mode:
ensure_subnet_associations(connection=connection, module=module, route_table=route_table,
subnets=[], check_mode=False, purge_subnets=purge_subnets)
try:
connection.delete_route_table(RouteTableId=route_table['RouteTableId'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Error deleting route table")
return {'changed': True}
def get_route_table_info(connection, module, route_table):
result = get_route_table_by_id(connection, module, route_table['RouteTableId'])
try:
result['Tags'] = describe_tags_with_backoff(connection, route_table['RouteTableId'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't get tags for route table")
result = camel_dict_to_snake_dict(result, ignore_list=['Tags'])
# backwards compatibility
result['id'] = result['route_table_id']
return result
def create_route_spec(connection, module, vpc_id):
routes = module.params.get('routes')
for route_spec in routes:
rename_key(route_spec, 'dest', 'destination_cidr_block')
if route_spec.get('gateway_id') and route_spec['gateway_id'].lower() == 'igw':
igw = find_igw(connection, module, vpc_id)
route_spec['gateway_id'] = igw
if route_spec.get('gateway_id') and route_spec['gateway_id'].startswith('nat-'):
rename_key(route_spec, 'gateway_id', 'nat_gateway_id')
return snake_dict_to_camel_dict(routes, capitalize_first=True)
def ensure_route_table_present(connection, module):
lookup = module.params.get('lookup')
propagating_vgw_ids = module.params.get('propagating_vgw_ids')
purge_routes = module.params.get('purge_routes')
purge_subnets = module.params.get('purge_subnets')
purge_tags = module.params.get('purge_tags')
route_table_id = module.params.get('route_table_id')
subnets = module.params.get('subnets')
tags = module.params.get('tags')
vpc_id = module.params.get('vpc_id')
routes = create_route_spec(connection, module, vpc_id)
changed = False
tags_valid = False
if lookup == 'tag':
if tags is not None:
try:
route_table = get_route_table_by_tags(connection, module, vpc_id, tags)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Error finding route table with lookup 'tag'")
else:
route_table = None
elif lookup == 'id':
try:
route_table = get_route_table_by_id(connection, module, route_table_id)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Error finding route table with lookup 'id'")
# If no route table returned then create new route table
if route_table is None:
changed = True
if not module.check_mode:
try:
route_table = connection.create_route_table(VpcId=vpc_id)['RouteTable']
# try to wait for route table to be present before moving on
get_waiter(
connection, 'route_table_exists'
).wait(
RouteTableIds=[route_table['RouteTableId']],
)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Error creating route table")
else:
route_table = {"id": "rtb-xxxxxxxx", "route_table_id": "rtb-xxxxxxxx", "vpc_id": vpc_id}
module.exit_json(changed=changed, route_table=route_table)
if routes is not None:
result = ensure_routes(connection=connection, module=module, route_table=route_table,
route_specs=routes, propagating_vgw_ids=propagating_vgw_ids,
check_mode=module.check_mode, purge_routes=purge_routes)
changed = changed or result['changed']
if propagating_vgw_ids is not None:
result = ensure_propagation(connection=connection, module=module, route_table=route_table,
propagating_vgw_ids=propagating_vgw_ids, check_mode=module.check_mode)
changed = changed or result['changed']
if not tags_valid and tags is not None:
result = ensure_tags(connection=connection, module=module, resource_id=route_table['RouteTableId'], tags=tags,
purge_tags=purge_tags, check_mode=module.check_mode)
route_table['Tags'] = result['tags']
changed = changed or result['changed']
if subnets is not None:
associated_subnets = find_subnets(connection, module, vpc_id, subnets)
result = ensure_subnet_associations(connection=connection, module=module, route_table=route_table,
subnets=associated_subnets, check_mode=module.check_mode,
purge_subnets=purge_subnets)
changed = changed or result['changed']
if changed:
# pause to allow route table routes/subnets/associations to be updated before exiting with final state
sleep(5)
module.exit_json(changed=changed, route_table=get_route_table_info(connection, module, route_table))
def main():
argument_spec = dict(
lookup=dict(default='tag', choices=['tag', 'id']),
propagating_vgw_ids=dict(type='list'),
purge_routes=dict(default=True, type='bool'),
purge_subnets=dict(default=True, type='bool'),
purge_tags=dict(default=False, type='bool'),
route_table_id=dict(),
routes=dict(default=[], type='list'),
state=dict(default='present', choices=['present', 'absent']),
subnets=dict(type='list'),
tags=dict(type='dict', aliases=['resource_tags']),
vpc_id=dict()
)
module = AnsibleAWSModule(argument_spec=argument_spec,
required_if=[['lookup', 'id', ['route_table_id']],
['lookup', 'tag', ['vpc_id']],
['state', 'present', ['vpc_id']]],
supports_check_mode=True)
connection = module.client('ec2')
state = module.params.get('state')
if state == 'present':
result = ensure_route_table_present(connection, module)
elif state == 'absent':
result = ensure_route_table_absent(connection, module)
module.exit_json(**result)
if __name__ == '__main__':
main()
Loading…
Cancel
Save