Commit 8fc46a3a by Michael DeHaan

Return inventory objects in the order they are presented. Additionally, fix…

Return inventory objects in the order they are presented.  Additionally, fix host slicing such that it works on Python terms with
zero indexed lists and a non-inclusive final element.
parent 19386c43
...@@ -14,7 +14,7 @@ Highlighted new features: ...@@ -14,7 +14,7 @@ Highlighted new features:
New modules: 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 * cloud:rax_clb -- manage Rackspace cloud load balancers
* system: firewalld -- manage the firewalld configuration * system: firewalld -- manage the firewalld configuration
* system: host -- manage `/etc/hosts` file entries * system: host -- manage `/etc/hosts` file entries
...@@ -28,9 +28,11 @@ Misc changes: ...@@ -28,9 +28,11 @@ Misc changes:
* Added `ansible_env` to the list of facts returned by the setup module. * 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 `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. * 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. * 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. * 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 1.3 "Top of the World" - September 13th, 2013
......
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
import fnmatch import fnmatch
import os import os
import re import re
import subprocess import subprocess
import ansible.constants as C import ansible.constants as C
from ansible.inventory.ini import InventoryParser from ansible.inventory.ini import InventoryParser
from ansible.inventory.script import InventoryScript from ansible.inventory.script import InventoryScript
...@@ -132,7 +132,11 @@ class Inventory(object): ...@@ -132,7 +132,11 @@ class Inventory(object):
# exclude hosts not in a subset, if defined # exclude hosts not in a subset, if defined
if self._subset: if self._subset:
subset = self._get_hosts(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) # exclude hosts mentioned in any restriction (ex: failed hosts)
if self._restriction is not None: if self._restriction is not None:
...@@ -140,7 +144,7 @@ class Inventory(object): ...@@ -140,7 +144,7 @@ class Inventory(object):
if self._also_restriction is not None: if self._also_restriction is not None:
hosts = [ h for h in hosts if h.name in self._also_restriction ] 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): def _get_hosts(self, patterns):
""" """
...@@ -169,17 +173,19 @@ class Inventory(object): ...@@ -169,17 +173,19 @@ class Inventory(object):
# first, then the &s, then the !s. # first, then the &s, then the !s.
patterns = pattern_regular + pattern_intersection + pattern_exclude patterns = pattern_regular + pattern_intersection + pattern_exclude
hosts = set() hosts = []
for p in patterns: for p in patterns:
that = self.__get_hosts(p)
if p.startswith("!"): if p.startswith("!"):
# Discard excluded hosts hosts = [ h for h in hosts if h not in that ]
hosts.difference_update(self.__get_hosts(p))
elif p.startswith("&"): elif p.startswith("&"):
# Only leave the intersected hosts hosts = [ h for h in hosts if h in that ]
hosts.intersection_update(self.__get_hosts(p))
else: else:
# Get all hosts from both patterns for h in that:
hosts.update(self.__get_hosts(p)) if h not in hosts:
hosts.append(h)
return hosts return hosts
def __get_hosts(self, pattern): def __get_hosts(self, pattern):
...@@ -190,9 +196,7 @@ class Inventory(object): ...@@ -190,9 +196,7 @@ class Inventory(object):
(name, enumeration_details) = self._enumeration_info(pattern) (name, enumeration_details) = self._enumeration_info(pattern)
hpat = self._hosts_in_unenumerated_pattern(name) hpat = self._hosts_in_unenumerated_pattern(name)
hpat = sorted(hpat, key=lambda x: x.name) return self._apply_ranges(pattern, hpat)
return set(self._apply_ranges(pattern, hpat))
def _enumeration_info(self, pattern): def _enumeration_info(self, pattern):
""" """
...@@ -205,9 +209,18 @@ class Inventory(object): ...@@ -205,9 +209,18 @@ class Inventory(object):
return (pattern, None) return (pattern, None)
(first, rest) = pattern.split("[") (first, rest) = pattern.split("[")
rest = rest.replace("]","") rest = rest.replace("]","")
try:
# support selectors like webservers[0]
x = int(rest)
return (first, (x,x))
except:
pass
if "-" in rest: if "-" in rest:
(left, right) = rest.split("-",1) (left, right) = rest.split("-",1)
return (first, (left, right)) return (first, (left, right))
elif ":" in rest:
(left, right) = rest.split(":",1)
return (first, (left, right))
else: else:
return (first, (rest, rest)) return (first, (rest, rest))
...@@ -222,30 +235,34 @@ class Inventory(object): ...@@ -222,30 +235,34 @@ class Inventory(object):
return hosts return hosts
(left, right) = limits (left, right) = limits
enumerated = enumerate(hosts)
if left == '': if left == '':
left = 0 left = 0
if right == '': if right == '':
right = 0 right = 0
left=int(left) left=int(left)
right=int(right) right=int(right)
enumerated = [ h for (i,h) in enumerated if i>=left and i<=right ] if left != right:
return enumerated return hosts[left:right]
else:
return [ hosts[left] ]
# TODO: cache this logic so if called a second time the result is not recalculated # TODO: cache this logic so if called a second time the result is not recalculated
def _hosts_in_unenumerated_pattern(self, pattern): def _hosts_in_unenumerated_pattern(self, pattern):
""" Get all host names matching the pattern """ """ Get all host names matching the pattern """
hosts = {} hosts = []
# ignore any negative checks here, this is handled elsewhere # ignore any negative checks here, this is handled elsewhere
pattern = pattern.replace("!","").replace("&", "") pattern = pattern.replace("!","").replace("&", "")
results = []
groups = self.get_groups() groups = self.get_groups()
for group in groups: for group in groups:
for host in group.get_hosts(): for host in group.get_hosts():
if pattern == 'all' or self._match(group.name, pattern) or self._match(host.name, pattern): if pattern == 'all' or self._match(group.name, pattern) or self._match(host.name, pattern):
hosts[host.name] = host if host not in results:
return sorted(hosts.values(), key=lambda x: x.name) results.append(host)
return results
def groups_for_host(self, host): def groups_for_host(self, host):
results = [] results = []
......
...@@ -53,11 +53,16 @@ class Group(object): ...@@ -53,11 +53,16 @@ class Group(object):
def get_hosts(self): def get_hosts(self):
hosts = set() hosts = []
for kid in self.child_groups: for kid in self.child_groups:
hosts.update(kid.get_hosts()) kid_hosts = kid.get_hosts()
hosts.update(self.hosts) for kk in kid_hosts:
return list(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): def get_variables(self):
return self.vars.copy() return self.vars.copy()
......
...@@ -126,15 +126,11 @@ class TestInventory(unittest.TestCase): ...@@ -126,15 +126,11 @@ class TestInventory(unittest.TestCase):
inventory.restrict_to(restricted_hosts) inventory.restrict_to(restricted_hosts)
hosts = inventory.list_hosts("norse:greek") hosts = inventory.list_hosts("norse:greek")
print "Hosts=%s" % hosts
print "Restricted=%s" % restricted_hosts
assert sorted(hosts) == sorted(restricted_hosts) assert sorted(hosts) == sorted(restricted_hosts)
inventory.lift_restriction() inventory.lift_restriction()
hosts = inventory.list_hosts("norse:greek") hosts = inventory.list_hosts("norse:greek")
print hosts
print expected_hosts
assert sorted(hosts) == sorted(expected_hosts) assert sorted(hosts) == sorted(expected_hosts)
def test_simple_string_ipv4(self): def test_simple_string_ipv4(self):
...@@ -171,7 +167,6 @@ class TestInventory(unittest.TestCase): ...@@ -171,7 +167,6 @@ class TestInventory(unittest.TestCase):
inventory = self.simple_inventory() inventory = self.simple_inventory()
vars = inventory.get_variables('thor') vars = inventory.get_variables('thor')
print vars
assert vars == {'group_names': ['norse'], assert vars == {'group_names': ['norse'],
'inventory_hostname': 'thor', 'inventory_hostname': 'thor',
'inventory_hostname_short': 'thor'} 'inventory_hostname_short': 'thor'}
...@@ -180,12 +175,10 @@ class TestInventory(unittest.TestCase): ...@@ -180,12 +175,10 @@ class TestInventory(unittest.TestCase):
inventory = self.simple_inventory() inventory = self.simple_inventory()
vars = inventory.get_variables('hera') vars = inventory.get_variables('hera')
print vars
expected = { 'ansible_ssh_port': 3000, expected = { 'ansible_ssh_port': 3000,
'group_names': ['greek'], 'group_names': ['greek'],
'inventory_hostname': 'hera', 'inventory_hostname': 'hera',
'inventory_hostname_short': 'hera' } 'inventory_hostname_short': 'hera' }
print expected
assert vars == expected assert vars == expected
def test_large_range(self): def test_large_range(self):
...@@ -257,21 +250,19 @@ class TestInventory(unittest.TestCase): ...@@ -257,21 +250,19 @@ class TestInventory(unittest.TestCase):
def test_complex_enumeration(self): def test_complex_enumeration(self):
expected1 = ['rtp_a', 'rtp_b'] expected1 = ['rtp_b']
expected2 = ['rtp_c', 'tri_a'] expected2 = ['rtp_a', 'rtp_b']
expected3 = ['rtp_b', 'rtp_c', 'tri_a', 'tri_b', 'tri_c'] expected3 = ['rtp_a', 'rtp_b', 'rtp_c', 'tri_a', 'tri_b', 'tri_c']
expected4 = ['orlando', 'rtp_c', 'tri_a'] expected4 = ['rtp_b', 'orlando' ]
inventory = self.complex_inventory() inventory = self.complex_inventory()
print "ALL NC=%s" % inventory.list_hosts("nc") hosts = inventory.list_hosts("nc[1]")
# use "-1" instead of 0-1 to test the syntax, on purpose
hosts = inventory.list_hosts("nc[-1]")
self.compare(hosts, expected1, sort=False) 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) 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) 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) self.compare(hosts, expected4, sort=False)
def test_complex_intersect(self): def test_complex_intersect(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment