[2.12] 'attributes' (#73707)
* wip 'attributes' * added version added tests * syntzx * not bile * correztlys merges * moved desc to frag * simpler as dict * unused * clog * Update lib/ansible/utils/plugin_docs.py Co-authored-by: Jacob Floyd <cognifloyd@gmail.com> * unnoted * added action plugins * longer list * add sttri schema * huh? * itsdict * dictit * yolo * gnore for now * moar attribs * allow extras * positive * added loop, documented 'imports' * support is now none/partial/full * import_playbook is outside host loop Co-authored-by: Jacob Floyd <cognifloyd@gmail.com>
This commit is contained in:
parent
185d410316
commit
07939b04f3
9 changed files with 166 additions and 16 deletions
2
changelogs/fragments/plugin_attributes.yml
Normal file
2
changelogs/fragments/plugin_attributes.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- ansible-doc now supports 'attributes' for plugins as per proposal.
|
|
@ -54,6 +54,7 @@ def jdump(text):
|
||||||
try:
|
try:
|
||||||
display.display(json.dumps(text, cls=AnsibleJSONEncoder, sort_keys=True, indent=4))
|
display.display(json.dumps(text, cls=AnsibleJSONEncoder, sort_keys=True, indent=4))
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
|
display.vvv(traceback.format_exc())
|
||||||
raise AnsibleError('We could not convert all the documentation into JSON as there was a conversion issue: %s' % to_native(e))
|
raise AnsibleError('We could not convert all the documentation into JSON as there was a conversion issue: %s' % to_native(e))
|
||||||
|
|
||||||
|
|
||||||
|
@ -780,7 +781,8 @@ class DocCLI(CLI, RoleMixin):
|
||||||
try:
|
try:
|
||||||
text = DocCLI.get_man_text(doc, collection_name, plugin_type)
|
text = DocCLI.get_man_text(doc, collection_name, plugin_type)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise AnsibleError("Unable to retrieve documentation from '%s' due to: %s" % (plugin, to_native(e)))
|
display.vvv(traceback.format_exc())
|
||||||
|
raise AnsibleError("Unable to retrieve documentation from '%s' due to: %s" % (plugin, to_native(e)), orig_exc=e)
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
@ -874,6 +876,7 @@ class DocCLI(CLI, RoleMixin):
|
||||||
pfiles[plugin] = filename
|
pfiles[plugin] = filename
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
display.vvv(traceback.format_exc())
|
||||||
raise AnsibleError("Failed reading docs at %s: %s" % (plugin, to_native(e)), orig_exc=e)
|
raise AnsibleError("Failed reading docs at %s: %s" % (plugin, to_native(e)), orig_exc=e)
|
||||||
|
|
||||||
return pfiles
|
return pfiles
|
||||||
|
@ -921,9 +924,7 @@ class DocCLI(CLI, RoleMixin):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _dump_yaml(struct, indent):
|
def _dump_yaml(struct, indent):
|
||||||
return DocCLI.tty_ify('\n'.join([indent + line for line in
|
return DocCLI.tty_ify('\n'.join([indent + line for line in yaml.dump(struct, default_flow_style=False, Dumper=AnsibleDumper).split('\n')]))
|
||||||
yaml.dump(struct, default_flow_style=False,
|
|
||||||
Dumper=AnsibleDumper).split('\n')]))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_fields(text, fields, limit, opt_indent, return_values=False, base_indent=''):
|
def add_fields(text, fields, limit, opt_indent, return_values=False, base_indent=''):
|
||||||
|
@ -1051,6 +1052,11 @@ class DocCLI(CLI, RoleMixin):
|
||||||
DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent)
|
DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent)
|
||||||
text.append('')
|
text.append('')
|
||||||
|
|
||||||
|
if doc.get('attributes'):
|
||||||
|
text.append("ATTRIBUTES:\n")
|
||||||
|
text.append(DocCLI._dump_yaml(doc.pop('attributes'), opt_indent))
|
||||||
|
text.append('')
|
||||||
|
|
||||||
# generic elements we will handle identically
|
# generic elements we will handle identically
|
||||||
for k in ('author',):
|
for k in ('author',):
|
||||||
if k not in doc:
|
if k not in doc:
|
||||||
|
@ -1115,6 +1121,11 @@ class DocCLI(CLI, RoleMixin):
|
||||||
DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent)
|
DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent)
|
||||||
text.append('')
|
text.append('')
|
||||||
|
|
||||||
|
if doc.get('attributes', False):
|
||||||
|
text.append("ATTRIBUTES:\n")
|
||||||
|
text.append(DocCLI._dump_yaml(doc.pop('attributes'), opt_indent))
|
||||||
|
text.append('')
|
||||||
|
|
||||||
if doc.get('notes', False):
|
if doc.get('notes', False):
|
||||||
text.append("NOTES:")
|
text.append("NOTES:")
|
||||||
for note in doc['notes']:
|
for note in doc['notes']:
|
||||||
|
|
|
@ -121,9 +121,9 @@ extends_documentation_fragment:
|
||||||
- decrypt
|
- decrypt
|
||||||
- files
|
- files
|
||||||
- validate
|
- validate
|
||||||
|
- action_common_attributes
|
||||||
notes:
|
notes:
|
||||||
- The M(ansible.builtin.copy) module recursively copy facility does not scale to lots (>hundreds) of files.
|
- The M(ansible.builtin.copy) module recursively copy facility does not scale to lots (>hundreds) of files.
|
||||||
- Supports C(check_mode).
|
|
||||||
seealso:
|
seealso:
|
||||||
- module: ansible.builtin.assemble
|
- module: ansible.builtin.assemble
|
||||||
- module: ansible.builtin.fetch
|
- module: ansible.builtin.fetch
|
||||||
|
@ -134,6 +134,15 @@ seealso:
|
||||||
author:
|
author:
|
||||||
- Ansible Core Team
|
- Ansible Core Team
|
||||||
- Michael DeHaan
|
- Michael DeHaan
|
||||||
|
attributes:
|
||||||
|
action:
|
||||||
|
support: full
|
||||||
|
check_mode:
|
||||||
|
version_added: '0.9'
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: full
|
||||||
|
version_added: '0.9'
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
|
|
|
@ -22,6 +22,27 @@ options:
|
||||||
free-form:
|
free-form:
|
||||||
description:
|
description:
|
||||||
- The name of the imported playbook is specified directly without any other option.
|
- The name of the imported playbook is specified directly without any other option.
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- action_common_attributes
|
||||||
|
attributes:
|
||||||
|
async:
|
||||||
|
support: none
|
||||||
|
become:
|
||||||
|
support: none
|
||||||
|
bypass_host_loop:
|
||||||
|
support: full
|
||||||
|
conditional:
|
||||||
|
support: none
|
||||||
|
connection:
|
||||||
|
support: none
|
||||||
|
delegation:
|
||||||
|
support: none
|
||||||
|
loops:
|
||||||
|
support: none
|
||||||
|
tags:
|
||||||
|
support: none
|
||||||
|
until:
|
||||||
|
support: none
|
||||||
notes:
|
notes:
|
||||||
- This is a core feature of Ansible, rather than a module, and cannot be overridden like a module.
|
- This is a core feature of Ansible, rather than a module, and cannot be overridden like a module.
|
||||||
seealso:
|
seealso:
|
||||||
|
|
|
@ -57,6 +57,27 @@ options:
|
||||||
type: bool
|
type: bool
|
||||||
default: yes
|
default: yes
|
||||||
version_added: '2.11'
|
version_added: '2.11'
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- action_common_attributes
|
||||||
|
attributes:
|
||||||
|
async:
|
||||||
|
support: none
|
||||||
|
become:
|
||||||
|
support: none
|
||||||
|
bypass_host_loop:
|
||||||
|
support: partial
|
||||||
|
conditional:
|
||||||
|
support: none
|
||||||
|
connection:
|
||||||
|
support: none
|
||||||
|
delegation:
|
||||||
|
support: none
|
||||||
|
loops:
|
||||||
|
support: none
|
||||||
|
tags:
|
||||||
|
support: none
|
||||||
|
until:
|
||||||
|
support: none
|
||||||
notes:
|
notes:
|
||||||
- Handlers are made available to the whole play.
|
- Handlers are made available to the whole play.
|
||||||
- Since Ansible 2.7 variables defined in C(vars) and C(defaults) for the role are exposed to the play at playbook parsing time.
|
- Since Ansible 2.7 variables defined in C(vars) and C(defaults) for the role are exposed to the play at playbook parsing time.
|
||||||
|
|
|
@ -22,6 +22,27 @@ options:
|
||||||
- The name of the imported file is specified directly without any other option.
|
- The name of the imported file is specified directly without any other option.
|
||||||
- Most keywords, including loops and conditionals, only applied to the imported tasks, not to this statement itself.
|
- Most keywords, including loops and conditionals, only applied to the imported tasks, not to this statement itself.
|
||||||
- If you need any of those to apply, use M(ansible.builtin.include_tasks) instead.
|
- If you need any of those to apply, use M(ansible.builtin.include_tasks) instead.
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- action_common_attributes
|
||||||
|
attributes:
|
||||||
|
async:
|
||||||
|
support: none
|
||||||
|
become:
|
||||||
|
support: none
|
||||||
|
bypass_host_loop:
|
||||||
|
support: partial
|
||||||
|
conditional:
|
||||||
|
support: none
|
||||||
|
connection:
|
||||||
|
support: none
|
||||||
|
delegation:
|
||||||
|
support: none
|
||||||
|
loops:
|
||||||
|
support: none
|
||||||
|
tags:
|
||||||
|
support: none
|
||||||
|
until:
|
||||||
|
support: none
|
||||||
notes:
|
notes:
|
||||||
- This is a core feature of Ansible, rather than a module, and cannot be overridden like a module.
|
- This is a core feature of Ansible, rather than a module, and cannot be overridden like a module.
|
||||||
seealso:
|
seealso:
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2015, Ansible, Inc
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
|
# Standard documentation fragment
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
attributes:
|
||||||
|
action:
|
||||||
|
description: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller
|
||||||
|
support: none
|
||||||
|
async:
|
||||||
|
description: Supports being used with the ``async`` keyword
|
||||||
|
support: full
|
||||||
|
become:
|
||||||
|
description: Is usable alongside become keywords
|
||||||
|
support: full
|
||||||
|
bypass_host_loop:
|
||||||
|
description: Forces a 'global' task that does not execute per host, cannot be used in non lockstep strategies
|
||||||
|
support: none
|
||||||
|
check_mode:
|
||||||
|
description: Can run in check_mode and return changed status prediction
|
||||||
|
support: none
|
||||||
|
connection:
|
||||||
|
description: Uses the target's configured connection information to execute code on it
|
||||||
|
support: full
|
||||||
|
conditional:
|
||||||
|
description: Will respect the `when` keyword per item loop or task (when no loop is present)
|
||||||
|
support: full
|
||||||
|
delegation:
|
||||||
|
description: Can be used in conjunction with delegate_to and related keywords
|
||||||
|
support: full
|
||||||
|
diff:
|
||||||
|
description: Will return details on what has changed when in diff is enabled
|
||||||
|
support: none
|
||||||
|
facts:
|
||||||
|
description: Action returns an ``ansible_facts`` dictionary that will update existing host facts
|
||||||
|
support: none
|
||||||
|
loops:
|
||||||
|
description: both ``loop`` and ``with_`` looping keywords will be honored.
|
||||||
|
support: full
|
||||||
|
proprietary:
|
||||||
|
description: Can only be run against specific proprietary OS, normally a network appliance or similar
|
||||||
|
support: none
|
||||||
|
posix:
|
||||||
|
description: Can be run against most POSIX (and GNU/Linux) OS targets
|
||||||
|
support: full
|
||||||
|
tags:
|
||||||
|
description: Tags will determine if this task considered for execution
|
||||||
|
support: full
|
||||||
|
until:
|
||||||
|
description: Usable inside until/retry loops
|
||||||
|
support: full
|
||||||
|
windows:
|
||||||
|
description: Can be run against Windows OS targets
|
||||||
|
support: none
|
||||||
|
'''
|
|
@ -6,13 +6,14 @@ __metaclass__ = type
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible.release import __version__ as ansible_version
|
from ansible.release import __version__ as ansible_version
|
||||||
from ansible.errors import AnsibleError, AnsibleAssertionError
|
from ansible.errors import AnsibleError
|
||||||
from ansible.module_utils.six import string_types
|
from ansible.module_utils.six import string_types
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
from ansible.module_utils.common._collections_compat import MutableMapping, MutableSet, MutableSequence
|
from ansible.module_utils.common._collections_compat import MutableMapping, MutableSet, MutableSequence
|
||||||
from ansible.parsing.plugin_docs import read_docstring
|
from ansible.parsing.plugin_docs import read_docstring
|
||||||
from ansible.parsing.yaml.loader import AnsibleLoader
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||||
from ansible.utils.display import Display
|
from ansible.utils.display import Display
|
||||||
|
from ansible.utils.vars import combine_vars
|
||||||
|
|
||||||
display = Display()
|
display = Display()
|
||||||
|
|
||||||
|
@ -179,17 +180,19 @@ def add_fragments(doc, filename, fragment_loader, is_module=False):
|
||||||
doc['seealso'] = []
|
doc['seealso'] = []
|
||||||
doc['seealso'].extend(seealso)
|
doc['seealso'].extend(seealso)
|
||||||
|
|
||||||
if 'options' not in fragment:
|
if 'options' not in fragment and 'attributes' not in fragment:
|
||||||
raise Exception("missing options in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename))
|
raise Exception("missing options or attributes in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename))
|
||||||
|
|
||||||
# ensure options themselves are directly merged
|
# ensure options themselves are directly merged
|
||||||
if 'options' in doc:
|
for doc_key in ['options', 'attributes']:
|
||||||
try:
|
if doc_key in fragment:
|
||||||
merge_fragment(doc['options'], fragment.pop('options'))
|
if doc_key in doc:
|
||||||
except Exception as e:
|
try:
|
||||||
raise AnsibleError("%s options (%s) of unknown type: %s" % (to_native(e), fragment_name, filename))
|
merge_fragment(doc[doc_key], fragment.pop(doc_key))
|
||||||
else:
|
except Exception as e:
|
||||||
doc['options'] = fragment.pop('options')
|
raise AnsibleError("%s %s (%s) of unknown type: %s" % (to_native(e), doc_key, fragment_name, filename))
|
||||||
|
else:
|
||||||
|
doc[doc_key] = fragment.pop(doc_key)
|
||||||
|
|
||||||
# merge rest of the sections
|
# merge rest of the sections
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -522,7 +522,7 @@ def doc_schema(module_name, for_collection=False, deprecated_module=False):
|
||||||
All(
|
All(
|
||||||
Schema(
|
Schema(
|
||||||
doc_schema_dict,
|
doc_schema_dict,
|
||||||
extra=PREVENT_EXTRA
|
extra=ALLOW_EXTRA
|
||||||
),
|
),
|
||||||
partial(version_added, error_code='module-invalid-version-added', accept_historical=not for_collection),
|
partial(version_added, error_code='module-invalid-version-added', accept_historical=not for_collection),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue