Add several new doc<->arg_spec checks (#36247)
* Add several new doc<->arg_spec checks. See #18183 * Update ignore.txt for validate-modules
This commit is contained in:
parent
77fa41795e
commit
50adc5409b
4 changed files with 2958 additions and 7 deletions
|
@ -110,6 +110,9 @@ Errors
|
||||||
321 ``Exception`` attempting to import module for ``argument_spec`` introspection
|
321 ``Exception`` attempting to import module for ``argument_spec`` introspection
|
||||||
322 argument is listed in the argument_spec, but not documented in the module
|
322 argument is listed in the argument_spec, but not documented in the module
|
||||||
323 argument is listed in DOCUMENTATION.options, but not accepted by the module
|
323 argument is listed in DOCUMENTATION.options, but not accepted by the module
|
||||||
|
324 Value for "default" from the argument_spec does not match the documentation
|
||||||
|
325 argument_spec defines type="bool" but documentation does not
|
||||||
|
326 Value for "choices" from the argument_spec does not match the documentation
|
||||||
..
|
..
|
||||||
--------- -------------------
|
--------- -------------------
|
||||||
**4xx** **Syntax**
|
**4xx** **Syntax**
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -45,7 +45,7 @@ from module_args import AnsibleModuleImportError, get_argument_spec
|
||||||
|
|
||||||
from schema import doc_schema, metadata_1_1_schema, return_schema
|
from schema import doc_schema, metadata_1_1_schema, return_schema
|
||||||
|
|
||||||
from utils import CaptureStd, parse_yaml
|
from utils import CaptureStd, compare_unordered_lists, maybe_convert_bool, parse_yaml
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from ansible.module_utils.six import PY3, with_metaclass
|
from ansible.module_utils.six import PY3, with_metaclass
|
||||||
|
@ -1058,6 +1058,34 @@ class ModuleValidator(Validator):
|
||||||
'should not be marked as required' % arg)
|
'should not be marked as required' % arg)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
doc_default = docs.get('options', {}).get(arg, {}).get('default', None)
|
||||||
|
if data.get('type') == 'bool':
|
||||||
|
doc_default = maybe_convert_bool(doc_default)
|
||||||
|
if 'default' in data and data['default'] != doc_default:
|
||||||
|
self.reporter.error(
|
||||||
|
path=self.object_path,
|
||||||
|
code=324,
|
||||||
|
msg=('Value for "default" from the argument_spec (%r) for "%s" does not match the '
|
||||||
|
'documentation (%r)' % (data['default'], arg, doc_default))
|
||||||
|
)
|
||||||
|
|
||||||
|
doc_type = docs.get('options', {}).get(arg, {}).get('type', 'str')
|
||||||
|
if 'type' in data and data['type'] == 'bool' and doc_type != 'bool':
|
||||||
|
self.reporter.error(
|
||||||
|
path=self.object_path,
|
||||||
|
code=325,
|
||||||
|
msg='argument_spec for "%s" defines type="bool" but documentation does not' % (arg,)
|
||||||
|
)
|
||||||
|
|
||||||
|
doc_choices = docs.get('options', {}).get(arg, {}).get('choices', [])
|
||||||
|
if not compare_unordered_lists(data.get('choices', []), doc_choices):
|
||||||
|
self.reporter.error(
|
||||||
|
path=self.object_path,
|
||||||
|
code=326,
|
||||||
|
msg=('Value for "choices" from the argument_spec (%r) for "%s" does not match the '
|
||||||
|
'documentation (%r)' % (data.get('choices', []), arg, doc_choices))
|
||||||
|
)
|
||||||
|
|
||||||
if docs:
|
if docs:
|
||||||
try:
|
try:
|
||||||
add_fragments(docs, self.object_path, fragment_loader=fragment_loader)
|
add_fragments(docs, self.object_path, fragment_loader=fragment_loader)
|
||||||
|
|
|
@ -25,6 +25,7 @@ import yaml
|
||||||
import yaml.reader
|
import yaml.reader
|
||||||
|
|
||||||
from ansible.module_utils._text import to_text
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.module_utils.parsing.convert_bool import boolean
|
||||||
|
|
||||||
|
|
||||||
class AnsibleTextIOWrapper(TextIOWrapper):
|
class AnsibleTextIOWrapper(TextIOWrapper):
|
||||||
|
@ -114,3 +115,24 @@ def parse_yaml(value, lineno, module, name, load_all=False):
|
||||||
})
|
})
|
||||||
|
|
||||||
return data, errors, traces
|
return data, errors, traces
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_convert_bool(value):
|
||||||
|
"""Safe conversion to boolean, catching TypeError and returning the original result
|
||||||
|
|
||||||
|
Only used in doc<->arg_spec comparisons
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return boolean(value)
|
||||||
|
except TypeError:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def compare_unordered_lists(a, b):
|
||||||
|
"""Safe list comparisons
|
||||||
|
|
||||||
|
Supports:
|
||||||
|
- unordered lists
|
||||||
|
- unhashable elements
|
||||||
|
"""
|
||||||
|
return len(a) == len(b) and all(x in b for x in a)
|
||||||
|
|
Loading…
Reference in a new issue