|
|
|
|
@ -13,7 +13,6 @@ from urllib.parse import urlparse
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
from ansible.parsing.quoting import unquote
|
|
|
|
|
@ -25,9 +24,8 @@ from antsibull_docs_parser.parser import parse, Context
|
|
|
|
|
|
|
|
|
|
from .utils import parse_isodate
|
|
|
|
|
|
|
|
|
|
list_string_types = list(string_types)
|
|
|
|
|
tuple_string_types = tuple(string_types)
|
|
|
|
|
any_string_types = Any(*string_types)
|
|
|
|
|
list_string_types = [str]
|
|
|
|
|
tuple_string_types = (str,)
|
|
|
|
|
|
|
|
|
|
# Valid DOCUMENTATION.author lines
|
|
|
|
|
# Based on Ansibulbot's extract_github_id()
|
|
|
|
|
@ -57,7 +55,7 @@ FULLY_QUALIFIED_COLLECTION_RESOURCE_RE = re.compile(r'^\w+(?:\.\w+){2,}$')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def collection_name(v, error_code=None):
|
|
|
|
|
if not isinstance(v, string_types):
|
|
|
|
|
if not isinstance(v, str):
|
|
|
|
|
raise _add_ansible_error_code(
|
|
|
|
|
Invalid('Collection name must be a string'), error_code or 'collection-invalid-name')
|
|
|
|
|
m = COLLECTION_NAME_RE.match(v)
|
|
|
|
|
@ -68,7 +66,7 @@ def collection_name(v, error_code=None):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fqcn(v, error_code=None):
|
|
|
|
|
if not isinstance(v, string_types):
|
|
|
|
|
if not isinstance(v, str):
|
|
|
|
|
raise _add_ansible_error_code(
|
|
|
|
|
Invalid('Module/plugin name must be a string'), error_code or 'invalid-documentation')
|
|
|
|
|
m = FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(v)
|
|
|
|
|
@ -87,8 +85,8 @@ def deprecation_versions():
|
|
|
|
|
def version(for_collection=False):
|
|
|
|
|
if for_collection:
|
|
|
|
|
# We do not accept floats for versions in collections
|
|
|
|
|
return Any(*string_types)
|
|
|
|
|
return Any(float, *string_types)
|
|
|
|
|
return str
|
|
|
|
|
return Any(float, str)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def date(error_code=None):
|
|
|
|
|
@ -128,7 +126,7 @@ def _check_url(directive, content):
|
|
|
|
|
|
|
|
|
|
def doc_string(v):
|
|
|
|
|
"""Match a documentation string."""
|
|
|
|
|
if not isinstance(v, string_types):
|
|
|
|
|
if not isinstance(v, str):
|
|
|
|
|
raise _add_ansible_error_code(
|
|
|
|
|
Invalid('Must be a string'), 'invalid-documentation')
|
|
|
|
|
errors = []
|
|
|
|
|
@ -216,12 +214,12 @@ seealso_schema = Schema(
|
|
|
|
|
'description': doc_string,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Required('ref'): Any(*string_types),
|
|
|
|
|
Required('ref'): str,
|
|
|
|
|
Required('description'): doc_string,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Required('name'): Any(*string_types),
|
|
|
|
|
Required('link'): Any(*string_types),
|
|
|
|
|
Required('name'): str,
|
|
|
|
|
Required('link'): str,
|
|
|
|
|
Required('description'): doc_string,
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
@ -238,7 +236,7 @@ argument_spec_modifiers = {
|
|
|
|
|
'required_together': sequence_of_sequences(min=2),
|
|
|
|
|
'required_one_of': sequence_of_sequences(min=2),
|
|
|
|
|
'required_if': sequence_of_sequences(min=3, max=4),
|
|
|
|
|
'required_by': Schema({str: Any(list_string_types, tuple_string_types, *string_types)}),
|
|
|
|
|
'required_by': Schema({str: Any(list_string_types, tuple_string_types, str)}),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -263,7 +261,7 @@ def options_with_apply_defaults(v):
|
|
|
|
|
def check_removal_version(v, version_field, collection_name_field, error_code='invalid-removal-version'):
|
|
|
|
|
version = v.get(version_field)
|
|
|
|
|
collection_name = v.get(collection_name_field)
|
|
|
|
|
if not isinstance(version, string_types) or not isinstance(collection_name, string_types):
|
|
|
|
|
if not isinstance(version, str) or not isinstance(collection_name, str):
|
|
|
|
|
# If they are not strings, schema validation will have already complained.
|
|
|
|
|
return v
|
|
|
|
|
if collection_name == 'ansible.builtin':
|
|
|
|
|
@ -313,9 +311,8 @@ def option_deprecation(v):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def argument_spec_schema(for_collection):
|
|
|
|
|
any_string_types = Any(*string_types)
|
|
|
|
|
schema = {
|
|
|
|
|
any_string_types: {
|
|
|
|
|
str: {
|
|
|
|
|
'type': Any(is_callable, *argument_spec_types),
|
|
|
|
|
'elements': Any(*argument_spec_types),
|
|
|
|
|
'default': object,
|
|
|
|
|
@ -336,12 +333,12 @@ def argument_spec_schema(for_collection):
|
|
|
|
|
'deprecated_aliases': Any([All(
|
|
|
|
|
Any(
|
|
|
|
|
{
|
|
|
|
|
Required('name'): Any(*string_types),
|
|
|
|
|
Required('name'): str,
|
|
|
|
|
Required('date'): date(),
|
|
|
|
|
Required('collection_name'): collection_name,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Required('name'): Any(*string_types),
|
|
|
|
|
Required('name'): str,
|
|
|
|
|
Required('version'): version(for_collection),
|
|
|
|
|
Required('collection_name'): collection_name,
|
|
|
|
|
},
|
|
|
|
|
@ -353,13 +350,13 @@ def argument_spec_schema(for_collection):
|
|
|
|
|
)]),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
schema[any_string_types].update(argument_spec_modifiers)
|
|
|
|
|
schema[str].update(argument_spec_modifiers)
|
|
|
|
|
schemas = All(
|
|
|
|
|
schema,
|
|
|
|
|
Schema({any_string_types: no_required_with_default}),
|
|
|
|
|
Schema({any_string_types: elements_with_list}),
|
|
|
|
|
Schema({any_string_types: options_with_apply_defaults}),
|
|
|
|
|
Schema({any_string_types: option_deprecation}),
|
|
|
|
|
Schema({str: no_required_with_default}),
|
|
|
|
|
Schema({str: elements_with_list}),
|
|
|
|
|
Schema({str: options_with_apply_defaults}),
|
|
|
|
|
Schema({str: option_deprecation}),
|
|
|
|
|
)
|
|
|
|
|
return Schema(schemas)
|
|
|
|
|
|
|
|
|
|
@ -385,14 +382,15 @@ json_value = Schema(Any(
|
|
|
|
|
int,
|
|
|
|
|
float,
|
|
|
|
|
[Self],
|
|
|
|
|
*(list({str_type: Self} for str_type in string_types) + list(string_types))
|
|
|
|
|
{str: Self},
|
|
|
|
|
str,
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def version_added(v, error_code='version-added-invalid', accept_historical=False):
|
|
|
|
|
if 'version_added' in v:
|
|
|
|
|
version_added = v.get('version_added')
|
|
|
|
|
if isinstance(version_added, string_types):
|
|
|
|
|
if isinstance(version_added, str):
|
|
|
|
|
# If it is not a string, schema validation will have already complained
|
|
|
|
|
# - or we have a float and we are in ansible/ansible, in which case we're
|
|
|
|
|
# also happy.
|
|
|
|
|
@ -451,7 +449,7 @@ def get_type_checker(v):
|
|
|
|
|
elt_checker, elt_name = get_type_checker({'type': v.get('elements')})
|
|
|
|
|
|
|
|
|
|
def list_checker(value):
|
|
|
|
|
if isinstance(value, string_types):
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
value = [unquote(x.strip()) for x in value.split(',')]
|
|
|
|
|
if not isinstance(value, list):
|
|
|
|
|
raise ValueError('Value must be a list')
|
|
|
|
|
@ -482,14 +480,14 @@ def get_type_checker(v):
|
|
|
|
|
|
|
|
|
|
if v_type in ('str', 'string', 'path', 'tmp', 'temppath', 'tmppath'):
|
|
|
|
|
def str_checker(value):
|
|
|
|
|
if not isinstance(value, string_types):
|
|
|
|
|
if not isinstance(value, str):
|
|
|
|
|
raise ValueError('Value must be string')
|
|
|
|
|
|
|
|
|
|
return str_checker, v_type
|
|
|
|
|
|
|
|
|
|
if v_type in ('pathspec', 'pathlist'):
|
|
|
|
|
def path_list_checker(value):
|
|
|
|
|
if not isinstance(value, string_types) and not is_iterable(value):
|
|
|
|
|
if not isinstance(value, str) and not is_iterable(value):
|
|
|
|
|
raise ValueError('Value must be string or list of strings')
|
|
|
|
|
|
|
|
|
|
return path_list_checker, v_type
|
|
|
|
|
@ -588,7 +586,7 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
'elements': element_types,
|
|
|
|
|
}
|
|
|
|
|
if plugin_type != 'module':
|
|
|
|
|
basic_option_schema['name'] = Any(*string_types)
|
|
|
|
|
basic_option_schema['name'] = str
|
|
|
|
|
deprecated_schema = All(
|
|
|
|
|
Schema(
|
|
|
|
|
All(
|
|
|
|
|
@ -605,10 +603,10 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
# This definition makes sure that everything we require is there
|
|
|
|
|
Required('why'): Any(*string_types),
|
|
|
|
|
Required(Any('alternatives', 'alternative')): Any(*string_types),
|
|
|
|
|
Required(Any('removed_at_date', 'version')): Any(*string_types),
|
|
|
|
|
Required('collection_name'): Any(*string_types),
|
|
|
|
|
Required('why'): str,
|
|
|
|
|
Required(Any('alternatives', 'alternative')): str,
|
|
|
|
|
Required(Any('removed_at_date', 'version')): str,
|
|
|
|
|
Required('collection_name'): str,
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
extra=PREVENT_EXTRA
|
|
|
|
|
@ -620,7 +618,7 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
)
|
|
|
|
|
env_schema = All(
|
|
|
|
|
Schema({
|
|
|
|
|
Required('name'): Any(*string_types),
|
|
|
|
|
Required('name'): str,
|
|
|
|
|
'deprecated': deprecated_schema,
|
|
|
|
|
'version_added': version(for_collection),
|
|
|
|
|
'version_added_collection': collection_name,
|
|
|
|
|
@ -629,8 +627,8 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
)
|
|
|
|
|
ini_schema = All(
|
|
|
|
|
Schema({
|
|
|
|
|
Required('key'): Any(*string_types),
|
|
|
|
|
Required('section'): Any(*string_types),
|
|
|
|
|
Required('key'): str,
|
|
|
|
|
Required('section'): str,
|
|
|
|
|
'deprecated': deprecated_schema,
|
|
|
|
|
'version_added': version(for_collection),
|
|
|
|
|
'version_added_collection': collection_name,
|
|
|
|
|
@ -639,7 +637,7 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
)
|
|
|
|
|
vars_schema = All(
|
|
|
|
|
Schema({
|
|
|
|
|
Required('name'): Any(*string_types),
|
|
|
|
|
Required('name'): str,
|
|
|
|
|
'deprecated': deprecated_schema,
|
|
|
|
|
'version_added': version(for_collection),
|
|
|
|
|
'version_added_collection': collection_name,
|
|
|
|
|
@ -648,8 +646,8 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
)
|
|
|
|
|
cli_schema = All(
|
|
|
|
|
Schema({
|
|
|
|
|
Required('name'): Any(*string_types),
|
|
|
|
|
'option': Any(*string_types),
|
|
|
|
|
Required('name'): str,
|
|
|
|
|
'option': str,
|
|
|
|
|
'deprecated': deprecated_schema,
|
|
|
|
|
'version_added': version(for_collection),
|
|
|
|
|
'version_added_collection': collection_name,
|
|
|
|
|
@ -658,7 +656,7 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
)
|
|
|
|
|
keyword_schema = All(
|
|
|
|
|
Schema({
|
|
|
|
|
Required('name'): Any(*string_types),
|
|
|
|
|
Required('name'): str,
|
|
|
|
|
'deprecated': deprecated_schema,
|
|
|
|
|
'version_added': version(for_collection),
|
|
|
|
|
'version_added_collection': collection_name,
|
|
|
|
|
@ -677,7 +675,7 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
suboption_schema = dict(basic_option_schema)
|
|
|
|
|
suboption_schema.update({
|
|
|
|
|
# Recursive suboptions
|
|
|
|
|
'suboptions': Any(None, *list({str_type: Self} for str_type in string_types)),
|
|
|
|
|
'suboptions': Any(None, {str: Self}),
|
|
|
|
|
})
|
|
|
|
|
suboption_schema = Schema(All(
|
|
|
|
|
suboption_schema,
|
|
|
|
|
@ -686,13 +684,9 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
check_option_default,
|
|
|
|
|
), extra=PREVENT_EXTRA)
|
|
|
|
|
|
|
|
|
|
# This generates list of dicts with keys from string_types and suboption_schema value
|
|
|
|
|
# for example in Python 3: {str: suboption_schema}
|
|
|
|
|
list_dict_suboption_schema = [{str_type: suboption_schema} for str_type in string_types]
|
|
|
|
|
|
|
|
|
|
option_schema = dict(basic_option_schema)
|
|
|
|
|
option_schema.update({
|
|
|
|
|
'suboptions': Any(None, *list_dict_suboption_schema),
|
|
|
|
|
'suboptions': Any(None, {str: suboption_schema}),
|
|
|
|
|
})
|
|
|
|
|
option_schema = Schema(All(
|
|
|
|
|
option_schema,
|
|
|
|
|
@ -703,20 +697,18 @@ def list_dict_option_schema(for_collection, plugin_type):
|
|
|
|
|
|
|
|
|
|
option_version_added = Schema(
|
|
|
|
|
All({
|
|
|
|
|
'suboptions': Any(None, *[{str_type: Self} for str_type in string_types]),
|
|
|
|
|
'suboptions': Any(None, {str: Self}),
|
|
|
|
|
}, partial(version_added, error_code='option-invalid-version-added')),
|
|
|
|
|
extra=ALLOW_EXTRA
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# This generates list of dicts with keys from string_types and option_schema value
|
|
|
|
|
# for example in Python 3: {str: option_schema}
|
|
|
|
|
return [{str_type: All(option_schema, option_version_added)} for str_type in string_types]
|
|
|
|
|
return [{str: All(option_schema, option_version_added)}]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def return_contains(v):
|
|
|
|
|
schema = Schema(
|
|
|
|
|
{
|
|
|
|
|
Required('contains'): Any(dict, list, *string_types)
|
|
|
|
|
Required('contains'): Any(dict, list, str)
|
|
|
|
|
},
|
|
|
|
|
extra=ALLOW_EXTRA
|
|
|
|
|
)
|
|
|
|
|
@ -752,7 +744,7 @@ def return_schema(for_collection, plugin_type='module'):
|
|
|
|
|
|
|
|
|
|
inner_return_option_schema = dict(basic_return_option_schema)
|
|
|
|
|
inner_return_option_schema.update({
|
|
|
|
|
'contains': Any(None, *list({str_type: Self} for str_type in string_types)),
|
|
|
|
|
'contains': Any(None, {str: Self}),
|
|
|
|
|
})
|
|
|
|
|
return_contains_schema = Any(
|
|
|
|
|
All(
|
|
|
|
|
@ -763,27 +755,23 @@ def return_schema(for_collection, plugin_type='module'):
|
|
|
|
|
Schema(type(None)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# This generates list of dicts with keys from string_types and return_contains_schema value
|
|
|
|
|
# for example in Python 3: {str: return_contains_schema}
|
|
|
|
|
list_dict_return_contains_schema = [{str_type: return_contains_schema} for str_type in string_types]
|
|
|
|
|
|
|
|
|
|
return_option_schema = dict(basic_return_option_schema)
|
|
|
|
|
return_option_schema.update({
|
|
|
|
|
'contains': Any(None, *list_dict_return_contains_schema),
|
|
|
|
|
'contains': Any(None, {str: return_contains_schema}),
|
|
|
|
|
})
|
|
|
|
|
if plugin_type == 'module':
|
|
|
|
|
# 'returned' is required on top-level
|
|
|
|
|
del return_option_schema['returned']
|
|
|
|
|
return_option_schema[Required('returned')] = Any(*string_types)
|
|
|
|
|
return_option_schema[Required('returned')] = str
|
|
|
|
|
return Any(
|
|
|
|
|
All(
|
|
|
|
|
Schema(
|
|
|
|
|
{
|
|
|
|
|
any_string_types: return_option_schema
|
|
|
|
|
str: return_option_schema
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
Schema({any_string_types: return_contains}),
|
|
|
|
|
Schema({any_string_types: partial(version_added, error_code='option-invalid-version-added')}),
|
|
|
|
|
Schema({str: return_contains}),
|
|
|
|
|
Schema({str: partial(version_added, error_code='option-invalid-version-added')}),
|
|
|
|
|
),
|
|
|
|
|
Schema(type(None)),
|
|
|
|
|
)
|
|
|
|
|
@ -840,7 +828,7 @@ def author(value):
|
|
|
|
|
value = [value]
|
|
|
|
|
|
|
|
|
|
for line in value:
|
|
|
|
|
if not isinstance(line, string_types):
|
|
|
|
|
if not isinstance(line, str):
|
|
|
|
|
continue # let schema checks handle
|
|
|
|
|
m = author_line.search(line)
|
|
|
|
|
if not m:
|
|
|
|
|
@ -868,14 +856,14 @@ def doc_schema(module_name, for_collection=False, deprecated_module=False, plugi
|
|
|
|
|
'requirements': [doc_string],
|
|
|
|
|
'todo': Any(None, doc_string_or_strings),
|
|
|
|
|
'options': Any(None, *list_dict_option_schema(for_collection, plugin_type)),
|
|
|
|
|
'extends_documentation_fragment': Any(list_string_types, *string_types),
|
|
|
|
|
'extends_documentation_fragment': Any(list_string_types, str),
|
|
|
|
|
'version_added_collection': collection_name,
|
|
|
|
|
}
|
|
|
|
|
if plugin_type == 'module':
|
|
|
|
|
doc_schema_dict[Required('author')] = All(Any(None, list_string_types, *string_types), author)
|
|
|
|
|
doc_schema_dict[Required('author')] = All(Any(None, list_string_types, str), author)
|
|
|
|
|
else:
|
|
|
|
|
# author is optional for plugins (for now)
|
|
|
|
|
doc_schema_dict['author'] = All(Any(None, list_string_types, *string_types), author)
|
|
|
|
|
doc_schema_dict['author'] = All(Any(None, list_string_types, str), author)
|
|
|
|
|
if plugin_type == 'callback':
|
|
|
|
|
doc_schema_dict[Required('type')] = Any('aggregate', 'notification', 'stdout')
|
|
|
|
|
|
|
|
|
|
@ -896,9 +884,9 @@ def doc_schema(module_name, for_collection=False, deprecated_module=False, plugi
|
|
|
|
|
schema = {
|
|
|
|
|
'description': doc_string_or_strings,
|
|
|
|
|
'details': doc_string_or_strings,
|
|
|
|
|
'support': any_string_types,
|
|
|
|
|
'version_added_collection': any_string_types,
|
|
|
|
|
'version_added': any_string_types,
|
|
|
|
|
'support': str,
|
|
|
|
|
'version_added_collection': str,
|
|
|
|
|
'version_added': str,
|
|
|
|
|
}
|
|
|
|
|
if more:
|
|
|
|
|
schema.update(more)
|
|
|
|
|
@ -907,7 +895,7 @@ def doc_schema(module_name, for_collection=False, deprecated_module=False, plugi
|
|
|
|
|
doc_schema_dict['attributes'] = Schema(
|
|
|
|
|
All(
|
|
|
|
|
Schema({
|
|
|
|
|
any_string_types: {
|
|
|
|
|
str: {
|
|
|
|
|
Required('description'): doc_string_or_strings,
|
|
|
|
|
Required('support'): Any('full', 'partial', 'none', 'N/A'),
|
|
|
|
|
'details': doc_string_or_strings,
|
|
|
|
|
@ -917,12 +905,12 @@ def doc_schema(module_name, for_collection=False, deprecated_module=False, plugi
|
|
|
|
|
}, extra=ALLOW_EXTRA),
|
|
|
|
|
partial(version_added, error_code='attribute-invalid-version-added', accept_historical=False),
|
|
|
|
|
Schema({
|
|
|
|
|
any_string_types: add_default_attributes(),
|
|
|
|
|
str: add_default_attributes(),
|
|
|
|
|
'action_group': add_default_attributes({
|
|
|
|
|
Required('membership'): list_string_types,
|
|
|
|
|
}),
|
|
|
|
|
'platform': add_default_attributes({
|
|
|
|
|
Required('platforms'): Any(list_string_types, *string_types)
|
|
|
|
|
Required('platforms'): Any(list_string_types, str)
|
|
|
|
|
}),
|
|
|
|
|
}, extra=PREVENT_EXTRA),
|
|
|
|
|
)
|
|
|
|
|
|