@ -112,6 +112,12 @@ def add_dirs_to_loader(which_loader, paths):
loader . add_directory ( path , with_subdir = True )
loader . add_directory ( path , with_subdir = True )
class PluginPathContext ( object ) :
def __init__ ( self , path , internal ) :
self . path = path
self . internal = internal
class PluginLoadContext ( object ) :
class PluginLoadContext ( object ) :
def __init__ ( self ) :
def __init__ ( self ) :
self . original_name = None
self . original_name = None
@ -123,6 +129,7 @@ class PluginLoadContext(object):
self . exit_reason = None
self . exit_reason = None
self . plugin_resolved_path = None
self . plugin_resolved_path = None
self . plugin_resolved_name = None
self . plugin_resolved_name = None
self . plugin_resolved_collection = None # empty string for resolved plugins from user-supplied paths
self . deprecated = False
self . deprecated = False
self . removal_date = None
self . removal_date = None
self . removal_version = None
self . removal_version = None
@ -152,10 +159,11 @@ class PluginLoadContext(object):
self . deprecation_warnings . append ( warning_text )
self . deprecation_warnings . append ( warning_text )
return self
return self
def resolve ( self , resolved_name , resolved_path , exit_reason) :
def resolve ( self , resolved_name , resolved_path , resolved_collection, exit_reason) :
self . pending_redirect = None
self . pending_redirect = None
self . plugin_resolved_name = resolved_name
self . plugin_resolved_name = resolved_name
self . plugin_resolved_path = resolved_path
self . plugin_resolved_path = resolved_path
self . plugin_resolved_collection = resolved_collection
self . exit_reason = exit_reason
self . exit_reason = exit_reason
self . resolved = True
self . resolved = True
return self
return self
@ -309,8 +317,8 @@ class PluginLoader:
return self . _all_directories ( self . package_path )
return self . _all_directories ( self . package_path )
return [ self . package_path ]
return [ self . package_path ]
def _get_paths ( self , subdirs = True ) :
def _get_paths _with_context ( self , subdirs = True ) :
''' Return a list of path s to search for plugins in '''
''' Return a list of PluginPathContext object s to search for plugins in '''
# FIXME: This is potentially buggy if subdirs is sometimes True and sometimes False.
# FIXME: This is potentially buggy if subdirs is sometimes True and sometimes False.
# In current usage, everything calls this with subdirs=True except for module_utils_loader and ansible-doc
# In current usage, everything calls this with subdirs=True except for module_utils_loader and ansible-doc
@ -318,7 +326,7 @@ class PluginLoader:
if self . _paths is not None :
if self . _paths is not None :
return self . _paths
return self . _paths
ret = self . _extra_dirs [ : ]
ret = [ PluginPathContext ( p , False ) for p in self . _extra_dirs ]
# look in any configured plugin paths, allow one level deep for subcategories
# look in any configured plugin paths, allow one level deep for subcategories
if self . config is not None :
if self . config is not None :
@ -328,14 +336,14 @@ class PluginLoader:
contents = glob . glob ( " %s /* " % path ) + glob . glob ( " %s /*/* " % path )
contents = glob . glob ( " %s /* " % path ) + glob . glob ( " %s /*/* " % path )
for c in contents :
for c in contents :
if os . path . isdir ( c ) and c not in ret :
if os . path . isdir ( c ) and c not in ret :
ret . append ( c)
ret . append ( PluginPathContext( c, False ) )
if path not in ret :
if path not in ret :
ret . append ( path)
ret . append ( PluginPathContext( path, False ) )
# look for any plugins installed in the package subtree
# look for any plugins installed in the package subtree
# Note package path always gets added last so that every other type of
# Note package path always gets added last so that every other type of
# path is searched before it.
# path is searched before it.
ret . extend ( self . _get_package_paths ( subdirs = subdirs ) )
ret . extend ( [ PluginPathContext ( p , True ) for p in self . _get_package_paths ( subdirs = subdirs ) ] )
# HACK: because powershell modules are in the same directory
# HACK: because powershell modules are in the same directory
# hierarchy as other modules we have to process them last. This is
# hierarchy as other modules we have to process them last. This is
@ -353,12 +361,18 @@ class PluginLoader:
# The expected sort order is paths in the order in 'ret' with paths ending in '/windows' at the end,
# The expected sort order is paths in the order in 'ret' with paths ending in '/windows' at the end,
# also in the original order they were found in 'ret'.
# also in the original order they were found in 'ret'.
# The .sort() method is guaranteed to be stable, so original order is preserved.
# The .sort() method is guaranteed to be stable, so original order is preserved.
ret . sort ( key = lambda p : p . endswith( ' /windows ' ) )
ret . sort ( key = lambda p : p . path. endswith( ' /windows ' ) )
# cache and return the result
# cache and return the result
self . _paths = ret
self . _paths = ret
return ret
return ret
def _get_paths ( self , subdirs = True ) :
''' Return a list of paths to search for plugins in '''
paths_with_context = self . _get_paths_with_context ( subdirs = subdirs )
return [ path_with_context . path for path_with_context in paths_with_context ]
def _load_config_defs ( self , name , module , path ) :
def _load_config_defs ( self , name , module , path ) :
''' Reads plugin docs to find configuration setting definitions, to push to config manager for later use '''
''' Reads plugin docs to find configuration setting definitions, to push to config manager for later use '''
@ -487,7 +501,8 @@ class PluginLoader:
# FIXME: and is file or file link or ...
# FIXME: and is file or file link or ...
if os . path . exists ( n_resource_path ) :
if os . path . exists ( n_resource_path ) :
return plugin_load_context . resolve ( full_name , to_text ( n_resource_path ) , ' found exact match for {0} in {1} ' . format ( full_name , acr . collection ) )
return plugin_load_context . resolve (
full_name , to_text ( n_resource_path ) , acr . collection , ' found exact match for {0} in {1} ' . format ( full_name , acr . collection ) )
if extension :
if extension :
# the request was extension-specific, don't try for an extensionless match
# the request was extension-specific, don't try for an extensionless match
@ -505,7 +520,8 @@ class PluginLoader:
# TODO: warn?
# TODO: warn?
pass
pass
return plugin_load_context . resolve ( full_name , to_text ( found_files [ 0 ] ) , ' found fuzzy extension match for {0} in {1} ' . format ( full_name , acr . collection ) )
return plugin_load_context . resolve (
full_name , to_text ( found_files [ 0 ] ) , acr . collection , ' found fuzzy extension match for {0} in {1} ' . format ( full_name , acr . collection ) )
def find_plugin ( self , name , mod_type = ' ' , ignore_deprecated = False , check_aliases = False , collection_list = None ) :
def find_plugin ( self , name , mod_type = ' ' , ignore_deprecated = False , check_aliases = False , collection_list = None ) :
''' Find a plugin named name '''
''' Find a plugin named name '''
@ -626,8 +642,10 @@ class PluginLoader:
# requested mod_type
# requested mod_type
pull_cache = self . _plugin_path_cache [ suffix ]
pull_cache = self . _plugin_path_cache [ suffix ]
try :
try :
plugin_load_context . plugin_resolved_path = pull_cache [ name ]
path_with_context = pull_cache [ name ]
plugin_load_context . plugin_resolved_path = path_with_context . path
plugin_load_context . plugin_resolved_name = name
plugin_load_context . plugin_resolved_name = name
plugin_load_context . plugin_resolved_collection = ' ansible.builtin ' if path_with_context . internal else ' '
plugin_load_context . resolved = True
plugin_load_context . resolved = True
return plugin_load_context
return plugin_load_context
except KeyError :
except KeyError :
@ -638,8 +656,9 @@ class PluginLoader:
# self._searched_paths we could use an iterator. Before enabling that
# self._searched_paths we could use an iterator. Before enabling that
# we need to make sure we don't want to add additional directories
# we need to make sure we don't want to add additional directories
# (add_directory()) once we start using the iterator.
# (add_directory()) once we start using the iterator.
# We can use _get_paths() since add_directory() forces a cache refresh.
# We can use _get_paths_with_context() since add_directory() forces a cache refresh.
for path in ( p for p in self . _get_paths ( ) if p not in self . _searched_paths and os . path . isdir ( p ) ) :
for path_context in ( p for p in self . _get_paths_with_context ( ) if p . path not in self . _searched_paths and os . path . isdir ( p . path ) ) :
path = path_context . path
display . debug ( ' trying %s ' % path )
display . debug ( ' trying %s ' % path )
plugin_load_context . load_attempts . append ( path )
plugin_load_context . load_attempts . append ( path )
try :
try :
@ -658,6 +677,7 @@ class PluginLoader:
splitname = os . path . splitext ( full_name )
splitname = os . path . splitext ( full_name )
base_name = splitname [ 0 ]
base_name = splitname [ 0 ]
internal = path_context . internal
try :
try :
extension = splitname [ 1 ]
extension = splitname [ 1 ]
except IndexError :
except IndexError :
@ -665,21 +685,23 @@ class PluginLoader:
# Module found, now enter it into the caches that match this file
# Module found, now enter it into the caches that match this file
if base_name not in self . _plugin_path_cache [ ' ' ] :
if base_name not in self . _plugin_path_cache [ ' ' ] :
self . _plugin_path_cache [ ' ' ] [ base_name ] = full_path
self . _plugin_path_cache [ ' ' ] [ base_name ] = PluginPathContext( full_path, internal )
if full_name not in self . _plugin_path_cache [ ' ' ] :
if full_name not in self . _plugin_path_cache [ ' ' ] :
self . _plugin_path_cache [ ' ' ] [ full_name ] = full_path
self . _plugin_path_cache [ ' ' ] [ full_name ] = PluginPathContext( full_path, internal )
if base_name not in self . _plugin_path_cache [ extension ] :
if base_name not in self . _plugin_path_cache [ extension ] :
self . _plugin_path_cache [ extension ] [ base_name ] = full_path
self . _plugin_path_cache [ extension ] [ base_name ] = PluginPathContext( full_path, internal )
if full_name not in self . _plugin_path_cache [ extension ] :
if full_name not in self . _plugin_path_cache [ extension ] :
self . _plugin_path_cache [ extension ] [ full_name ] = full_path
self . _plugin_path_cache [ extension ] [ full_name ] = PluginPathContext( full_path, internal )
self . _searched_paths . add ( path )
self . _searched_paths . add ( path )
try :
try :
plugin_load_context . plugin_resolved_path = pull_cache [ name ]
path_with_context = pull_cache [ name ]
plugin_load_context . plugin_resolved_path = path_with_context . path
plugin_load_context . plugin_resolved_name = name
plugin_load_context . plugin_resolved_name = name
plugin_load_context . plugin_resolved_collection = ' ansible.builtin ' if path_with_context . internal else ' '
plugin_load_context . resolved = True
plugin_load_context . resolved = True
return plugin_load_context
return plugin_load_context
except KeyError :
except KeyError :
@ -691,12 +713,14 @@ class PluginLoader:
alias_name = ' _ ' + name
alias_name = ' _ ' + name
# We've already cached all the paths at this point
# We've already cached all the paths at this point
if alias_name in pull_cache :
if alias_name in pull_cache :
if not ignore_deprecated and not os . path . islink ( pull_cache [ alias_name ] ) :
path_with_context = pull_cache [ alias_name ]
if not ignore_deprecated and not os . path . islink ( path_with_context . path ) :
# FIXME: this is not always the case, some are just aliases
# FIXME: this is not always the case, some are just aliases
display . deprecated ( ' %s is kept for backwards compatibility but usage is discouraged. ' # pylint: disable=ansible-deprecated-no-version
display . deprecated ( ' %s is kept for backwards compatibility but usage is discouraged. ' # pylint: disable=ansible-deprecated-no-version
' The module documentation details page may explain more about this rationale. ' % name . lstrip ( ' _ ' ) )
' The module documentation details page may explain more about this rationale. ' % name . lstrip ( ' _ ' ) )
plugin_load_context . plugin_resolved_path = p ull_cache[ alias_name ]
plugin_load_context . plugin_resolved_path = p ath_with_context. path
plugin_load_context . plugin_resolved_name = alias_name
plugin_load_context . plugin_resolved_name = alias_name
plugin_load_context . plugin_resolved_collection = ' ansible.builtin ' if path_with_context . internal else ' '
plugin_load_context . resolved = True
plugin_load_context . resolved = True
return plugin_load_context
return plugin_load_context