Repository: ansible/ansible

Issue:
v2 (known) regression: with_inventory_hostnames doesn't work
This is what `lib/ansible/plugins/lookup/inventory_hostnames.py` says::

```
        # FIXME: the inventory is no longer available this way, so we may have
        #        to dump the host list into the list of variables and read it back
        #        in here (or the inventory sources, so we can recreate the list
        #        of hosts)
        #return self._flatten(inventory.Inventory(self.host_list).list_hosts(terms))
```



Discussion:
@amenonsen yes, I think we just need to add the hosts names as a magic variable and add a deprecation message and failure to this lookup.


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
@@ -159,7 +159,7 @@ def get_hosts(self, pattern="all", ignore_limits_and_restrictions=False):
         or applied subsets
         """
 
-        patterns = self._split_pattern(pattern)
+        patterns = Inventory.split_host_pattern(pattern)
         hosts = self._evaluate_patterns(patterns)
 
         # mainly useful for hostvars[host] access
@@ -175,7 +175,8 @@ def get_hosts(self, pattern="all", ignore_limits_and_restrictions=False):
 
         return hosts
 
-    def _split_pattern(self, pattern):
+    @classmethod
+    def split_host_pattern(cls, pattern):
         """
         Takes a string containing host patterns separated by commas (or a list
         thereof) and returns a list of single patterns (which may not contain
@@ -188,7 +189,7 @@ def _split_pattern(self, pattern):
         """
 
         if isinstance(pattern, list):
-            return list(itertools.chain(*map(self._split_pattern, pattern)))
+            return list(itertools.chain(*map(cls.split_host_pattern, pattern)))
 
         if ';' in pattern:
             display.deprecated("Use ',' instead of ';' to separate host patterns")
@@ -226,11 +227,8 @@ def _split_pattern(self, pattern):
 
         return [p.strip() for p in patterns]
 
-    def _evaluate_patterns(self, patterns):
-        """
-        Takes a list of patterns and returns a list of matching host names,
-        taking into account any negative and intersection patterns.
-        """
+    @classmethod
+    def order_patterns(cls, patterns):
 
         # Host specifiers should be sorted to ensure consistent behavior
         pattern_regular = []
@@ -251,8 +249,15 @@ def _evaluate_patterns(self, patterns):
 
         # when applying the host selectors, run those without the "&" or "!"
         # first, then the &s, then the !s.
-        patterns = pattern_regular + pattern_intersection + pattern_exclude
+        return pattern_regular + pattern_intersection + pattern_exclude
+
+    def _evaluate_patterns(self, patterns):
+        """
+        Takes a list of patterns and returns a list of matching host names,
+        taking into account any negative and intersection patterns.
+        """
 
+        patterns = Inventory.order_patterns(patterns)
         hosts = []
 
         for p in patterns:
@@ -575,7 +580,7 @@ def subset(self, subset_pattern):
         if subset_pattern is None:
             self._subset = None
         else:
-            subset_patterns = self._split_pattern(subset_pattern)
+            subset_patterns = Inventory.split_host_pattern(subset_pattern)
             results = []
             # allow Unix style @filename data
             for x in subset_patterns:
diff --git a/lib/ansible/plugins/lookup/inventory_hostnames.py b/lib/ansible/plugins/lookup/inventory_hostnames.py
--- a/lib/ansible/plugins/lookup/inventory_hostnames.py
+++ b/lib/ansible/plugins/lookup/inventory_hostnames.py
@@ -21,18 +21,35 @@
 
 from ansible.errors import *
 from ansible.plugins.lookup import LookupBase
+from ansible.inventory import Inventory
 
 class LookupModule(LookupBase):
 
-    def run(self, terms, inject=None, **kwargs):
-        ### FIXME: Is this needed now that listify is run on all lookup plugin terms?
-        if not isinstance(terms, list):
-            raise AnsibleError("with_inventory_hostnames expects a list")
+    def get_hosts(self, variables, pattern):
+        #print(variables)
+        #print(variables['groups'])
+        hosts = []
+        if pattern in variables['groups']:
+            hosts = variables['groups'][pattern]
+        elif pattern in variables['groups']['all']:
+            hosts = [pattern]
+        return hosts
 
-        # FIXME: the inventory is no longer available this way, so we may have
-        #        to dump the host list into the list of variables and read it back
-        #        in here (or the inventory sources, so we can recreate the list
-        #        of hosts)
-        #return self._flatten(inventory.Inventory(self.host_list).list_hosts(terms))
-        return terms
+    def run(self, terms, variables=None, **kwargs):
 
+        host_list = []
+
+        for term in terms:
+            patterns = Inventory.order_patterns(Inventory.split_host_pattern(term))
+
+            for p in patterns:
+                that = self.get_hosts(variables, p)
+                if p.startswith("!"):
+                    host_list = [ h for h in host_list if h not in that]
+                elif p.startswith("&"):
+                    host_list = [ h for h in host_list if h in that ]
+                else:
+                    host_list.extend(that)
+
+        # return unique list
+        return list(set(host_list))


Test patch:
diff --git a/test/units/inventory/test_inventory.py b/test/units/inventory/test_inventory.py
--- a/test/units/inventory/test_inventory.py
+++ b/test/units/inventory/test_inventory.py
@@ -92,10 +92,10 @@ def test_split_patterns(self):
 
         for p in self.patterns:
             r = self.patterns[p]
-            self.assertEqual(r, self.i._split_pattern(p))
+            self.assertEqual(r, self.i.split_host_pattern(p))
 
         for p, r in self.pattern_lists:
-            self.assertEqual(r, self.i._split_pattern(p))
+            self.assertEqual(r, self.i.split_host_pattern(p))
 
     def test_ranges(self):