Repository: ansible/ansible

Issue:
hosts defined in two groups in different inventory files each is used multiple times
when using multiple inventory files defining different groups for the same hosts, ansible adds the hosts multiple times to the inventory list.

version: 1.4.4+dfsg-1 (from debian jessie)
same problem with current devel 00b3f627ebb2b8e9cf743671a9ddd2d8d8c9fe45

reproduce: 
- use files from https://gist.github.com/g-k-r/8599442
- run **ansible all --list**
- run **ansible group2 --list** and compare with **ansible type2 --list**

actual output:

``` shell
$ ansible all --list
    www.google.com
    www.yahoo.com
    www.yahoo.com
$ ansible group2 --list
    www.yahoo.com
$ ansible type2 --list
    www.yahoo.com
    www.yahoo.com
```

note when the group is defined in both files like [type1] this does not happen.

``` shell
$ ansible type1 --list
    www.google.com
```

expected output: www.yahoo.com shows up once only i.e.

``` shell
$ ansible all --list
    www.google.com
    www.yahoo.com
$ ansible group2 --list
    www.yahoo.com
$ ansible type2 --list
    www.yahoo.com
```

additional information:

to verify that tasks would actually be executed multiple times use

``` shell
$ ansible all -m debug -a '"msg=TEST {{inventory_hostname}}"'
www.google.com | success >> {
    "msg": "TEST www.google.com"
}

www.yahoo.com | success >> {
    "msg": "TEST www.yahoo.com"
}

www.yahoo.com | success >> {
    "msg": "TEST www.yahoo.com"
}
```



Discussion:
Per contributing.md can you please provide your ansible version?  (`ansible --version`)

If I remember correctly, this has been resolved in devel (1.5)

Yes, this was resolved by https://github.com/ansible/ansible/commit/559e89036b98127510794125beeaeebc82e0c141

Please rebase and try again if you are running an older version.

@sivel: amended the report

@g-k-r this is resolved in devel, and will be in the 1.5 release.

@jctanner: tested with devel, not solved, will provide unit test in PR based on devel

Reopening for testing.

@g-k-r unable to reproduce ...

```
jtanner@u1204:~/issues/5631-duplicate-task-runs$ ansible all -i inventory --list
    www.google.com
    www.yahoo.com
jtanner@u1204:~/issues/5631-duplicate-task-runs$ ansible group2 -i inventory --list
    www.yahoo.com
jtanner@u1204:~/issues/5631-duplicate-task-runs$ ansible type2 -i inventory --list
    www.yahoo.com
```

@g-k-r how do you install ansible and how are you using the checkout to verify devel's behavior?

I do the following:

``` bash
git clone https://github.com/g-k-r/ansible.git
. ./hacking/env-setup
ansible --version
make tests
```

or instead of `make tests`

``` bash
PYTHONPATH=./lib ANSIBLE_LIBRARY=./library  nosetests -d -v test/TestInventory.py:TestInventory.test_dir_inventory_multiple_groups
```

what is strange, is that testing with the original reproduction tests from the first post on this issue do work as you say, while the test started by nosetests doesn't.

```
PYTHONPATH=./lib ANSIBLE_LIBRARY=./library  nosetests -d -v test/TestInventory.py:TestInventory.test_dir_inventory_multiple_groups
test_dir_inventory_multiple_groups (TestInventory.TestInventory) ... FAIL

======================================================================
FAIL: test_dir_inventory_multiple_groups (TestInventory.TestInventory)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/gkr/workspaces/ansible_issue_5749/ansible/test/TestInventory.py", line 423, in test_dir_inventory_multiple_groups
    assert actual_host_names == ['zeus','morpheus']
AssertionError: 
    <ansible.inventory.group.Group object at 0x208e2c0> = <ansible.inventory.Inventory object at 0x204c510>.get_group('major-god')
    ['zeus', 'morpheus', 'zeus', 'morpheus'] = [<ansible.inventory.host.Host object at 0x208d050>.name for <ansible.inventory.host.Host object at 0x208d050> in <ansible.inventory.group.Group object at 0x208e120>.get_hosts()];
    print "%s : %s " % (<ansible.inventory.group.Group object at 0x208e120>.name, ['zeus', 'morpheus', 'zeus', 'morpheus'])
>>  assert ['zeus', 'morpheus', 'zeus', 'morpheus'] == ['zeus','morpheus'] 

-------------------- >> begin captured stdout << ---------------------
greek : ['zeus', 'morpheus', 'zeus', 'morpheus'] 

--------------------- >> end captured stdout << ----------------------

----------------------------------------------------------------------
Ran 1 test in 0.009s
```

