diff --git a/CHANGELOG.md b/CHANGELOG.md index def8f2b963a..b75a925f729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Highlighted new features: New modules: -* cloud:ec2_eip -- manage AWS elastic IP's +* cloud:ec2_eip -- manage AWS elastic IPs * cloud:rax_clb -- manage Rackspace cloud load balancers * system: firewalld -- manage the firewalld configuration * system: host -- manage `/etc/hosts` file entries @@ -28,9 +28,11 @@ Misc changes: * Added `ansible_env` to the list of facts returned by the setup module. * Added `state=touch` to the file module, which functions similarly to the command-line version of `touch`. * Added a -vvvv level, which will show SSH client debugging information in the event of a failure. -* Includes now support the more standard syntax, similar to that of role includes and dependencies. It is no longer necessary to specify a special "vas" field for the variables passed to the include. +* Includes now support the more standard syntax, similar to that of role includes and dependencies. It is no longer necessary to specify a special "vars" field for the variables passed to the include. * Changed the `user:` parameter on plays to `remote_user:` to prevent confusion with the module of the same name. Still backwards compatible on play parameters. * Added parameter to allow the fetch module to skip the md5 validation step ('validate_md5=false'). This is usefull when fetching files that are actively being written to, such as live log files. +* Inventory hosts are used in the order they appear in the inventory. +* in hosts: foo[2-5] type syntax, the iterators now are zero indexed and the last index is non-inclusive, to match Python standards. 1.3 "Top of the World" - September 13th, 2013 diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py index 16d11246ffa..e33334380a6 100644 --- a/lib/ansible/inventory/__init__.py +++ b/lib/ansible/inventory/__init__.py @@ -20,8 +20,8 @@ import fnmatch import os import re - import subprocess + import ansible.constants as C from ansible.inventory.ini import InventoryParser from ansible.inventory.script import InventoryScript @@ -132,7 +132,11 @@ class Inventory(object): # exclude hosts not in a subset, if defined if self._subset: subset = self._get_hosts(self._subset) - hosts.intersection_update(subset) + new_hosts = [] + for h in hosts: + if h in subset and h not in new_hosts: + new_hosts.append(h) + hosts = new_hosts # exclude hosts mentioned in any restriction (ex: failed hosts) if self._restriction is not None: @@ -140,7 +144,7 @@ class Inventory(object): if self._also_restriction is not None: hosts = [ h for h in hosts if h.name in self._also_restriction ] - return sorted(hosts, key=lambda x: x.name) + return hosts def _get_hosts(self, patterns): """ @@ -169,17 +173,19 @@ class Inventory(object): # first, then the &s, then the !s. patterns = pattern_regular + pattern_intersection + pattern_exclude - hosts = set() + hosts = [] + for p in patterns: + that = self.__get_hosts(p) if p.startswith("!"): - # Discard excluded hosts - hosts.difference_update(self.__get_hosts(p)) + hosts = [ h for h in hosts if h not in that ] elif p.startswith("&"): - # Only leave the intersected hosts - hosts.intersection_update(self.__get_hosts(p)) + hosts = [ h for h in hosts if h in that ] else: - # Get all hosts from both patterns - hosts.update(self.__get_hosts(p)) + for h in that: + if h not in hosts: + hosts.append(h) + return hosts def __get_hosts(self, pattern): @@ -190,9 +196,7 @@ class Inventory(object): (name, enumeration_details) = self._enumeration_info(pattern) hpat = self._hosts_in_unenumerated_pattern(name) - hpat = sorted(hpat, key=lambda x: x.name) - - return set(self._apply_ranges(pattern, hpat)) + return self._apply_ranges(pattern, hpat) def _enumeration_info(self, pattern): """ @@ -205,9 +209,18 @@ class Inventory(object): return (pattern, None) (first, rest) = pattern.split("[") rest = rest.replace("]","") + try: + # support selectors like webservers[0] + x = int(rest) + return (first, (x,x)) + except: + pass if "-" in rest: (left, right) = rest.split("-",1) return (first, (left, right)) + elif ":" in rest: + (left, right) = rest.split(":",1) + return (first, (left, right)) else: return (first, (rest, rest)) @@ -222,30 +235,34 @@ class Inventory(object): return hosts (left, right) = limits - enumerated = enumerate(hosts) + if left == '': left = 0 if right == '': right = 0 left=int(left) right=int(right) - enumerated = [ h for (i,h) in enumerated if i>=left and i<=right ] - return enumerated + if left != right: + return hosts[left:right] + else: + return [ hosts[left] ] # TODO: cache this logic so if called a second time the result is not recalculated def _hosts_in_unenumerated_pattern(self, pattern): """ Get all host names matching the pattern """ - hosts = {} + hosts = [] # ignore any negative checks here, this is handled elsewhere pattern = pattern.replace("!","").replace("&", "") + results = [] groups = self.get_groups() for group in groups: for host in group.get_hosts(): if pattern == 'all' or self._match(group.name, pattern) or self._match(host.name, pattern): - hosts[host.name] = host - return sorted(hosts.values(), key=lambda x: x.name) + if host not in results: + results.append(host) + return results def groups_for_host(self, host): results = [] diff --git a/lib/ansible/inventory/group.py b/lib/ansible/inventory/group.py index 61ef1342bd2..cafb76e6002 100644 --- a/lib/ansible/inventory/group.py +++ b/lib/ansible/inventory/group.py @@ -53,11 +53,16 @@ class Group(object): def get_hosts(self): - hosts = set() + hosts = [] for kid in self.child_groups: - hosts.update(kid.get_hosts()) - hosts.update(self.hosts) - return list(hosts) + kid_hosts = kid.get_hosts() + for kk in kid_hosts: + if kk not in hosts: + hosts.append(kk) + for mine in self.hosts: + if mine not in hosts: + hosts.append(mine) + return hosts def get_variables(self): return self.vars.copy() diff --git a/test/TestInventory.py b/test/TestInventory.py index 8194ce3e4e6..8cc0c67c2a7 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -126,15 +126,11 @@ class TestInventory(unittest.TestCase): inventory.restrict_to(restricted_hosts) hosts = inventory.list_hosts("norse:greek") - print "Hosts=%s" % hosts - print "Restricted=%s" % restricted_hosts assert sorted(hosts) == sorted(restricted_hosts) inventory.lift_restriction() hosts = inventory.list_hosts("norse:greek") - print hosts - print expected_hosts assert sorted(hosts) == sorted(expected_hosts) def test_simple_string_ipv4(self): @@ -171,7 +167,6 @@ class TestInventory(unittest.TestCase): inventory = self.simple_inventory() vars = inventory.get_variables('thor') - print vars assert vars == {'group_names': ['norse'], 'inventory_hostname': 'thor', 'inventory_hostname_short': 'thor'} @@ -180,12 +175,10 @@ class TestInventory(unittest.TestCase): inventory = self.simple_inventory() vars = inventory.get_variables('hera') - print vars expected = { 'ansible_ssh_port': 3000, 'group_names': ['greek'], 'inventory_hostname': 'hera', 'inventory_hostname_short': 'hera' } - print expected assert vars == expected def test_large_range(self): @@ -257,21 +250,19 @@ class TestInventory(unittest.TestCase): def test_complex_enumeration(self): - expected1 = ['rtp_a', 'rtp_b'] - expected2 = ['rtp_c', 'tri_a'] - expected3 = ['rtp_b', 'rtp_c', 'tri_a', 'tri_b', 'tri_c'] - expected4 = ['orlando', 'rtp_c', 'tri_a'] + 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' ] inventory = self.complex_inventory() - print "ALL NC=%s" % inventory.list_hosts("nc") - # use "-1" instead of 0-1 to test the syntax, on purpose - hosts = inventory.list_hosts("nc[-1]") + hosts = inventory.list_hosts("nc[1]") self.compare(hosts, expected1, sort=False) - hosts = inventory.list_hosts("nc[2-3]") + hosts = inventory.list_hosts("nc[0-2]") self.compare(hosts, expected2, sort=False) - hosts = inventory.list_hosts("nc[1-99999]") + hosts = inventory.list_hosts("nc[0-99999]") self.compare(hosts, expected3, sort=False) - hosts = inventory.list_hosts("nc[2-3]:florida[1-2]") + hosts = inventory.list_hosts("nc[1-2]:florida[0-1]") self.compare(hosts, expected4, sort=False) def test_complex_intersect(self):