From 9fd9bda042baed7670bf6b494585efa7cb3f8ca8 Mon Sep 17 00:00:00 2001 From: Artem Timchenko Date: Mon, 10 Jul 2023 13:58:10 +0300 Subject: [PATCH 1/3] 1550: add az id as hostvar --- .../fragments/1550-az-id-as-hostvar.yml | 3 ++ plugins/inventory/aws_ec2.py | 33 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/1550-az-id-as-hostvar.yml diff --git a/changelogs/fragments/1550-az-id-as-hostvar.yml b/changelogs/fragments/1550-az-id-as-hostvar.yml new file mode 100644 index 00000000000..e5064e713ab --- /dev/null +++ b/changelogs/fragments/1550-az-id-as-hostvar.yml @@ -0,0 +1,3 @@ +--- +minor_changes: +- inventory aws ec2 - add AZ id to hostvars () diff --git a/plugins/inventory/aws_ec2.py b/plugins/inventory/aws_ec2.py index 6de182bd59e..c6ba960ecaf 100644 --- a/plugins/inventory/aws_ec2.py +++ b/plugins/inventory/aws_ec2.py @@ -408,6 +408,7 @@ def _get_tag_hostname(preference, instance): def _prepare_host_vars( original_host_vars, + availability_zone_ids, hostvars_prefix=None, hostvars_suffix=None, use_contrib_script_compatible_ec2_tag_keys=False, @@ -415,8 +416,9 @@ def _prepare_host_vars( host_vars = camel_dict_to_snake_dict(original_host_vars, ignore_list=["Tags"]) host_vars["tags"] = boto3_tag_list_to_ansible_dict(original_host_vars.get("Tags", [])) - # Allow easier grouping by region + # Allow easier grouping by region or by AZ id host_vars["placement"]["region"] = host_vars["placement"]["availability_zone"][:-1] + host_vars["placement"]["availability_zone_id"] = availability_zone_ids.get(host_vars["placement"]["availability_zone"]) if use_contrib_script_compatible_ec2_tag_keys: for k, v in host_vars["tags"].items(): @@ -540,6 +542,26 @@ def _get_instances_by_region(self, regions, filters, strict_permissions): return all_instances + def _get_availability_zones_id(self, strict_permissions): + """ + :param strict_permissions: a boolean determining whether to fail or ignore 403 error codes + :return a dictionary of az_name -> az_id mappings + """ + availability_zones = {} + + for connection, _region in self.all_clients("ec2"): + try: + for availability_zone in connection.describe_availability_zones().get('AvailabilityZones'): + availability_zones[availability_zone['ZoneName']] = availability_zone['ZoneId'] + except is_boto3_error_code("UnauthorizedOperation") as e: + if not strict_permissions: + continue + self.fail_aws("Failed to describe availability zones", exception=e) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + self.fail_aws("Failed to describe availability zones", exception=e) + + return availability_zones + def _sanitize_hostname(self, hostname): if ":" in to_text(hostname): return self._sanitize_group_name(to_text(hostname)) @@ -663,6 +685,7 @@ def _populate( self, groups, hostnames, + availability_zone_ids, allow_duplicated_hosts=False, hostvars_prefix=None, hostvars_suffix=None, @@ -674,6 +697,7 @@ def _populate( hosts=groups[group], group=group, hostnames=hostnames, + availability_zone_ids=availability_zone_ids, allow_duplicated_hosts=allow_duplicated_hosts, hostvars_prefix=hostvars_prefix, hostvars_suffix=hostvars_suffix, @@ -685,6 +709,7 @@ def iter_entry( self, hosts, hostnames, + availability_zone_ids, allow_duplicated_hosts=False, hostvars_prefix=None, hostvars_suffix=None, @@ -700,6 +725,7 @@ def iter_entry( host_vars = _prepare_host_vars( host, + availability_zone_ids, hostvars_prefix, hostvars_suffix, use_contrib_script_compatible_ec2_tag_keys, @@ -712,6 +738,7 @@ def _add_hosts( hosts, group, hostnames, + availability_zone_ids, allow_duplicated_hosts=False, hostvars_prefix=None, hostvars_suffix=None, @@ -721,6 +748,7 @@ def _add_hosts( :param hosts: a list of hosts to be added to a group :param group: the name of the group to which the hosts belong :param hostnames: a list of hostname destination variables in order of preference + :param availability_zone_ids: a dictionary of az_name -> az_id mappings :param bool allow_duplicated_hosts: if true, accept same host with different names :param str hostvars_prefix: starts the hostvars variable name with this prefix :param str hostvars_suffix: ends the hostvars variable name with this suffix @@ -730,6 +758,7 @@ def _add_hosts( for name, host_vars in self.iter_entry( hosts, hostnames, + availability_zone_ids, allow_duplicated_hosts=allow_duplicated_hosts, hostvars_prefix=hostvars_prefix, hostvars_suffix=hostvars_suffix, @@ -771,6 +800,7 @@ def parse(self, inventory, loader, path, cache=True): hostnames = self.get_option("hostnames") strict_permissions = self.get_option("strict_permissions") allow_duplicated_hosts = self.get_option("allow_duplicated_hosts") + availability_zone_ids = self._get_availability_zones_id(strict_permissions) hostvars_prefix = self.get_option("hostvars_prefix") hostvars_suffix = self.get_option("hostvars_suffix") @@ -795,6 +825,7 @@ def parse(self, inventory, loader, path, cache=True): self._populate( results, hostnames, + availability_zone_ids, allow_duplicated_hosts=allow_duplicated_hosts, hostvars_prefix=hostvars_prefix, hostvars_suffix=hostvars_suffix, From 96db6d96b6c8c493c4d6585a3fa812a770729d3d Mon Sep 17 00:00:00 2001 From: Artem Timchenko Date: Mon, 10 Jul 2023 14:02:52 +0300 Subject: [PATCH 2/3] changelog update --- changelogs/fragments/1550-az-id-as-hostvar.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/fragments/1550-az-id-as-hostvar.yml b/changelogs/fragments/1550-az-id-as-hostvar.yml index e5064e713ab..d2d0867bfc4 100644 --- a/changelogs/fragments/1550-az-id-as-hostvar.yml +++ b/changelogs/fragments/1550-az-id-as-hostvar.yml @@ -1,3 +1,3 @@ --- minor_changes: -- inventory aws ec2 - add AZ id to hostvars () +- inventory aws ec2 - add AZ id to hostvars (https://github.com/ansible-collections/amazon.aws/pull/1651) From 8cfc09b643bdea6641d21b3ca82fc06a4a2a17cd Mon Sep 17 00:00:00 2001 From: Artem Timchenko Date: Tue, 12 Sep 2023 17:17:00 +0300 Subject: [PATCH 3/3] 1550: add integration tests for az id hostvar --- .../playbooks/test_az_id.yml | 43 +++++++++++++++++++ .../targets/inventory_aws_ec2/runme.sh | 4 ++ .../templates/inventory_with_az_id.yml.j2 | 13 ++++++ 3 files changed, 60 insertions(+) create mode 100644 tests/integration/targets/inventory_aws_ec2/playbooks/test_az_id.yml create mode 100644 tests/integration/targets/inventory_aws_ec2/templates/inventory_with_az_id.yml.j2 diff --git a/tests/integration/targets/inventory_aws_ec2/playbooks/test_az_id.yml b/tests/integration/targets/inventory_aws_ec2/playbooks/test_az_id.yml new file mode 100644 index 00000000000..2967b47d0f1 --- /dev/null +++ b/tests/integration/targets/inventory_aws_ec2/playbooks/test_az_id.yml @@ -0,0 +1,43 @@ +--- +- 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: tasks/setup.yml + + # Create new host, refresh inventory + - name: create a new host + ec2_instance: + image_id: '{{ image_id }}' + name: '{{ resource_prefix }}' + tags: + OtherTag: value + instance_type: t2.micro + security_groups: '{{ sg_id }}' + vpc_subnet_id: '{{ subnet_id }}' + wait: false + + - meta: refresh_inventory + + - name: Get AZ information from AWS + aws_az_info: + filters: + zone-name: "{{ hostvars[resource_prefix]['placement']['availability_zone'] }}" + register: az_info + + - name: Run tests + assert: + that: + - hostvars[resource_prefix]['placement']['availability_zone_id'] is defined # Ensure AZ id is set + - hostvars[resource_prefix]['placement']['availability_zone_id'] == az_info['availability_zones'][0]['zone_id'] # Ensure AZ id is equale to the one from AWS API call diff --git a/tests/integration/targets/inventory_aws_ec2/runme.sh b/tests/integration/targets/inventory_aws_ec2/runme.sh index 4423e21f422..f52fb9b7e98 100755 --- a/tests/integration/targets/inventory_aws_ec2/runme.sh +++ b/tests/integration/targets/inventory_aws_ec2/runme.sh @@ -78,6 +78,10 @@ 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 az id information +ansible-playbook playbooks/create_inventory_config.yml -e "template='inventory_with_az_id.yml.j2'" "$@" +ansible-playbook playbooks/test_az_id.yml "$@" + # remove inventory cache rm -r aws_ec2_cache_dir/ diff --git a/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_az_id.yml.j2 b/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_az_id.yml.j2 new file mode 100644 index 00000000000..bbf3e21e9e8 --- /dev/null +++ b/tests/integration/targets/inventory_aws_ec2/templates/inventory_with_az_id.yml.j2 @@ -0,0 +1,13 @@ +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 }}' +hostnames: +- tag:Name