Repository: ansible/ansible

Issue:
Ansible inventory allows groups to have same name as hosts
##### Issue Type

Bug Report
##### Ansible Version:

ansible 1.6 (devel 9da26da335) last updated 2014/03/18 11:15:04 (GMT +1000)
##### Environment:

N/A
##### Summary:

If you define a group containing another group without the :children, then the intended group is a host, but will also become a group when defining its contents. 

The net result is that the host that should inherit characteristics of several layers of parents will not inherit those characteristics as the definitions will be:

grandparent -> parent(host)
parent(group) -> host

rather than
grandparent -> parent -> host

I believe that it should not be possible to define a group with the same name as a host (or at least a warning suggesting it might not be what you want should happen)
##### Steps To Reproduce:

See https://gist.github.com/willthames/9614054 
##### Expected Results:

See https://gist.github.com/willthames/9614054
##### Actual Results:

See https://gist.github.com/willthames/9614054



Discussion:
This has probably always been a thing, I agree it should probably not be a thing such that if you define one when the other exists it's an error, but it's probably not in the super-urgent status category of bug reports.

Since Serge is working on some inventory upgrades, let's wait until this is done and applied and we can see about whether this can be dealt with efficiently.

Agreed - it just caught us out earlier ("why isn't this host inheriting the properties of the parent groups") and it was a very subtle one that would be good to flag. Happy to leave it until the dust settles behind Serge - at least it's documented for now. 

@willthames having the groups and hosts named in the same way introduces also another interesting feature: #6314 :-)

this is something we can fix when implementing https://github.com/ansible/proposals/issues/41, as of now both the inventory base class and each type of inventory source implements their own logic, it requires a major refactor to fix this (which the proposal already has)

Resolving patch:
diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py
--- a/lib/ansible/inventory/__init__.py
+++ b/lib/ansible/inventory/__init__.py
@@ -119,6 +119,7 @@ def parse_inventory(self, host_list):
         ungrouped = Group('ungrouped')
         all = Group('all')
         all.add_child_group(ungrouped)
+        base_groups = frozenset([all, ungrouped])
 
         self.groups = dict(all=all, ungrouped=ungrouped)
 
@@ -159,16 +160,22 @@ def parse_inventory(self, host_list):
 
         self._vars_plugins = [ x for x in vars_loader.all(self) ]
 
+        ### POST PROCESS groups and hosts after specific parser was invoked
+
+        group_names = set()
         # set group vars from group_vars/ files and vars plugins
         for g in self.groups:
             group = self.groups[g]
             group.vars = combine_vars(group.vars, self.get_group_variables(group.name))
             self.get_group_vars(group)
+            group_names.add(group.name)
 
+        host_names = set()
         # get host vars from host_vars/ files and vars plugins
         for host in self.get_hosts(ignore_limits=True, ignore_restrictions=True):
             host.vars = combine_vars(host.vars, self.get_host_variables(host.name))
             self.get_host_vars(host)
+            host_names.add(host.name)
 
             mygroups = host.get_groups()
 
@@ -178,7 +185,7 @@ def parse_inventory(self, host_list):
 
             if ungrouped in mygroups:
                 # clear ungrouped of any incorrectly stored by parser
-                if set(mygroups).difference(set([all, ungrouped])):
+                if set(mygroups).difference(base_groups):
                     host.remove_group(ungrouped)
             else:
                 # add ungrouped hosts to ungrouped
@@ -186,6 +193,9 @@ def parse_inventory(self, host_list):
                 if length == 0 or (length == 1 and all in mygroups):
                     ungrouped.add_host(host)
 
+        # warn if overloading identifier as both group and host
+        for conflict in group_names.intersection(host_names):
+            display.warning("Found both group and host with same name: %s" % conflict)
 
     def _match(self, str, pattern_str):
         try:


Test patch: