@ -121,6 +121,11 @@ OS_CALL_REGEX = re.compile(r'os\.call.*')
LOOSE_ANSIBLE_VERSION = LooseVersion ( ' . ' . join ( ansible_version . split ( ' . ' ) [ : 3 ] ) )
LOOSE_ANSIBLE_VERSION = LooseVersion ( ' . ' . join ( ansible_version . split ( ' . ' ) [ : 3 ] ) )
PLUGINS_WITH_RETURN_VALUES = ( ' module ' , )
PLUGINS_WITH_EXAMPLES = ( ' module ' , )
PLUGINS_WITH_YAML_EXAMPLES = ( ' module ' , )
def is_potential_secret_option ( option_name ) :
def is_potential_secret_option ( option_name ) :
if not NO_LOG_REGEX . search ( option_name ) :
if not NO_LOG_REGEX . search ( option_name ) :
return False
return False
@ -303,14 +308,15 @@ class ModuleValidator(Validator):
ACCEPTLIST_FUTURE_IMPORTS = frozenset ( ( ' absolute_import ' , ' division ' , ' print_function ' ) )
ACCEPTLIST_FUTURE_IMPORTS = frozenset ( ( ' absolute_import ' , ' division ' , ' print_function ' ) )
def __init__ ( self , path , analyze_arg_spec = False , collection = None , collection_version = None ,
def __init__ ( self , path , analyze_arg_spec = False , collection = None , collection_version = None ,
base_branch = None , git_cache = None , reporter = None , routing = None ):
base_branch = None , git_cache = None , reporter = None , routing = None , plugin_type = ' module ' ):
super ( ModuleValidator , self ) . __init__ ( reporter = reporter or Reporter ( ) )
super ( ModuleValidator , self ) . __init__ ( reporter = reporter or Reporter ( ) )
self . path = path
self . path = path
self . basename = os . path . basename ( self . path )
self . basename = os . path . basename ( self . path )
self . name = os . path . splitext ( self . basename ) [ 0 ]
self . name = os . path . splitext ( self . basename ) [ 0 ]
self . plugin_type = plugin_type
self . analyze_arg_spec = analyze_arg_spec
self . analyze_arg_spec = analyze_arg_spec and plugin_type == ' module '
self . _Version = LooseVersion
self . _Version = LooseVersion
self . _StrictVersion = StrictVersion
self . _StrictVersion = StrictVersion
@ -914,7 +920,9 @@ class ModuleValidator(Validator):
# We are testing a collection
# We are testing a collection
if self . routing :
if self . routing :
routing_deprecation = self . routing . get ( ' plugin_routing ' , { } ) . get ( ' modules ' , { } ) . get ( self . name , { } ) . get ( ' deprecation ' , { } )
routing_deprecation = self . routing . get ( ' plugin_routing ' , { } )
routing_deprecation = routing_deprecation . get ( ' modules ' if self . plugin_type == ' module ' else self . plugin_type , { } )
routing_deprecation = routing_deprecation . get ( self . name , { } ) . get ( ' deprecation ' , { } )
if routing_deprecation :
if routing_deprecation :
# meta/runtime.yml says this is deprecated
# meta/runtime.yml says this is deprecated
routing_says_deprecated = True
routing_says_deprecated = True
@ -935,7 +943,8 @@ class ModuleValidator(Validator):
self . name , ' DOCUMENTATION '
self . name , ' DOCUMENTATION '
)
)
if doc :
if doc :
add_collection_to_versions_and_dates ( doc , self . collection_name , is_module = True )
add_collection_to_versions_and_dates ( doc , self . collection_name ,
is_module = self . plugin_type == ' module ' )
for error in errors :
for error in errors :
self . reporter . error (
self . reporter . error (
path = self . object_path ,
path = self . object_path ,
@ -952,7 +961,8 @@ class ModuleValidator(Validator):
with CaptureStd ( ) :
with CaptureStd ( ) :
try :
try :
get_docstring ( self . path , fragment_loader , verbose = True ,
get_docstring ( self . path , fragment_loader , verbose = True ,
collection_name = self . collection_name , is_module = True )
collection_name = self . collection_name ,
is_module = self . plugin_type == ' module ' )
except AssertionError :
except AssertionError :
fragment = doc [ ' extends_documentation_fragment ' ]
fragment = doc [ ' extends_documentation_fragment ' ]
self . reporter . error (
self . reporter . error (
@ -973,7 +983,8 @@ class ModuleValidator(Validator):
)
)
if not missing_fragment :
if not missing_fragment :
add_fragments ( doc , self . object_path , fragment_loader = fragment_loader , is_module = True )
add_fragments ( doc , self . object_path , fragment_loader = fragment_loader ,
is_module = self . plugin_type == ' module ' )
if ' options ' in doc and doc [ ' options ' ] is None :
if ' options ' in doc and doc [ ' options ' ] is None :
self . reporter . error (
self . reporter . error (
@ -1005,6 +1016,7 @@ class ModuleValidator(Validator):
os . readlink ( self . object_path ) . split ( ' . ' ) [ 0 ] ,
os . readlink ( self . object_path ) . split ( ' . ' ) [ 0 ] ,
for_collection = bool ( self . collection ) ,
for_collection = bool ( self . collection ) ,
deprecated_module = deprecated ,
deprecated_module = deprecated ,
plugin_type = self . plugin_type ,
) ,
) ,
' DOCUMENTATION ' ,
' DOCUMENTATION ' ,
' invalid-documentation ' ,
' invalid-documentation ' ,
@ -1017,6 +1029,7 @@ class ModuleValidator(Validator):
self . object_name . split ( ' . ' ) [ 0 ] ,
self . object_name . split ( ' . ' ) [ 0 ] ,
for_collection = bool ( self . collection ) ,
for_collection = bool ( self . collection ) ,
deprecated_module = deprecated ,
deprecated_module = deprecated ,
plugin_type = self . plugin_type ,
) ,
) ,
' DOCUMENTATION ' ,
' DOCUMENTATION ' ,
' invalid-documentation ' ,
' invalid-documentation ' ,
@ -1027,12 +1040,13 @@ class ModuleValidator(Validator):
self . _check_version_added ( doc , existing_doc )
self . _check_version_added ( doc , existing_doc )
if not bool ( doc_info [ ' EXAMPLES ' ] [ ' value ' ] ) :
if not bool ( doc_info [ ' EXAMPLES ' ] [ ' value ' ] ) :
if self . plugin_type in PLUGINS_WITH_EXAMPLES :
self . reporter . error (
self . reporter . error (
path = self . object_path ,
path = self . object_path ,
code = ' missing-examples ' ,
code = ' missing-examples ' ,
msg = ' No EXAMPLES provided '
msg = ' No EXAMPLES provided '
)
)
el se:
el if self. plugin_type in PLUGINS_WITH_YAML_EXAMPLES :
_doc , errors , traces = parse_yaml ( doc_info [ ' EXAMPLES ' ] [ ' value ' ] ,
_doc , errors , traces = parse_yaml ( doc_info [ ' EXAMPLES ' ] [ ' value ' ] ,
doc_info [ ' EXAMPLES ' ] [ ' lineno ' ] ,
doc_info [ ' EXAMPLES ' ] [ ' lineno ' ] ,
self . name , ' EXAMPLES ' , load_all = True ,
self . name , ' EXAMPLES ' , load_all = True ,
@ -1050,6 +1064,7 @@ class ModuleValidator(Validator):
)
)
if not bool ( doc_info [ ' RETURN ' ] [ ' value ' ] ) :
if not bool ( doc_info [ ' RETURN ' ] [ ' value ' ] ) :
if self . plugin_type in PLUGINS_WITH_RETURN_VALUES :
if self . _is_new_module ( ) :
if self . _is_new_module ( ) :
self . reporter . error (
self . reporter . error (
path = self . object_path ,
path = self . object_path ,
@ -1067,8 +1082,10 @@ class ModuleValidator(Validator):
doc_info [ ' RETURN ' ] [ ' lineno ' ] ,
doc_info [ ' RETURN ' ] [ ' lineno ' ] ,
self . name , ' RETURN ' )
self . name , ' RETURN ' )
if data :
if data :
add_collection_to_versions_and_dates ( data , self . collection_name , is_module = True , return_docs = True )
add_collection_to_versions_and_dates ( data , self . collection_name ,
self . _validate_docs_schema ( data , return_schema ( for_collection = bool ( self . collection ) ) ,
is_module = self . plugin_type == ' module ' , return_docs = True )
self . _validate_docs_schema ( data ,
return_schema ( for_collection = bool ( self . collection ) , plugin_type = self . plugin_type ) ,
' RETURN ' , ' return-syntax-error ' )
' RETURN ' , ' return-syntax-error ' )
for error in errors :
for error in errors :
@ -1421,7 +1438,8 @@ class ModuleValidator(Validator):
try :
try :
if not context :
if not context :
add_fragments ( docs , self . object_path , fragment_loader = fragment_loader , is_module = True )
add_fragments ( docs , self . object_path , fragment_loader = fragment_loader ,
is_module = self . plugin_type == ' module ' )
except Exception :
except Exception :
# Cannot merge fragments
# Cannot merge fragments
return
return
@ -2005,7 +2023,8 @@ class ModuleValidator(Validator):
with CaptureStd ( ) :
with CaptureStd ( ) :
try :
try :
existing_doc , dummy_examples , dummy_return , existing_metadata = get_docstring (
existing_doc , dummy_examples , dummy_return , existing_metadata = get_docstring (
self . base_module , fragment_loader , verbose = True , collection_name = self . collection_name , is_module = True )
self . base_module , fragment_loader , verbose = True , collection_name = self . collection_name ,
is_module = self . plugin_type == ' module ' )
existing_options = existing_doc . get ( ' options ' , { } ) or { }
existing_options = existing_doc . get ( ' options ' , { } ) or { }
except AssertionError :
except AssertionError :
fragment = doc [ ' extends_documentation_fragment ' ]
fragment = doc [ ' extends_documentation_fragment ' ]
@ -2207,13 +2226,16 @@ class ModuleValidator(Validator):
pass
pass
if self . _python_module ( ) and not self . _just_docs ( ) and not end_of_deprecation_should_be_removed_only :
if self . _python_module ( ) and not self . _just_docs ( ) and not end_of_deprecation_should_be_removed_only :
if self . plugin_type == ' module ' :
self . _validate_ansible_module_call ( docs )
self . _validate_ansible_module_call ( docs )
self . _check_for_sys_exit ( )
self . _check_for_sys_exit ( )
self . _find_rejectlist_imports ( )
self . _find_rejectlist_imports ( )
if self . plugin_type == ' module ' :
self . _find_module_utils ( )
self . _find_module_utils ( )
self . _find_has_import ( )
self . _find_has_import ( )
first_callable = self . _get_first_callable ( ) or 1000000 # use a bogus "high" line number if no callable exists
first_callable = self . _get_first_callable ( ) or 1000000 # use a bogus "high" line number if no callable exists
self . _ensure_imports_below_docs ( doc_info , first_callable )
self . _ensure_imports_below_docs ( doc_info , first_callable )
if self . plugin_type == ' module ' :
self . _check_for_subprocess ( )
self . _check_for_subprocess ( )
self . _check_for_os_call ( )
self . _check_for_os_call ( )
@ -2233,6 +2255,7 @@ class ModuleValidator(Validator):
self . _check_gpl3_header ( )
self . _check_gpl3_header ( )
if not self . _just_docs ( ) and not end_of_deprecation_should_be_removed_only :
if not self . _just_docs ( ) and not end_of_deprecation_should_be_removed_only :
if self . plugin_type == ' module ' :
self . _check_interpreter ( powershell = self . _powershell_module ( ) )
self . _check_interpreter ( powershell = self . _powershell_module ( ) )
self . _check_type_instead_of_isinstance (
self . _check_type_instead_of_isinstance (
powershell = self . _powershell_module ( )
powershell = self . _powershell_module ( )
@ -2288,8 +2311,8 @@ def re_compile(value):
def run ( ) :
def run ( ) :
parser = argparse . ArgumentParser ( prog = " validate-modules " )
parser = argparse . ArgumentParser ( prog = " validate-modules " )
parser . add_argument ( ' module s' , nargs = ' + ' ,
parser . add_argument ( ' plugin s' , nargs = ' + ' ,
help = ' Path to module or module directory' )
help = ' Path to module /plugin or module/plugin directory' )
parser . add_argument ( ' -w ' , ' --warnings ' , help = ' Show warnings ' ,
parser . add_argument ( ' -w ' , ' --warnings ' , help = ' Show warnings ' ,
action = ' store_true ' )
action = ' store_true ' )
parser . add_argument ( ' --exclude ' , help = ' RegEx exclusion pattern ' ,
parser . add_argument ( ' --exclude ' , help = ' RegEx exclusion pattern ' ,
@ -2311,13 +2334,16 @@ def run():
parser . add_argument ( ' --collection-version ' ,
parser . add_argument ( ' --collection-version ' ,
help = ' The collection \' s version number used to check '
help = ' The collection \' s version number used to check '
' deprecations ' )
' deprecations ' )
parser . add_argument ( ' --plugin-type ' ,
default = ' module ' ,
help = ' The plugin type to validate. Defaults to %(default)s ' )
args = parser . parse_args ( )
args = parser . parse_args ( )
args . module s = [ m . rstrip ( ' / ' ) for m in args . module s]
args . plugin s = [ m . rstrip ( ' / ' ) for m in args . plugin s]
reporter = Reporter ( )
reporter = Reporter ( )
git_cache = GitCache ( args . base_branch )
git_cache = GitCache ( args . base_branch , args . plugin_type )
check_dirs = set ( )
check_dirs = set ( )
@ -2334,25 +2360,26 @@ def run():
except Exception as ex : # pylint: disable=broad-except
except Exception as ex : # pylint: disable=broad-except
print ( ' %s : %d : %d : YAML load failed: %s ' % ( routing_file , 0 , 0 , re . sub ( r ' \ s+ ' , ' ' , str ( ex ) ) ) )
print ( ' %s : %d : %d : YAML load failed: %s ' % ( routing_file , 0 , 0 , re . sub ( r ' \ s+ ' , ' ' , str ( ex ) ) ) )
for module in args . module s:
for plugin in args . plugin s:
if os . path . isfile ( module ) :
if os . path . isfile ( plugin ) :
path = module
path = plugin
if args . exclude and args . exclude . search ( path ) :
if args . exclude and args . exclude . search ( path ) :
continue
continue
if ModuleValidator . is_on_rejectlist ( path ) :
if ModuleValidator . is_on_rejectlist ( path ) :
continue
continue
with ModuleValidator ( path , collection = args . collection , collection_version = args . collection_version ,
with ModuleValidator ( path , collection = args . collection , collection_version = args . collection_version ,
analyze_arg_spec = args . arg_spec , base_branch = args . base_branch ,
analyze_arg_spec = args . arg_spec , base_branch = args . base_branch ,
git_cache = git_cache , reporter = reporter , routing = routing ) as mv1 :
git_cache = git_cache , reporter = reporter , routing = routing ,
plugin_type = args . plugin_type ) as mv1 :
mv1 . validate ( )
mv1 . validate ( )
check_dirs . add ( os . path . dirname ( path ) )
check_dirs . add ( os . path . dirname ( path ) )
for root , dirs , files in os . walk ( module ) :
for root , dirs , files in os . walk ( plugin ) :
basedir = root [ len ( module ) + 1 : ] . split ( ' / ' , 1 ) [ 0 ]
basedir = root [ len ( plugin ) + 1 : ] . split ( ' / ' , 1 ) [ 0 ]
if basedir in REJECTLIST_DIRS :
if basedir in REJECTLIST_DIRS :
continue
continue
for dirname in dirs :
for dirname in dirs :
if root == module and dirname in REJECTLIST_DIRS :
if root == plugin and dirname in REJECTLIST_DIRS :
continue
continue
path = os . path . join ( root , dirname )
path = os . path . join ( root , dirname )
if args . exclude and args . exclude . search ( path ) :
if args . exclude and args . exclude . search ( path ) :
@ -2367,10 +2394,11 @@ def run():
continue
continue
with ModuleValidator ( path , collection = args . collection , collection_version = args . collection_version ,
with ModuleValidator ( path , collection = args . collection , collection_version = args . collection_version ,
analyze_arg_spec = args . arg_spec , base_branch = args . base_branch ,
analyze_arg_spec = args . arg_spec , base_branch = args . base_branch ,
git_cache = git_cache , reporter = reporter , routing = routing ) as mv2 :
git_cache = git_cache , reporter = reporter , routing = routing ,
plugin_type = args . plugin_type ) as mv2 :
mv2 . validate ( )
mv2 . validate ( )
if not args . collection :
if not args . collection and args . plugin_type == ' module ' :
for path in sorted ( check_dirs ) :
for path in sorted ( check_dirs ) :
pv = PythonPackageValidator ( path , reporter = reporter )
pv = PythonPackageValidator ( path , reporter = reporter )
pv . validate ( )
pv . validate ( )
@ -2382,16 +2410,21 @@ def run():
class GitCache :
class GitCache :
def __init__ ( self , base_branch ):
def __init__ ( self , base_branch , plugin_type ):
self . base_branch = base_branch
self . base_branch = base_branch
self . plugin_type = plugin_type
self . rel_path = ' lib/ansible/modules/ '
if plugin_type != ' module ' :
self . rel_path = ' lib/ansible/plugins/ %s / ' % plugin_type
if self . base_branch :
if self . base_branch :
self . base_tree = self . _git ( [ ' ls-tree ' , ' -r ' , ' --name-only ' , self . base_branch , ' lib/ansible/modules/ ' ] )
self . base_tree = self . _git ( [ ' ls-tree ' , ' -r ' , ' --name-only ' , self . base_branch , self . rel_path ] )
else :
else :
self . base_tree = [ ]
self . base_tree = [ ]
try :
try :
self . head_tree = self . _git ( [ ' ls-tree ' , ' -r ' , ' --name-only ' , ' HEAD ' , ' lib/ansible/modules/ ' ] )
self . head_tree = self . _git ( [ ' ls-tree ' , ' -r ' , ' --name-only ' , ' HEAD ' , self . rel_path ] )
except GitError as ex :
except GitError as ex :
if ex . status == 128 :
if ex . status == 128 :
# fallback when there is no .git directory
# fallback when there is no .git directory
@ -2405,7 +2438,10 @@ class GitCache:
else :
else :
raise
raise
self . base_module_paths = dict ( ( os . path . basename ( p ) , p ) for p in self . base_tree if os . path . splitext ( p ) [ 1 ] in ( ' .py ' , ' .ps1 ' ) )
allowed_exts = ( ' .py ' , ' .ps1 ' )
if plugin_type != ' module ' :
allowed_exts = ( ' .py ' , )
self . base_module_paths = dict ( ( os . path . basename ( p ) , p ) for p in self . base_tree if os . path . splitext ( p ) [ 1 ] in allowed_exts )
self . base_module_paths . pop ( ' __init__.py ' , None )
self . base_module_paths . pop ( ' __init__.py ' , None )
@ -2418,11 +2454,10 @@ class GitCache:
if os . path . islink ( path ) :
if os . path . islink ( path ) :
self . head_aliased_modules . add ( os . path . basename ( os . path . realpath ( path ) ) )
self . head_aliased_modules . add ( os . path . basename ( os . path . realpath ( path ) ) )
@staticmethod
def _get_module_files ( self ) :
def _get_module_files ( ) :
module_files = [ ]
module_files = [ ]
for ( dir_path , dir_names , file_names ) in os . walk ( ' lib/ansible/modules/ ' ) :
for ( dir_path , dir_names , file_names ) in os . walk ( self . rel_path ) :
for file_name in file_names :
for file_name in file_names :
module_files . append ( os . path . join ( dir_path , file_name ) )
module_files . append ( os . path . join ( dir_path , file_name ) )