diff --git a/plugins/inventory/aws_ec2.py b/plugins/inventory/aws_ec2.py index 927014f1c76..30acaa73813 100644 --- a/plugins/inventory/aws_ec2.py +++ b/plugins/inventory/aws_ec2.py @@ -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: @@ -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 @@ -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] @@ -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', [])) @@ -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 @@ -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 diff --git a/tests/unit/plugins/inventory/test_aws_ec2.py b/tests/unit/plugins/inventory/test_aws_ec2.py index 028d4a37fde..6dfa4042ff8 100644 --- a/tests/unit/plugins/inventory/test_aws_ec2.py +++ b/tests/unit/plugins/inventory/test_aws_ec2.py @@ -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): @@ -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()