Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
ansible
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
ansible
Commits
4c65306e
Commit
4c65306e
authored
Aug 28, 2015
by
James Cammarata
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'hostrange' of
https://github.com/amenonsen/ansible
into amenonsen-hostrange
parents
120243d3
8bf0dbb7
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
133 additions
and
75 deletions
+133
-75
docsite/rst/intro_patterns.rst
+10
-4
lib/ansible/inventory/__init__.py
+123
-71
No files found.
docsite/rst/intro_patterns.rst
View file @
4c65306e
...
...
@@ -68,13 +68,19 @@ It's also ok to mix wildcard patterns and groups at the same time::
one*.com:dbservers
As an advanced usage, you can also select the numbered server in a
group::
You can select a host or subset of hosts from a group by their position. For example, given the following
group::
webservers[0]
[webservers]
cobweb
webbing
weber
Or a range of servers in a group:
:
You can refer to hosts within the group by adding a subscript to the group name
:
webservers[0:25]
webservers[0] # == cobweb
webservers[-1] # == weber
webservers[0:1] # == webservers[0]:webservers[1]
# == cobweb:webbing
Most people don't specify patterns as regular expressions, but you can. Just start the pattern with a '~'::
...
...
lib/ansible/inventory/__init__.py
View file @
4c65306e
...
...
@@ -132,7 +132,6 @@ class Inventory(object):
for
host
in
self
.
get_hosts
():
host
.
vars
=
combine_vars
(
host
.
vars
,
self
.
get_host_variables
(
host
.
name
))
def
_match
(
self
,
str
,
pattern_str
):
try
:
if
pattern_str
.
startswith
(
'~'
):
...
...
@@ -205,6 +204,23 @@ class Inventory(object):
return
hosts
def
_split_pattern
(
self
,
pattern
):
"""
takes e.g. "webservers[0:5]:dbservers:others"
and returns ["webservers[0:5]", "dbservers", "others"]
"""
term
=
re
.
compile
(
r'''(?: # We want to match something comprising:
[^:\[\]] # (anything other than ':', '[', or ']'
| # ...or...
\[[^\]]*\] # a single complete bracketed expression)
)* # repeated as many times as possible
'''
,
re
.
X
)
return
[
x
for
x
in
term
.
findall
(
pattern
)
if
x
]
def
_evaluate_patterns
(
self
,
patterns
):
"""
Takes a list of patterns and returns a list of matching host names,
...
...
@@ -251,103 +267,125 @@ class Inventory(object):
def
_match_one_pattern
(
self
,
pattern
):
"""
Takes a single pattern (i.e., not "p1:p2") and returns a list of
matching hosts names. Does not take negatives or intersections
into account.
Takes a single pattern and returns a list of matching host names.
Ignores intersection (&) and exclusion (!) specifiers.
The pattern may be:
1. A regex starting with ~, e.g. '~[abc]*'
2. A shell glob pattern with ?/*/[chars]/[!chars], e.g. 'foo'
3. An ordinary word that matches itself only, e.g. 'foo'
The pattern is matched using the following rules:
1. If it's 'all', it matches all hosts in all groups.
2. Otherwise, for each known group name:
(a) if it matches the group name, the results include all hosts
in the group or any of its children.
(b) otherwise, if it matches any hosts in the group, the results
include the matching hosts.
This means that 'foo*' may match one or more groups (thus including all
hosts therein) but also hosts in other groups.
The built-in groups 'all' and 'ungrouped' are special. No pattern can
match these group names (though 'all' behaves as though it matches, as
described above). The word 'ungrouped' can match a host of that name,
and patterns like 'ungr*' and 'al*' can match either hosts or groups
other than all and ungrouped.
If the pattern matches one or more group names according to these rules,
it may have an optional range suffix to select a subset of the results.
This is allowed only if the pattern is not a regex, i.e. '~foo[1]' does
not work (the [1] is interpreted as part of the regex), but 'foo*[1]'
would work if 'foo*' matched the name of one or more groups.
Duplicate matches are always eliminated from the results.
"""
if
pattern
in
self
.
_pattern_cache
:
return
self
.
_pattern_cache
[
pattern
]
if
pattern
.
startswith
(
"&"
)
or
pattern
.
startswith
(
"!"
)
:
pattern
=
pattern
[
1
:
]
(
name
,
enumeration_details
)
=
self
.
_enumeration_info
(
pattern
)
hpat
=
self
.
_hosts_in_unenumerated_pattern
(
name
)
result
=
self
.
_apply_ranges
(
pattern
,
hpat
)
self
.
_pattern_cache
[
pattern
]
=
result
return
result
if
pattern
not
in
self
.
_pattern_cache
:
(
expr
,
slice
)
=
self
.
_split_subscript
(
pattern
)
hosts
=
self
.
_enumerate_matches
(
expr
)
try
:
hosts
=
self
.
_apply_subscript
(
hosts
,
slice
)
except
IndexError
:
raise
AnsibleError
(
"No hosts matched the subscripted pattern '
%
s'"
%
pattern
)
self
.
_pattern_cache
[
pattern
]
=
hosts
def
_enumeration_info
(
self
,
pattern
):
return
self
.
_pattern_cache
[
pattern
]
def
_split_subscript
(
self
,
pattern
):
"""
returns (pattern, limits) taking a regular pattern and finding out
which parts of it correspond to start/stop offsets. limits is
a tuple of (start, stop) or None
Takes a pattern, checks if it has a subscript, and returns the pattern
without the subscript and a (start,end) tuple representing the given
subscript (or None if there is no subscript).
Validates that the subscript is in the right syntax, but doesn't make
sure the actual indices make sense in context.
"""
# Do not parse regexes for enumeration info
if
pattern
.
startswith
(
'~'
):
return
(
pattern
,
None
)
# The regex used to match on the range, which can be [x] or [x-y].
pattern_re
=
re
.
compile
(
"^(.*)
\
[([-]?[0-9]+)(?:(?:-)([0-9]+))?
\
](.*)$"
)
m
=
pattern_re
.
match
(
pattern
)
# We want a pattern followed by an integer or range subscript.
# (We can't be more restrictive about the expression because the
# fnmatch semantics permit [\[:\]] to occur.)
pattern_with_subscript
=
re
.
compile
(
r'''^
(.+) # A pattern expression ending with...
\[(?: # A [subscript] expression comprising:
(-?[0-9]+) # A single positive or negative number
| # Or a numeric range
([0-9]+)([:-])([0-9]+)
)\]
$
'''
,
re
.
X
)
subscript
=
None
m
=
pattern_with_subscript
.
match
(
pattern
)
if
m
:
(
target
,
first
,
last
,
rest
)
=
m
.
groups
()
first
=
int
(
first
)
if
last
:
if
first
<
0
:
raise
AnsibleError
(
"invalid range: negative indices cannot be used as the first item in a range"
)
last
=
int
(
last
)
else
:
last
=
first
return
(
target
,
(
first
,
last
))
(
pattern
,
idx
,
start
,
sep
,
end
)
=
m
.
groups
()
if
idx
:
subscript
=
(
int
(
idx
),
None
)
else
:
return
(
pattern
,
None
)
subscript
=
(
int
(
start
),
int
(
end
))
if
sep
==
'-'
:
display
.
deprecated
(
"Use [x:y] inclusive subscripts instead of [x-y]"
,
version
=
2.0
,
removed
=
True
)
return
(
pattern
,
subscript
)
def
_apply_
ranges
(
self
,
pat
,
hosts
):
def
_apply_
subscript
(
self
,
hosts
,
subscript
):
"""
given a pattern like foo, that matches hosts, return all of hosts
given a pattern like foo[0:5], where foo matches hosts, return the first 6 hosts
Takes a list of hosts and a (start,end) tuple and returns the subset of
hosts based on the subscript (which may be None to return all hosts).
"""
# If there are no hosts to select from, just return the
# empty set. This prevents trying to do selections on an empty set.
# issue#6258
if
not
hosts
:
if
not
hosts
or
not
subscript
:
return
hosts
(
loose_pattern
,
limits
)
=
self
.
_enumeration_info
(
pat
)
if
not
limits
:
return
hosts
(
start
,
end
)
=
subscript
(
left
,
right
)
=
limits
if
left
==
''
:
left
=
0
if
right
==
''
:
right
=
0
left
=
int
(
left
)
right
=
int
(
right
)
try
:
if
left
!=
right
:
return
hosts
[
left
:
right
]
if
end
:
return
hosts
[
start
:
end
+
1
]
else
:
return
[
hosts
[
left
]
]
except
IndexError
:
raise
AnsibleError
(
"no hosts matching the pattern '
%
s' were found"
%
pat
)
return
[
hosts
[
start
]
]
def
_create_implicit_localhost
(
self
,
pattern
):
new_host
=
Host
(
pattern
)
new_host
.
set_variable
(
"ansible_python_interpreter"
,
sys
.
executable
)
new_host
.
set_variable
(
"ansible_connection"
,
"local"
)
new_host
.
ipv4_address
=
'127.0.0.1'
ungrouped
=
self
.
get_group
(
"ungrouped"
)
if
ungrouped
is
None
:
self
.
add_group
(
Group
(
'ungrouped'
))
ungrouped
=
self
.
get_group
(
'ungrouped'
)
self
.
get_group
(
'all'
)
.
add_child_group
(
ungrouped
)
ungrouped
.
add_host
(
new_host
)
return
new_host
def
_hosts_in_unenumerated_pattern
(
self
,
pattern
):
""" Get all host names matching the pattern """
def
_enumerate_matches
(
self
,
pattern
):
"""
Returns a list of host names matching the given pattern according to the
rules explained above in _match_one_pattern.
"""
results
=
[]
hosts
=
[]
hostnames
=
set
()
# ignore any negative checks here, this is handled elsewhere
pattern
=
pattern
.
replace
(
"!"
,
""
)
.
replace
(
"&"
,
""
)
def
__append_host_to_results
(
host
):
if
host
.
name
not
in
hostnames
:
hostnames
.
add
(
host
.
name
)
...
...
@@ -372,6 +410,20 @@ class Inventory(object):
results
.
append
(
new_host
)
return
results
def
_create_implicit_localhost
(
self
,
pattern
):
new_host
=
Host
(
pattern
)
new_host
.
set_variable
(
"ansible_python_interpreter"
,
sys
.
executable
)
new_host
.
set_variable
(
"ansible_connection"
,
"local"
)
new_host
.
ipv4_address
=
'127.0.0.1'
ungrouped
=
self
.
get_group
(
"ungrouped"
)
if
ungrouped
is
None
:
self
.
add_group
(
Group
(
'ungrouped'
))
ungrouped
=
self
.
get_group
(
'ungrouped'
)
self
.
get_group
(
'all'
)
.
add_child_group
(
ungrouped
)
ungrouped
.
add_host
(
new_host
)
return
new_host
def
clear_pattern_cache
(
self
):
''' called exclusively by the add_host plugin to allow patterns to be recalculated '''
self
.
_pattern_cache
=
{}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment