diff options
35 files changed, 603 insertions, 42 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 6c867365..566c2d66 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -29,7 +29,7 @@ recursive-include lib/ansible/modules *.yml recursive-include lib/ansible/plugins/test *.yml recursive-include lib/ansible/plugins/filter *.yml recursive-include licenses *.txt -recursive-include packaging Makefile *.py +recursive-include packaging Makefile *.py *.j2 recursive-include test/ansible_test *.py Makefile recursive-include test/integration * recursive-include test/lib/ansible_test/config *.yml *.template @@ -49,6 +49,7 @@ include changelogs/CHANGELOG*.rst include changelogs/changelog.yaml recursive-include hacking/build_library *.py include hacking/build-ansible.py +include hacking/templates/*.j2 include hacking/test-module.py include hacking/update-sanity-requirements.py include bin/* @@ -27,7 +27,7 @@ ASCII2MAN = @echo "ERROR: rst2man from docutils command is not installed but is endif PYTHON ?= python -GENERATE_CLI = hacking/build-ansible.py generate-man +GENERATE_CLI = packaging/pep517_backend/_generate_man.py # fetch version from project release.py as single source-of-truth VERSION := $(shell $(PYTHON) packaging/release/versionhelper/version_helper.py --raw || echo error) @@ -146,7 +146,7 @@ changelog: .PHONY: generate_rst generate_rst: lib/ansible/cli/*.py mkdir -p ./docs/man/man1/ ; \ - $(PYTHON) $(GENERATE_CLI) --template-file=docs/templates/man.j2 --output-dir=docs/man/man1/ --output-format man lib/ansible/cli/*.py + $(PYTHON) $(GENERATE_CLI) --template-file=packaging/pep517_backend/_templates/man.j2 --output-dir=docs/man/man1/ --output-format man lib/ansible/cli/*.py .PHONY: docs docs: generate_rst @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ansible-core -Version: 2.14.6 +Version: 2.14.7 Summary: Radically simple IT automation Home-page: https://ansible.com/ Author: Ansible, Inc. diff --git a/changelogs/CHANGELOG-v2.14.rst b/changelogs/CHANGELOG-v2.14.rst index 51436b14..0c912e3b 100644 --- a/changelogs/CHANGELOG-v2.14.rst +++ b/changelogs/CHANGELOG-v2.14.rst @@ -5,6 +5,29 @@ ansible-core 2.14 "C'mon Everybody" Release Notes .. contents:: Topics +v2.14.7 +======= + +Release Summary +--------------- + +| Release Date: 2023-06-20 +| `Porting Guide <https://docs.ansible.com/ansible-core/2.14/porting_guides/porting_guide_core_2.14.html>`__ + + +Minor Changes +------------- + +- Removed ``straight.plugin`` from the build and packaging requirements. + +Bugfixes +-------- + +- ansible-test - Fix a traceback that occurs when attempting to test Ansible source using a different ansible-test. A clear error message is now given when this scenario occurs. +- ansible-test local change detection - use ``git merge-base <branch> HEAD`` instead of ``git merge-base --fork-point <branch>`` (https://github.com/ansible/ansible/pull/79734). +- man page build - Remove the dependency on the ``docs`` directory for building man pages. +- uri - fix search for JSON type to include complex strings containing '+' + v2.14.6 ======= diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 18bf7b13..f0137291 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -1304,3 +1304,41 @@ releases: - ansible-test-utcnow.yml - pep517-backend-traceback-fix.yml release_date: '2023-05-15' + 2.14.7: + changes: + release_summary: '| Release Date: 2023-06-20 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.14/porting_guides/porting_guide_core_2.14.html>`__ + + ' + codename: C'mon Everybody + fragments: + - 2.14.7_summary.yaml + release_date: '2023-06-20' + 2.14.7rc1: + changes: + bugfixes: + - ansible-test - Fix a traceback that occurs when attempting to test Ansible + source using a different ansible-test. A clear error message is now given + when this scenario occurs. + - ansible-test local change detection - use ``git merge-base <branch> HEAD`` + instead of ``git merge-base --fork-point <branch>`` (https://github.com/ansible/ansible/pull/79734). + - man page build - Remove the dependency on the ``docs`` directory for building + man pages. + - uri - fix search for JSON type to include complex strings containing '+' + minor_changes: + - Removed ``straight.plugin`` from the build and packaging requirements. + release_summary: '| Release Date: 2023-06-12 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.14/porting_guides/porting_guide_core_2.14.html>`__ + + ' + codename: C'mon Everybody + fragments: + - 2.14.7rc1_summary.yaml + - 79734-ansible-test-change-detection.yml + - ansible-test-source-detection.yml + - build-no-straight.yaml + - man-page-build-docs-dependency.yml + - update-maybe-json-uri.yml + release_date: '2023-06-12' diff --git a/docs/docsite/requirements.txt b/docs/docsite/requirements.txt index 66e707c6..dac425da 100644 --- a/docs/docsite/requirements.txt +++ b/docs/docsite/requirements.txt @@ -15,4 +15,3 @@ sphinx-notfound-page >= 0.6 sphinx-intl sphinx-ansible-theme >= 0.9.1 resolvelib -straight.plugin # Needed for hacking/build-ansible.py which is the backend build script diff --git a/docs/docsite/sphinx_conf/core_conf.py b/docs/docsite/sphinx_conf/core_conf.py index a279c02f..a1ce0ee1 100644 --- a/docs/docsite/sphinx_conf/core_conf.py +++ b/docs/docsite/sphinx_conf/core_conf.py @@ -202,9 +202,9 @@ html_context = { 'github_root_dir': 'devel/lib/ansible', 'github_cli_version': 'devel/lib/ansible/cli/', 'current_version': version, - 'latest_version': '2.14', + 'latest_version': '2.15', # list specifically out of order to make latest work - 'available_versions': ('2.14', '2.13', '2.12', 'devel',), + 'available_versions': ('2.15', '2.14', '2.13', 'devel',), } # Add extra CSS styles to the resulting HTML pages diff --git a/docs/man/man1/ansible-config.1 b/docs/man/man1/ansible-config.1 index a5724586..009cb4ef 100644 --- a/docs/man/man1/ansible-config.1 +++ b/docs/man/man1/ansible-config.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE-CONFIG" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE-CONFIG" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible-config \- View ansible configuration. .SH SYNOPSIS diff --git a/docs/man/man1/ansible-console.1 b/docs/man/man1/ansible-console.1 index 026ae8a5..9ee48cfe 100644 --- a/docs/man/man1/ansible-console.1 +++ b/docs/man/man1/ansible-console.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE-CONSOLE" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE-CONSOLE" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible-console \- REPL console for executing Ansible tasks. .SH SYNOPSIS diff --git a/docs/man/man1/ansible-doc.1 b/docs/man/man1/ansible-doc.1 index 8c262a24..fd92c60b 100644 --- a/docs/man/man1/ansible-doc.1 +++ b/docs/man/man1/ansible-doc.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE-DOC" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE-DOC" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible-doc \- plugin documentation tool .SH SYNOPSIS diff --git a/docs/man/man1/ansible-galaxy.1 b/docs/man/man1/ansible-galaxy.1 index 36d42830..8ed2713a 100644 --- a/docs/man/man1/ansible-galaxy.1 +++ b/docs/man/man1/ansible-galaxy.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE-GALAXY" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE-GALAXY" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible-galaxy \- Perform various Role and Collection related operations. .SH SYNOPSIS diff --git a/docs/man/man1/ansible-inventory.1 b/docs/man/man1/ansible-inventory.1 index e35df15c..eaff6ae0 100644 --- a/docs/man/man1/ansible-inventory.1 +++ b/docs/man/man1/ansible-inventory.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE-INVENTORY" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE-INVENTORY" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible-inventory \- None .SH SYNOPSIS diff --git a/docs/man/man1/ansible-playbook.1 b/docs/man/man1/ansible-playbook.1 index 82f0599e..412e1ace 100644 --- a/docs/man/man1/ansible-playbook.1 +++ b/docs/man/man1/ansible-playbook.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE-PLAYBOOK" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE-PLAYBOOK" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible-playbook \- Runs Ansible playbooks, executing the defined tasks on the targeted hosts. .SH SYNOPSIS diff --git a/docs/man/man1/ansible-pull.1 b/docs/man/man1/ansible-pull.1 index bafa0193..46b6099b 100644 --- a/docs/man/man1/ansible-pull.1 +++ b/docs/man/man1/ansible-pull.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE-PULL" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE-PULL" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible-pull \- pulls playbooks from a VCS repo and executes them for the local host .SH SYNOPSIS diff --git a/docs/man/man1/ansible-vault.1 b/docs/man/man1/ansible-vault.1 index 4127214d..6bd3785b 100644 --- a/docs/man/man1/ansible-vault.1 +++ b/docs/man/man1/ansible-vault.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE-VAULT" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE-VAULT" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible-vault \- encryption/decryption utility for Ansible data files .SH SYNOPSIS diff --git a/docs/man/man1/ansible.1 b/docs/man/man1/ansible.1 index 2d2a551c..51295aff 100644 --- a/docs/man/man1/ansible.1 +++ b/docs/man/man1/ansible.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ANSIBLE" 1 "" "Ansible 2.14.6" "System administration commands" +.TH "ANSIBLE" 1 "" "Ansible 2.14.7" "System administration commands" .SH NAME ansible \- Define and run a single task 'playbook' against a set of hosts .SH SYNOPSIS diff --git a/hacking/build-ansible.py b/hacking/build-ansible.py index c108c186..717cf1c1 100755 --- a/hacking/build-ansible.py +++ b/hacking/build-ansible.py @@ -10,16 +10,20 @@ __metaclass__ = type import argparse +import importlib +import inspect import os.path +import pkgutil import sys - -from straight.plugin import load +import typing as t try: import argcomplete except ImportError: argcomplete = None +C = t.TypeVar('C') + def build_lib_path(this_script=__file__): """Return path to the common build library directory.""" @@ -55,6 +59,27 @@ def create_arg_parser(program_name): return parser +def load(package: str, subclasses: t.Type[C]) -> list[t.Type[C]]: + """Load modules in the specified package and return concrete types that derive from the specified base class.""" + for module in pkgutil.iter_modules(importlib.import_module(package).__path__, f'{package}.'): + try: + importlib.import_module(module.name) + except ImportError: + pass # ignore plugins which are missing dependencies + + types: set[t.Type[C]] = set() + queue: list[t.Type[C]] = [subclasses] + + while queue: + for child in queue.pop().__subclasses__(): + queue.append(child) + + if not inspect.isabstract(child): + types.add(child) + + return sorted(types, key=lambda sc: sc.__name__) + + def main(): """ Start our run. @@ -69,7 +94,9 @@ def main(): help='Show tracebacks and other debugging information') subparsers = arg_parser.add_subparsers(title='Subcommands', dest='command', help='for help use build-ansible.py SUBCOMMANDS -h') - subcommands.pipe('init_parser', subparsers.add_parser) + + for subcommand in subcommands: + subcommand.init_parser(subparsers.add_parser) if argcomplete: argcomplete.autocomplete(arg_parser) diff --git a/hacking/build_library/build_ansible/command_plugins/generate_man.py b/hacking/build_library/build_ansible/command_plugins/generate_man.py index 3795c0d2..a8c668a0 100644 --- a/hacking/build_library/build_ansible/command_plugins/generate_man.py +++ b/hacking/build_library/build_ansible/command_plugins/generate_man.py @@ -21,7 +21,7 @@ from ..change_detection import update_file_if_different # pylint: disable=relat from ..commands import Command # pylint: disable=relative-beyond-top-level -DEFAULT_TEMPLATE_FILE = pathlib.Path(__file__).parents[4] / 'docs/templates/man.j2' +DEFAULT_TEMPLATE_FILE = pathlib.Path(__file__).parents[4] / 'hacking/templates/man.j2' # from https://www.python.org/dev/peps/pep-0257/ diff --git a/docs/templates/man.j2 b/hacking/templates/man.j2 index 8bd3644c..8bd3644c 100644 --- a/docs/templates/man.j2 +++ b/hacking/templates/man.j2 diff --git a/lib/ansible/module_utils/ansible_release.py b/lib/ansible/module_utils/ansible_release.py index f59976b8..67de85c3 100644 --- a/lib/ansible/module_utils/ansible_release.py +++ b/lib/ansible/module_utils/ansible_release.py @@ -19,6 +19,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -__version__ = '2.14.6' +__version__ = '2.14.7' __author__ = 'Ansible, Inc.' __codename__ = "C'mon Everybody" diff --git a/lib/ansible/modules/uri.py b/lib/ansible/modules/uri.py index 7919b9b2..9f01e1f7 100644 --- a/lib/ansible/modules/uri.py +++ b/lib/ansible/modules/uri.py @@ -706,7 +706,15 @@ def main(): sub_type = 'octet-stream' content_encoding = 'utf-8' - maybe_json = content_type and sub_type.lower() in JSON_CANDIDATES + if sub_type and '+' in sub_type: + # https://www.rfc-editor.org/rfc/rfc6839#section-3.1 + sub_type_suffix = sub_type.partition('+')[2] + maybe_json = content_type and sub_type_suffix.lower() in JSON_CANDIDATES + elif sub_type: + maybe_json = content_type and sub_type.lower() in JSON_CANDIDATES + else: + maybe_json = False + maybe_output = maybe_json or return_content or info['status'] not in status_code if maybe_output: diff --git a/lib/ansible/release.py b/lib/ansible/release.py index f59976b8..67de85c3 100644 --- a/lib/ansible/release.py +++ b/lib/ansible/release.py @@ -19,6 +19,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -__version__ = '2.14.6' +__version__ = '2.14.7' __author__ = 'Ansible, Inc.' __codename__ = "C'mon Everybody" diff --git a/lib/ansible_core.egg-info/PKG-INFO b/lib/ansible_core.egg-info/PKG-INFO index b625a17a..d2ff788a 100644 --- a/lib/ansible_core.egg-info/PKG-INFO +++ b/lib/ansible_core.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ansible-core -Version: 2.14.6 +Version: 2.14.7 Summary: Radically simple IT automation Home-page: https://ansible.com/ Author: Ansible, Inc. diff --git a/lib/ansible_core.egg-info/SOURCES.txt b/lib/ansible_core.egg-info/SOURCES.txt index dd37d2ed..4e770d7c 100644 --- a/lib/ansible_core.egg-info/SOURCES.txt +++ b/lib/ansible_core.egg-info/SOURCES.txt @@ -642,7 +642,6 @@ docs/man/man3/.gitdir docs/templates/cli_rst.j2 docs/templates/collections_galaxy_meta.rst.j2 docs/templates/config.rst.j2 -docs/templates/man.j2 docs/templates/modules_by_category.rst.j2 docs/templates/playbooks_keywords.rst.j2 examples/ansible.cfg @@ -667,6 +666,7 @@ hacking/build_library/build_ansible/command_plugins/generate_man.py hacking/build_library/build_ansible/command_plugins/porting_guide.py hacking/build_library/build_ansible/command_plugins/release_announcement.py hacking/build_library/build_ansible/command_plugins/update_intersphinx.py +hacking/templates/man.j2 lib/ansible/__init__.py lib/ansible/__main__.py lib/ansible/constants.py @@ -1357,7 +1357,9 @@ licenses/simplified_bsd.txt packaging/release.py packaging/pep517_backend/__init__.py packaging/pep517_backend/_backend.py +packaging/pep517_backend/_generate_man.py packaging/pep517_backend/hooks.py +packaging/pep517_backend/_templates/man.j2 packaging/release/Makefile packaging/release/tests/__init__.py packaging/release/tests/version_helper_test.py diff --git a/packaging/pep517_backend/_backend.py b/packaging/pep517_backend/_backend.py index ce97cfab..5086645d 100644 --- a/packaging/pep517_backend/_backend.py +++ b/packaging/pep517_backend/_backend.py @@ -77,9 +77,7 @@ def _generate_rst_in_templates() -> t.Iterable[Path]: """Create ``*.1.rst.in`` files out of CLI Python modules.""" generate_man_cmd = ( sys.executable, - 'hacking/build-ansible.py', - 'generate-man', - '--template-file=docs/templates/man.j2', + Path(__file__).parent / '_generate_man.py', '--output-dir=docs/man/man1/', '--output-format=man', *Path('lib/ansible/cli/').glob('*.py'), @@ -163,8 +161,7 @@ def get_requires_for_build_sdist( manpage_build_deps = [ 'docutils', # provides `rst2man` - 'jinja2', # used in `hacking/build-ansible.py generate-man` - 'straight.plugin', # used in `hacking/build-ansible.py` for subcommand + 'jinja2', # used to generate man pages 'pyyaml', # needed for importing in-tree `ansible-core` from `lib/` ] if build_manpages_requested else [] diff --git a/packaging/pep517_backend/_generate_man.py b/packaging/pep517_backend/_generate_man.py new file mode 100644 index 00000000..d858d4dd --- /dev/null +++ b/packaging/pep517_backend/_generate_man.py @@ -0,0 +1,310 @@ +# coding: utf-8 +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +"""Generate cli documentation from cli docstrings.""" + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +import argparse +import os.path +import pathlib +import sys + +from jinja2 import Environment, FileSystemLoader + +DEFAULT_TEMPLATE_FILE = pathlib.Path(__file__).parent / '_templates/man.j2' + + +# from https://www.python.org/dev/peps/pep-0257/ +def trim_docstring(docstring): + if not docstring: + return '' + # Convert tabs to spaces (following the normal Python rules) + # and split into a list of lines: + lines = docstring.expandtabs().splitlines() + # Determine minimum indentation (first line doesn't count): + indent = sys.maxsize + for line in lines[1:]: + stripped = line.lstrip() + if stripped: + indent = min(indent, len(line) - len(stripped)) + # Remove indentation (first line is special): + trimmed = [lines[0].strip()] + if indent < sys.maxsize: + for line in lines[1:]: + trimmed.append(line[indent:].rstrip()) + # Strip off trailing and leading blank lines: + while trimmed and not trimmed[-1]: + trimmed.pop() + while trimmed and not trimmed[0]: + trimmed.pop(0) + # Return a single string: + return '\n'.join(trimmed) + + +def get_options(optlist): + ''' get actual options ''' + + opts = [] + for opt in optlist: + res = { + 'desc': opt.help, + 'options': opt.option_strings + } + if isinstance(opt, argparse._StoreAction): + res['arg'] = opt.dest.upper() + elif not res['options']: + continue + opts.append(res) + + return opts + + +def dedupe_groups(parser): + action_groups = [] + for action_group in parser._action_groups: + found = False + for a in action_groups: + if a._actions == action_group._actions: + found = True + break + if not found: + action_groups.append(action_group) + return action_groups + + +def get_option_groups(option_parser): + groups = [] + for action_group in dedupe_groups(option_parser)[1:]: + group_info = {} + group_info['desc'] = action_group.description + group_info['options'] = action_group._actions + group_info['group_obj'] = action_group + groups.append(group_info) + return groups + + +def opt_doc_list(parser): + ''' iterate over options lists ''' + + results = [] + for option_group in dedupe_groups(parser)[1:]: + results.extend(get_options(option_group._actions)) + + results.extend(get_options(parser._actions)) + + return results + + +# def opts_docs(cli, name): +def opts_docs(cli_class_name, cli_module_name): + ''' generate doc structure from options ''' + + cli_name = 'ansible-%s' % cli_module_name + if cli_module_name == 'adhoc': + cli_name = 'ansible' + + # WIth no action/subcommand + # shared opts set + # instantiate each cli and ask its options + cli_klass = getattr(__import__("ansible.cli.%s" % cli_module_name, + fromlist=[cli_class_name]), cli_class_name) + cli = cli_klass([cli_name]) + + # parse the common options + try: + cli.init_parser() + except Exception: + pass + + # base/common cli info + docs = { + 'cli': cli_module_name, + 'cli_name': cli_name, + 'usage': cli.parser.format_usage(), + 'short_desc': cli.parser.description, + 'long_desc': trim_docstring(cli.__doc__), + 'actions': {}, + 'content_depth': 2, + } + option_info = {'option_names': [], + 'options': [], + 'groups': []} + + for extras in ('ARGUMENTS'): + if hasattr(cli, extras): + docs[extras.lower()] = getattr(cli, extras) + + common_opts = opt_doc_list(cli.parser) + groups_info = get_option_groups(cli.parser) + shared_opt_names = [] + for opt in common_opts: + shared_opt_names.extend(opt.get('options', [])) + + option_info['options'] = common_opts + option_info['option_names'] = shared_opt_names + + option_info['groups'].extend(groups_info) + + docs.update(option_info) + + # now for each action/subcommand + # force populate parser with per action options + + def get_actions(parser, docs): + # use class attrs not the attrs on a instance (not that it matters here...) + try: + subparser = parser._subparsers._group_actions[0].choices + except AttributeError: + subparser = {} + + depth = 0 + + for action, parser in subparser.items(): + action_info = {'option_names': [], + 'options': [], + 'actions': {}} + # docs['actions'][action] = {} + # docs['actions'][action]['name'] = action + action_info['name'] = action + action_info['desc'] = trim_docstring(getattr(cli, 'execute_%s' % action).__doc__) + + # docs['actions'][action]['desc'] = getattr(cli, 'execute_%s' % action).__doc__.strip() + action_doc_list = opt_doc_list(parser) + + uncommon_options = [] + for action_doc in action_doc_list: + # uncommon_options = [] + + option_aliases = action_doc.get('options', []) + for option_alias in option_aliases: + + if option_alias in shared_opt_names: + continue + + # TODO: use set + if option_alias not in action_info['option_names']: + action_info['option_names'].append(option_alias) + + if action_doc in action_info['options']: + continue + + uncommon_options.append(action_doc) + + action_info['options'] = uncommon_options + + depth = 1 + get_actions(parser, action_info) + + docs['actions'][action] = action_info + + return depth + + action_depth = get_actions(cli.parser, docs) + docs['content_depth'] = action_depth + 1 + + docs['options'] = opt_doc_list(cli.parser) + return docs + + +class GenerateMan: + name = 'generate-man' + + @classmethod + def init_parser(cls, parser: argparse.ArgumentParser): + parser.add_argument("-t", "--template-file", action="store", dest="template_file", + default=DEFAULT_TEMPLATE_FILE, help="path to jinja2 template") + parser.add_argument("-o", "--output-dir", action="store", dest="output_dir", + default='/tmp/', help="Output directory for rst files") + parser.add_argument("-f", "--output-format", action="store", dest="output_format", + default='man', + help="Output format for docs (the default 'man' or 'rst')") + parser.add_argument('cli_modules', help='CLI module name(s)', metavar='MODULE_NAME', nargs='*') + + @staticmethod + def main(args): + template_file = args.template_file + template_path = os.path.expanduser(template_file) + template_dir = os.path.abspath(os.path.dirname(template_path)) + template_basename = os.path.basename(template_file) + + output_dir = os.path.abspath(args.output_dir) + output_format = args.output_format + + cli_modules = args.cli_modules + + # various cli parsing things checks sys.argv if the 'args' that are passed in are [] + # so just remove any args so the cli modules dont try to parse them resulting in warnings + sys.argv = [sys.argv[0]] + + allvars = {} + output = {} + cli_list = [] + cli_bin_name_list = [] + + # for binary in os.listdir('../../lib/ansible/cli'): + for cli_module_name in cli_modules: + binary = os.path.basename(os.path.expanduser(cli_module_name)) + + if not binary.endswith('.py'): + continue + elif binary == '__init__.py': + continue + + cli_name = os.path.splitext(binary)[0] + + if cli_name == 'adhoc': + cli_class_name = 'AdHocCLI' + # myclass = 'AdHocCLI' + output[cli_name] = 'ansible.1.rst.in' + cli_bin_name = 'ansible' + else: + # myclass = "%sCLI" % libname.capitalize() + cli_class_name = "%sCLI" % cli_name.capitalize() + output[cli_name] = 'ansible-%s.1.rst.in' % cli_name + cli_bin_name = 'ansible-%s' % cli_name + + # FIXME: + allvars[cli_name] = opts_docs(cli_class_name, cli_name) + cli_bin_name_list.append(cli_bin_name) + + cli_list = allvars.keys() + + doc_name_formats = {'man': '%s.1.rst.in', + 'rst': '%s.rst'} + + for cli_name in cli_list: + + # template it! + env = Environment(loader=FileSystemLoader(template_dir)) + template = env.get_template(template_basename) + + # add rest to vars + tvars = allvars[cli_name] + tvars['cli_list'] = cli_list + tvars['cli_bin_name_list'] = cli_bin_name_list + tvars['cli'] = cli_name + if '-i' in tvars['options']: + print('uses inventory') + + manpage = template.render(tvars) + filename = os.path.join(output_dir, doc_name_formats[output_format] % tvars['cli_name']) + pathlib.Path(filename).write_text(manpage) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + + GenerateMan.init_parser(parser) + + args = parser.parse_args() + + sys.path.insert(0, str(pathlib.Path(__file__).parent.parent.parent / 'lib')) + + GenerateMan.main(args) + + +if __name__ == '__main__': + main() diff --git a/packaging/pep517_backend/_templates/man.j2 b/packaging/pep517_backend/_templates/man.j2 new file mode 100644 index 00000000..8bd3644c --- /dev/null +++ b/packaging/pep517_backend/_templates/man.j2 @@ -0,0 +1,128 @@ +{% set name = ('ansible' if cli == 'adhoc' else 'ansible-%s' % cli) -%} +{{name}} +{{ '=' * ( name|length|int ) }} + +{{ '-' * ( short_desc|default('')|string|length|int ) }} +{{short_desc|default('')}} +{{ '-' * ( short_desc|default('')|string|length|int ) }} + +:Version: Ansible %VERSION% +:Manual section: 1 +:Manual group: System administration commands + + + +SYNOPSIS +-------- +{{ usage|replace('%prog', name) }} + + +DESCRIPTION +----------- +{{ long_desc|default('', True)|wordwrap }} + +{% if options %} +COMMON OPTIONS +-------------- +{% for option in options|sort(attribute='options') %} +{% for switch in option['options'] %}**{{switch}}**{% if option['arg'] %} '{{option['arg']}}'{% endif %}{% if not loop.last %}, {% endif %}{% endfor %} + + {{ option['desc'] }} +{% endfor %} +{% endif %} + +{% if arguments %} +ARGUMENTS +--------- + +{% for arg in arguments %} +{{ arg }} + +{{ (arguments[arg]|default(' '))|wordwrap }} + +{% endfor %} +{% endif %} + +{% if actions %} +ACTIONS +------- +{% for action in actions %} +**{{ action }}** + {{ (actions[action]['desc']|default(' ')) |replace('\n', ' ')}} + +{% if actions[action]['options'] %} +{% for option in actions[action]['options']|sort(attribute='options') %} +{% for switch in option['options'] if switch in actions[action]['option_names'] %} **{{switch}}**{% if option['arg'] %} '{{option['arg']}}'{% endif %}{% if not loop.last %}, {% endif %}{% endfor %} + + {{ (option['desc']) }} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} + + +{% if inventory %} +INVENTORY +--------- + +Ansible stores the hosts it can potentially operate on in an inventory. +This can be an YAML file, ini-like file, a script, directory, list, etc. +For additional options, see the documentation on https://docs.ansible.com/. + +{% endif %} +ENVIRONMENT +----------- + +The following environment variables may be specified. + +{% if inventory %} +ANSIBLE_INVENTORY -- Override the default ansible inventory sources + +{% endif %} +{% if library %} +ANSIBLE_LIBRARY -- Override the default ansible module library path + +{% endif %} +ANSIBLE_CONFIG -- Specify override location for the ansible config file + +Many more are available for most options in ansible.cfg + +For a full list check https://docs.ansible.com/. or use the `ansible-config` command. + +FILES +----- + +{% if inventory %} +/etc/ansible/hosts -- Default inventory file + +{% endif %} +/etc/ansible/ansible.cfg -- Config file, used if present + +~/.ansible.cfg -- User config file, overrides the default config if present + +./ansible.cfg -- Local config file (in current working directory) assumed to be 'project specific' and overrides the rest if present. + +As mentioned above, the ANSIBLE_CONFIG environment variable will override all others. + +AUTHOR +------ + +Ansible was originally written by Michael DeHaan. + + +COPYRIGHT +--------- + +Copyright © 2018 Red Hat, Inc | Ansible. +Ansible is released under the terms of the GPLv3 license. + + +SEE ALSO +-------- + +{% for other in cli_list|sort %}{% if other != cli %}**ansible{% if other != 'adhoc' %}-{{other}}{% endif %}** (1){% if not loop.last %}, {% endif %}{% endif %}{% endfor %} + +Extensive documentation is available in the documentation site: +<https://docs.ansible.com>. +IRC and mailing list info can be found in file CONTRIBUTING.md, +available in: <https://github.com/ansible/ansible> diff --git a/test/integration/targets/canonical-pep517-self-packaging/minimum-build-constraints.txt b/test/integration/targets/canonical-pep517-self-packaging/minimum-build-constraints.txt index ea5d8084..3ba47aeb 100644 --- a/test/integration/targets/canonical-pep517-self-packaging/minimum-build-constraints.txt +++ b/test/integration/targets/canonical-pep517-self-packaging/minimum-build-constraints.txt @@ -13,4 +13,3 @@ docutils == 0.16 Jinja2 == 3.0.0 MarkupSafe == 2.0.0 PyYAML == 5.3 -straight.plugin == 1.4.2 diff --git a/test/integration/targets/canonical-pep517-self-packaging/modernish-build-constraints.txt b/test/integration/targets/canonical-pep517-self-packaging/modernish-build-constraints.txt index 7f744afd..9b8e9d0a 100644 --- a/test/integration/targets/canonical-pep517-self-packaging/modernish-build-constraints.txt +++ b/test/integration/targets/canonical-pep517-self-packaging/modernish-build-constraints.txt @@ -8,4 +8,3 @@ docutils == 0.19 Jinja2 == 3.1.2 MarkupSafe == 2.1.2 PyYAML == 6.0 -straight.plugin == 1.5.0 # WARNING: v1.5.0 doesn't have a Git tag / src diff --git a/test/integration/targets/uri/tasks/main.yml b/test/integration/targets/uri/tasks/main.yml index 7fa687b4..9ba09ece 100644 --- a/test/integration/targets/uri/tasks/main.yml +++ b/test/integration/targets/uri/tasks/main.yml @@ -687,6 +687,18 @@ that: - result.json.json[0] == 'JSON Test Pattern pass1' +- name: Test find JSON as subtype + uri: + url: "https://{{ httpbin_host }}/response-headers?content-type=application/ld%2Bjson" + method: POST + return_content: true + register: result + +- name: Validate JSON as subtype + assert: + that: + - result.json is defined + - name: Make request that includes password in JSON keys uri: url: "https://{{ httpbin_host}}/get?key-password=value-password" diff --git a/test/lib/ansible_test/_internal/data.py b/test/lib/ansible_test/_internal/data.py index 379ee7b0..67f7a06c 100644 --- a/test/lib/ansible_test/_internal/data.py +++ b/test/lib/ansible_test/_internal/data.py @@ -10,7 +10,6 @@ from .util import ( ApplicationError, import_plugins, is_subdir, - is_valid_identifier, ANSIBLE_LIB_ROOT, ANSIBLE_TEST_ROOT, ANSIBLE_SOURCE_ROOT, @@ -219,12 +218,8 @@ class DataContext: elif 'ansible_collections' not in cwd.split(os.path.sep): blocks.append('No "ansible_collections" parent directory was found.') - if self.content.collection: - if not is_valid_identifier(self.content.collection.namespace): - blocks.append(f'The namespace "{self.content.collection.namespace}" is an invalid identifier or a reserved keyword.') - - if not is_valid_identifier(self.content.collection.name): - blocks.append(f'The name "{self.content.collection.name}" is an invalid identifier or a reserved keyword.') + if isinstance(self.content.unsupported, list): + blocks.extend(self.content.unsupported) message = '\n'.join(blocks) diff --git a/test/lib/ansible_test/_internal/git.py b/test/lib/ansible_test/_internal/git.py index 4685f1d2..b6c5c7b4 100644 --- a/test/lib/ansible_test/_internal/git.py +++ b/test/lib/ansible_test/_internal/git.py @@ -77,7 +77,7 @@ class Git: def get_branch_fork_point(self, branch: str) -> str: """Return a reference to the point at which the given branch was forked.""" - cmd = ['merge-base', '--fork-point', branch] + cmd = ['merge-base', branch, 'HEAD'] return self.run_git(cmd).strip() def is_valid_ref(self, ref: str) -> bool: diff --git a/test/lib/ansible_test/_internal/provider/layout/__init__.py b/test/lib/ansible_test/_internal/provider/layout/__init__.py index 4eca05ce..a0a0609b 100644 --- a/test/lib/ansible_test/_internal/provider/layout/__init__.py +++ b/test/lib/ansible_test/_internal/provider/layout/__init__.py @@ -95,7 +95,7 @@ class ContentLayout(Layout): unit_module_path: str, unit_module_utils_path: str, unit_messages: t.Optional[LayoutMessages], - unsupported: bool = False, + unsupported: bool | list[str] = False, ) -> None: super().__init__(root, paths) diff --git a/test/lib/ansible_test/_internal/provider/layout/ansible.py b/test/lib/ansible_test/_internal/provider/layout/ansible.py index d2f8cc81..3ee818a5 100644 --- a/test/lib/ansible_test/_internal/provider/layout/ansible.py +++ b/test/lib/ansible_test/_internal/provider/layout/ansible.py @@ -8,6 +8,11 @@ from . import ( LayoutProvider, ) +from ...util import ( + ANSIBLE_SOURCE_ROOT, + ANSIBLE_TEST_ROOT, +) + class AnsibleLayout(LayoutProvider): """Layout provider for Ansible source.""" @@ -26,6 +31,15 @@ class AnsibleLayout(LayoutProvider): module_utils='lib/ansible/module_utils', ) + errors: list[str] = [] + + if root != ANSIBLE_SOURCE_ROOT: + errors.extend(( + f'Cannot test "{root}" with ansible-test from "{ANSIBLE_TEST_ROOT}".', + '', + f'Did you intend to run "{root}/bin/ansible-test" instead?', + )) + return ContentLayout( root, paths, @@ -43,4 +57,5 @@ class AnsibleLayout(LayoutProvider): unit_module_path='test/units/modules', unit_module_utils_path='test/units/module_utils', unit_messages=None, + unsupported=errors, ) diff --git a/test/lib/ansible_test/_internal/provider/layout/collection.py b/test/lib/ansible_test/_internal/provider/layout/collection.py index d747f31f..a9221be6 100644 --- a/test/lib/ansible_test/_internal/provider/layout/collection.py +++ b/test/lib/ansible_test/_internal/provider/layout/collection.py @@ -53,6 +53,14 @@ class CollectionLayout(LayoutProvider): integration_targets_path = self.__check_integration_path(paths, integration_messages) self.__check_unit_path(paths, unit_messages) + errors: list[str] = [] + + if not is_valid_identifier(collection_namespace): + errors.append(f'The namespace "{collection_namespace}" is an invalid identifier or a reserved keyword.') + + if not is_valid_identifier(collection_name): + errors.append(f'The name "{collection_name}" is an invalid identifier or a reserved keyword.') + return ContentLayout( root, paths, @@ -74,7 +82,7 @@ class CollectionLayout(LayoutProvider): unit_module_path='tests/unit/plugins/modules', unit_module_utils_path='tests/unit/plugins/module_utils', unit_messages=unit_messages, - unsupported=not (is_valid_identifier(collection_namespace) and is_valid_identifier(collection_name)), + unsupported=errors, ) @staticmethod |