From 320f716fd05b33e99353a99501b12f6df548ad5b Mon Sep 17 00:00:00 2001 From: s-hertel <19572925+s-hertel@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:04:32 -0400 Subject: [PATCH] ansible-galaxy collection list - add --deduplicate toggle to see the first of each FQCN in the configured paths This will match the collection used by a task, assuming -p is provided to reflect a playbook-adjacent collections path --- .../ansible-galaxy-list-unique-fqcn.yml | 3 ++ lib/ansible/cli/galaxy.py | 28 ++++++++-- .../ansible-galaxy-collection/tasks/list.yml | 52 +++++++++++++++++++ .../galaxy/test_execute_list_collection.py | 3 +- 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 changelogs/fragments/ansible-galaxy-list-unique-fqcn.yml diff --git a/changelogs/fragments/ansible-galaxy-list-unique-fqcn.yml b/changelogs/fragments/ansible-galaxy-list-unique-fqcn.yml new file mode 100644 index 00000000000..9714c90e77e --- /dev/null +++ b/changelogs/fragments/ansible-galaxy-list-unique-fqcn.yml @@ -0,0 +1,3 @@ +minor_changes: + - >- + ``ansible-galaxy collection list`` - add ``--deduplicate`` toggle to see only the first of each FQCN using the configured path order. diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index 9ce8cf7cf4b..e1ccb126381 100755 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -386,6 +386,8 @@ class GalaxyCLI(CLI): if galaxy_type == 'collection': list_parser.add_argument('--format', dest='output_format', choices=('human', 'yaml', 'json'), default='human', help="Format to display the list of collections in.") + list_parser.add_argument('--deduplicate', dest='deduplicate', action='store_true', + help='List only the first of each FQCN using the configured path order.') def add_search_options(self, parser, parents=None): search_parser = parser.add_parser('search', parents=parents, @@ -1638,11 +1640,14 @@ class GalaxyCLI(CLI): artifacts_manager.require_build_metadata = False output_format = context.CLIARGS['output_format'] + deduplicate_fqcn = context.CLIARGS['deduplicate'] collection_name = context.CLIARGS['collection'] - default_collections_path = set(C.COLLECTIONS_PATHS) - collections_search_paths = ( - set(context.CLIARGS['collections_path'] or []) | default_collections_path | set(self.collection_paths) + ordered_search_paths = ( + list(context.CLIARGS['collections_path'] or []) + + C.COLLECTIONS_PATHS + + self.collection_paths ) + collections_search_paths = set(ordered_search_paths) collections_in_paths = {} warnings = [] @@ -1665,9 +1670,22 @@ class GalaxyCLI(CLI): dedupe=False )) + def configuration_order(collection): + src = pathlib.Path(to_text(collection.src)) + for path in ordered_search_paths: + if src.is_relative_to(path): + return (ordered_search_paths.index(path), collection.src) + return (float("inf"), collection.src) + seen = set() + seen_fqcn = set() fqcn_width, version_width = _get_collection_widths(collections) - for collection in sorted(collections, key=lambda c: c.src): + for collection in sorted(collections, key=configuration_order): + if deduplicate_fqcn: + if collection.fqcn in seen_fqcn: + continue + seen_fqcn.add(collection.fqcn) + collection_found = True collection_path = pathlib.Path(to_text(collection.src)).parent.parent.as_posix() @@ -1689,7 +1707,7 @@ class GalaxyCLI(CLI): path_found = False for path in collections_search_paths: if not os.path.exists(path): - if path in default_collections_path: + if path in C.COLLECTIONS_PATHS: # don't warn for missing default paths continue warnings.append("- the configured path {0} does not exist.".format(path)) diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml index 11f9c9a253c..961bceb712f 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml @@ -241,3 +241,55 @@ that: - list_result is failed - "'is expected to have a valid SemVer version value but got None' in list_result.stderr" + +- name: Test showing the first FQCN in the configured paths + vars: + dev: "{{ galaxy_dir }}/dev/ansible_collections" + prod: "{{ galaxy_dir }}/prod/ansible_collections" + test_paths: + - "{{ dev }}" + - "{{ prod }}" + block: + - name: Remove collection paths + file: + path: "{{ item }}" + state: absent + loop: "{{ test_paths }}" + + - name: Initialize collection paths + file: + path: "{{ item }}" + state: directory + loop: "{{ test_paths }}" + + - name: Initialize a collection in two paths + command: ansible-galaxy collection init common.collection --init-path {{ item }} + loop: "{{ test_paths }}" + + - name: List deduplicated collection by name + command: ansible-galaxy collection list common.collection --deduplicate --format yaml + register: first_by_name + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ test_paths | join(':') }}" + + - name: Reverse the search order and list all deduplicated collections + command: ansible-galaxy collection list --deduplicate --format yaml + register: first_all + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ test_paths | reverse | join(':') }}" + + - name: List deduplicated collections with -p + command: ansible-galaxy collection list -p {{ test_paths | first }} --deduplicate --format yaml + register: first_override + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ test_paths | last }}" + + - assert: + that: + - 'first_by_name.stdout | from_yaml == {dev: found}' + - 'first_all.stdout | from_yaml == {prod: found}' + - 'first_override.stdout | from_yaml == {dev: found}' + vars: + found: + common.collection: + version: 1.0.0 diff --git a/test/units/cli/galaxy/test_execute_list_collection.py b/test/units/cli/galaxy/test_execute_list_collection.py index b468be6a8aa..fcafd6c1ea0 100644 --- a/test/units/cli/galaxy/test_execute_list_collection.py +++ b/test/units/cli/galaxy/test_execute_list_collection.py @@ -33,7 +33,8 @@ def cliargs(collections_paths=None, collection_name=None): 'collections_path': collections_paths, 'collection': collection_name, 'type': 'collection', - 'output_format': 'human' + 'output_format': 'human', + 'deduplicate': False, }