summaryrefslogtreecommitdiff
path: root/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py')
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py113
1 files changed, 63 insertions, 50 deletions
diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py
index b2623ff7..a6068c60 100644
--- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py
+++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py
@@ -11,7 +11,8 @@ from ansible.module_utils.compat.version import StrictVersion
from functools import partial
from urllib.parse import urlparse
-from voluptuous import ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Invalid, Length, Required, Schema, Self, ValueInvalid, Exclusive
+from voluptuous import ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Invalid, Length, MultipleInvalid, Required, Schema, Self, ValueInvalid, Exclusive
+from ansible.constants import DOCUMENTABLE_PLUGINS
from ansible.module_utils.six import string_types
from ansible.module_utils.common.collections import is_iterable
from ansible.module_utils.parsing.convert_bool import boolean
@@ -19,6 +20,9 @@ from ansible.parsing.quoting import unquote
from ansible.utils.version import SemanticVersion
from ansible.release import __version__
+from antsibull_docs_parser import dom
+from antsibull_docs_parser.parser import parse, Context
+
from .utils import parse_isodate
list_string_types = list(string_types)
@@ -80,26 +84,8 @@ def date(error_code=None):
return Any(isodate, error_code=error_code)
-_MODULE = re.compile(r"\bM\(([^)]+)\)")
-_LINK = re.compile(r"\bL\(([^)]+)\)")
-_URL = re.compile(r"\bU\(([^)]+)\)")
-_REF = re.compile(r"\bR\(([^)]+)\)")
-
-
-def _check_module_link(directive, content):
- if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(content):
- raise _add_ansible_error_code(
- Invalid('Directive "%s" must contain a FQCN' % directive), 'invalid-documentation-markup')
-
-
-def _check_link(directive, content):
- if ',' not in content:
- raise _add_ansible_error_code(
- Invalid('Directive "%s" must contain a comma' % directive), 'invalid-documentation-markup')
- idx = content.rindex(',')
- title = content[:idx]
- url = content[idx + 1:].lstrip(' ')
- _check_url(directive, url)
+# Roles can also be referenced by semantic markup
+_VALID_PLUGIN_TYPES = set(DOCUMENTABLE_PLUGINS + ('role', ))
def _check_url(directive, content):
@@ -107,15 +93,10 @@ def _check_url(directive, content):
parsed_url = urlparse(content)
if parsed_url.scheme not in ('', 'http', 'https'):
raise ValueError('Schema must be HTTP, HTTPS, or not specified')
- except ValueError as exc:
- raise _add_ansible_error_code(
- Invalid('Directive "%s" must contain an URL' % directive), 'invalid-documentation-markup')
-
-
-def _check_ref(directive, content):
- if ',' not in content:
- raise _add_ansible_error_code(
- Invalid('Directive "%s" must contain a comma' % directive), 'invalid-documentation-markup')
+ return []
+ except ValueError:
+ return [_add_ansible_error_code(
+ Invalid('Directive %s must contain a valid URL' % directive), 'invalid-documentation-markup')]
def doc_string(v):
@@ -123,25 +104,55 @@ def doc_string(v):
if not isinstance(v, string_types):
raise _add_ansible_error_code(
Invalid('Must be a string'), 'invalid-documentation')
- for m in _MODULE.finditer(v):
- _check_module_link(m.group(0), m.group(1))
- for m in _LINK.finditer(v):
- _check_link(m.group(0), m.group(1))
- for m in _URL.finditer(v):
- _check_url(m.group(0), m.group(1))
- for m in _REF.finditer(v):
- _check_ref(m.group(0), m.group(1))
+ errors = []
+ for par in parse(v, Context(), errors='message', strict=True, add_source=True):
+ for part in par:
+ if part.type == dom.PartType.ERROR:
+ errors.append(_add_ansible_error_code(Invalid(part.message), 'invalid-documentation-markup'))
+ if part.type == dom.PartType.URL:
+ errors.extend(_check_url('U()', part.url))
+ if part.type == dom.PartType.LINK:
+ errors.extend(_check_url('L()', part.url))
+ if part.type == dom.PartType.MODULE:
+ if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.fqcn):
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.fqcn)),
+ 'invalid-documentation-markup'))
+ if part.type == dom.PartType.PLUGIN:
+ if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
+ 'invalid-documentation-markup'))
+ if part.plugin.type not in _VALID_PLUGIN_TYPES:
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
+ 'invalid-documentation-markup'))
+ if part.type == dom.PartType.OPTION_NAME:
+ if part.plugin is not None and not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
+ 'invalid-documentation-markup'))
+ if part.plugin is not None and part.plugin.type not in _VALID_PLUGIN_TYPES:
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
+ 'invalid-documentation-markup'))
+ if part.type == dom.PartType.RETURN_VALUE:
+ if part.plugin is not None and not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
+ 'invalid-documentation-markup'))
+ if part.plugin is not None and part.plugin.type not in _VALID_PLUGIN_TYPES:
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
+ 'invalid-documentation-markup'))
+ if len(errors) == 1:
+ raise errors[0]
+ if errors:
+ raise MultipleInvalid(errors)
return v
-def doc_string_or_strings(v):
- """Match a documentation string, or list of strings."""
- if isinstance(v, string_types):
- return doc_string(v)
- if isinstance(v, (list, tuple)):
- return [doc_string(vv) for vv in v]
- raise _add_ansible_error_code(
- Invalid('Must be a string or list of strings'), 'invalid-documentation')
+doc_string_or_strings = Any(doc_string, [doc_string])
def is_callable(v):
@@ -173,6 +184,11 @@ seealso_schema = Schema(
'description': doc_string,
},
{
+ Required('plugin'): Any(*string_types),
+ Required('plugin_type'): Any(*DOCUMENTABLE_PLUGINS),
+ 'description': doc_string,
+ },
+ {
Required('ref'): Any(*string_types),
Required('description'): doc_string,
},
@@ -794,7 +810,7 @@ def author(value):
def doc_schema(module_name, for_collection=False, deprecated_module=False, plugin_type='module'):
- if module_name.startswith('_'):
+ if module_name.startswith('_') and not for_collection:
module_name = module_name[1:]
deprecated_module = True
if for_collection is False and plugin_type == 'connection' and module_name == 'paramiko_ssh':
@@ -864,9 +880,6 @@ def doc_schema(module_name, for_collection=False, deprecated_module=False, plugi
'action_group': add_default_attributes({
Required('membership'): list_string_types,
}),
- 'forced_action_plugin': add_default_attributes({
- Required('action_plugin'): any_string_types,
- }),
'platform': add_default_attributes({
Required('platforms'): Any(list_string_types, *string_types)
}),