diff --git a/changelogs/fragments/81796-ansible-doc-roles.yml b/changelogs/fragments/81796-ansible-doc-roles.yml new file mode 100644 index 00000000000..6eed4cfd3eb --- /dev/null +++ b/changelogs/fragments/81796-ansible-doc-roles.yml @@ -0,0 +1,2 @@ +minor_changes: + - "ansible-doc - show ``notes``, ``seealso``, ``requirements``, and top-level ``version_added`` for role entrypoints (https://github.com/ansible/ansible/pull/81796)." diff --git a/lib/ansible/cli/doc.py b/lib/ansible/cli/doc.py index 16033f8e112..e8519c8e1e5 100755 --- a/lib/ansible/cli/doc.py +++ b/lib/ansible/cli/doc.py @@ -1267,6 +1267,51 @@ class DocCLI(CLI, RoleMixin): text.append("%s%s:" % (opt_indent, subkey)) DocCLI.add_fields(text, subdata, limit, opt_indent + ' ', return_values, opt_indent) + @staticmethod + def _add_seealso(text, seealsos, limit, opt_indent): + for item in seealsos: + if 'module' in item: + text.append(DocCLI.warp_fill(DocCLI.tty_ify('Module %s' % item['module']), + limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + description = item.get('description') + if description is None and item['module'].startswith('ansible.builtin.'): + description = 'The official documentation on the %s module.' % item['module'] + if description is not None: + text.append(DocCLI.warp_fill(DocCLI.tty_ify(description), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + if item['module'].startswith('ansible.builtin.'): + relative_url = 'collections/%s_module.html' % item['module'].replace('.', '/', 2) + text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent)) + elif 'plugin' in item and 'plugin_type' in item: + plugin_suffix = ' plugin' if item['plugin_type'] not in ('module', 'role') else '' + text.append(DocCLI.warp_fill(DocCLI.tty_ify('%s%s %s' % (item['plugin_type'].title(), plugin_suffix, item['plugin'])), + limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + description = item.get('description') + if description is None and item['plugin'].startswith('ansible.builtin.'): + description = 'The official documentation on the %s %s%s.' % (item['plugin'], item['plugin_type'], plugin_suffix) + if description is not None: + text.append(DocCLI.warp_fill(DocCLI.tty_ify(description), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + if item['plugin'].startswith('ansible.builtin.'): + relative_url = 'collections/%s_%s.html' % (item['plugin'].replace('.', '/', 2), item['plugin_type']) + text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent)) + elif 'name' in item and 'link' in item and 'description' in item: + text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['name']), + limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['link']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + elif 'ref' in item and 'description' in item: + text.append(DocCLI.warp_fill(DocCLI.tty_ify('Ansible documentation [%s]' % item['ref']), + limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink('/#stq=%s&stp=1' % item['ref'])), + limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) + def get_role_man_text(self, role, role_json): '''Generate text for the supplied role suitable for display. @@ -1294,6 +1339,10 @@ class DocCLI(CLI, RoleMixin): text.append("ENTRY POINT: %s %s" % (_format(entry_point, "BOLD"), desc)) text.append('') + if 'version_added' in doc: + version_added = doc.pop('version_added') + text.append(_format("ADDED IN:", 'bold') + " %s\n" % DocCLI._format_version_added(version_added)) + if doc.get('description'): if isinstance(doc['description'], list): desc = " ".join(doc['description']) @@ -1324,6 +1373,26 @@ class DocCLI(CLI, RoleMixin): text.append(DocCLI._indent_lines(DocCLI._dump_yaml(doc['attributes'][k]), opt_indent)) del doc['attributes'] + if doc.get('notes', False): + text.append("") + text.append(_format("NOTES:", 'bold')) + for note in doc['notes']: + text.append(DocCLI.warp_fill(DocCLI.tty_ify(note), limit - 6, + initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) + del doc['notes'] + + if doc.get('seealso', False): + text.append("") + text.append(_format("SEE ALSO:", 'bold')) + DocCLI._add_seealso(text, doc['seealso'], limit=limit, opt_indent=opt_indent) + del doc['seealso'] + + if doc.get('requirements', False): + text.append('') + req = ", ".join(doc.pop('requirements')) + text.append(_format("REQUIREMENTS:", 'bold') + "%s\n" % DocCLI.warp_fill(DocCLI.tty_ify(req), limit - 16, initial_indent=" ", + subsequent_indent=opt_indent)) + # generic elements we will handle identically for k in ('author',): if k not in doc: @@ -1409,49 +1478,7 @@ class DocCLI(CLI, RoleMixin): if doc.get('seealso', False): text.append("") text.append(_format("SEE ALSO:", 'bold')) - for item in doc['seealso']: - if 'module' in item: - text.append(DocCLI.warp_fill(DocCLI.tty_ify('Module %s' % item['module']), - limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) - description = item.get('description') - if description is None and item['module'].startswith('ansible.builtin.'): - description = 'The official documentation on the %s module.' % item['module'] - if description is not None: - text.append(DocCLI.warp_fill(DocCLI.tty_ify(description), - limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) - if item['module'].startswith('ansible.builtin.'): - relative_url = 'collections/%s_module.html' % item['module'].replace('.', '/', 2) - text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)), - limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent)) - elif 'plugin' in item and 'plugin_type' in item: - plugin_suffix = ' plugin' if item['plugin_type'] not in ('module', 'role') else '' - text.append(DocCLI.warp_fill(DocCLI.tty_ify('%s%s %s' % (item['plugin_type'].title(), plugin_suffix, item['plugin'])), - limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) - description = item.get('description') - if description is None and item['plugin'].startswith('ansible.builtin.'): - description = 'The official documentation on the %s %s%s.' % (item['plugin'], item['plugin_type'], plugin_suffix) - if description is not None: - text.append(DocCLI.warp_fill(DocCLI.tty_ify(description), - limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) - if item['plugin'].startswith('ansible.builtin.'): - relative_url = 'collections/%s_%s.html' % (item['plugin'].replace('.', '/', 2), item['plugin_type']) - text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)), - limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent)) - elif 'name' in item and 'link' in item and 'description' in item: - text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['name']), - limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) - text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']), - limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) - text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['link']), - limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) - elif 'ref' in item and 'description' in item: - text.append(DocCLI.warp_fill(DocCLI.tty_ify('Ansible documentation [%s]' % item['ref']), - limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent)) - text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']), - limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) - text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink('/#stq=%s&stp=1' % item['ref'])), - limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' ')) - + DocCLI._add_seealso(text, doc['seealso'], limit=limit, opt_indent=opt_indent) del doc['seealso'] if doc.get('requirements', False): diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/roles/testrole/meta/main.yml b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/roles/testrole/meta/main.yml index ce7962990df..9c42d8a2563 100644 --- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/roles/testrole/meta/main.yml +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/roles/testrole/meta/main.yml @@ -13,6 +13,7 @@ argument_specs: description: opt1 description type: "str" required: true + version_added: 1.2.0 alternate: short_description: testns.testcol.testrole short description for alternate entry point @@ -23,6 +24,9 @@ argument_specs: check_mode: description: Can run in check_mode and return changed status prediction without modifying target support: full + requirements: + - Linux + version_added: 2.1.0 options: altopt1: description: altopt1 description diff --git a/test/integration/targets/ansible-doc/fakecollrole.output b/test/integration/targets/ansible-doc/fakecollrole.output index b41a35d4d5e..0771f5ad49b 100644 --- a/test/integration/targets/ansible-doc/fakecollrole.output +++ b/test/integration/targets/ansible-doc/fakecollrole.output @@ -2,6 +2,8 @@ ENTRY POINT: *alternate* - testns.testcol.testrole short description for alternate entry point +ADDED IN: version 2.1.0 + Longer description for testns.testcol.testrole alternate entry point. @@ -17,4 +19,7 @@ ATTRIBUTES: target support: full +REQUIREMENTS: Linux + + AUTHOR: Ansible Core (@ansible) diff --git a/test/integration/targets/ansible-doc/fakerole.output b/test/integration/targets/ansible-doc/fakerole.output index 0e82b7f9de4..a722f09461e 100644 --- a/test/integration/targets/ansible-doc/fakerole.output +++ b/test/integration/targets/ansible-doc/fakerole.output @@ -2,6 +2,8 @@ ENTRY POINT: *main* - test_role1 from roles subdir +ADDED IN: version 1.2.0 + In to am attended desirous raptures *declared* diverted confined at. Collected instantly remaining up certainly to `necessary' as. Over walk dull into son boy door went new. @@ -33,4 +35,20 @@ ATTRIBUTES: details: Not all modules used support this support: partial +NOTES: + * This is a role. + * More text. + +SEE ALSO: + * Module ansible.builtin.file + The official documentation on the + ansible.builtin.file module. + https://docs.ansible.com/ansible-core/devel/collections/ansible/builtin/file_module.html + * Lookup plugin ansible.builtin.file + Reads a file from the controller. + https://docs.ansible.com/ansible-core/devel/collections/ansible/builtin/file_lookup.html + +REQUIREMENTS: foo, bar + + AUTHOR: John Doe (@john), Jane Doe (@jane) diff --git a/test/integration/targets/ansible-doc/roles/test_role1/meta/argument_specs.yml b/test/integration/targets/ansible-doc/roles/test_role1/meta/argument_specs.yml index 42857cd7ff1..aaffe611d51 100644 --- a/test/integration/targets/ansible-doc/roles/test_role1/meta/argument_specs.yml +++ b/test/integration/targets/ansible-doc/roles/test_role1/meta/argument_specs.yml @@ -16,6 +16,18 @@ argument_specs: description: Will return details on what has changed (or possibly needs changing in check_mode), when in diff mode support: partial details: Not all modules used support this + version_added: 1.2.0 + notes: + - This is a role. + - More text. + seealso: + - module: ansible.builtin.file + - plugin: ansible.builtin.file + plugin_type: lookup + description: Reads a file from the controller. + requirements: + - foo + - bar options: myopt1: description: