From 6603737e4d52f09c83c75cbf34385cd033e9076f Mon Sep 17 00:00:00 2001 From: "Norman J. Harman Jr" Date: Mon, 10 Dec 2012 20:48:38 -0600 Subject: [PATCH] Alphabetic inventory hostname patterns. - Code, docs, tests. - Also added test of large range 000-142 to verify alpha range did not break this. --- docsite/rst/patterns.rst | 13 ++++---- lib/ansible/inventory/expand_hosts.py | 29 +++++++++++------- test/TestInventory.py | 44 +++++++++++++++------------ test/large_range | 1 + test/simple_hosts | 4 +++ 5 files changed, 54 insertions(+), 37 deletions(-) create mode 100644 test/large_range diff --git a/docsite/rst/patterns.rst b/docsite/rst/patterns.rst index a4fc4536dc..0beed4adc0 100644 --- a/docsite/rst/patterns.rst +++ b/docsite/rst/patterns.rst @@ -48,8 +48,9 @@ Adding a lot of hosts? In 0.6 and later, if you have a lot of hosts following s [webservers] www[01:50].example.com + db-[a:f].example.com -Leading zeros can be included or removed, as desired, and the ranges are inclusive. +For numeric patterns, leading zeros can be included or removed, as desired. Ranges are inclusive. Selecting Targets +++++++++++++++++ @@ -57,7 +58,7 @@ Selecting Targets We'll go over how to use the command line in :doc:`examples` section, however, basically it looks like this:: ansible -m -a - + Such as:: ansible webservers -m service -a "name=httpd state=restarted" @@ -70,7 +71,7 @@ This is done by designating particular host names or groups of hosts. The following patterns target all hosts in the inventory file:: all - * + * Basically 'all' is an alias for '*'. It is also possible to address a specific host or hosts:: @@ -78,7 +79,7 @@ Basically 'all' is an alias for '*'. It is also possible to address a specific one.example.com:two.example.com 192.168.1.50 192.168.1.* - + The following patterns address one or more groups, which are denoted with the aforementioned bracket headers in the inventory file:: @@ -105,7 +106,7 @@ Host Variables ++++++++++++++ It is easy to assign variables to hosts that will be used later in playbooks:: - + [atlanta] host1 http_port=80 maxRequestsPerChild=808 host2 http_port=303 maxRequestsPerChild=909 @@ -188,7 +189,7 @@ the 'raleigh' group might look like:: It is ok if these files do not exist, this is an optional feature. -Tip: Keeping your inventory file and variables in a git repo (or other version control) +Tip: Keeping your inventory file and variables in a git repo (or other version control) is an excellent way to track changes to your inventory and host variables. .. versionadded:: 0.5 diff --git a/lib/ansible/inventory/expand_hosts.py b/lib/ansible/inventory/expand_hosts.py index 718cec3caf..b71be68689 100644 --- a/lib/ansible/inventory/expand_hosts.py +++ b/lib/ansible/inventory/expand_hosts.py @@ -20,8 +20,8 @@ ''' This module is for enhancing ansible's inventory parsing capability such that it can deal with hostnames specified using a simple pattern in the -form of [beg:end], example: [1:5] where if beg is not specified, it -defaults to 0. +form of [beg:end], example: [1:5], [a:c], [D:G]. If beg is not specified, +it defaults to 0. If beg is given and is left-zero-padded, e.g. '001', it is taken as a formatting hint when the range is expanded. e.g. [001:010] is to be @@ -30,6 +30,7 @@ expanded into 001, 002 ...009, 010. Note that when beg is specified with left zero padding, then the length of end must be the same as that of beg, else a exception is raised. ''' +import string from ansible import errors @@ -81,17 +82,23 @@ def expand_hostname_range(line = None): raise errors.AnsibleError("host range end value missing") if beg[0] == '0' and len(beg) > 1: rlen = len(beg) # range length formatting hint + if rlen != len(end): + raise errors.AnsibleError("host range format incorrectly specified!") + fill = lambda _: str(_).zfill(rlen) # range sequence else: - rlen = None - if rlen > 1 and rlen != len(end): - raise errors.AnsibleError("host range format incorrectly specified!") + fill = str - for _ in range(int(beg), int(end)+1): - if rlen: - rseq = str(_).zfill(rlen) # range sequence - else: - rseq = str(_) - hname = ''.join((head, rseq, tail)) + try: + i_beg = string.ascii_letters.index(beg) + i_end = string.ascii_letters.index(end) + if i_beg > i_end: + raise errors.AnsibleError("host range format incorrectly specified!") + seq = string.ascii_letters[i_beg:i_end+1] + except ValueError: # not a alpha range + seq = range(int(beg), int(end)+1) + + for rseq in seq: + hname = ''.join((head, fill(rseq), tail)) all_hosts.append(hname) return all_hosts diff --git a/test/TestInventory.py b/test/TestInventory.py index 5c444d9e96..06dffd1fd7 100644 --- a/test/TestInventory.py +++ b/test/TestInventory.py @@ -9,9 +9,10 @@ class TestInventory(unittest.TestCase): self.cwd = os.getcwd() self.test_dir = os.path.join(self.cwd, 'test') - self.inventory_file = os.path.join(self.test_dir, 'simple_hosts') - self.complex_inventory_file = os.path.join(self.test_dir, 'complex_hosts') - self.inventory_script = os.path.join(self.test_dir, 'inventory_api.py') + self.inventory_file = os.path.join(self.test_dir, 'simple_hosts') + self.large_range_inventory_file = os.path.join(self.test_dir, 'large_range') + self.complex_inventory_file = os.path.join(self.test_dir, 'complex_hosts') + self.inventory_script = os.path.join(self.test_dir, 'inventory_api.py') os.chmod(self.inventory_script, 0755) @@ -29,38 +30,36 @@ class TestInventory(unittest.TestCase): def simple_inventory(self): return Inventory(self.inventory_file) + def large_range_inventory(self): + return Inventory(self.large_range_inventory_file) + def script_inventory(self): return Inventory(self.inventory_script) def complex_inventory(self): return Inventory(self.complex_inventory_file) + 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', ] + ##################################### ### Simple inventory format tests def test_simple(self): inventory = self.simple_inventory() hosts = inventory.list_hosts() - - expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', - 'cerberus001','cerberus002','cerberus003', - 'cottus99', 'cottus100', - 'poseidon', 'thor', 'odin', 'loki', - 'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2', - 'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5'] - assert sorted(hosts) == sorted(expected_hosts) + self.assertEqual(sorted(hosts), sorted(self.all_simple_hosts)) def test_simple_all(self): inventory = self.simple_inventory() hosts = inventory.list_hosts('all') - - expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', - 'cerberus001','cerberus002','cerberus003', - 'cottus99', 'cottus100', - 'poseidon', 'thor', 'odin', 'loki', - 'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2', - 'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5'] - assert sorted(hosts) == sorted(expected_hosts) + self.assertEqual(sorted(hosts), sorted(self.all_simple_hosts)) def test_simple_norse(self): inventory = self.simple_inventory() @@ -132,6 +131,11 @@ class TestInventory(unittest.TestCase): print expected 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))) + ################################################### ### INI file advanced tests @@ -252,7 +256,7 @@ class TestInventory(unittest.TestCase): vars = inventory.get_variables('zeus') print "VARS=%s" % vars - + assert vars == {'inventory_hostname': 'zeus', 'inventory_hostname_short': 'zeus', 'group_names': ['greek', 'major-god']} diff --git a/test/large_range b/test/large_range new file mode 100644 index 0000000000..18cfc22078 --- /dev/null +++ b/test/large_range @@ -0,0 +1 @@ +bob[000:142] diff --git a/test/simple_hosts b/test/simple_hosts index c774afd4b4..669d92a32a 100644 --- a/test/simple_hosts +++ b/test/simple_hosts @@ -13,3 +13,7 @@ cottus[99:100] thor odin loki + +[egyptian] +Hotep-[a:c] +Bast[C:D]