2014-01-04 18:31:44 +00:00
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
2012-04-13 12:39:54 +00:00
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#############################################
2015-05-04 02:47:26 +00:00
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
2012-04-13 12:39:54 +00:00
import fnmatch
2012-05-06 18:47:05 +00:00
import os
2014-01-20 23:26:14 +00:00
import sys
2012-11-27 15:36:58 +00:00
import re
2015-09-17 16:52:54 +00:00
import itertools
2013-10-07 19:06:15 +00:00
2015-10-16 00:55:23 +00:00
from ansible . compat . six import string_types
2015-09-23 07:00:27 +00:00
2015-05-04 02:47:26 +00:00
from ansible import constants as C
2015-07-18 19:24:44 +00:00
from ansible . errors import AnsibleError
2015-05-04 02:47:26 +00:00
2015-07-18 19:24:44 +00:00
from ansible . inventory . dir import InventoryDirectory , get_file_parser
2012-05-25 23:34:13 +00:00
from ansible . inventory . group import Group
from ansible . inventory . host import Host
2015-05-04 02:47:26 +00:00
from ansible . plugins import vars_loader
2016-04-30 05:19:12 +00:00
from ansible . utils . unicode import to_unicode , to_bytes
2015-05-04 02:47:26 +00:00
from ansible . utils . vars import combine_vars
2015-08-31 05:07:09 +00:00
from ansible . parsing . utils . addresses import parse_address
2012-04-13 12:39:54 +00:00
2015-11-04 21:15:02 +00:00
HOSTS_PATTERNS_CACHE = { }
2015-08-13 18:50:28 +00:00
try :
from __main__ import display
except ImportError :
from ansible . utils . display import Display
display = Display ( )
2012-04-13 12:39:54 +00:00
class Inventory ( object ) :
2012-08-07 00:07:02 +00:00
"""
2012-05-05 20:31:03 +00:00
Host inventory for ansible .
2012-04-13 12:39:54 +00:00
"""
2015-05-04 02:47:26 +00:00
def __init__ ( self , loader , variable_manager , host_list = C . DEFAULT_HOST_LIST ) :
2012-04-13 12:39:54 +00:00
2012-05-08 04:27:37 +00:00
# the host file file, or script path, or list of hosts
# if a list, inventory data will NOT be loaded
2012-05-06 18:47:05 +00:00
self . host_list = host_list
2015-05-04 02:47:26 +00:00
self . _loader = loader
self . _variable_manager = variable_manager
2016-06-08 18:01:47 +00:00
self . localhost = None
2012-05-08 03:16:20 +00:00
2012-07-22 15:53:19 +00:00
# caching to avoid repeated calculations, particularly with
# external inventory scripts.
2012-08-07 00:07:02 +00:00
2012-07-22 15:53:19 +00:00
self . _vars_per_host = { }
self . _vars_per_group = { }
self . _hosts_cache = { }
2013-10-12 02:24:37 +00:00
self . _pattern_cache = { }
2015-07-18 19:24:44 +00:00
self . _vars_plugins = [ ]
2012-07-22 15:53:19 +00:00
2016-05-27 14:43:11 +00:00
self . _basedir = self . basedir ( )
# Contains set of filenames under group_vars directories
self . _group_vars_files = self . _find_group_vars_files ( self . _basedir )
self . _host_vars_files = self . _find_host_vars_files ( self . _basedir )
2014-03-19 10:09:38 +00:00
# to be set by calling set_playbook_basedir by playbook code
2013-06-01 14:38:16 +00:00
self . _playbook_basedir = None
2012-05-08 03:16:20 +00:00
# the inventory object holds a list of groups
2015-09-16 20:36:11 +00:00
self . groups = { }
2012-08-07 00:07:02 +00:00
2012-05-08 03:16:20 +00:00
# a list of host(names) to contain current inquiries to
2012-05-05 20:31:03 +00:00
self . _restriction = None
2012-08-10 06:45:29 +00:00
self . _subset = None
2012-05-08 03:16:20 +00:00
2016-01-20 23:23:30 +00:00
# clear the cache here, which is only useful if more than
# one Inventory objects are created when using the API directly
self . clear_pattern_cache ( )
2015-07-18 19:24:44 +00:00
self . parse_inventory ( host_list )
2016-06-08 18:01:47 +00:00
if self . localhost is None :
self . localhost = self . _create_implicit_localhost ( )
2015-07-18 19:24:44 +00:00
2015-11-04 16:26:06 +00:00
def serialize ( self ) :
data = dict ( )
return data
def deserialize ( self , data ) :
pass
2015-07-18 19:24:44 +00:00
def parse_inventory ( self , host_list ) :
2015-09-23 07:00:27 +00:00
if isinstance ( host_list , string_types ) :
2013-05-23 16:37:30 +00:00
if " , " in host_list :
2012-07-15 13:32:47 +00:00
host_list = host_list . split ( " , " )
2012-07-18 20:56:41 +00:00
host_list = [ h for h in host_list if h and h . strip ( ) ]
2012-05-10 03:26:45 +00:00
2015-08-31 05:07:09 +00:00
self . parser = None
2015-09-16 15:27:03 +00:00
# Always create the 'all' and 'ungrouped' groups, even if host_list is
# empty: in this case we will subsequently an the implicit 'localhost' to it.
2015-09-16 10:24:06 +00:00
2016-04-07 20:22:36 +00:00
ungrouped = Group ( ' ungrouped ' )
2015-09-16 10:24:06 +00:00
all = Group ( ' all ' )
2015-09-16 15:27:03 +00:00
all . add_child_group ( ungrouped )
self . groups = dict ( all = all , ungrouped = ungrouped )
2015-09-16 10:24:06 +00:00
2013-09-25 12:59:11 +00:00
if host_list is None :
2015-08-31 05:07:09 +00:00
pass
2013-09-25 12:59:11 +00:00
elif isinstance ( host_list , list ) :
2015-08-31 05:07:09 +00:00
for h in host_list :
2015-12-21 18:06:48 +00:00
try :
( host , port ) = parse_address ( h , allow_ranges = False )
except AnsibleError as e :
2016-01-01 14:55:51 +00:00
display . vvv ( " Unable to parse address from hostname, leaving unchanged: %s " % to_unicode ( e ) )
2015-12-21 18:06:48 +00:00
host = h
port = None
2016-06-08 18:01:47 +00:00
new_host = Host ( host , port )
all . add_host ( new_host )
if new_host . name in C . LOCALHOST :
if self . localhost is None :
self . localhost = new_host
else :
display . warning ( " A duplicate localhost-like entry was found ( %s ). First found localhost was %s " % ( new_host . name , self . localhost . name ) )
2015-09-04 20:41:38 +00:00
elif self . _loader . path_exists ( host_list ) :
2015-07-18 19:24:44 +00:00
#TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins'
2015-10-13 16:03:06 +00:00
if self . is_directory ( host_list ) :
2013-02-28 15:58:09 +00:00
# Ensure basedir is inside the directory
2015-07-18 19:24:44 +00:00
host_list = os . path . join ( self . host_list , " " )
2015-09-16 15:27:03 +00:00
self . parser = InventoryDirectory ( loader = self . _loader , groups = self . groups , filename = host_list )
2012-08-07 00:07:02 +00:00
else :
2015-09-16 15:27:03 +00:00
self . parser = get_file_parser ( host_list , self . groups , self . _loader )
2016-05-27 14:43:11 +00:00
vars_loader . add_directory ( self . _basedir , with_subdir = True )
2013-02-28 15:58:09 +00:00
2015-09-16 20:36:11 +00:00
if not self . parser :
2015-07-18 19:24:44 +00:00
# should never happen, but JIC
raise AnsibleError ( " Unable to parse %s as an inventory source " % host_list )
2016-02-29 21:18:06 +00:00
else :
display . warning ( " Host file not found: %s " % to_unicode ( host_list ) )
2012-08-07 00:07:02 +00:00
2015-08-26 15:40:33 +00:00
self . _vars_plugins = [ x for x in vars_loader . all ( self ) ]
2013-04-20 13:59:40 +00:00
2016-04-07 20:22:36 +00:00
# set group vars from group_vars/ files and vars plugins
for g in self . groups :
group = self . groups [ g ]
2015-05-04 02:47:26 +00:00
group . vars = combine_vars ( group . vars , self . get_group_variables ( group . name ) )
2016-06-06 07:38:37 +00:00
self . get_group_vars ( group )
2014-03-17 17:05:30 +00:00
2016-05-16 05:09:56 +00:00
# get host vars from host_vars/ files and vars plugins
for host in self . get_hosts ( ignore_limits_and_restrictions = True ) :
2015-05-04 02:47:26 +00:00
host . vars = combine_vars ( host . vars , self . get_host_variables ( host . name ) )
2016-06-06 07:38:37 +00:00
self . get_host_vars ( host )
2014-03-17 17:05:30 +00:00
2012-05-05 20:31:03 +00:00
def _match ( self , str , pattern_str ) :
2014-08-14 17:13:40 +00:00
try :
if pattern_str . startswith ( ' ~ ' ) :
return re . search ( pattern_str [ 1 : ] , str )
else :
return fnmatch . fnmatch ( str , pattern_str )
2015-10-19 18:13:56 +00:00
except Exception :
2015-07-18 19:24:44 +00:00
raise AnsibleError ( ' invalid host pattern: %s ' % pattern_str )
2012-05-05 20:31:03 +00:00
2014-06-27 03:40:31 +00:00
def _match_list ( self , items , item_attr , pattern_str ) :
results = [ ]
2014-08-14 17:13:40 +00:00
try :
if not pattern_str . startswith ( ' ~ ' ) :
2014-08-14 17:26:52 +00:00
pattern = re . compile ( fnmatch . translate ( pattern_str ) )
2014-08-14 17:13:40 +00:00
else :
pattern = re . compile ( pattern_str [ 1 : ] )
2015-10-19 18:13:56 +00:00
except Exception :
2015-07-18 19:24:44 +00:00
raise AnsibleError ( ' invalid host pattern: %s ' % pattern_str )
2014-08-14 17:13:40 +00:00
2014-06-27 03:40:31 +00:00
for item in items :
2014-08-14 17:26:52 +00:00
if pattern . match ( getattr ( item , item_attr ) ) :
2014-06-27 03:40:31 +00:00
results . append ( item )
return results
2015-08-26 18:41:52 +00:00
def get_hosts ( self , pattern = " all " , ignore_limits_and_restrictions = False ) :
2012-08-11 15:36:59 +00:00
"""
2015-08-13 12:11:47 +00:00
Takes a pattern or list of patterns and returns a list of matching
inventory host names , taking into account any active restrictions
or applied subsets
2012-08-11 15:36:59 +00:00
"""
2015-11-04 21:15:02 +00:00
# Check if pattern already computed
2015-11-10 01:55:13 +00:00
if isinstance ( pattern , list ) :
pattern_hash = u " : " . join ( pattern )
else :
pattern_hash = pattern
2015-11-10 13:23:31 +00:00
if not ignore_limits_and_restrictions :
if self . _subset :
2015-11-10 13:28:45 +00:00
pattern_hash + = u " : %s " % to_unicode ( self . _subset )
2015-11-10 13:23:31 +00:00
if self . _restriction :
2015-11-10 13:28:45 +00:00
pattern_hash + = u " : %s " % to_unicode ( self . _restriction )
2015-11-10 01:55:13 +00:00
2015-12-05 15:10:25 +00:00
if pattern_hash not in HOSTS_PATTERNS_CACHE :
2015-11-04 21:15:02 +00:00
2015-12-05 15:10:25 +00:00
patterns = Inventory . split_host_pattern ( pattern )
hosts = self . _evaluate_patterns ( patterns )
2012-08-11 15:36:59 +00:00
2015-12-05 15:10:25 +00:00
# mainly useful for hostvars[host] access
if not ignore_limits_and_restrictions :
# exclude hosts not in a subset, if defined
if self . _subset :
subset = self . _evaluate_patterns ( self . _subset )
hosts = [ h for h in hosts if h in subset ]
# exclude hosts mentioned in any restriction (ex: failed hosts)
if self . _restriction is not None :
2016-05-05 12:02:13 +00:00
hosts = [ h for h in hosts if h . name in self . _restriction ]
2012-08-11 15:36:59 +00:00
2015-12-11 20:35:57 +00:00
seen = set ( )
HOSTS_PATTERNS_CACHE [ pattern_hash ] = [ x for x in hosts if x not in seen and not seen . add ( x ) ]
2012-08-11 15:36:59 +00:00
2015-12-05 14:28:37 +00:00
return HOSTS_PATTERNS_CACHE [ pattern_hash ] [ : ]
2012-08-11 15:36:59 +00:00
2015-10-15 15:19:11 +00:00
@classmethod
def split_host_pattern ( cls , pattern ) :
2015-09-17 09:12:16 +00:00
"""
Takes a string containing host patterns separated by commas ( or a list
thereof ) and returns a list of single patterns ( which may not contain
commas ) . Whitespace is ignored .
Also accepts ' : ' as a separator for backwards compatibility , but it is
not recommended due to the conflict with IPv6 addresses and host ranges .
Example : ' a,b[1], c[2:3] , d ' - > [ ' a ' , ' b[1] ' , ' c[2:3] ' , ' d ' ]
"""
if isinstance ( pattern , list ) :
2015-10-15 15:19:11 +00:00
return list ( itertools . chain ( * map ( cls . split_host_pattern , pattern ) ) )
2015-09-17 09:12:16 +00:00
if ' ; ' in pattern :
2015-10-31 20:25:57 +00:00
patterns = re . split ( ' \ s*; \ s* ' , pattern )
display . deprecated ( " Use ' , ' or ' : ' instead of ' ; ' to separate host patterns " )
2015-09-17 09:12:16 +00:00
# If it's got commas in it, we'll treat it as a straightforward
# comma-separated list of patterns.
elif ' , ' in pattern :
patterns = re . split ( ' \ s*, \ s* ' , pattern )
# If it doesn't, it could still be a single pattern. This accounts for
# non-separator uses of colons: IPv6 addresses and [x:y] host ranges.
else :
2015-12-21 18:06:48 +00:00
try :
( base , port ) = parse_address ( pattern , allow_ranges = True )
2015-09-17 09:12:16 +00:00
patterns = [ pattern ]
2015-12-21 18:06:48 +00:00
except :
# The only other case we accept is a ':'-separated list of patterns.
# This mishandles IPv6 addresses, and is retained only for backwards
# compatibility.
2015-09-17 09:12:16 +00:00
patterns = re . findall (
r ''' (?: # We want to match something comprising:
[ ^ \s : \[ \] ] # (anything other than whitespace or ':[]'
| # ...or...
\[ [ ^ \] ] * \] # a single complete bracketed expression)
) + # occurring once or more
''' , pattern, re.X
)
return [ p . strip ( ) for p in patterns ]
2015-10-15 15:19:11 +00:00
@classmethod
def order_patterns ( cls , patterns ) :
2013-08-08 16:07:57 +00:00
# Host specifiers should be sorted to ensure consistent behavior
pattern_regular = [ ]
pattern_intersection = [ ]
pattern_exclude = [ ]
for p in patterns :
if p . startswith ( " ! " ) :
pattern_exclude . append ( p )
elif p . startswith ( " & " ) :
pattern_intersection . append ( p )
2014-07-08 02:08:39 +00:00
elif p :
2013-08-08 16:07:57 +00:00
pattern_regular . append ( p )
# if no regular pattern was given, hence only exclude and/or intersection
# make that magically work
if pattern_regular == [ ] :
pattern_regular = [ ' all ' ]
# when applying the host selectors, run those without the "&" or "!"
# first, then the &s, then the !s.
2015-10-15 15:19:11 +00:00
return pattern_regular + pattern_intersection + pattern_exclude
def _evaluate_patterns ( self , patterns ) :
"""
Takes a list of patterns and returns a list of matching host names ,
taking into account any negative and intersection patterns .
"""
2012-08-11 15:36:59 +00:00
2015-10-15 15:19:11 +00:00
patterns = Inventory . order_patterns ( patterns )
2015-11-05 20:41:17 +00:00
hosts = [ ]
2013-10-07 19:06:15 +00:00
2012-08-11 15:36:59 +00:00
for p in patterns :
2014-03-26 15:24:54 +00:00
# avoid resolving a pattern that is a plain host
if p in self . _hosts_cache :
2015-11-05 20:40:52 +00:00
hosts . append ( self . get_host ( p ) )
2012-12-10 14:54:07 +00:00
else :
2015-08-13 12:11:47 +00:00
that = self . _match_one_pattern ( p )
2014-03-26 15:24:54 +00:00
if p . startswith ( " ! " ) :
2015-11-05 20:41:17 +00:00
hosts = [ h for h in hosts if h not in that ]
2014-03-26 15:24:54 +00:00
elif p . startswith ( " & " ) :
2015-11-05 20:41:17 +00:00
hosts = [ h for h in hosts if h in that ]
2014-03-26 15:24:54 +00:00
else :
2015-11-05 20:41:17 +00:00
to_append = [ h for h in that if h . name not in [ y . name for y in hosts ] ]
hosts . extend ( to_append )
2012-12-10 14:54:07 +00:00
return hosts
2012-08-11 15:36:59 +00:00
2015-08-13 12:11:47 +00:00
def _match_one_pattern ( self , pattern ) :
2012-12-10 14:54:07 +00:00
"""
2015-08-23 14:36:06 +00:00
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]* '
2015-09-17 09:12:16 +00:00
2. A shell glob pattern with ? / * / [ chars ] / [ ! chars ] , e . g . ' foo* '
2015-08-23 14:36:06 +00:00
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 .
2012-12-10 14:54:07 +00:00
"""
2012-08-11 15:36:59 +00:00
2015-08-23 08:24:22 +00:00
if pattern . startswith ( " & " ) or pattern . startswith ( " ! " ) :
pattern = pattern [ 1 : ]
2015-08-23 18:30:37 +00:00
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
2013-10-12 02:24:37 +00:00
2015-08-23 18:30:37 +00:00
return self . _pattern_cache [ pattern ]
2012-08-11 15:36:59 +00:00
2015-08-23 18:30:37 +00:00
def _split_subscript ( self , pattern ) :
2012-08-11 15:36:59 +00:00
"""
2015-08-23 18:30:37 +00:00
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 .
2012-08-11 15:36:59 +00:00
"""
2014-07-10 00:08:12 +00:00
# Do not parse regexes for enumeration info
if pattern . startswith ( ' ~ ' ) :
return ( pattern , None )
2015-08-23 18:30:37 +00:00
# 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:
2015-09-18 16:58:34 +00:00
( - ? [ 0 - 9 ] + ) | # A single positive or negative number
( [ 0 - 9 ] + ) ( [ : - ] ) # Or an x:y or x: range.
( [ 0 - 9 ] * )
2015-08-23 18:30:37 +00:00
) \]
$
''' , re.X
)
subscript = None
m = pattern_with_subscript . match ( pattern )
2014-02-05 19:43:52 +00:00
if m :
2015-08-23 18:30:37 +00:00
( pattern , idx , start , sep , end ) = m . groups ( )
if idx :
subscript = ( int ( idx ) , None )
2014-02-05 19:43:52 +00:00
else :
2015-09-18 16:58:34 +00:00
if not end :
end = - 1
2015-08-23 18:30:37 +00:00
subscript = ( int ( start ) , int ( end ) )
if sep == ' - ' :
2015-12-09 20:37:56 +00:00
display . warning ( " Use [x:y] inclusive subscripts instead of [x-y] which has been removed " )
2012-08-11 15:36:59 +00:00
2015-08-23 18:30:37 +00:00
return ( pattern , subscript )
2012-08-11 15:36:59 +00:00
2015-08-23 18:30:37 +00:00
def _apply_subscript ( self , hosts , subscript ) :
2012-08-11 17:49:18 +00:00
"""
2015-08-23 18:30:37 +00:00
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 ) .
2012-08-11 17:49:18 +00:00
"""
2015-08-23 18:30:37 +00:00
if not hosts or not subscript :
2012-08-11 15:36:59 +00:00
return hosts
2012-08-11 17:49:18 +00:00
2015-08-23 18:30:37 +00:00
( start , end ) = subscript
2015-05-04 02:47:26 +00:00
2015-08-23 18:30:37 +00:00
if end :
2015-09-18 16:58:34 +00:00
if end == - 1 :
end = len ( hosts ) - 1
2015-08-23 18:30:37 +00:00
return hosts [ start : end + 1 ]
else :
return [ hosts [ start ] ]
2014-01-30 18:45:19 +00:00
2015-08-23 18:30:37 +00:00
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 .
"""
2012-08-11 15:36:59 +00:00
2014-06-27 03:40:31 +00:00
results = [ ]
2014-01-07 01:07:31 +00:00
hostnames = set ( )
2014-01-06 19:19:20 +00:00
2014-06-27 03:40:31 +00:00
def __append_host_to_results ( host ) :
2015-01-17 17:49:13 +00:00
if host . name not in hostnames :
2014-06-27 03:40:31 +00:00
hostnames . add ( host . name )
results . append ( host )
2013-10-12 02:24:37 +00:00
groups = self . get_groups ( )
2015-09-16 20:36:11 +00:00
for group in groups . values ( ) :
2014-06-27 03:40:31 +00:00
if pattern == ' all ' :
for host in group . get_hosts ( ) :
2016-06-04 23:53:47 +00:00
if host . implicit :
continue
2014-06-27 03:40:31 +00:00
__append_host_to_results ( host )
else :
2015-06-17 20:25:58 +00:00
if self . _match ( group . name , pattern ) and group . name not in ( ' all ' , ' ungrouped ' ) :
2014-06-27 03:40:31 +00:00
for host in group . get_hosts ( ) :
2016-06-04 23:53:47 +00:00
if host . implicit :
continue
2014-06-27 03:40:31 +00:00
__append_host_to_results ( host )
else :
matching_hosts = self . _match_list ( group . get_hosts ( ) , ' name ' , pattern )
for host in matching_hosts :
__append_host_to_results ( host )
2014-01-20 23:26:14 +00:00
2013-10-12 02:24:37 +00:00
return results
2012-05-05 20:31:03 +00:00
2016-06-08 18:01:47 +00:00
def _create_implicit_localhost ( self , pattern = ' localhost ' ) :
2015-08-23 08:10:42 +00:00
new_host = Host ( pattern )
2015-10-08 13:26:20 +00:00
new_host . address = " 127.0.0.1 "
2016-06-04 23:53:47 +00:00
new_host . implicit = True
2015-10-08 13:26:20 +00:00
new_host . vars = self . get_host_vars ( new_host )
2015-08-23 08:10:42 +00:00
new_host . set_variable ( " ansible_connection " , " local " )
2015-10-08 13:26:20 +00:00
if " ansible_python_interpreter " not in new_host . vars :
2016-06-29 15:50:22 +00:00
py_interp = sys . executable
if not py_interp :
# sys.executable is not set in some cornercases. #13585
display . warning ( ' Unable to determine python interpreter from sys.executable. Using /usr/bin/python default. You can correct this by setting ansible_python_interpreter for localhost ' )
py_interp = ' /usr/bin/python '
new_host . set_variable ( " ansible_python_interpreter " , py_interp )
2015-09-16 15:27:03 +00:00
self . get_group ( " ungrouped " ) . add_host ( new_host )
2015-08-23 08:10:42 +00:00
return new_host
2013-10-11 20:36:48 +00:00
def clear_pattern_cache ( self ) :
''' called exclusively by the add_host plugin to allow patterns to be recalculated '''
2015-11-16 18:53:10 +00:00
global HOSTS_PATTERNS_CACHE
HOSTS_PATTERNS_CACHE = { }
2013-10-12 02:24:37 +00:00
self . _pattern_cache = { }
2013-10-11 20:36:48 +00:00
2012-08-14 23:48:33 +00:00
def groups_for_host ( self , host ) :
2014-07-08 02:08:39 +00:00
if host in self . _hosts_cache :
return self . _hosts_cache [ host ] . get_groups ( )
else :
return [ ]
2012-08-14 23:48:33 +00:00
2012-05-05 20:31:03 +00:00
def get_groups ( self ) :
return self . groups
def get_host ( self , hostname ) :
2012-07-22 15:53:19 +00:00
if hostname not in self . _hosts_cache :
self . _hosts_cache [ hostname ] = self . _get_host ( hostname )
return self . _hosts_cache [ hostname ]
def _get_host ( self , hostname ) :
2016-06-08 18:01:47 +00:00
if hostname in C . LOCALHOST and self . localhost :
self . localhost
2015-01-17 17:49:13 +00:00
matching_host = None
2015-09-16 20:36:11 +00:00
for group in self . groups . values ( ) :
2015-01-17 17:49:13 +00:00
for host in group . get_hosts ( ) :
if hostname == host . name :
matching_host = host
self . _hosts_cache [ host . name ] = host
return matching_host
2012-05-05 20:31:03 +00:00
def get_group ( self , groupname ) :
2015-09-21 01:54:02 +00:00
return self . groups . get ( groupname )
2012-08-07 00:07:02 +00:00
2014-03-21 18:05:18 +00:00
def get_group_variables ( self , groupname , update_cached = False , vault_password = None ) :
if groupname not in self . _vars_per_group or update_cached :
2014-03-17 17:05:30 +00:00
self . _vars_per_group [ groupname ] = self . _get_group_variables ( groupname , vault_password = vault_password )
2012-07-22 15:53:19 +00:00
return self . _vars_per_group [ groupname ]
2014-03-17 17:05:30 +00:00
def _get_group_variables ( self , groupname , vault_password = None ) :
2012-05-05 20:31:03 +00:00
group = self . get_group ( groupname )
if group is None :
2015-05-04 02:47:26 +00:00
raise Exception ( " group not found: %s " % groupname )
2012-05-05 20:31:03 +00:00
2014-03-17 17:05:30 +00:00
vars = { }
2014-03-21 18:05:18 +00:00
# plugin.get_group_vars retrieves just vars for specific group
2014-03-17 17:05:30 +00:00
vars_results = [ plugin . get_group_vars ( group , vault_password = vault_password ) for plugin in self . _vars_plugins if hasattr ( plugin , ' get_group_vars ' ) ]
for updated in vars_results :
if updated is not None :
2015-05-04 02:47:26 +00:00
vars = combine_vars ( vars , updated )
2014-03-21 18:05:18 +00:00
# Read group_vars/ files
2015-05-04 02:47:26 +00:00
vars = combine_vars ( vars , self . get_group_vars ( group ) )
2014-03-17 17:05:30 +00:00
return vars
2012-05-05 20:31:03 +00:00
2015-05-04 02:47:26 +00:00
def get_vars ( self , hostname , update_cached = False , vault_password = None ) :
2014-08-19 12:55:57 +00:00
2014-10-10 06:18:18 +00:00
host = self . get_host ( hostname )
if not host :
2015-08-28 19:42:57 +00:00
raise AnsibleError ( " no vars as host is not in inventory: %s " % hostname )
2015-05-04 02:47:26 +00:00
return host . get_vars ( )
2014-08-19 12:55:57 +00:00
def get_host_variables ( self , hostname , update_cached = False , vault_password = None ) :
2014-03-21 18:05:18 +00:00
if hostname not in self . _vars_per_host or update_cached :
2014-08-19 12:55:57 +00:00
self . _vars_per_host [ hostname ] = self . _get_host_variables ( hostname , vault_password = vault_password )
2012-07-22 15:53:19 +00:00
return self . _vars_per_host [ hostname ]
2014-08-19 12:55:57 +00:00
def _get_host_variables ( self , hostname , vault_password = None ) :
2012-05-06 18:47:05 +00:00
2012-10-19 14:26:12 +00:00
host = self . get_host ( hostname )
if host is None :
2015-08-28 19:42:57 +00:00
raise AnsibleError ( " no host vars as host is not in inventory: %s " % hostname )
2012-10-19 14:26:12 +00:00
2012-10-26 23:55:59 +00:00
vars = { }
2014-03-17 17:05:30 +00:00
2014-03-21 18:05:18 +00:00
# plugin.run retrieves all vars (also from groups) for host
vars_results = [ plugin . run ( host , vault_password = vault_password ) for plugin in self . _vars_plugins if hasattr ( plugin , ' run ' ) ]
2014-03-17 17:05:30 +00:00
for updated in vars_results :
if updated is not None :
2015-05-04 02:47:26 +00:00
vars = combine_vars ( vars , updated )
2014-03-17 17:05:30 +00:00
2014-03-21 18:05:18 +00:00
# plugin.get_host_vars retrieves just vars for specific host
vars_results = [ plugin . get_host_vars ( host , vault_password = vault_password ) for plugin in self . _vars_plugins if hasattr ( plugin , ' get_host_vars ' ) ]
2013-04-20 13:59:40 +00:00
for updated in vars_results :
2012-10-29 16:11:57 +00:00
if updated is not None :
2015-05-04 02:47:26 +00:00
vars = combine_vars ( vars , updated )
2012-10-26 23:55:59 +00:00
2014-03-17 17:05:30 +00:00
# still need to check InventoryParser per host vars
# which actually means InventoryScript per host,
# which is not performant
2013-03-04 11:37:15 +00:00
if self . parser is not None :
2015-05-04 02:47:26 +00:00
vars = combine_vars ( vars , self . parser . get_host_variables ( host ) )
2014-03-17 17:05:30 +00:00
2012-10-26 23:55:59 +00:00
return vars
2012-05-05 20:31:03 +00:00
def add_group ( self , group ) :
2015-09-16 20:36:11 +00:00
if group . name not in self . groups :
self . groups [ group . name ] = group
2014-03-10 12:06:04 +00:00
else :
2015-07-18 19:24:44 +00:00
raise AnsibleError ( " group already in inventory: %s " % group . name )
2012-04-13 12:39:54 +00:00
def list_hosts ( self , pattern = " all " ) :
2014-01-20 23:26:14 +00:00
""" return a list of hostnames for a pattern """
2015-05-04 02:47:26 +00:00
result = [ h for h in self . get_hosts ( pattern ) ]
2015-08-19 00:02:03 +00:00
if len ( result ) == 0 and pattern in C . LOCALHOST :
2014-01-20 23:26:14 +00:00
result = [ pattern ]
return result
2012-05-05 20:31:03 +00:00
def list_groups ( self ) :
2015-09-16 20:36:11 +00:00
return sorted ( self . groups . keys ( ) , key = lambda x : x )
2012-04-13 12:39:54 +00:00
2015-05-04 02:47:26 +00:00
def restrict_to_hosts ( self , restriction ) :
2012-08-10 06:45:29 +00:00
"""
Restrict list operations to the hosts given in restriction . This is used
2015-08-13 00:36:38 +00:00
to batch serial operations in main playbook code , don ' t use this for other
2012-08-10 06:45:29 +00:00
reasons .
"""
2015-08-21 20:08:21 +00:00
if restriction is None :
return
elif not isinstance ( restriction , list ) :
2012-07-15 13:32:47 +00:00
restriction = [ restriction ]
2016-05-05 12:02:13 +00:00
self . _restriction = [ h . name for h in restriction ]
2012-04-13 12:39:54 +00:00
2012-08-10 06:45:29 +00:00
def subset ( self , subset_pattern ) :
"""
Limits inventory results to a subset of inventory that matches a given
pattern , such as to select a given geographic of numeric slice amongst
a previous ' hosts ' selection that only select roles , or vice versa .
Corresponds to - - limit parameter to ansible - playbook
"""
if subset_pattern is None :
self . _subset = None
else :
2015-10-15 15:19:11 +00:00
subset_patterns = Inventory . split_host_pattern ( subset_pattern )
2013-04-08 16:36:01 +00:00
results = [ ]
# allow Unix style @filename data
2015-08-13 11:02:11 +00:00
for x in subset_patterns :
2013-04-10 20:37:49 +00:00
if x . startswith ( " @ " ) :
fd = open ( x [ 1 : ] )
results . extend ( fd . read ( ) . split ( " \n " ) )
fd . close ( )
else :
results . append ( x )
2013-04-08 16:36:01 +00:00
self . _subset = results
2012-08-10 06:45:29 +00:00
2015-05-04 02:47:26 +00:00
def remove_restriction ( self ) :
2012-04-13 12:39:54 +00:00
""" Do not restrict list operations """
2012-05-08 03:16:20 +00:00
self . _restriction = None
2015-08-23 17:31:21 +00:00
2012-07-20 13:43:45 +00:00
def is_file ( self ) :
2015-10-13 16:03:06 +00:00
"""
Did inventory come from a file ? We don ' t use the equivalent loader
methods in inventory , due to the fact that the loader does an implict
DWIM on the path , which may be incorrect for inventory paths relative
to the playbook basedir .
"""
if not isinstance ( self . host_list , string_types ) :
return False
return os . path . isfile ( self . host_list ) or self . host_list == os . devnull
def is_directory ( self , path ) :
"""
Is the inventory host list a directory ? Same caveat for here as with
the is_file ( ) method above .
"""
2015-09-23 07:00:27 +00:00
if not isinstance ( self . host_list , string_types ) :
2012-07-20 13:43:45 +00:00
return False
2015-10-13 16:03:06 +00:00
return os . path . isdir ( path )
2012-07-20 13:43:45 +00:00
def basedir ( self ) :
""" if inventory came from a file, what ' s the directory? """
2015-08-23 17:31:21 +00:00
dname = self . host_list
2015-10-14 15:01:52 +00:00
if self . is_directory ( self . host_list ) :
2015-08-23 17:31:21 +00:00
dname = self . host_list
2015-10-14 15:01:52 +00:00
elif not self . is_file ( ) :
dname = None
2015-08-23 17:31:21 +00:00
else :
dname = os . path . dirname ( self . host_list )
if dname is None or dname == ' ' or dname == ' . ' :
2015-10-13 16:03:06 +00:00
dname = os . getcwd ( )
2015-08-26 13:28:23 +00:00
if dname :
dname = os . path . abspath ( dname )
return dname
2013-06-01 14:38:16 +00:00
2013-08-10 10:59:17 +00:00
def src ( self ) :
""" if inventory came from a file, what ' s the directory and file name? """
if not self . is_file ( ) :
return None
return self . host_list
2013-06-01 14:38:16 +00:00
def playbook_basedir ( self ) :
""" returns the directory of the current playbook """
return self . _playbook_basedir
2015-07-14 13:26:24 +00:00
def set_playbook_basedir ( self , dir_name ) :
2013-06-01 14:38:16 +00:00
"""
2014-03-21 18:05:18 +00:00
sets the base directory of the playbook so inventory can use it as a
basedir for host_ and group_vars , and other things .
"""
# Only update things if dir is a different playbook basedir
2015-07-14 13:26:24 +00:00
if dir_name != self . _playbook_basedir :
self . _playbook_basedir = dir_name
2014-03-21 18:05:18 +00:00
# get group vars from group_vars/ files
2015-10-22 20:03:37 +00:00
# TODO: excluding the new_pb_basedir directory may result in group_vars
# files loading more than they should, however with the file caching
# we do this shouldn't be too much of an issue. Still, this should
# be fixed at some point to allow a "first load" to touch all of the
# directories, then later runs only touch the new basedir specified
2016-05-27 14:43:11 +00:00
found_group_vars = self . _find_group_vars_files ( self . _playbook_basedir )
if found_group_vars :
self . _group_vars_files = self . _group_vars_files . union ( found_group_vars )
for group in self . groups . values ( ) :
2016-06-02 20:08:52 +00:00
self . get_group_vars ( group )
2016-05-27 14:43:11 +00:00
found_host_vars = self . _find_host_vars_files ( self . _playbook_basedir )
if found_host_vars :
2016-06-06 07:38:37 +00:00
self . _host_vars_files = self . _host_vars_files . union ( found_host_vars )
2016-05-27 14:43:11 +00:00
# get host vars from host_vars/ files
for host in self . get_hosts ( ) :
2016-06-02 20:08:52 +00:00
self . get_host_vars ( host )
2014-07-14 13:21:33 +00:00
# invalidate cache
self . _vars_per_host = { }
self . _vars_per_group = { }
2014-03-21 18:05:18 +00:00
2016-07-21 09:26:57 +00:00
def get_host_vars ( self , host , new_pb_basedir = False , return_results = False ) :
2014-03-21 18:05:18 +00:00
""" Read host_vars/ files """
2016-07-21 09:26:57 +00:00
return self . _get_hostgroup_vars ( host = host , group = None , new_pb_basedir = new_pb_basedir , return_results = return_results )
2013-06-01 14:38:16 +00:00
2016-07-21 09:26:57 +00:00
def get_group_vars ( self , group , new_pb_basedir = False , return_results = False ) :
2014-03-21 18:05:18 +00:00
""" Read group_vars/ files """
2016-07-21 09:26:57 +00:00
return self . _get_hostgroup_vars ( host = None , group = group , new_pb_basedir = new_pb_basedir , return_results = return_results )
2014-03-21 18:05:18 +00:00
2016-05-27 14:43:11 +00:00
def _find_group_vars_files ( self , basedir ) :
""" Find group_vars/ files """
if basedir in ( ' ' , None ) :
basedir = ' ./ '
path = os . path . realpath ( os . path . join ( basedir , ' group_vars ' ) )
found_vars = set ( )
if os . path . exists ( path ) :
2016-06-05 02:05:33 +00:00
found_vars = set ( os . listdir ( to_unicode ( path ) ) )
2016-05-27 14:43:11 +00:00
return found_vars
def _find_host_vars_files ( self , basedir ) :
""" Find host_vars/ files """
if basedir in ( ' ' , None ) :
basedir = ' ./ '
path = os . path . realpath ( os . path . join ( basedir , ' host_vars ' ) )
found_vars = set ( )
if os . path . exists ( path ) :
2016-06-05 02:05:33 +00:00
found_vars = set ( os . listdir ( to_unicode ( path ) ) )
2016-05-27 14:43:11 +00:00
return found_vars
2016-07-21 09:26:57 +00:00
def _get_hostgroup_vars ( self , host = None , group = None , new_pb_basedir = False , return_results = False ) :
2014-03-21 18:05:18 +00:00
"""
Loads variables from group_vars / < groupname > and host_vars / < hostname > in directories parallel
to the inventory base directory or in the same directory as the playbook . Variables in the playbook
dir will win over the inventory dir if files are in both .
"""
results = { }
scan_pass = 0
2016-05-27 14:43:11 +00:00
_basedir = self . _basedir
_playbook_basedir = self . _playbook_basedir
2013-06-01 14:38:16 +00:00
2014-03-21 18:05:18 +00:00
# look in both the inventory base directory and the playbook base directory
# unless we do an update for a new playbook base dir
if not new_pb_basedir :
2016-05-27 14:43:11 +00:00
basedirs = [ _basedir , _playbook_basedir ]
2014-03-21 18:05:18 +00:00
else :
2016-05-27 14:43:11 +00:00
basedirs = [ _playbook_basedir ]
2013-06-01 14:38:16 +00:00
2014-03-21 18:05:18 +00:00
for basedir in basedirs :
# this can happen from particular API usages, particularly if not run
# from /usr/bin/ansible-playbook
2015-10-02 06:47:09 +00:00
if basedir in ( ' ' , None ) :
2015-07-14 13:26:24 +00:00
basedir = ' ./ '
2014-03-21 18:05:18 +00:00
scan_pass = scan_pass + 1
# it's not an eror if the directory does not exist, keep moving
if not os . path . exists ( basedir ) :
continue
# save work of second scan if the directories are the same
2016-05-27 14:43:11 +00:00
if _basedir == _playbook_basedir and scan_pass != 1 :
2014-03-21 18:05:18 +00:00
continue
2016-05-27 14:43:11 +00:00
# Before trying to load vars from file, check that the directory contains relvant file names
if host is None and any ( map ( lambda ext : group . name + ext in self . _group_vars_files , C . YAML_FILENAME_EXTENSIONS ) ) :
2014-03-21 18:05:18 +00:00
# load vars in dir/group_vars/name_of_group
2016-04-30 14:28:41 +00:00
base_path = to_unicode ( os . path . abspath ( os . path . join ( to_bytes ( basedir ) , b " group_vars/ " + to_bytes ( group . name ) ) ) , errors = ' strict ' )
2016-07-21 09:26:57 +00:00
host_results = self . _variable_manager . add_group_vars_file ( base_path , self . _loader )
if return_results :
results = combine_vars ( results , host_results )
2016-05-27 14:43:11 +00:00
elif group is None and any ( map ( lambda ext : host . name + ext in self . _host_vars_files , C . YAML_FILENAME_EXTENSIONS ) ) :
2014-03-21 18:05:18 +00:00
# same for hostvars in dir/host_vars/name_of_host
2016-04-30 14:28:41 +00:00
base_path = to_unicode ( os . path . abspath ( os . path . join ( to_bytes ( basedir ) , b " host_vars/ " + to_bytes ( host . name ) ) ) , errors = ' strict ' )
2016-07-21 09:26:57 +00:00
group_results = self . _variable_manager . add_host_vars_file ( base_path , self . _loader )
if return_results :
results = combine_vars ( results , group_results )
2014-03-21 18:05:18 +00:00
# all done, results is a dictionary of variables for this particular host.
return results
2013-06-01 14:38:16 +00:00
2015-07-18 19:24:44 +00:00
def refresh_inventory ( self ) :
self . clear_pattern_cache ( )
self . _hosts_cache = { }
self . _vars_per_host = { }
self . _vars_per_group = { }
2015-09-16 20:36:11 +00:00
self . groups = { }
2015-07-18 19:24:44 +00:00
self . parse_inventory ( self . host_list )