Skip to content

Commit 416b8d5

Browse files
committed
Merge branch 'devel' of https://github.com/fangchin/ansible into devel
2 parents 6a0326b + 5315dd1 commit 416b8d5

File tree

8 files changed

+181
-16
lines changed

8 files changed

+181
-16
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ Ansible Changes By Release
33

44
0.6 "Cabo" ------------ pending
55

6+
* inventory file can use a line of the form base[beg:end]tail to define a
7+
set of hosts, where [beg:end] defines a numerical range. 'beg' can be a
8+
a string padded with zero(s) to the left. If so provided, it acts as
9+
a formatting hint during hostname expansion. The hint must be confirmed
10+
by having an 'end' that has the same length as 'beg'
611
* groups variable available as a hash to return the hosts in each group name
712
* fetch module now does not fail a system when requesting file paths (ex: logs) that don't exist
813
* apt module now takes an optional install-recommends=yes|no (default yes)

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# useful targets:
77
# make sdist ---------------- produce a tarball
88
# make rpm ----------------- produce RPMs
9-
# make debian --------------- produce a dpkg (FIXME?)
9+
# make deb ------------------ produce a DEB
1010
# make docs ----------------- rebuild the manpages (results are checked in)
1111
# make tests ---------------- run the tests
1212
# make pyflakes, make pep8 -- source code checks

docs/man/man1/ansible.1

