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.
This commit is contained in:
parent
19386c43a7
commit
8fc46a3a5a
4 changed files with 57 additions and 42 deletions
|
@ -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:
|
||||||
* 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 @@
|
||||||
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):
|
||||||
# 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):
|
||||||
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):
|
||||||
# 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):
|
||||||
|
|
||||||
(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):
|
||||||
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):
|
||||||
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):
|
||||||
|
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
|
|
Loading…
Reference in a new issue