From 015f935295c5ff08677ed8456af721265847258a Mon Sep 17 00:00:00 2001 From: Maxwell G Date: Sun, 26 Mar 2023 10:12:54 -0500 Subject: [PATCH] validate-tags: allow ignoring certain collections (#491) * validate-tags: allow ignoring certain collections This adds a `-I` / `--ignore` flag to `antsibull-build validate-tags` and `antsibull-build validate-tags-file` to ignore errors for specific collections. `--ignores-file` can be specified separately or simultaneously to ignore collections from a file with a newline separated list of names. Useless ignores are treated as errors by default, but this can be changed with `--no-warn-useless-ignores`. `ignores` and `warn_useless_ignores` arguments were added to `antsibull.tagging.validate_tags()` to support this functionality. * correct changelog frag syntax and rm invalid entry Co-authored-by: Felix Fontein * fix argparse help message grammar Co-authored-by: Felix Fontein * s/warn_useless_ignores/error_on_useless_ignores/ --------- Co-authored-by: Felix Fontein --- changelogs/fragments/491.yaml | 6 +++ src/antsibull/cli/antsibull_build.py | 26 +++++++++++- src/antsibull/tagging.py | 63 +++++++++++++++++++++++----- 3 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 changelogs/fragments/491.yaml diff --git a/changelogs/fragments/491.yaml b/changelogs/fragments/491.yaml new file mode 100644 index 00000000..05937fb3 --- /dev/null +++ b/changelogs/fragments/491.yaml @@ -0,0 +1,6 @@ +--- +minor_changes: + - Add a ``-I`` / ``--ignore`` and a ``--ignores-file`` flag to the + ``antsibull-build validate-tags`` and ``antsibull-build validate-tags-file`` + subcommands to ignore errors for certain collections + (https://github.com/ansible-community/antsibull/pull/491). diff --git a/src/antsibull/cli/antsibull_build.py b/src/antsibull/cli/antsibull_build.py index 6e9d3567..4bb9a18b 100644 --- a/src/antsibull/cli/antsibull_build.py +++ b/src/antsibull/cli/antsibull_build.py @@ -25,6 +25,10 @@ InvalidArgumentError, get_toplevel_parser, normalize_toplevel_options ) from antsibull_core.config import ConfigError, load_config # noqa: E402 +from antsibull_core.vendored._argparse_booleanoptionalaction import ( # noqa: E402 + BooleanOptionalAction +) + from ..build_collection import build_collection_command # noqa: E402 from ..build_ansible_commands import ( # noqa: E402 @@ -370,10 +374,29 @@ def parse_args(program_name: str, args: list[str]) -> argparse.Namespace: validate_deps.add_argument('collection_root', help='Path to a ansible_collections directory containing a' ' collection tree to check.') + validate_tags_shared = argparse.ArgumentParser(add_help=False) + validate_tags_shared.add_argument( + '-I', '--ignore', + action='append', help='Ignore these collections when reporting errors.' + ) + validate_tags_shared.add_argument( + '--ignores-file', + help='Path to a file with newline separated list of collections to ignore', + type=argparse.FileType('r'), + ) + validate_tags_shared.add_argument( + '-E', '--error-on-useless-ignores', + action=BooleanOptionalAction, + dest='error_on_useless_ignores', + default=True, + help='By default, useless ignores (e.g. passing' + ' `--ignore collection.collection` when that collection is' + ' properly tagged) will be considered an error.' + ) validate_tags = subparsers.add_parser( 'validate-tags', - parents=[build_parser], + parents=[build_parser, validate_tags_shared], description="Ensure that collection versions in an Ansible release are tagged" " in collections' respective git repositories." ) @@ -393,6 +416,7 @@ def parse_args(program_name: str, args: list[str]) -> argparse.Namespace: validate_tags_file = subparsers.add_parser( 'validate-tags-file', + parents=[validate_tags_shared], description="Ensure that collection versions in an Ansible release are tagged" " in collections' respective git repositories." " This validates the tags file generated by" diff --git a/src/antsibull/tagging.py b/src/antsibull/tagging.py index 8cb7ece4..a1a5d0e8 100644 --- a/src/antsibull/tagging.py +++ b/src/antsibull/tagging.py @@ -13,7 +13,8 @@ import os import re import sys -from collections.abc import AsyncGenerator +from collections.abc import AsyncGenerator, Collection +from typing import TextIO import asyncio_pool # type: ignore[import] @@ -37,8 +38,13 @@ def validate_tags_file_command() -> int: generated by the 'validate-tags' subcommand. """ app_ctx = app_context.app_ctx.get() + ignores = _get_ignores(app_ctx.extra['ignore'], app_ctx.extra['ignores_file']) tag_data = load_yaml_file(app_ctx.extra['tags_file']) - return _print_validation_errors(tag_data) + return _print_validation_errors( + tag_data, + ignores, + app_ctx.extra['error_on_useless_ignores'], + ) def validate_tags_command() -> int: @@ -50,6 +56,7 @@ def validate_tags_command() -> int: and then verifies the data. """ app_ctx = app_context.app_ctx.get() + ignores = _get_ignores(app_ctx.extra['ignore'], app_ctx.extra['ignores_file']) tag_data = asyncio.run( get_collections_tags( app_ctx.extra['data_dir'], app_ctx.extra['deps_file'] @@ -57,16 +64,22 @@ def validate_tags_command() -> int: ) if app_ctx.extra['output']: store_yaml_file(app_ctx.extra['output'], tag_data) - return _print_validation_errors(tag_data) + return _print_validation_errors( + tag_data, + ignores, + app_ctx.extra['error_on_useless_ignores'], + ) def _print_validation_errors( - tag_data: dict[str, dict[str, str | None]] + tag_data: dict[str, dict[str, str | None]], + ignores: Collection[str] = (), + error_on_useless_ignores: bool = True, ) -> int: """ This takes the tag_data and prints any validation errors to stderr. """ - errors = validate_tags(tag_data) + errors = validate_tags(tag_data, ignores, error_on_useless_ignores) if not errors: return 0 for error in errors: @@ -74,29 +87,57 @@ def _print_validation_errors( return 1 +def _get_ignores( + ignores: Collection[str], ignore_fp: TextIO | None +) -> set[str]: + ignores = set(ignores) + if ignore_fp: + ignores.update( + line.strip() for line in ignore_fp if not line.startswith('#') + ) + return ignores + + ############## # Library code ############## def validate_tags( - tag_data: dict[str, dict[str, str | None]] + tag_data: dict[str, dict[str, str | None]], + ignores: Collection[str] = (), + error_on_useless_ignores: bool = True, ) -> list[str]: """ Validate that each collection in tag_data has a repository and a tag associated with it. Return a list of validation errors. + + :param tag_data: A tag data dictionary as returned by `get_collections_tags` + :param ignores: A list of collection names for which to ignore errors + :param error_on_useless_ignores: Whether to error for useless ignores """ errors = [] + ignore_set = set(ignores) for name, data in tag_data.items(): - if not data['repository']: + version = data['version'] + if name in ignore_set: + ignore_set.remove(name) + if data['repository'] and data['tag'] and error_on_useless_ignores: + errors.append( + f'useless ignore {name!r}: {name} {version} is properly tagged' + ) + elif not data['repository']: errors.append( f"{name}'s repository is not specified at all in collection-meta.yaml" ) - continue - if not data['tag']: + elif not data['tag']: + errors.append( + f'{name} {version} is not tagged in {data["repository"]}' + ) + if ignore_set and error_on_useless_ignores: + for name in ignore_set: errors.append( - f"{name} {data['version']} is not tagged in " - f"{data['repository']}" + f'invalid ignore {name!r}: {name} does not match any collection' ) return errors