that is confusing, calling ansible directly succeeds:

```
cd tests
ansible greek --list -i inventory_dir
```

what doesn't work anymore is:

```
$ ansible all -i inventory_dir/ --list-hosts --limit greek
No hosts matched
```

with ansible 1.4.4 this results in:

```
$ ansible all -i inventory_dir/ --list -l greek
    zeus
    morpheus
    zeus
    morpheus
```

sorry about the many commits, i changed the test which now works identical to direct ansible calls, will now create a pull request, if you want to add the test to the code.
otherwise i think this issue is fixed already and I made a mistake during verification. 
Maybe you'd like to look into why get_group contains the hosts multiple times and why the --limit doesn't seem to work anymore, I could prepare a test again if needed.

thanks!

@g-k-r i think you are mixing up installed ansible lib paths with the checkout lib paths. Can you please retest on a clean machine with a fresh checkout and no ansible packages installed?

ok i will test in a cleanrooom VM, hopefully tonight otherwise this needs to wait until the week after next

hi @jctanner i tested in a clean debian wheezy 
only installed ansible dependencies, but not ansible itself as it is not available in pure wheezy.

to test the initial implementation with get_group i had to checkout the second commit in my pull request. since the latest commit overwrites the test implementation to use get_hosts instead of get_group. 

```
$ git clone https://github.com/g-k-r/ansible.git
...
$ git checkout --track origin/test_hosts_defined_in_multiple_groups_in_different_files
...
$ git checkout 3fe1341990babde2b3dd5295bdd3072aac88fec2
...
$ make tests
...
======================================================================
FAIL: test_dir_inventory_multiple_groups (TestInventory.TestInventory)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/g-k-r/issue_5749/ansible/test/TestInventory.py", line 423, in test_dir_inventory_multiple_groups
    assert actual_host_names == ['zeus','morpheus']
AssertionError: 
    <ansible.inventory.group.Group object at 0x28d5050> = <ansible.inventory.Inventory object at 0x2736510>.get_group('major-god')
    ['zeus', 'morpheus', 'zeus', 'morpheus'] = [<ansible.inventory.host.Host object at 0x28d2c68>.name for <ansible.inventory.host.Host object at 0x28d2c68> in <ansible.inventory.group.Group object at 0x2848e88>.get_hosts()];
    print "%s : %s " % (<ansible.inventory.group.Group object at 0x2848e88>.name, ['zeus', 'morpheus', 'zeus', 'morpheus'])
>>  assert ['zeus', 'morpheus', 'zeus', 'morpheus'] == ['zeus','morpheus'] 

-------------------- >> begin captured stdout << ---------------------
greek : ['zeus', 'morpheus', 'zeus', 'morpheus'] 

--------------------- >> end captured stdout << ----------------------
```

inventory.get_group is not behaving like i though it should. anyway the get_hosts version (HEAD of the pull request) works.

It looks like there isn't any outstanding issues with this ticket so I am going to close it out based on your final comment here. I don't see the duplication that you are pointing out in the recent branches.

If you are continuing to see problems feel free to ping me @ansible.com and we can sort it out.

This sounds like this comment was about a pull request that wasn't applied yet so I'm reopening this one for now, unless we can't reproduce this on head, etc.

ok beat me to it, thanks!


Resolving patch:
diff --git a/lib/ansible/inventory/ini.py b/lib/ansible/inventory/ini.py
--- a/lib/ansible/inventory/ini.py
+++ b/lib/ansible/inventory/ini.py
@@ -23,10 +23,12 @@
 from ansible.inventory.expand_hosts import detect_range
 from ansible.inventory.expand_hosts import expand_hostname_range
 from ansible import errors
+import ansible.utils as utils
 import shlex
 import re
 import ast
 
+
 class InventoryParser(object):
     """
     Host inventory for ansible.
@@ -47,7 +49,6 @@ def _parse(self):
         self._parse_group_variables()
         return self.groups
 
-
     # [webservers]
     # alpha
     # beta:2345
@@ -65,9 +66,16 @@ def _parse_base_groups(self):
         active_group_name = 'ungrouped'
 
         for line in self.lines:
-            line = line.split("#")[0].strip()
+
+            # Split off any comments that are not contained in a variable.
+            if "#" in line:
+                line = utils.split_unquoted_hash(line)
+
+            # Clean up the end of the line.
+            line = line.strip()
+
             if line.startswith("[") and line.endswith("]"):
-                active_group_name = line.replace("[","").replace("]","")
+                active_group_name = line.replace("[", "").replace("]", "")
                 if line.find(":vars") != -1 or line.find(":children") != -1:
                     active_group_name = active_group_name.rsplit(":", 1)[0]
                     if active_group_name not in self.groups:
@@ -95,20 +103,18 @@ def _parse_base_groups(self):
                     if hostname.count(".") == 1:
                         (hostname, port) = hostname.rsplit(".", 1)
                 elif (hostname.find("[") != -1 and
-                    hostname.find("]") != -1 and
-                    hostname.find(":") != -1 and
-                    (hostname.rindex("]") < hostname.rindex(":")) or
-                    (hostname.find("]") == -1 and hostname.find(":") != -1)):
-                        (hostname, port) = hostname.rsplit(":", 1)
+                      hostname.find("]") != -1 and
+                      hostname.find(":") != -1 and
+                      (hostname.rindex("]") < hostname.rindex(":")) or
+                      (hostname.find("]") == -1 and hostname.find(":") != -1)):
+                    (hostname, port) = hostname.rsplit(":", 1)
 
-                hostnames = []
                 if detect_range(hostname):
                     hostnames = expand_hostname_range(hostname)
                 else:
                     hostnames = [hostname]
 
                 for hn in hostnames:
-                    host = None
                     if hn in self.hosts:
                         host = self.hosts[hn]
                     else:
@@ -119,15 +125,24 @@ def _parse_base_groups(self):
                             if t.startswith('#'):
                                 break
                             try:
-                                (k,v) = t.split("=", 1)
+                                (k, v) = t.split("=", 1)
                             except ValueError, e:
                                 raise errors.AnsibleError("Invalid ini entry: %s - %s" % (t, str(e)))
-                            try:
-                                host.set_variable(k,ast.literal_eval(v))
-                            except:
-                                # most likely a string that literal_eval
-                                # doesn't like, so just set it
-                                host.set_variable(k,v)
+                            # I am not sure where a variable with a hash needs to be evaluated via ast.
+                            # If an instance comes up this is the condition we need to modify.
+                            if "#" in v:
+                                host.set_variable(k, v)
+                            else:
+                                try:
+                                    host.set_variable(k, ast.literal_eval(v))
+                                # Using explicit exceptions.
+                                # Likely a string that literal_eval does not like. We wil then just set it.
+                                except ValueError:
+                                    # For some reason this was thought to be malformed.
+                                    host.set_variable(k, v)
+                                except SyntaxError:
+                                    # Is this a hash with an equals at the end?
+                                    host.set_variable(k, v)
                     self.groups[active_group_name].add_host(host)
 
     # [southeast:children]
@@ -142,7 +157,7 @@ def _parse_group_children(self):
             if line is None or line == '':
                 continue
             if line.startswith("[") and line.find(":children]") != -1:
-                line = line.replace("[","").replace(":children]","")
+                line = line.replace("[", "").replace(":children]", "")
                 group = self.groups.get(line, None)
                 if group is None:
                     group = self.groups[line] = Group(name=line)
@@ -157,7 +172,6 @@ def _parse_group_children(self):
                 else:
                     group.add_child_group(kid_group)
 
-
     # [webservers:vars]
     # http_port=1234
     # maxRequestsPerChild=200
@@ -167,7 +181,7 @@ def _parse_group_variables(self):
         for line in self.lines:
             line = line.strip()
             if line.startswith("[") and line.find(":vars]") != -1:
-                line = line.replace("[","").replace(":vars]","")
+                line = line.replace("[", "").replace(":vars]", "")
                 group = self.groups.get(line, None)
                 if group is None:
                     raise errors.AnsibleError("can't add vars to undefined group: %s" % line)
diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py
--- a/lib/ansible/utils/__init__.py
+++ b/lib/ansible/utils/__init__.py
@@ -1071,3 +1071,37 @@ def random_password(length=20, chars=C.DEFAULT_PASSWORD_CHARS):
             password.append(new_char)
 
     return ''.join(password)
+
+
+def split_unquoted_hash(line):
+    '''
+     Carve off comments from a line which are not contained in quotes and a part of an assignment.
+    '''
+
+    # We would really like to have this using a regex to make it less code. For instance:
+    # line = re.split('(?<!=["|\'].*)#(?!.*?["|\']).*', line)[0]
+    # this has the problem that it comes back with a "sre_constants.error: look-behind requires fixed-width pattern"
+
+    if "#" in line:
+        split_line = line.split("#")
+        instances = len(split_line) - 1
+        if instances > 0:
+            marker = 0
+            while marker < instances:
+                if ("=\"" in split_line[marker] and "\"" in split_line[marker + 1]) or (
+                        "='" in split_line[marker] and "'" in split_line[marker + 1]):
+                    marker += 1
+                else:
+                    if marker == 0:
+                        line = split_line[marker]
+                    else:
+                        # We have multiple fragments that we need to combine back together.
+                        # rekram is us reversing that work we did with marker.
+                        rekram = 0
+                        new_line = split_line[rekram]
+                        while marker > rekram:
+                            rekram += 1
+                            new_line = new_line + "#" + split_line[rekram]
+                        line = new_line
+                    break
+        return line
\ No newline at end of file


Test patch:
diff --git a/test/units/TestInventory.py b/test/units/TestInventory.py
--- a/test/units/TestInventory.py
+++ b/test/units/TestInventory.py
@@ -5,6 +5,7 @@
 from ansible import errors
 from ansible.inventory import Inventory
 
+
 class TestInventory(unittest.TestCase):
 
     def setUp(self):
@@ -49,14 +50,14 @@ def complex_inventory(self):
     def dir_inventory(self):
         return Inventory(self.inventory_dir)
 
-    all_simple_hosts=['jupiter', 'saturn', 'zeus', 'hera',
-            'cerberus001','cerberus002','cerberus003',
-            'cottus99', 'cottus100',
-            'poseidon', 'thor', 'odin', 'loki',
-            'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
-            'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5',
-            'Hotep-a', 'Hotep-b', 'Hotep-c',
-            'BastC', 'BastD', 'neptun', ]
+    all_simple_hosts = ['jupiter', 'saturn', 'zeus', 'hera',
+                        'cerberus001', 'cerberus002', 'cerberus003',
+                        'cottus99', 'cottus100',
+                        'poseidon', 'thor', 'odin', 'loki',
+                        'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
+                        'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5',
+                        'Hotep-a', 'Hotep-b', 'Hotep-c',
+                        'BastC', 'BastD', 'neptun', ]
 
     #####################################
     ### Empty inventory format tests
@@ -93,36 +94,36 @@ def test_simple_norse(self):
         inventory = self.simple_inventory()
         hosts = inventory.list_hosts("norse")
 
-        expected_hosts=['thor', 'odin', 'loki']
+        expected_hosts = ['thor', 'odin', 'loki']
         assert sorted(hosts) == sorted(expected_hosts)
 
     def test_simple_ungrouped(self):
         inventory = self.simple_inventory()
         hosts = inventory.list_hosts("ungrouped")
 
-        expected_hosts=['jupiter', 'saturn',
-                        'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
-                        'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5']
+        expected_hosts = ['jupiter', 'saturn',
+                          'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
+                          'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5']
         assert sorted(hosts) == sorted(expected_hosts)
 
     def test_simple_combined(self):
         inventory = self.simple_inventory()
         hosts = inventory.list_hosts("norse:greek")
 
-        expected_hosts=['zeus', 'hera', 'poseidon',
-                        'cerberus001','cerberus002','cerberus003',
-                        'cottus99','cottus100',
-                        'thor', 'odin', 'loki']
+        expected_hosts = ['zeus', 'hera', 'poseidon',
+                          'cerberus001', 'cerberus002', 'cerberus003',
+                          'cottus99', 'cottus100',
+                          'thor', 'odin', 'loki']
         assert sorted(hosts) == sorted(expected_hosts)
 
     def test_simple_restrict(self):
         inventory = self.simple_inventory()
 
         restricted_hosts = ['hera', 'poseidon', 'thor']
-        expected_hosts=['zeus', 'hera', 'poseidon',
-                        'cerberus001','cerberus002','cerberus003',
-                        'cottus99', 'cottus100',
-                        'thor', 'odin', 'loki']
+        expected_hosts = ['zeus', 'hera', 'poseidon',
+                          'cerberus001', 'cerberus002', 'cerberus003',
+                          'cottus99', 'cottus100',
+                          'thor', 'odin', 'loki']
 
         inventory.restrict_to(restricted_hosts)
         hosts = inventory.list_hosts("norse:greek")
@@ -137,12 +138,12 @@ def test_simple_restrict(self):
     def test_simple_string_ipv4(self):
         inventory = Inventory('127.0.0.1,192.168.1.1')
         hosts = inventory.list_hosts()
-        self.assertEqual(sorted(hosts), sorted(['127.0.0.1','192.168.1.1']))
+        self.assertEqual(sorted(hosts), sorted(['127.0.0.1', '192.168.1.1']))
 
     def test_simple_string_ipv4_port(self):
         inventory = Inventory('127.0.0.1:2222,192.168.1.1')
         hosts = inventory.list_hosts()
-        self.assertEqual(sorted(hosts), sorted(['127.0.0.1','192.168.1.1']))
+        self.assertEqual(sorted(hosts), sorted(['127.0.0.1', '192.168.1.1']))
 
     def test_simple_string_ipv4_vars(self):
         inventory = Inventory('127.0.0.1:2222,192.168.1.1')
@@ -152,12 +153,12 @@ def test_simple_string_ipv4_vars(self):
     def test_simple_string_ipv6(self):
         inventory = Inventory('FE80:EF45::12:1,192.168.1.1')
         hosts = inventory.list_hosts()
-        self.assertEqual(sorted(hosts), sorted(['FE80:EF45::12:1','192.168.1.1']))
+        self.assertEqual(sorted(hosts), sorted(['FE80:EF45::12:1', '192.168.1.1']))
 
     def test_simple_string_ipv6_port(self):
         inventory = Inventory('[FE80:EF45::12:1]:2222,192.168.1.1')
         hosts = inventory.list_hosts()
-        self.assertEqual(sorted(hosts), sorted(['FE80:EF45::12:1','192.168.1.1']))
+        self.assertEqual(sorted(hosts), sorted(['FE80:EF45::12:1', '192.168.1.1']))
 
     def test_simple_string_ipv6_vars(self):
         inventory = Inventory('[FE80:EF45::12:1]:2222,192.168.1.1')
@@ -167,12 +168,12 @@ def test_simple_string_ipv6_vars(self):
     def test_simple_string_fqdn(self):
         inventory = Inventory('foo.example.com,bar.example.com')
         hosts = inventory.list_hosts()
-        self.assertEqual(sorted(hosts), sorted(['foo.example.com','bar.example.com']))
+        self.assertEqual(sorted(hosts), sorted(['foo.example.com', 'bar.example.com']))
 
     def test_simple_string_fqdn_port(self):
         inventory = Inventory('foo.example.com:2222,bar.example.com')
         hosts = inventory.list_hosts()
-        self.assertEqual(sorted(hosts), sorted(['foo.example.com','bar.example.com']))
+        self.assertEqual(sorted(hosts), sorted(['foo.example.com', 'bar.example.com']))
 
     def test_simple_string_fqdn_vars(self):
         inventory = Inventory('foo.example.com:2222,bar.example.com')
@@ -191,26 +192,26 @@ def test_simple_port(self):
         inventory = self.simple_inventory()
         vars = inventory.get_variables('hera')
 
-        expected = { 'ansible_ssh_port': 3000,
-                     'group_names': ['greek'],
-                     'inventory_hostname': 'hera',
-                     'inventory_hostname_short': 'hera' }
+        expected = {'ansible_ssh_port': 3000,
+                    'group_names': ['greek'],
+                    'inventory_hostname': 'hera',
+                    'inventory_hostname_short': 'hera'}
         assert vars == expected
 
     def test_large_range(self):
         inventory = self.large_range_inventory()
         hosts = inventory.list_hosts()
-        self.assertEqual(sorted(hosts),  sorted('bob%03i' %i  for i in range(0, 143)))
+        self.assertEqual(sorted(hosts),  sorted('bob%03i' % i for i in range(0, 143)))
 
     def test_subset(self):
         inventory = self.simple_inventory()
         inventory.subset('odin;thor,loki')
-        self.assertEqual(sorted(inventory.list_hosts()),  sorted(['thor','odin','loki']))
+        self.assertEqual(sorted(inventory.list_hosts()),  sorted(['thor', 'odin', 'loki']))
 
     def test_subset_range(self):
         inventory = self.simple_inventory()
         inventory.subset('greek[0-2];norse[0]')
-        self.assertEqual(sorted(inventory.list_hosts()),  sorted(['zeus','hera','thor']))
+        self.assertEqual(sorted(inventory.list_hosts()),  sorted(['zeus', 'hera', 'thor']))
 
     def test_subet_range_empty_group(self):
         inventory = self.simple_inventory()
@@ -220,11 +221,11 @@ def test_subet_range_empty_group(self):
     def test_subset_filename(self):
         inventory = self.simple_inventory()
         inventory.subset('@' + os.path.join(self.test_dir, 'restrict_pattern'))
-        self.assertEqual(sorted(inventory.list_hosts()),  sorted(['thor','odin']))
+        self.assertEqual(sorted(inventory.list_hosts()),  sorted(['thor', 'odin']))
 
     @raises(errors.AnsibleError)
     def testinvalid_entry(self):
-       Inventory('1234')
+        Inventory('1234')
 
     ###################################################
     ### INI file advanced tests
@@ -240,7 +241,7 @@ def test_complex_vars(self):
             g='  g  ', h='  h  ', i="'  i  \"", j='"  j',
             rga='1', rgb='2', rgc='3',
             inventory_hostname='rtp_a', inventory_hostname_short='rtp_a',
-            group_names=[ 'eastcoast', 'nc', 'redundantgroup', 'redundantgroup2', 'redundantgroup3', 'rtp', 'us' ]
+            group_names=['eastcoast', 'nc', 'redundantgroup', 'redundantgroup2', 'redundantgroup3', 'rtp', 'us']
         )
         print vars
         print expected
@@ -249,9 +250,9 @@ def test_complex_vars(self):
     def test_complex_group_names(self):
         inventory = self.complex_inventory()
         tests = {
-            'host1': [ 'role1', 'role3' ],
-            'host2': [ 'role1', 'role2' ],
-            'host3': [ 'role2', 'role3' ]
+            'host1': ['role1', 'role3'],
+            'host2': ['role1', 'role2'],
+            'host3': ['role2', 'role3']
         }
         for host, roles in tests.iteritems():
             group_names = inventory.get_variables(host)['group_names']
@@ -275,11 +276,10 @@ def test_regex_exclude(self):
 
     def test_complex_enumeration(self):
 
-
         expected1 = ['rtp_b']
         expected2 = ['rtp_a', 'rtp_b']
         expected3 = ['rtp_a', 'rtp_b', 'rtp_c', 'tri_a', 'tri_b', 'tri_c']
-        expected4 = ['rtp_b', 'orlando' ]
+        expected4 = ['rtp_b', 'orlando']
         expected5 = ['blade-a-1']
 
         inventory = self.complex_inventory()
@@ -303,34 +303,34 @@ def test_complex_intersect(self):
 
     @raises(errors.AnsibleError)
     def test_invalid_range(self):
-        Inventory(os.path.join(self.test_dir, 'inventory','test_incorrect_range'))
+        Inventory(os.path.join(self.test_dir, 'inventory', 'test_incorrect_range'))
 
     @raises(errors.AnsibleError)
     def test_missing_end(self):
-        Inventory(os.path.join(self.test_dir, 'inventory','test_missing_end'))
+        Inventory(os.path.join(self.test_dir, 'inventory', 'test_missing_end'))
 
     @raises(errors.AnsibleError)
     def test_incorrect_format(self):
-        Inventory(os.path.join(self.test_dir, 'inventory','test_incorrect_format'))
+        Inventory(os.path.join(self.test_dir, 'inventory', 'test_incorrect_format'))
 
     @raises(errors.AnsibleError)
     def test_alpha_end_before_beg(self):
-        Inventory(os.path.join(self.test_dir, 'inventory','test_alpha_end_before_beg'))
+        Inventory(os.path.join(self.test_dir, 'inventory', 'test_alpha_end_before_beg'))
 
     def test_combined_range(self):
-        i = Inventory(os.path.join(self.test_dir, 'inventory','test_combined_range'))
+        i = Inventory(os.path.join(self.test_dir, 'inventory', 'test_combined_range'))
         hosts = i.list_hosts('test')
-        expected_hosts=['host1A','host2A','host1B','host2B']
+        expected_hosts = ['host1A', 'host2A', 'host1B', 'host2B']
         assert sorted(hosts) == sorted(expected_hosts)
 
     def test_leading_range(self):
-        i = Inventory(os.path.join(self.test_dir, 'inventory','test_leading_range'))
+        i = Inventory(os.path.join(self.test_dir, 'inventory', 'test_leading_range'))
         hosts = i.list_hosts('test')
-        expected_hosts=['1.host','2.host','A.host','B.host']
+        expected_hosts = ['1.host', '2.host', 'A.host', 'B.host']
         assert sorted(hosts) == sorted(expected_hosts)
 
         hosts2 = i.list_hosts('test2')
-        expected_hosts2=['1.host','2.host','3.host']
+        expected_hosts2 = ['1.host', '2.host', '3.host']
         assert sorted(hosts2) == sorted(expected_hosts2)
 
     ###################################################
@@ -340,38 +340,38 @@ def test_script(self):
         inventory = self.script_inventory()
         hosts = inventory.list_hosts()
 
-        expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
+        expected_hosts = ['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
 
-        print "Expected: %s"%(expected_hosts)
-        print "Got     : %s"%(hosts)
+        print "Expected: %s" % expected_hosts
+        print "Got     : %s" % hosts
         assert sorted(hosts) == sorted(expected_hosts)
 
     def test_script_all(self):
         inventory = self.script_inventory()
         hosts = inventory.list_hosts('all')
 
-        expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
+        expected_hosts = ['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
         assert sorted(hosts) == sorted(expected_hosts)
 
     def test_script_norse(self):
         inventory = self.script_inventory()
         hosts = inventory.list_hosts("norse")
 
-        expected_hosts=['thor', 'odin', 'loki']
+        expected_hosts = ['thor', 'odin', 'loki']
         assert sorted(hosts) == sorted(expected_hosts)
 
     def test_script_combined(self):
         inventory = self.script_inventory()
         hosts = inventory.list_hosts("norse:greek")
 
-        expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
+        expected_hosts = ['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
         assert sorted(hosts) == sorted(expected_hosts)
 
     def test_script_restrict(self):
         inventory = self.script_inventory()
 
         restricted_hosts = ['hera', 'poseidon', 'thor']
-        expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
+        expected_hosts = ['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
 
         inventory.restrict_to(restricted_hosts)
         hosts = inventory.list_hosts("norse:greek")
@@ -389,7 +389,7 @@ def test_script_vars(self):
 
         print "VARS=%s" % vars
 
-        assert vars == {'hammer':True,
+        assert vars == {'hammer': True,
                         'group_names': ['norse'],
                         'inventory_hostname': 'thor',
                         'inventory_hostname_short': 'thor'}
@@ -417,15 +417,24 @@ def test_allows_equals_sign_in_var(self):
         auth = inventory.get_variables('neptun')['auth']
         assert auth == 'YWRtaW46YWRtaW4='
 
-    # test disabled as needs to be updated to model desired behavior
-    #
-    #def test_dir_inventory(self):
-    #    inventory = self.dir_inventory()
-    #    vars = inventory.get_variables('zeus')
-    #
-    #   print "VARS=%s" % vars
-    #
-    #    assert vars == {'inventory_hostname': 'zeus',
-    #                    'inventory_hostname_short': 'zeus',
-    #                    'group_names': ['greek', 'major-god', 'ungrouped'],
-    #                    'var_a': '1#2'}
+    def test_dir_inventory(self):
+        inventory = self.dir_inventory()
+
+        host_vars = inventory.get_variables('zeus')
+
+        expected_vars = {'inventory_hostname': 'zeus',
+                         'inventory_hostname_short': 'zeus',
+                         'group_names': ['greek', 'major-god', 'ungrouped'],
+                         'var_a': '2#3'}
+
+        print "HOST     VARS=%s" % host_vars
+        print "EXPECTED VARS=%s" % expected_vars
+
+        assert host_vars == expected_vars
+
+    def test_dir_inventory_multiple_groups(self):
+        inventory = self.dir_inventory()
+        group_greek = inventory.get_hosts('greek')
+        actual_host_names = [host.name for host in group_greek]
+        print "greek : %s " % actual_host_names
+        assert actual_host_names == ['zeus', 'morpheus']
diff --git a/test/units/inventory_test_data/inventory_dir/0hosts b/test/units/inventory_test_data/inventory_dir/0hosts
--- a/test/units/inventory_test_data/inventory_dir/0hosts
+++ b/test/units/inventory_test_data/inventory_dir/0hosts
@@ -1,3 +1,3 @@
-zeus var_a=2
+zeus var_a=0
 morpheus
 thor
diff --git a/test/units/inventory_test_data/inventory_dir/3comments b/test/units/inventory_test_data/inventory_dir/3comments
--- a/test/units/inventory_test_data/inventory_dir/3comments
+++ b/test/units/inventory_test_data/inventory_dir/3comments
@@ -1,5 +1,8 @@
+[titan]
+cronus var_a="a#b" var_b="b#c" var_c="c#d" # Is this overkill?
+
 [major-god] # group with inline comments
-zeus var_a="1#2" # host with inline comments and "#" in the var string
+zeus var_a="2#3" # host with inline comments and "#" in the var string
 # A comment
 thor