[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:
Brian Coca 2021-04-16 12:17:00 -04:00 committed by GitHub
parent 185d410316
commit 07939b04f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 166 additions and 16 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- ansible-doc now supports 'attributes' for plugins as per proposal.

View file

@ -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']:

View file

@ -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'''

View file

@ -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:

View file

@ -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.

View file

@ -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:

View file

@ -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
'''

View file

@ -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']:
if doc_key in fragment:
if doc_key in doc:
try: try:
merge_fragment(doc['options'], fragment.pop('options')) merge_fragment(doc[doc_key], fragment.pop(doc_key))
except Exception as e: except Exception as e:
raise AnsibleError("%s options (%s) of unknown type: %s" % (to_native(e), fragment_name, filename)) raise AnsibleError("%s %s (%s) of unknown type: %s" % (to_native(e), doc_key, fragment_name, filename))
else: else:
doc['options'] = fragment.pop('options') doc[doc_key] = fragment.pop(doc_key)
# merge rest of the sections # merge rest of the sections
try: try:

View file

@ -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),
) )