+13-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,19 @@ Connection type to use\&. Possible options are
140140
.RE
141141
.SH "INVENTORY"
142142
.sp
143-
Ansible stores the hosts it can potentially operate on in an inventory file\&. The syntax is one host per line\&. Groups headers are allowed and are included on their own line, enclosed in square brackets\&.
143+
Ansible stores the hosts it can potentially operate on in an inventory
144+
file\&. The syntax is one host per line\&. Optionally, ansible can use a
145+
line of the form base[beg:end]tail to define a set of hosts, where
146+
[beg:end] defines a numerical range. If 'beg' is left out, it
147+
defaults to 0\&. An example: mail[1:6].example.com, where 'head'
148+
is 'mail', 'beg' is 1, 'end' is 6, and 'tail' is '.example.com'\&. In
149+
addition, 'beg' can be a a string padded with zero(s) to the left. If so
150+
provided, it acts as a formatting hint during hostname expansion. The usage
151+
must be confirmed by having an 'end' that has the same length as 'beg',
152+
else an exception is raised. An example: mail[001:003].example.com is to be
153+
expanded to mail001.example.com, mail002.example.com, and
154+
mail003.example.com\&. Groups headers are allowed and are included on their
155+
own line, enclosed in square brackets\&.
144156
.SH "FILES"
145157
.sp
146158
/etc/ansible/hosts \(em Default inventory file

examples/hosts

+8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ bikeshed.org
1616
bastion.secure.bikeshed.org
1717
192.168.100.1
1818
192.168.100.10
19+
# An example for host expansion that uses the default 'beg' and an 'end'
20+
mail[:5].example.com
1921

2022
# Ex 2: A collection of hosts belonging to the 'webservers' group
2123
[webservers]
@@ -26,6 +28,9 @@ wheel.colors.com
2628
192.168.1.110
2729
# Your personal website also runs a webserver:
2830
myserver.com
31+
# An example for host expansion that uses both a 'beg' and an 'end', with
32+
# the 'beg' acting as a formatting hint during host name expansion
33+
www[001:006].example.com
2934

3035
# Ex 3: A collection of database servers in the 'dbservers' group
3136
[dbservers]
@@ -35,3 +40,6 @@ db02.intranet.mydomain.net
3540
10.25.1.57
3641
# Perhaps you serve a db off your personal server too:
3742
myserver.com
43+
# An example for host expansion that uses a regular 'beg' and a regular
44+
# 'end'
45+
db-[99:101]-node.example.com

lib/ansible/inventory/expand_hosts.py

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# (c) 2012, Zettar Inc.
2+
# Written by Chin Fang <[email protected]>
3+
#
4+
# This file is part of Ansible
5+
#
6+
# This module is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This software is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this software. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
'''
20+
This module is for enhancing ansible's inventory parsing capability such
21+
that it can deal with hostnames specified using a simple pattern in the
22+
form of [beg:end], example: [1:5] where if beg is not specified, it
23+
defaults to 0.
24+
25+
If beg is given and is left-zero-padded, e.g. '001', it is taken as a
26+
formatting hint when the range is expanded. e.g. [001:010] is to be
27+
expanded into 001, 002 ...009, 010.
28+
29+
Note that when beg is specified with left zero padding, then the length of
30+
end must be the same as that of beg, else a exception is raised.
31+
'''
32+
33+
def detect_range(line = None):
34+
'''
35+
A helper function that checks a given host line to see if it contains
36+
a range pattern descibed in the docstring above.
37+
38+
Returnes True if the given line contains a pattern, else False.
39+
'''
40+
if (not line.startswith("[") and
41+
line.find("[") != -1 and
42+
line.find(":") != -1 and
43+
line.find("]") != -1 and
44+
line.index("[") < line.index(":") < line.index("]")):
45+
return True
46+
else:
47+
return False
48+
49+
def expand_hostname_range(line = None):
50+
'''
51+
A helper function that expands a given line that contains a pattern
52+
specified in top docstring, and returns a list that consists of the
53+
expanded version.
54+
55+
The '[' and ']' characters are used to maintain the pseudo-code
56+
appearance. They are replaced in this function with '|' to ease
57+
string splitting.
58+
59+
References: http://ansible.github.com/patterns.html#hosts-and-groups
60+
'''
61+
all_hosts = []
62+
if line:
63+
# A hostname such as db[1:6]-node is considered to consists
64+
# three parts:
65+
# head: 'db'
66+
# nrange: [1:6]; range() is a built-in. Can't use the name
67+
# tail: '-node'
68+
69+
(head, nrange, tail) = line.replace('[','|').replace(']','|').split('|')
70+
bounds = nrange.split(":")
71+
if len(bounds) != 2:
72+
raise ValueError("host range incorrectly specified!")
73+
beg = bounds[0]
74+
end = bounds[1]
75+
if not beg:
76+
beg = "0"
77+
if not end:
78+
raise ValueError("host range end value missing!")
79+
if beg[0] == '0' and len(beg) > 1:
80+
rlen = len(beg) # range length formatting hint
81+
else:
82+
rlen = None
83+
if rlen > 1 and rlen != len(end):
84+
raise ValueError("host range format incorrectly specified!")
85+
86+
for _ in range(int(beg), int(end)):
87+
if rlen:
88+
rseq = str(_).zfill(rlen) # range sequence
89+
else:
90+
rseq = str(_)
91+
hname = ''.join((head, rseq, tail))
92+
all_hosts.append(hname)
93+
94+
return all_hosts

lib/ansible/inventory/ini.py

+28-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import ansible.constants as C
2525
from ansible.inventory.host import Host
2626
from ansible.inventory.group import Group
27+
from ansible.inventory.expand_hosts import detect_range
28+
from ansible.inventory.expand_hosts import expand_hostname_range
2729
from ansible import errors
2830
from ansible import utils
2931

@@ -80,21 +82,40 @@ def _parse_base_groups(self):
8082
continue
8183
hostname = tokens[0]
8284
port = C.DEFAULT_REMOTE_PORT
83-
if hostname.find(":") != -1:
84-
tokens2 = hostname.split(":")
85-
hostname = tokens2[0]
86-
port = tokens2[1]
85+
# Two cases to check:
86+
# 0. A hostname that contains a range pesudo-code and a port
87+
# 1. A hostname that contains just a port
88+
if (hostname.find("[") != -1 and
89+
hostname.find("]") != -1 and
90+
hostname.find(":") != -1 and
91+
(hostname.rindex("]") < hostname.rindex(":")) or
92+
(hostname.find("]") == -1 and hostname.find(":") != -1)):
93+
tokens2 = hostname.rsplit(":", 1)
94+
hostname = tokens2[0]
95+
port = tokens2[1]
96+
8797
host = None
98+
_all_hosts = []
8899
if hostname in self.hosts:
89100
host = self.hosts[hostname]
101+
_all_hosts.append(host)
90102
else:
91-
host = Host(name=hostname, port=port)
92-
self.hosts[hostname] = host
103+
if detect_range(hostname):
104+
_hosts = expand_hostname_range(hostname)
105+
for _ in _hosts:
106+
host = Host(name=_, port=port)
107+
self.hosts[_] = host
108+
_all_hosts.append(host)
109+
else:
110+
host = Host(name=hostname, port=port)
111+
self.hosts[hostname] = host
112+
_all_hosts.append(host)
93113
if len(tokens) > 1:
94114
for t in tokens[1:]:
95115
(k,v) = t.split("=")
96116
host.set_variable(k,v)
97-
self.groups[active_group_name].add_host(host)
117+
for _ in _all_hosts:
118+
self.groups[active_group_name].add_host(_)
98119

99120
# [southeast:children]
100121
# atlanta

test/TestInventory.py

+29-7
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,24 @@ def test_simple(self):
4444
inventory = self.simple_inventory()
4545
hosts = inventory.list_hosts()
4646

47-
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
47+
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera',
48+
'cerberus001','cerberus002','cerberus003',
49+
'cottus99', 'cottus100',
50+
'poseidon', 'thor', 'odin', 'loki',
51+
'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
52+
'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5']
4853
assert sorted(hosts) == sorted(expected_hosts)
4954

5055
def test_simple_all(self):
5156
inventory = self.simple_inventory()
5257
hosts = inventory.list_hosts('all')
5358

54-
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
59+
expected_hosts=['jupiter', 'saturn', 'zeus', 'hera',
60+
'cerberus001','cerberus002','cerberus003',
61+
'cottus99', 'cottus100',
62+
'poseidon', 'thor', 'odin', 'loki',
63+
'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
64+
'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5']
5565
assert sorted(hosts) == sorted(expected_hosts)
5666

5767
def test_simple_norse(self):
@@ -65,21 +75,29 @@ def test_simple_ungrouped(self):
6575
inventory = self.simple_inventory()
6676
hosts = inventory.list_hosts("ungrouped")
6777

68-
expected_hosts=['jupiter', 'saturn']
78+
expected_hosts=['jupiter', 'saturn',
79+
'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
80+
'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5']
6981
assert sorted(hosts) == sorted(expected_hosts)
7082

7183
def test_simple_combined(self):
7284
inventory = self.simple_inventory()
7385
hosts = inventory.list_hosts("norse:greek")
7486

75-
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
87+
expected_hosts=['zeus', 'hera', 'poseidon',
88+
'cerberus001','cerberus002','cerberus003',
89+
'cottus99','cottus100',
90+
'thor', 'odin', 'loki']
7691
assert sorted(hosts) == sorted(expected_hosts)
7792

7893
def test_simple_restrict(self):
7994
inventory = self.simple_inventory()
8095

8196
restricted_hosts = ['hera', 'poseidon', 'thor']
82-
expected_hosts=['zeus', 'hera', 'poseidon', 'thor', 'odin', 'loki']
97+
expected_hosts=['zeus', 'hera', 'poseidon',
98+
'cerberus001','cerberus002','cerberus003',
99+
'cottus99', 'cottus100',
100+
'thor', 'odin', 'loki']
83101

84102
inventory.restrict_to(restricted_hosts)
85103
hosts = inventory.list_hosts("norse:greek")
@@ -99,11 +117,15 @@ def test_simple_exclude(self):
99117
inventory = self.simple_inventory()
100118

101119
hosts = inventory.list_hosts("all:!greek")
102-
expected_hosts=['jupiter', 'saturn', 'thor', 'odin', 'loki']
120+
expected_hosts=['jupiter', 'saturn', 'thor', 'odin', 'loki',
121+
'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
122+
'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5']
103123
assert sorted(hosts) == sorted(expected_hosts)
104124

105125
hosts = inventory.list_hosts("all:!norse:!greek")
106-
expected_hosts=['jupiter', 'saturn']
126+
expected_hosts=['jupiter', 'saturn',
127+
'thrudgelmir0', 'thrudgelmir1', 'thrudgelmir2',
128+
'thrudgelmir3', 'thrudgelmir4', 'thrudgelmir5']
107129
assert sorted(hosts) == sorted(expected_hosts)
108130

109131
def test_simple_vars(self):

test/simple_hosts

+3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
jupiter
22
saturn
3+
thrudgelmir[:6]
34

45
[greek]
56
zeus
67
hera:3000
78
poseidon
9+
cerberus[001:004]
10+
cottus[99:101]
811

912
[norse]
1013
thor

0 commit comments

Comments
 (0)