Skip to content

Commit

Permalink
inventory/aws_ec2: allow multi-entries for one host
Browse files Browse the repository at this point in the history
Add an option to allow multiple duplicated entry for on single instance.

Closes: ansible-collections#950
  • Loading branch information
goneri committed Sep 15, 2022
1 parent 100399a commit 8a374f2
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 14 deletions.
60 changes: 52 additions & 8 deletions plugins/inventory/aws_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@
separator: '-' # Hostname will be aws-test_literal
prefix: 'aws'
# Returns all the hostnames for a given instance
allow_duplicated_hosts: False
# Example using constructed features to create groups and set ansible_host
plugin: aws_ec2
regions:
Expand Down Expand Up @@ -626,7 +629,7 @@ def _sanitize_hostname(self, hostname):
else:
return to_text(hostname)

def _get_hostname(self, instance, hostnames):
def _get_preferred_hostname(self, instance, hostnames):
'''
:param instance: an instance dict returned by boto3 ec2 describe_instances()
:param hostnames: a list of hostname destination variables in order of preference
Expand All @@ -635,14 +638,47 @@ def _get_hostname(self, instance, hostnames):
if not hostnames:
hostnames = ['dns-name', 'private-dns-name']

hostname = None
for preference in hostnames:
if isinstance(preference, dict):
if 'name' not in preference:
raise AnsibleError("A 'name' key must be defined in a hostnames dictionary.")
hostname = self._get_preferred_hostname(instance, [preference["name"]])
hostname_from_prefix = self._get_preferred_hostname(instance, [preference["prefix"]])
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 = self._get_tag_hostname(preference, instance)
hostname = tags[0] if tags else None
else:
hostname = self._get_boto_attr_chain(preference, instance)
if hostname:
break
if hostname:
if ':' in to_text(hostname):
return self._sanitize_group_name((to_text(hostname)))
else:
return to_text(hostname)


def get_all_hostnames(self, instance, hostnames):
'''
:param instance: an instance dict returned by boto3 ec2 describe_instances()
:param hostnames: a list of hostname destination variables
:return all the candidats matching the expectation
'''
if not hostnames:
hostnames = ['dns-name', 'private-dns-name']

hostname = None
hostname_list = []
for preference in hostnames:
if isinstance(preference, dict):
if 'name' not in preference:
raise AnsibleError("A 'name' key must be defined in a hostnames dictionary.")
hostname = self._get_hostname(instance, [preference["name"]])
hostname_from_prefix = self._get_hostname(instance, [preference["prefix"]])
hostname = self.get_all_hostnames(instance, [preference["name"]])
hostname_from_prefix = self.get_all_hostnames(instance, [preference["prefix"]])
separator = preference.get("separator", "_")
if hostname and hostname_from_prefix and 'prefix' in preference:
hostname = hostname_from_prefix[0] + separator + hostname[0]
Expand Down Expand Up @@ -689,20 +725,27 @@ def _query(self, regions, include_filters, exclude_filters, strict_permissions):

return {'aws_ec2': instances}

def _populate(self, groups, hostnames):
def _populate(self, groups, hostnames, allow_duplicated_hosts=False):
for group in groups:
group = self.inventory.add_group(group)
self._add_hosts(hosts=groups[group], group=group, hostnames=hostnames)
self._add_hosts(
hosts=groups[group],
group=group,
hostnames=hostnames,
allow_duplicated_hosts=allow_duplicated_hosts)
self.inventory.add_child('all', group)

def _add_hosts(self, hosts, group, hostnames):
def _add_hosts(self, hosts, group, hostnames, allow_duplicated_hosts=False):
'''
: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
'''
for host in hosts:
hostname_list = self._get_hostname(host, hostnames)
if allow_duplicated_hosts:
hostname_list = self.get_all_hostnames(host, hostnames)
else:
hostname_list = [self._get_preferred_hostname(host, hostnames)]

host = camel_dict_to_snake_dict(host, ignore_list=['Tags'])
host['tags'] = boto3_tag_list_to_ansible_dict(host.get('tags', []))
Expand Down Expand Up @@ -820,6 +863,7 @@ def parse(self, inventory, loader, path, cache=True):
exclude_filters = self.get_option('exclude_filters')
hostnames = self.get_option('hostnames')
strict_permissions = self.get_option('strict_permissions')
allow_duplicated_hosts = self.get_option('allow_duplicated_hosts')

cache_key = self.get_cache_key(path)
# false when refresh_cache or --flush-cache is used
Expand All @@ -839,7 +883,7 @@ def parse(self, inventory, loader, path, cache=True):
if not cache or cache_needs_update:
results = self._query(regions, include_filters, exclude_filters, strict_permissions)

self._populate(results, hostnames)
self._populate(results, hostnames, allow_duplicated_hosts=allow_duplicated_hosts)

# If the cache has expired/doesn't exist or if refresh_inventory/flush cache is used
# when the user is using caching, update the cached inventory
Expand Down
47 changes: 41 additions & 6 deletions tests/unit/plugins/inventory/test_aws_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,21 +139,51 @@ def test_boto3_conn(inventory):
assert "Insufficient credentials found" in error_message


def test_get_hostname_default(inventory):
def testget_all_hostnames_default(inventory):
instance = instances['Instances'][0]
assert inventory._get_hostname(instance, hostnames=None)[0] == "ec2-12-345-67-890.compute-1.amazonaws.com"
assert inventory.get_all_hostnames(instance, hostnames=None) == ["ec2-12-345-67-890.compute-1.amazonaws.com", "ip-098-76-54-321.ec2.internal"]


def test_get_hostname(inventory):
def testget_all_hostnames(inventory):
hostnames = ['ip-address', 'dns-name']
instance = instances['Instances'][0]
assert inventory._get_hostname(instance, hostnames)[0] == "12.345.67.890"
assert inventory.get_all_hostnames(instance, hostnames) == ["12.345.67.890", "ec2-12-345-67-890.compute-1.amazonaws.com"]


def test_get_hostname_dict(inventory):
def testget_all_hostnames_dict(inventory):
hostnames = [{'name': 'private-ip-address', 'separator': '_', 'prefix': 'tag:Name'}]
instance = instances['Instances'][0]
assert inventory._get_hostname(instance, hostnames)[0] == "aws_ec2_098.76.54.321"
assert inventory.get_all_hostnames(instance, hostnames) == ["aws_ec2_098.76.54.321"]


def testget_all_hostnames_with_2_tags(inventory):
hostnames = ['tag:ansible', 'tag:Name']
instance = instances['Instances'][0]
assert inventory.get_all_hostnames(instance, hostnames) == ["test", "aws_ec2"]



def test_get_preferred_hostname_default(inventory):
instance = instances['Instances'][0]
assert inventory._get_preferred_hostname(instance, hostnames=None) == "ec2-12-345-67-890.compute-1.amazonaws.com"


def test_get_preferred_hostname(inventory):
hostnames = ['ip-address', 'dns-name']
instance = instances['Instances'][0]
assert inventory._get_preferred_hostname(instance, hostnames) == "12.345.67.890"


def test_get_preferred_hostname_dict(inventory):
hostnames = [{'name': 'private-ip-address', 'separator': '_', 'prefix': 'tag:Name'}]
instance = instances['Instances'][0]
assert inventory._get_preferred_hostname(instance, hostnames) == "aws_ec2_098.76.54.321"


def test_get_preferred_hostname_with_2_tags(inventory):
hostnames = ['tag:ansible', 'tag:Name']
instance = instances['Instances'][0]
assert inventory._get_preferred_hostname(instance, hostnames) == "test"


def test_set_credentials(inventory):
Expand Down Expand Up @@ -216,3 +246,8 @@ def test_include_filters_with_filter_and_include_filters(inventory):
assert inventory.build_include_filters() == [
{"from_filter": 1},
{"from_include_filter": "bar"}]


def test_add_host(inventory):
# TODO
inventory.add_hosts()

0 comments on commit 8a374f2

Please sign in to comment.