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

inventory aws_ec2 - Add az id as hostvar #1651

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions changelogs/fragments/1550-az-id-as-hostvar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- inventory aws ec2 - add AZ id to hostvars (https://github.com/ansible-collections/amazon.aws/pull/1651)
33 changes: 32 additions & 1 deletion plugins/inventory/aws_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,15 +408,17 @@ 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,
):
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():
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -663,6 +685,7 @@ def _populate(
self,
groups,
hostnames,
availability_zone_ids,
allow_duplicated_hosts=False,
hostvars_prefix=None,
hostvars_suffix=None,
Expand All @@ -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,
Expand All @@ -685,6 +709,7 @@ def iter_entry(
self,
hosts,
hostnames,
availability_zone_ids,
allow_duplicated_hosts=False,
hostvars_prefix=None,
hostvars_suffix=None,
Expand All @@ -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,
Expand All @@ -712,6 +738,7 @@ def _add_hosts(
hosts,
group,
hostnames,
availability_zone_ids,
allow_duplicated_hosts=False,
hostvars_prefix=None,
hostvars_suffix=None,
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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")
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions tests/integration/targets/inventory_aws_ec2/runme.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Loading