Change validate-modules for removed modules

Removed modules now don't have documentation.  Need to account for that
when checking them in validte-modules
pull/44661/merge
Toshio Kuratomi 6 years ago
parent 3ccdb35f59
commit 68c60ad307

@ -106,7 +106,9 @@ Errors
316 Invalid ``ANSIBLE_METADATA`` schema 316 Invalid ``ANSIBLE_METADATA`` schema
317 option is marked as required but specifies a default. 317 option is marked as required but specifies a default.
Arguments with a default should not be marked as required Arguments with a default should not be marked as required
318 Module deprecated, but DOCUMENTATION.deprecated is missing 318 Module marked as deprecated or removed in at least one of the filename, its metadata, or
in DOCUMENTATION (setting DOCUMENTATION.deprecated for deprecation or removing all
documentation for removed) but not in all three places.
319 ``RETURN`` fragments missing or invalid 319 ``RETURN`` fragments missing or invalid
320 ``DOCUMENTATION.options`` must be a dictionary/hash when used 320 ``DOCUMENTATION.options`` must be a dictionary/hash when used
321 ``Exception`` attempting to import module for ``argument_spec`` introspection 321 ``Exception`` attempting to import module for ``argument_spec`` introspection
@ -121,6 +123,8 @@ Errors
330 Choices value from the argument_spec is not compatible with type defined in the argument_spec 330 Choices value from the argument_spec is not compatible with type defined in the argument_spec
331 argument in argument_spec must be a dictionary/hash when used 331 argument in argument_spec must be a dictionary/hash when used
332 ``AnsibleModule`` schema validation error 332 ``AnsibleModule`` schema validation error
333 ``ANSIBLE_METADATA.status`` of deprecated or removed can't include other statuses
.. ..
--------- ------------------- --------- -------------------
**4xx** **Syntax** **4xx** **Syntax**

@ -831,135 +831,21 @@ class ModuleValidator(Validator):
def _validate_docs(self): def _validate_docs(self):
doc_info = self._get_docs() doc_info = self._get_docs()
deprecated = False
doc = None doc = None
if not bool(doc_info['DOCUMENTATION']['value']): documentation_exists = False
self.reporter.error( examples_exist = False
path=self.object_path, returns_exist = False
code=301, # We have three ways of marking deprecated/removed files. Have to check each one
msg='No DOCUMENTATION provided' # individually and then make sure they all agree
) filename_deprecated_or_removed = False
else: deprecated = False
doc, errors, traces = parse_yaml( removed = False
doc_info['DOCUMENTATION']['value'], doc_deprecated = None # doc legally might not exist
doc_info['DOCUMENTATION']['lineno'],
self.name, 'DOCUMENTATION'
)
for error in errors:
self.reporter.error(
path=self.object_path,
code=302,
**error
)
for trace in traces:
self.reporter.trace(
path=self.object_path,
tracebk=trace
)
if not errors and not traces:
with CaptureStd():
try:
get_docstring(self.path, fragment_loader, verbose=True)
except AssertionError:
fragment = doc['extends_documentation_fragment']
self.reporter.error(
path=self.object_path,
code=303,
msg='DOCUMENTATION fragment missing: %s' % fragment
)
except Exception as e:
self.reporter.trace(
path=self.object_path,
tracebk=traceback.format_exc()
)
self.reporter.error(
path=self.object_path,
code=304,
msg='Unknown DOCUMENTATION error, see TRACE: %s' % e
)
if 'options' in doc and doc['options'] is None:
self.reporter.error(
path=self.object_path,
code=320,
msg='DOCUMENTATION.options must be a dictionary/hash when used',
)
if self.object_name.startswith('_') and not os.path.islink(self.object_path):
deprecated = True
if 'deprecated' not in doc or not doc.get('deprecated'):
self.reporter.error(
path=self.object_path,
code=318,
msg='Module deprecated, but DOCUMENTATION.deprecated is missing'
)
if os.path.islink(self.object_path):
# This module has an alias, which we can tell as it's a symlink
# Rather than checking for `module: $filename` we need to check against the true filename
self._validate_docs_schema(doc, doc_schema(os.readlink(self.object_path).split('.')[0]), 'DOCUMENTATION', 305)
else:
# This is the normal case
self._validate_docs_schema(doc, doc_schema(self.object_name.split('.')[0]), 'DOCUMENTATION', 305)
self._check_version_added(doc)
self._check_for_new_args(doc)
if not bool(doc_info['EXAMPLES']['value']): if self.object_name.startswith('_') and not os.path.islink(self.object_path):
self.reporter.error( filename_deprecated_or_removed = True
path=self.object_path,
code=310,
msg='No EXAMPLES provided'
)
else:
_, errors, traces = parse_yaml(doc_info['EXAMPLES']['value'],
doc_info['EXAMPLES']['lineno'],
self.name, 'EXAMPLES', load_all=True)
for error in errors:
self.reporter.error(
path=self.object_path,
code=311,
**error
)
for trace in traces:
self.reporter.trace(
path=self.object_path,
tracebk=trace
)
if not bool(doc_info['RETURN']['value']):
if self._is_new_module():
self.reporter.error(
path=self.object_path,
code=312,
msg='No RETURN provided'
)
else:
self.reporter.warning(
path=self.object_path,
code=312,
msg='No RETURN provided'
)
else:
data, errors, traces = parse_yaml(doc_info['RETURN']['value'],
doc_info['RETURN']['lineno'],
self.name, 'RETURN')
if data:
for ret_key in data:
self._validate_docs_schema(data[ret_key], return_schema(data[ret_key]), 'RETURN.%s' % ret_key, 319)
for error in errors:
self.reporter.error(
path=self.object_path,
code=313,
**error
)
for trace in traces:
self.reporter.trace(
path=self.object_path,
tracebk=trace
)
# Have to check the metadata first so that we know if the module is removed or deprecated
if not bool(doc_info['ANSIBLE_METADATA']['value']): if not bool(doc_info['ANSIBLE_METADATA']['value']):
self.reporter.error( self.reporter.error(
path=self.object_path, path=self.object_path,
@ -1001,8 +887,167 @@ class ModuleValidator(Validator):
) )
if metadata: if metadata:
self._validate_docs_schema(metadata, metadata_1_1_schema(deprecated), self._validate_docs_schema(metadata, metadata_1_1_schema(),
'ANSIBLE_METADATA', 316) 'ANSIBLE_METADATA', 316)
# We could validate these via the schema if we knew what the values are ahead of
# time. We can figure that out for deprecated but we can't for removed. Only the
# metadata has that information.
if 'removed' in metadata['status']:
removed = True
if 'deprecated' in metadata['status']:
deprecated = True
if (deprecated or removed) and len(metadata['status']) > 1:
self.reporter.error(
path=self.object_path,
code=333,
msg='ANSIBLE_METADATA.status must be exactly one of "deprecated" or "removed"'
)
if not removed:
if not bool(doc_info['DOCUMENTATION']['value']):
self.reporter.error(
path=self.object_path,
code=301,
msg='No DOCUMENTATION provided'
)
else:
documentation_exists = True
doc, errors, traces = parse_yaml(
doc_info['DOCUMENTATION']['value'],
doc_info['DOCUMENTATION']['lineno'],
self.name, 'DOCUMENTATION'
)
for error in errors:
self.reporter.error(
path=self.object_path,
code=302,
**error
)
for trace in traces:
self.reporter.trace(
path=self.object_path,
tracebk=trace
)
if not errors and not traces:
with CaptureStd():
try:
get_docstring(self.path, fragment_loader, verbose=True)
except AssertionError:
fragment = doc['extends_documentation_fragment']
self.reporter.error(
path=self.object_path,
code=303,
msg='DOCUMENTATION fragment missing: %s' % fragment
)
except Exception as e:
self.reporter.trace(
path=self.object_path,
tracebk=traceback.format_exc()
)
self.reporter.error(
path=self.object_path,
code=304,
msg='Unknown DOCUMENTATION error, see TRACE: %s' % e
)
if 'options' in doc and doc['options'] is None:
self.reporter.error(
path=self.object_path,
code=320,
msg='DOCUMENTATION.options must be a dictionary/hash when used',
)
if 'deprecated' in doc and doc.get('deprecated'):
doc_deprecated = True
else:
doc_deprecated = False
if os.path.islink(self.object_path):
# This module has an alias, which we can tell as it's a symlink
# Rather than checking for `module: $filename` we need to check against the true filename
self._validate_docs_schema(doc, doc_schema(os.readlink(self.object_path).split('.')[0]), 'DOCUMENTATION', 305)
else:
# This is the normal case
self._validate_docs_schema(doc, doc_schema(self.object_name.split('.')[0]), 'DOCUMENTATION', 305)
self._check_version_added(doc)
self._check_for_new_args(doc)
if not bool(doc_info['EXAMPLES']['value']):
self.reporter.error(
path=self.object_path,
code=310,
msg='No EXAMPLES provided'
)
else:
examples_exists = True
_, errors, traces = parse_yaml(doc_info['EXAMPLES']['value'],
doc_info['EXAMPLES']['lineno'],
self.name, 'EXAMPLES', load_all=True)
for error in errors:
self.reporter.error(
path=self.object_path,
code=311,
**error
)
for trace in traces:
self.reporter.trace(
path=self.object_path,
tracebk=trace
)
if not bool(doc_info['RETURN']['value']):
returns_exists = True
if self._is_new_module():
self.reporter.error(
path=self.object_path,
code=312,
msg='No RETURN provided'
)
else:
self.reporter.warning(
path=self.object_path,
code=312,
msg='No RETURN provided'
)
else:
data, errors, traces = parse_yaml(doc_info['RETURN']['value'],
doc_info['RETURN']['lineno'],
self.name, 'RETURN')
if data:
for ret_key in data:
self._validate_docs_schema(data[ret_key], return_schema(data[ret_key]), 'RETURN.%s' % ret_key, 319)
for error in errors:
self.reporter.error(
path=self.object_path,
code=313,
**error
)
for trace in traces:
self.reporter.trace(
path=self.object_path,
tracebk=trace
)
# Check for mismatched deprecation
mismatched_deprecation = True
if not (filename_deprecated_or_removed or removed or deprecated or doc_deprecated):
mismatched_deprecation = False
else:
if (filename_deprecated_or_removed and deprecated and doc_deprecated):
mismatched_deprecation = False
if (filename_deprecated_or_removed and removed and not (documentation_exists or examples_exist or returns_exist)):
mismatched_deprecation = False
if mismatched_deprecation:
self.reporter.error(
path=self.object_path,
code=318,
msg='Module deprecation/removed must agree in Metadata, by prepending filename with'
' "_", and setting DOCUMENTATION.deprecated for deprecation or by removing all'
' documentation for removed'
)
return doc_info, doc return doc_info, doc
@ -1368,22 +1413,23 @@ class ModuleValidator(Validator):
) )
return return
end_of_deprecation_should_be_docs_only = False end_of_deprecation_should_be_removed_only = False
if self._python_module(): if self._python_module():
doc_info, docs = self._validate_docs() doc_info, docs = self._validate_docs()
# See if current version => deprecated.removed_in, ie, should be docs only # See if current version => deprecated.removed_in, ie, should be docs only
if docs and 'deprecated' in docs and docs['deprecated'] is not None: if 'removed' in ast.literal_eval(doc_info['ANSIBLE_METADATA']['value'])['status']:
end_of_deprecation_should_be_removed_only = True
elif docs and 'deprecated' in docs and docs['deprecated'] is not None:
try: try:
removed_in = StrictVersion(str(docs.get('deprecated')['removed_in'])) removed_in = StrictVersion(str(docs.get('deprecated')['removed_in']))
except ValueError: except ValueError:
end_of_deprecation_should_be_docs_only = False end_of_deprecation_should_be_removed_only = False
else: else:
strict_ansible_version = StrictVersion('.'.join(ansible_version.split('.')[:2])) strict_ansible_version = StrictVersion('.'.join(ansible_version.split('.')[:2]))
end_of_deprecation_should_be_docs_only = strict_ansible_version >= removed_in end_of_deprecation_should_be_removed_only = strict_ansible_version >= removed_in
# FIXME if +2 then file should be empty? - maybe add this only in the future
if self._python_module() and not self._just_docs() and not end_of_deprecation_should_be_docs_only: if self._python_module() and not self._just_docs() and not end_of_deprecation_should_be_removed_only:
self._validate_ansible_module_call(docs) self._validate_ansible_module_call(docs)
self._check_for_sys_exit() self._check_for_sys_exit()
self._find_blacklist_imports() self._find_blacklist_imports()
@ -1400,14 +1446,17 @@ class ModuleValidator(Validator):
self._find_ps_docs_py_file() self._find_ps_docs_py_file()
self._check_gpl3_header() self._check_gpl3_header()
if not self._just_docs() and not end_of_deprecation_should_be_docs_only: if not self._just_docs() and not end_of_deprecation_should_be_removed_only:
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()
) )
if end_of_deprecation_should_be_docs_only: if end_of_deprecation_should_be_removed_only:
# Ensure that `if __name__ == '__main__':` calls `removed_module()` which ensure that the module has no code in # Ensure that `if __name__ == '__main__':` calls `removed_module()` which ensure that the module has no code in
main = self._find_main_call('removed_module') main = self._find_main_call('removed_module')
# FIXME: Ensure that the version in the call to removed_module is less than +2.
# Otherwise it's time to remove the file (This may need to be done in another test to
# avoid breaking whenever the Ansible version bumps)
class PythonPackageValidator(Validator): class PythonPackageValidator(Validator):

@ -179,10 +179,8 @@ def metadata_1_0_schema(deprecated):
) )
def metadata_1_1_schema(deprecated): def metadata_1_1_schema():
valid_status = Any('stableinterface', 'preview', 'deprecated', 'removed') valid_status = Any('stableinterface', 'preview', 'deprecated', 'removed')
if deprecated:
valid_status = Any('deprecated')
return Schema( return Schema(
{ {

Loading…
Cancel
Save