Skip to content

Commit

Permalink
inventory/aws_ec2 - support jinja2 filters in hostnames option (#2427) (
Browse files Browse the repository at this point in the history
#2456)

This is a backport of PR #2427 as merged into main (995bf94).
SUMMARY

Closes #2402

ISSUE TYPE


Feature Pull Request

COMPONENT NAME

inventory/aws_ec2

Reviewed-by: Helen Bailey <[email protected]>
Reviewed-by: Bikouo Aubin
  • Loading branch information
patchback[bot] authored Jan 14, 2025
1 parent 52ad905 commit a60cf3c
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- inventory/aws_ec2 - Support jinja2 expression in ``hostnames`` variable(https://github.com/ansible-collections/amazon.aws/issues/2402).
39 changes: 32 additions & 7 deletions plugins/inventory/aws_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- Can be one of the options specified in U(http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html#options).
- If value provided does not exist in the above options, it will be used as a literal string.
- To use tags as hostnames use the syntax tag:Name=Value to use the hostname Name_Value, or tag:Name to use the value of the Name tag.
- Jinja2 filters can be added to the hostnames string. Added in version 9.2.0.
type: list
elements: raw
default: []
Expand Down Expand Up @@ -268,6 +269,15 @@
- us-east-1
hostvars_prefix: 'aws_'
hostvars_suffix: '_ec2'
---
# Define hostnames variables with jinja2 filters.
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
hostnames:
- "tag:Name | replace('test', 'prod')"
"""

import re
Expand Down Expand Up @@ -549,6 +559,26 @@ def _sanitize_hostname(self, hostname):
else:
return to_text(hostname)

def _get_hostname_with_jinja2_filter(self, instance, preference, return_single_hostname=False):
jinja2_filter = None
is_template = False
if "|" in preference:
preference, jinja2_filter = preference.split("|", maxsplit=1)
preference = preference.rstrip()
is_template = True
if preference.startswith("tag:"):
hostname = _get_tag_hostname(preference, instance)
else:
hostname = _get_boto_attr_chain(preference, instance)
if is_template:
template_var = "{{'%s'|%s}}" % (hostname, jinja2_filter)
if isinstance(hostname, list):
template_var = "{{%s|%s}}" % (hostname, jinja2_filter)
hostname = self.templar.template(variable=template_var, disable_lookups=False)
if isinstance(hostname, list) and return_single_hostname:
hostname = hostname[0] if hostname else None
return hostname

def _get_preferred_hostname(self, instance, hostnames):
"""
:param instance: an instance dict returned by boto3 ec2 describe_instances()
Expand All @@ -570,11 +600,8 @@ def _get_preferred_hostname(self, instance, hostnames):
separator = preference.get("separator", "_")
if hostname and hostname_from_prefix and "prefix" in preference:
hostname = hostname_from_prefix + separator + hostname
elif preference.startswith("tag:"):
tags = _get_tag_hostname(preference, instance)
hostname = tags[0] if tags else None
else:
hostname = _get_boto_attr_chain(preference, instance)
hostname = self._get_hostname_with_jinja2_filter(instance, preference, return_single_hostname=True)
if hostname:
break
if hostname:
Expand Down Expand Up @@ -602,10 +629,8 @@ def _get_all_hostnames(self, instance, hostnames):
separator = preference.get("separator", "_")
if hostname and hostname_from_prefix and "prefix" in preference:
hostname = hostname_from_prefix[0] + separator + hostname[0]
elif preference.startswith("tag:"):
hostname = _get_tag_hostname(preference, instance)
else:
hostname = _get_boto_attr_chain(preference, instance)
hostname = self._get_hostname_with_jinja2_filter(instance, preference)

if hostname:
if isinstance(hostname, list):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
- hosts: 127.0.0.1
connection: local
gather_facts: false
environment: "{{ ansible_test.environment }}"
tasks:
- module_defaults:
group/aws:
access_key: "{{ aws_access_key }}"
secret_key: "{{ aws_secret_key }}"
session_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region }}"
block:
# Create VPC, subnet, security group, and find image_id to create instance
- name: Setup EC2 network
ansible.builtin.include_tasks: tasks/setup.yml

- name: Create a new host
amazon.aws.ec2_instance:
image_id: "{{ image_id }}"
name: "{{ resource_prefix }}"
tags:
Tag1: tag1.test-ansible
Tag2: tag2.test-ansible
purge_tags: true
instance_type: t2.micro
security_groups: "{{ sg_id }}"
vpc_subnet_id: "{{ subnet_id }}"
wait: false
register: setup_instance

# refresh inventory
- ansible.builtin.meta: refresh_inventory

- name: Display ansible hostvars variable
ansible.builtin.debug:
var: hostvars

- name: Assert that hostvars contain multiple hostnames (hostnames with multiple tags and allow_duplicated_hosts=true)
ansible.builtin.assert:
that:
- hostvars.keys() | length == 2
- '"tag1.prod-Ansible" in hostvars'
- '"tag2.prod-Ansible" in hostvars'
when:
- search_multiple_tags | default(false) | bool
- (allow_duplicated_hosts | default(false) | bool)

- name: Assert that hostvars contain only 1 hostname (hostnames with multiple tags and allow_duplicated_hosts=false)
ansible.builtin.assert:
that:
- hostvars.keys() | length == 1
- '"tag1.prod-Ansible" in hostvars'
when:
- search_multiple_tags | default(false) | bool
- not (allow_duplicated_hosts | default(false) | bool)

- name: Assert that hostvars contain only 1 hostname (hostnames with single tag)
ansible.builtin.assert:
that:
- hostvars.keys() | length == 1
- '"TAG1.PROD-ANSIBLE" in hostvars'
when:
- not (search_multiple_tags | default(false) | bool)
- not (allow_duplicated_hosts | default(false) | bool)
12 changes: 12 additions & 0 deletions tests/integration/targets/inventory_aws_ec2/runme.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ ansible-playbook playbooks/test_inventory_cache.yml "$@"
ansible-playbook playbooks/create_inventory_config.yml -e "template='inventory_with_ssm.yml.j2'" "$@"
ansible-playbook playbooks/test_inventory_ssm.yml "$@"

# generate inventory config with hostnames containing multiple tags and jinja2 filters (allow_duplicated_hosts=False)
ansible-playbook playbooks/create_inventory_config.yml -e "template='inventory_with_hostnames_with_jinja2_filters.yml.j2'" -e "search_multiple_tags=true" "$@"
ansible-playbook playbooks/test_populating_inventory_with_hostnames_with_jinja2_filters.yml -e "search_multiple_tags=true" "$@"

# generate inventory config with hostnames containing multiple tags and jinja2 filters (allow_duplicated_hosts=True)
ansible-playbook playbooks/create_inventory_config.yml -e "template='inventory_with_hostnames_with_jinja2_filters.yml.j2'" -e "allow_duplicated_hosts=true" -e "search_multiple_tags=true" "$@"
ansible-playbook playbooks/test_populating_inventory_with_hostnames_with_jinja2_filters.yml -e "allow_duplicated_hosts=true" -e "search_multiple_tags=true" "$@"

# generate inventory config with hostnames containing single tag and jinja2 filters
ansible-playbook playbooks/create_inventory_config.yml -e "template='inventory_with_hostnames_with_jinja2_filters.yml.j2'" "$@"
ansible-playbook playbooks/test_populating_inventory_with_hostnames_with_jinja2_filters.yml "$@"

# remove inventory cache
rm -r aws_ec2_cache_dir/

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugin: amazon.aws.aws_ec2
access_key: '{{ aws_access_key }}'
secret_key: '{{ aws_secret_key }}'
{% if security_token | default(false) %}
session_token: '{{ security_token }}'
{% endif %}
{% if allow_duplicated_hosts | default(false) %}
allow_duplicated_hosts: True
{% endif %}
regions:
- '{{ aws_region }}'
hostnames:
{% if search_multiple_tags | default(false) %}
- "tag:Tag1,Tag2 | replace('test', 'prod') | title()"
{% else %}
- "tag:Tag1 | replace('test', 'prod') | upper()"
{% endif %}

0 comments on commit a60cf3c

Please sign in to comment.