mirror of https://github.com/ansible/ansible.git
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 modulespull/72138/head
parent
9ffa84cc1c
commit
960e4c0809
@ -0,0 +1 @@
|
|||||||
|
shippable/posix/group1
|
@ -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,2 +0,0 @@
|
|||||||
dependencies:
|
|
||||||
- incidental_setup_ec2
|
|
@ -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,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…
Reference in New Issue