Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aws_ec2 inventory: add includes_filters and excludes_filters #328

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- "aws_ec2 inventory - expose to new keys called ``include_filters`` and ``exclude_filters`` to give the user the ability to compose an inventory with multiple queries (https://github.com/ansible-collections/amazon.aws/pull/328)."
66 changes: 59 additions & 7 deletions plugins/inventory/aws_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@
- Available filters are listed here U(http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html#options).
type: dict
default: {}
include_filters:
description:
- A list of filters. Any instances matching at least one of the filters are included in the result.
- Available filters are listed here U(http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html#options).
- Every entry in this list triggers a search query. As such, from a performance point of view, it's better to
keep the list as short as possible.
type: list
default: []
exclude_filters:
description:
- A list of filters. Any instances matching one of the filters are excluded from the result.
tremble marked this conversation as resolved.
Show resolved Hide resolved
- The filters from C(exclude_filters) take priority over the C(include_filters) and C(filters) keys
- Available filters are listed here U(http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html#options).
- Every entry in this list triggers a search query. As such, from a performance point of view, it's better to
keep the list as short as possible.
type: list
default: []
include_extra_api_calls:
description:
- Add two additional API calls for every instance to include 'persistent' and 'events' host variables.
Expand Down Expand Up @@ -151,6 +168,20 @@
# Use the private IP address to connect to the host
# (note: this does not modify inventory_hostname, which is set via I(hostnames))
ansible_host: private_ip_address

# Example using include_filters and exclude_filters to compose the inventory.
plugin: aws_ec2
regions:
- us-east-1
- us-west-1
include_filters:
- tag:Name:
- 'my_second_tag'
- tag:Name:
- 'my_third_tag'
exclude_filters:
- tag:Name:
- 'my_first_tag'
'''

import re
Expand Down Expand Up @@ -456,7 +487,7 @@ def _get_instances_by_region(self, regions, filters, strict_permissions):

all_instances.extend(instances)

return sorted(all_instances, key=lambda x: x['InstanceId'])
return all_instances
tremble marked this conversation as resolved.
Show resolved Hide resolved

def _get_reservation_details(self, reservation):
return {
Expand Down Expand Up @@ -537,14 +568,34 @@ def _get_hostname(self, instance, hostnames):
else:
return to_text(hostname)

def _query(self, regions, filters, strict_permissions):
def _query(self, regions, include_filters, exclude_filters, strict_permissions):
'''
:param regions: a list of regions to query
:param filters: a list of boto3 filter dictionaries
:param hostnames: a list of hostname destination variables in order of preference
:param include_filters: a list of boto3 filter dictionaries
:param exclude_filters: a list of boto3 filter dictionaries
:param strict_permissions: a boolean determining whether to fail or ignore 403 error codes

'''
return {'aws_ec2': self._get_instances_by_region(regions, filters, strict_permissions)}
instances = []
ids_to_ignore = []
for filter in exclude_filters:
for i in self._get_instances_by_region(
regions,
ansible_dict_to_boto3_filter_list(filter),
strict_permissions):
ids_to_ignore.append(i['InstanceId'])
for filter in include_filters:
for i in self._get_instances_by_region(
regions,
ansible_dict_to_boto3_filter_list(filter),
strict_permissions):
if i['InstanceId'] not in ids_to_ignore:
instances.append(i)
goneri marked this conversation as resolved.
Show resolved Hide resolved
ids_to_ignore.append(i['InstanceId'])

instances = sorted(instances, key=lambda x: x['InstanceId'])

return {'aws_ec2': instances}

def _populate(self, groups, hostnames):
for group in groups:
Expand Down Expand Up @@ -649,7 +700,8 @@ def parse(self, inventory, loader, path, cache=True):

# get user specifications
regions = self.get_option('regions')
filters = ansible_dict_to_boto3_filter_list(self.get_option('filters'))
include_filters = [self.get_option('filters')] + self.get_option('include_filters')
exclude_filters = self.get_option('exclude_filters')
tremble marked this conversation as resolved.
Show resolved Hide resolved
hostnames = self.get_option('hostnames')
strict_permissions = self.get_option('strict_permissions')

Expand All @@ -669,7 +721,7 @@ def parse(self, inventory, loader, path, cache=True):
cache_needs_update = True

if not cache or cache_needs_update:
results = self._query(regions, filters, strict_permissions)
results = self._query(regions, include_filters, exclude_filters, strict_permissions)

self._populate(results, hostnames)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
- hosts: 127.0.0.1
connection: local
gather_facts: no
environment: "{{ ansible_test.environment }}"
tasks:

- module_defaults:
group/aws:
aws_access_key: '{{ aws_access_key }}'
aws_secret_key: '{{ aws_secret_key }}'
security_token: '{{ security_token | default(omit) }}'
region: '{{ aws_region }}'
block:

# Create VPC, subnet, security group, and find image_id to create instance

- include_tasks: setup.yml

# Create new host, refresh inventory
- name: create a new host (1/3)
ec2:
tremble marked this conversation as resolved.
Show resolved Hide resolved
image: '{{ image_id }}'
exact_count: 1
count_tag:
Name: '{{ resource_prefix }}_1'
instance_tags:
Name: '{{ resource_prefix }}_1'
tag_instance1: foo
instance_type: t2.micro
wait: yes
group_id: '{{ sg_id }}'
vpc_subnet_id: '{{ subnet_id }}'
register: setup_instance_1

- name: create a new host (2/3)
ec2:
image: '{{ image_id }}'
exact_count: 1
count_tag:
Name: '{{ resource_prefix }}_2'
instance_tags:
Name: '{{ resource_prefix }}_2'
tag_instance2: bar
instance_type: t2.micro
wait: yes
group_id: '{{ sg_id }}'
vpc_subnet_id: '{{ subnet_id }}'
register: setup_instance_2

- name: create a new host (3/3)
ec2:
image: '{{ image_id }}'
exact_count: 1
count_tag:
Name: '{{ resource_prefix }}_3'
instance_tags:
Name: '{{ resource_prefix }}_3'
tag_instance2: bar
instance_type: t2.micro
wait: yes
group_id: '{{ sg_id }}'
vpc_subnet_id: '{{ subnet_id }}'
register: setup_instance_3

- meta: refresh_inventory

- name: assert the keyed groups and groups from constructed config were added to inventory and composite var added to hostvars
assert:
that:
# There are 9 groups: all, ungrouped, aws_ec2, sg keyed group, 3 tag keyed group (one per tag), arch keyed group, constructed group
- "groups['all'] | length == 2"
- "'{{ resource_prefix }}_1' in groups['all']"
- "'{{ resource_prefix }}_2' in groups['all']"
- "not ('{{ resource_prefix }}_3' in groups['all'])"

always:

- name: remove setup ec2 instance (1/3)
ec2:
instance_type: t2.micro
instance_ids: '{{ setup_instance_1.instance_ids }}'
state: absent
wait: yes
instance_tags:
Name: '{{ resource_prefix }}'
group_id: "{{ sg_id }}"
vpc_subnet_id: "{{ subnet_id }}"
ignore_errors: yes
when: setup_instance_1 is defined

- name: remove setup ec2 instance (2/3)
ec2:
instance_type: t2.micro
instance_ids: '{{ setup_instance_2.instance_ids }}'
state: absent
wait: yes
instance_tags:
Name: '{{ resource_prefix }}'
group_id: "{{ sg_id }}"
vpc_subnet_id: "{{ subnet_id }}"
ignore_errors: yes
when: setup_instance_2 is defined

- name: remove setup ec2 instance (3/3)
ec2:
instance_type: t2.micro
instance_ids: '{{ setup_instance_3.instance_ids }}'
state: absent
wait: yes
instance_tags:
Name: '{{ resource_prefix }}'
group_id: "{{ sg_id }}"
vpc_subnet_id: "{{ subnet_id }}"
ignore_errors: yes
when: setup_instance_3 is defined

- include_tasks: tear_down.yml
3 changes: 3 additions & 0 deletions tests/integration/targets/inventory_aws_ec2/runme.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ ansible-playbook playbooks/create_inventory_config.yml -e "template='inventory_w
ansible-playbook playbooks/test_populating_inventory_with_constructed.yml "$@"
ansible-playbook playbooks/create_inventory_config.yml -e "template='inventory_with_concatenation.yml.j2'" "$@"
ansible-playbook playbooks/test_populating_inventory_with_concatenation.yml "$@"
# generate inventory config with includes_entries_matching and prepare the tests
ansible-playbook playbooks/create_inventory_config.yml -e "template='inventory_with_include_or_exclude_filters.yml.j2'" "$@"
ansible-playbook playbooks/test_populating_inventory_with_include_or_exclude_filters.yml "$@"

# cleanup inventory config
ansible-playbook playbooks/empty_inventory_config.yml "$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugin: amazon.aws.aws_ec2
aws_access_key_id: '{{ aws_access_key }}'
aws_secret_access_key: '{{ aws_secret_key }}'
{% if security_token | default(false) %}
aws_security_token: '{{ security_token }}'
{% endif %}
regions:
- '{{ aws_region }}'
filters:
tag:Name:
- '{{ resource_prefix }}_1'
- '{{ resource_prefix }}_3'
include_filters:
- tag:Name:
- '{{ resource_prefix }}_2'
- '{{ resource_prefix }}_4'
exclude_filters:
- tag:Name:
- '{{ resource_prefix }}_3'
- '{{ resource_prefix }}_4'
hostnames:
- tag:Name
- dns-name