|
|
|
@ -89,31 +89,99 @@ class Inventory(object):
|
|
|
|
|
def _match(self, str, pattern_str):
|
|
|
|
|
return fnmatch.fnmatch(str, pattern_str)
|
|
|
|
|
|
|
|
|
|
# TODO: cache this logic so if called a second time the result is not recalculated
|
|
|
|
|
def get_hosts(self, pattern="all"):
|
|
|
|
|
""" Get all host objects matching the pattern """
|
|
|
|
|
hosts = {}
|
|
|
|
|
"""
|
|
|
|
|
find all host names matching a pattern string, taking into account any inventory restrictions or
|
|
|
|
|
applied subsets.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# process patterns
|
|
|
|
|
patterns = pattern.replace(";",":").split(":")
|
|
|
|
|
positive_patterns = [ p for p in patterns if not p.startswith("!") ]
|
|
|
|
|
negative_patterns = [ p for p in patterns if p.startswith("!") ]
|
|
|
|
|
|
|
|
|
|
# find hosts matching positive patterns
|
|
|
|
|
hosts = self._get_hosts(positive_patterns)
|
|
|
|
|
|
|
|
|
|
# exclude hosts mentioned in a negative pattern
|
|
|
|
|
if len(negative_patterns):
|
|
|
|
|
exclude_hosts = self._get_hosts(negative_patterns)
|
|
|
|
|
print "EXCLUDING HOSTS: %s" % exclude_hosts
|
|
|
|
|
hosts = [ h for h in hosts if h not in exclude_hosts ]
|
|
|
|
|
|
|
|
|
|
# exclude hosts not in a subset, if defined
|
|
|
|
|
if self._subset:
|
|
|
|
|
positive_subsetp = [ p for p in self._subset if not p.startswith("!") ]
|
|
|
|
|
negative_subsetp = [ p for p in self._subset if p.startswith("!") ]
|
|
|
|
|
if len(positive_subsetp):
|
|
|
|
|
positive_subset = self._get_hosts(positive_subsetp)
|
|
|
|
|
hosts = [ h for h in hosts if (h in positive_subset) ]
|
|
|
|
|
if len(negative_subsetp):
|
|
|
|
|
negative_subset = self._get_hosts(negative_subsetp)
|
|
|
|
|
hosts = [ h for h in hosts if (h not in negative_subset)]
|
|
|
|
|
|
|
|
|
|
# exclude hosts mentioned in any restriction (ex: failed hosts)
|
|
|
|
|
if self._restriction is not None:
|
|
|
|
|
hosts = [ h for h in hosts if h.name in self._restriction ]
|
|
|
|
|
|
|
|
|
|
return hosts
|
|
|
|
|
|
|
|
|
|
def _get_hosts(self, patterns):
|
|
|
|
|
"""
|
|
|
|
|
finds hosts that postively match a particular list of patterns. Does not
|
|
|
|
|
take into account negative matches.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
by_pattern = {}
|
|
|
|
|
for p in patterns:
|
|
|
|
|
(name, enumeration_details) = self._enumeration_info(p)
|
|
|
|
|
by_pattern[p] = self._hosts_in_unenumerated_pattern(name)
|
|
|
|
|
|
|
|
|
|
ranged = {}
|
|
|
|
|
for (pat, hosts) in by_pattern.iteritems():
|
|
|
|
|
ranged[pat] = self._apply_ranges(pat, hosts)
|
|
|
|
|
|
|
|
|
|
results = []
|
|
|
|
|
for (pat, hosts) in ranged.iteritems():
|
|
|
|
|
results.extend(hosts)
|
|
|
|
|
|
|
|
|
|
return list(set(results))
|
|
|
|
|
|
|
|
|
|
def _enumeration_info(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
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if not "[" in pattern:
|
|
|
|
|
return (pattern, None)
|
|
|
|
|
(first, rest) = pattern.split("[")
|
|
|
|
|
rest.replace("]","")
|
|
|
|
|
if not "-" in rest:
|
|
|
|
|
raise errors.AnsibleError("invalid pattern: %s" % pattern)
|
|
|
|
|
(left, right) = rest.split("-",1)
|
|
|
|
|
return (first, (left, right))
|
|
|
|
|
|
|
|
|
|
def _apply_ranges(self, pat, hosts):
|
|
|
|
|
(loose_pattern, limits) = self._enumeration_info(pat)
|
|
|
|
|
if not limits:
|
|
|
|
|
return hosts
|
|
|
|
|
raise Exception("ranges are not yet supported")
|
|
|
|
|
|
|
|
|
|
# TODO: cache this logic so if called a second time the result is not recalculated
|
|
|
|
|
def _hosts_in_unenumerated_pattern(self, pattern):
|
|
|
|
|
""" Get all host names matching the pattern """
|
|
|
|
|
|
|
|
|
|
hosts = {}
|
|
|
|
|
# ignore any negative checks here, this is handled elsewhere
|
|
|
|
|
pattern = pattern.replace("!","")
|
|
|
|
|
|
|
|
|
|
groups = self.get_groups()
|
|
|
|
|
for pat in patterns:
|
|
|
|
|
if pat.startswith("!"):
|
|
|
|
|
pat = pat[1:]
|
|
|
|
|
inverted = True
|
|
|
|
|
else:
|
|
|
|
|
inverted = False
|
|
|
|
|
for group in groups:
|
|
|
|
|
for host in group.get_hosts():
|
|
|
|
|
if self._subset and host.name not in self._subset:
|
|
|
|
|
continue
|
|
|
|
|
if pat == 'all' or self._match(group.name, pat) or self._match(host.name, pat):
|
|
|
|
|
# must test explicitly for None because [] means no hosts allowed
|
|
|
|
|
if self._restriction==None or host.name in self._restriction:
|
|
|
|
|
if inverted:
|
|
|
|
|
if host.name in hosts:
|
|
|
|
|
del hosts[host.name]
|
|
|
|
|
else:
|
|
|
|
|
hosts[host.name] = host
|
|
|
|
|
for group in groups:
|
|
|
|
|
for host in group.get_hosts():
|
|
|
|
|
if pattern == 'all' or self._match(group.name, pattern) or self._match(host.name, pattern):
|
|
|
|
|
hosts[host.name] = host
|
|
|
|
|
return sorted(hosts.values(), key=lambda x: x.name)
|
|
|
|
|
|
|
|
|
|
def get_groups(self):
|
|
|
|
@ -210,7 +278,7 @@ class Inventory(object):
|
|
|
|
|
if subset_pattern is None:
|
|
|
|
|
self._subset = None
|
|
|
|
|
else:
|
|
|
|
|
self._subset = self.list_hosts(subset_pattern)
|
|
|
|
|
self._subset = subset_pattern.replace(";",":").split(":")
|
|
|
|
|
|
|
|
|
|
def lift_restriction(self):
|
|
|
|
|
""" Do not restrict list operations """
|
|
|
|
|