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
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
135 additions
and
77 deletions
+135
-77
docsite/rst/intro_patterns.rst
+11
-5
lib/ansible/inventory/__init__.py
+124
-72
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::
...
@@ -68,13 +68,19 @@ It's also ok to mix wildcard patterns and groups at the same time::
one*.com:dbservers
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]
Or a range of servers in a group::
[webservers]
cobweb
webbing
weber
webservers[0:25]
You can refer to hosts within the group by adding a subscript to the group name:
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 '~'::
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):
...
@@ -132,7 +132,6 @@ class Inventory(object):
for
host
in
self
.
get_hosts
():
for
host
in
self
.
get_hosts
():
host
.
vars
=
combine_vars
(
host
.
vars
,
self
.
get_host_variables
(
host
.
name
))
host
.
vars
=
combine_vars
(
host
.
vars
,
self
.
get_host_variables
(
host
.
name
))
def
_match
(
self
,
str
,
pattern_str
):
def
_match
(
self
,
str
,
pattern_str
):
try
:
try
:
if
pattern_str
.
startswith
(
'~'
):
if
pattern_str
.
startswith
(
'~'
):
...
@@ -205,6 +204,23 @@ class Inventory(object):
...
@@ -205,6 +204,23 @@ class Inventory(object):
return
hosts
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
):
def
_evaluate_patterns
(
self
,
patterns
):
"""
"""
Takes a list of patterns and returns a list of matching host names,
Takes a list of patterns and returns a list of matching host names,
...
@@ -251,103 +267,125 @@ class Inventory(object):
...
@@ -251,103 +267,125 @@ class Inventory(object):
def
_match_one_pattern
(
self
,
pattern
):
def
_match_one_pattern
(
self
,
pattern
):
"""
"""
Takes a single pattern (i.e., not "p1:p2") and returns a list of
Takes a single pattern and returns a list of matching host names.
matching hosts names. Does not take negatives or intersections
Ignores intersection (&) and exclusion (!) specifiers.
into account.
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
:
if
pattern
.
startswith
(
"&"
)
or
pattern
.
startswith
(
"!"
)
:
return
self
.
_pattern_cache
[
pattern
]
pattern
=
pattern
[
1
:
]
(
name
,
enumeration_details
)
=
self
.
_enumeration_info
(
pattern
)
if
pattern
not
in
self
.
_pattern_cache
:
hpat
=
self
.
_hosts_in_unenumerated_pattern
(
name
)
(
expr
,
slice
)
=
self
.
_split_subscript
(
pattern
)
result
=
self
.
_apply_ranges
(
pattern
,
hpat
)
hosts
=
self
.
_enumerate_matches
(
expr
)
self
.
_pattern_cache
[
pattern
]
=
result
try
:
return
result
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
Takes a pattern, checks if it has a subscript, and returns the pattern
which parts of it correspond to start/stop offsets. limits is
without the subscript and a (start,end) tuple representing the given
a tuple of (start, stop) or None
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
# Do not parse regexes for enumeration info
if
pattern
.
startswith
(
'~'
):
if
pattern
.
startswith
(
'~'
):
return
(
pattern
,
None
)
return
(
pattern
,
None
)
# The regex used to match on the range, which can be [x] or [x-y].
# We want a pattern followed by an integer or range subscript.
pattern_re
=
re
.
compile
(
"^(.*)
\
[([-]?[0-9]+)(?:(?:-)([0-9]+))?
\
](.*)$"
)
# (We can't be more restrictive about the expression because the
m
=
pattern_re
.
match
(
pattern
)
# 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
:
if
m
:
(
target
,
first
,
last
,
rest
)
=
m
.
groups
()
(
pattern
,
idx
,
start
,
sep
,
end
)
=
m
.
groups
()
first
=
int
(
first
)
if
idx
:
if
last
:
subscript
=
(
int
(
idx
),
None
)
if
first
<
0
:
raise
AnsibleError
(
"invalid range: negative indices cannot be used as the first item in a range"
)
last
=
int
(
last
)
else
:
else
:
last
=
first
subscript
=
(
int
(
start
),
int
(
end
))
return
(
target
,
(
first
,
last
))
if
sep
==
'-'
:
else
:
display
.
deprecated
(
"Use [x:y] inclusive subscripts instead of [x-y]"
,
version
=
2.0
,
removed
=
True
)
return
(
pattern
,
None
)
def
_apply_ranges
(
self
,
pat
,
hosts
):
return
(
pattern
,
subscript
)
def
_apply_subscript
(
self
,
hosts
,
subscript
):
"""
"""
given a pattern like foo, that matches hosts, return all of hosts
Takes a list of hosts and a (start,end) tuple and returns the subset of
given a pattern like foo[0:5], where foo matches hosts, return the first 6 hosts
hosts based on the subscript (which may be None to return all hosts).
"""
"""
# If there are no hosts to select from, just return the
if
not
hosts
or
not
subscript
:
# empty set. This prevents trying to do selections on an empty set.
# issue#6258
if
not
hosts
:
return
hosts
return
hosts
(
loose_pattern
,
limits
)
=
self
.
_enumeration_info
(
pat
)
(
start
,
end
)
=
subscript
if
not
limits
:
return
hosts
(
left
,
right
)
=
limits
if
end
:
return
hosts
[
start
:
end
+
1
]
if
left
==
''
:
else
:
left
=
0
return
[
hosts
[
start
]
]
if
right
==
''
:
right
=
0
left
=
int
(
left
)
right
=
int
(
right
)
try
:
if
left
!=
right
:
return
hosts
[
left
:
right
]
else
:
return
[
hosts
[
left
]
]
except
IndexError
:
raise
AnsibleError
(
"no hosts matching the pattern '
%
s' were found"
%
pat
)
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
):
def
_enumerate_matches
(
self
,
pattern
):
""" Get all host names matching the pattern """
"""
Returns a list of host names matching the given pattern according to the
rules explained above in _match_one_pattern.
"""
results
=
[]
results
=
[]
hosts
=
[]
hosts
=
[]
hostnames
=
set
()
hostnames
=
set
()
# ignore any negative checks here, this is handled elsewhere
pattern
=
pattern
.
replace
(
"!"
,
""
)
.
replace
(
"&"
,
""
)
def
__append_host_to_results
(
host
):
def
__append_host_to_results
(
host
):
if
host
.
name
not
in
hostnames
:
if
host
.
name
not
in
hostnames
:
hostnames
.
add
(
host
.
name
)
hostnames
.
add
(
host
.
name
)
...
@@ -372,6 +410,20 @@ class Inventory(object):
...
@@ -372,6 +410,20 @@ class Inventory(object):
results
.
append
(
new_host
)
results
.
append
(
new_host
)
return
results
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
):
def
clear_pattern_cache
(
self
):
''' called exclusively by the add_host plugin to allow patterns to be recalculated '''
''' called exclusively by the add_host plugin to allow patterns to be recalculated '''
self
.
_pattern_cache
=
{}
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