From b36968bf94aff9d71c272ce5f22956919b7f0ec5 Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Wed, 18 Dec 2024 08:47:50 +0100 Subject: [PATCH] inventory/aws_ec2 - support jinja2 filters in hostnames option --- ...support-jinja2-in-hostnames-variables.yaml | 3 + plugins/inventory/aws_ec2.py | 39 +++++++++-- ...ory_with_hostnames_with_jinja2_filters.yml | 65 +++++++++++++++++++ .../targets/inventory_aws_ec2/runme.sh | 12 ++++ ..._with_hostnames_with_jinja2_filters.yml.j2 | 17 +++++ 5 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/20241217-inventory-aws_ec2-support-jinja2-in-hostnames-variables.yaml create mode 100644 tests/integration/targets/inventory_aws_ec2/playbooks/test_populating_inventory_with_hostnames_with_jinja2_filters.yml create mode 100644 tests/integration/targets/inventory_aws_ec2/templates/inventory_with_hostnames_with_jinja2_filters.yml.j2 diff --git a/changelogs/fragments/20241217-inventory-aws_ec2-support-jinja2-in-hostnames-variables.yaml b/changelogs/fragments/20241217-inventory-aws_ec2-support-jinja2-in-hostnames-variables.yaml new file mode 100644 index 00000000000..65a4d6278b7 --- /dev/null +++ b/changelogs/fragments/20241217-inventory-aws_ec2-support-jinja2-in-hostnames-variables.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - inventory/aws_ec2 - Support jinja2 expression in ``hostnames`` variable(https://github.com/ansible-collections/amazon.aws/issues/2402). \ No newline at end of file diff --git a/plugins/inventory/aws_ec2.py b/plugins/inventory/aws_ec2.py index d74d8dd1ade..b8d120f81e0 100644 --- a/plugins/inventory/aws_ec2.py +++ b/plugins/inventory/aws_ec2.py @@ -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: [] @@ -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 @@ -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() @@ -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: @@ -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): diff --git a/tests/integration/targets/inventory_aws_ec2/playbooks/test_populating_inventory_with_hostnames_with_jinja2_filters.yml b/tests/integration/targets/inventory_aws_ec2/playbooks/test_populating_inventory_with_hostnames_with_jinja2_filters.yml new file mode 100644 index 00000000000..8fee67973f7 --- /dev/null +++ b/tests/integration/targets/inventory_aws_ec2/playbooks/test_populating_inventory_with_hostnames_with_jinja2_filters.yml @@ -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) diff --git a/tests/integration/targets/inventory_aws_ec2/runme.sh b/tests/integration/targets/inventory_aws_ec2/runme.sh index 7fd48ef5b5c..aa2c6a2e867 100755 --- a/tests/integration/targets/inventory_aws_ec2/runme.sh +++ b/tests/integration/targets/inventory_aws_ec2/runme.sh @@ -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/ diff --git a/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_hostnames_with_jinja2_filters.yml.j2 b/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_hostnames_with_jinja2_filters.yml.j2 new file mode 100644 index 00000000000..71203b38d61 --- /dev/null +++ b/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_hostnames_with_jinja2_filters.yml.j2 @@ -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 %} \ No newline at end of file