validate-modules: deprecated modules in collections (#68646)

* validate-modules: deprecated modules in collections

In Collections a module is marked as deprecated via meta/routing.yml

Use this file, rather than the leading `_` as part of the deprecated
test.

* Correct variable

* review comments

* indentation

* Read routing.yml only once

* pep8

* Apply suggestions from code review

Co-authored-by: Matt Clay <matt@mystile.com>

* review: remove duplicated conditional

Co-authored-by: Matt Clay <matt@mystile.com>
This commit is contained in:
John R Barker 2020-05-07 08:11:46 +01:00 committed by GitHub
parent 366f808683
commit 084587913a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 22 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- validate-modules checks for deprecated in collections against meta/routing.yml

View file

@ -34,6 +34,7 @@ from collections import OrderedDict
from contextlib import contextmanager from contextlib import contextmanager
from distutils.version import StrictVersion from distutils.version import StrictVersion
from fnmatch import fnmatch from fnmatch import fnmatch
import yaml
from ansible import __version__ as ansible_version from ansible import __version__ as ansible_version
from ansible.executor.module_common import REPLACER_WINDOWS from ansible.executor.module_common import REPLACER_WINDOWS
@ -237,7 +238,7 @@ class ModuleValidator(Validator):
WHITELIST_FUTURE_IMPORTS = frozenset(('absolute_import', 'division', 'print_function')) WHITELIST_FUTURE_IMPORTS = frozenset(('absolute_import', 'division', 'print_function'))
def __init__(self, path, analyze_arg_spec=False, collection=None, base_branch=None, git_cache=None, reporter=None): def __init__(self, path, analyze_arg_spec=False, collection=None, base_branch=None, git_cache=None, reporter=None, routing=None):
super(ModuleValidator, self).__init__(reporter=reporter or Reporter()) super(ModuleValidator, self).__init__(reporter=reporter or Reporter())
self.path = path self.path = path
@ -247,6 +248,7 @@ class ModuleValidator(Validator):
self.analyze_arg_spec = analyze_arg_spec self.analyze_arg_spec = analyze_arg_spec
self.collection = collection self.collection = collection
self.routing = routing
self.base_branch = base_branch self.base_branch = base_branch
self.git_cache = git_cache or GitCache() self.git_cache = git_cache or GitCache()
@ -882,6 +884,7 @@ class ModuleValidator(Validator):
deprecated = False deprecated = False
removed = False removed = False
doc_deprecated = None # doc legally might not exist doc_deprecated = None # doc legally might not exist
routing_says_deprecated = False
if self.object_name.startswith('_') and not os.path.islink(self.object_path): if self.object_name.startswith('_') and not os.path.islink(self.object_path):
filename_deprecated_or_removed = True filename_deprecated_or_removed = True
@ -923,6 +926,12 @@ class ModuleValidator(Validator):
code='missing-metadata-status', code='missing-metadata-status',
msg='ANSIBLE_METADATA.status must be exactly one of "deprecated" or "removed"' msg='ANSIBLE_METADATA.status must be exactly one of "deprecated" or "removed"'
) )
else:
# We are testing a collection
if self.routing and self.routing.get('plugin_routing', {}).get('modules', {}).get(self.name, {}).get('deprecation', {}):
# meta/routing.yml says this is deprecated
routing_says_deprecated = True
deprecated = True
if not removed: if not removed:
if not bool(doc_info['DOCUMENTATION']['value']): if not bool(doc_info['DOCUMENTATION']['value']):
@ -995,7 +1004,8 @@ class ModuleValidator(Validator):
doc, doc,
doc_schema( doc_schema(
os.readlink(self.object_path).split('.')[0], os.readlink(self.object_path).split('.')[0],
version_added=not bool(self.collection) version_added=not bool(self.collection),
deprecated_module=deprecated,
), ),
'DOCUMENTATION', 'DOCUMENTATION',
'invalid-documentation', 'invalid-documentation',
@ -1006,7 +1016,8 @@ class ModuleValidator(Validator):
doc, doc,
doc_schema( doc_schema(
self.object_name.split('.')[0], self.object_name.split('.')[0],
version_added=not bool(self.collection) version_added=not bool(self.collection),
deprecated_module=deprecated,
), ),
'DOCUMENTATION', 'DOCUMENTATION',
'invalid-documentation', 'invalid-documentation',
@ -1070,23 +1081,42 @@ class ModuleValidator(Validator):
) )
# Check for mismatched deprecation # Check for mismatched deprecation
mismatched_deprecation = True if not self.collection:
if not (filename_deprecated_or_removed or removed or deprecated or doc_deprecated): mismatched_deprecation = True
mismatched_deprecation = False if not (filename_deprecated_or_removed or removed or deprecated or doc_deprecated):
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 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: if mismatched_deprecation:
self.reporter.error( self.reporter.error(
path=self.object_path, path=self.object_path,
code='deprecation-mismatch', code='deprecation-mismatch',
msg='Module deprecation/removed must agree in Metadata, by prepending filename with' msg='Module deprecation/removed must agree in Metadata, by prepending filename with'
' "_", and setting DOCUMENTATION.deprecated for deprecation or by removing all' ' "_", and setting DOCUMENTATION.deprecated for deprecation or by removing all'
' documentation for removed' ' documentation for removed'
) )
else:
# We are testing a collection
if self.object_name.startswith('_'):
self.reporter.error(
path=self.object_path,
code='collections-no-underscore-on-deprecation',
msg='Deprecated content in collections MUST NOT start with "_", update meta/routing.yml instead',
)
if not (doc_deprecated == routing_says_deprecated):
# DOCUMENTATION.deprecated and meta/routing.yml disagree
self.reporter.error(
path=self.object_path,
code='deprecation-mismatch',
msg='"meta/routing.yml" and DOCUMENTATION.deprecation do not agree.'
)
# In the future we should error if ANSIBLE_METADATA exists in a collection
return doc_info, doc return doc_info, doc
@ -2111,8 +2141,19 @@ def run():
check_dirs = set() check_dirs = set()
routing = None
if args.collection: if args.collection:
setup_collection_loader() setup_collection_loader()
routing_file = 'meta/routing.yml'
# Load meta/routing.yml if it exists, as it may contain deprecation information
if os.path.isfile(routing_file):
try:
with open(routing_file) as f:
routing = yaml.safe_load(f)
except yaml.error.MarkedYAMLError as ex:
print('%s:%d:%d: YAML load failed: %s' % (routing_file, ex.context_mark.line + 1, ex.context_mark.column + 1, re.sub(r'\s+', ' ', str(ex))))
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))))
for module in args.modules: for module in args.modules:
if os.path.isfile(module): if os.path.isfile(module):
@ -2122,7 +2163,7 @@ def run():
if ModuleValidator.is_blacklisted(path): if ModuleValidator.is_blacklisted(path):
continue continue
with ModuleValidator(path, collection=args.collection, analyze_arg_spec=args.arg_spec, with ModuleValidator(path, collection=args.collection, analyze_arg_spec=args.arg_spec,
base_branch=args.base_branch, git_cache=git_cache, reporter=reporter) as mv1: base_branch=args.base_branch, git_cache=git_cache, reporter=reporter, routing=routing) as mv1:
mv1.validate() mv1.validate()
check_dirs.add(os.path.dirname(path)) check_dirs.add(os.path.dirname(path))
@ -2145,7 +2186,7 @@ def run():
if ModuleValidator.is_blacklisted(path): if ModuleValidator.is_blacklisted(path):
continue continue
with ModuleValidator(path, collection=args.collection, analyze_arg_spec=args.arg_spec, with ModuleValidator(path, collection=args.collection, analyze_arg_spec=args.arg_spec,
base_branch=args.base_branch, git_cache=git_cache, reporter=reporter) as mv2: base_branch=args.base_branch, git_cache=git_cache, reporter=reporter, routing=routing) as mv2:
mv2.validate() mv2.validate()
if not args.collection: if not args.collection:

View file

@ -285,8 +285,7 @@ def author(value):
raise Invalid("Invalid author") raise Invalid("Invalid author")
def doc_schema(module_name, version_added=True): def doc_schema(module_name, version_added=True, deprecated_module=False):
deprecated_module = False
if module_name.startswith('_'): if module_name.startswith('_'):
module_name = module_name[1:] module_name = module_name[1:]