From 67f5bb39c7cf732c4e398318128d599e56ffdf67 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Fri, 5 Feb 2021 13:24:59 -0500 Subject: [PATCH] galaxy - add format options for collection list (#73474) * Include all collections in single json object / yaml document * Add tests * For galaxy list yaml/json output, use dictionary of dictionaries instead of list * Add tests for listing single collection in yaml / output format * --output -> --format * Add explicit test for listing collection in human format * Fix bug where empty json object was emitted + add test --- .../73474-galaxy-list-format-options.yml | 2 + lib/ansible/cli/galaxy.py | 26 +++++++ .../ansible-galaxy-collection/tasks/list.yml | 76 +++++++++++++++++++ .../galaxy/test_execute_list_collection.py | 1 + 4 files changed, 105 insertions(+) create mode 100644 changelogs/fragments/73474-galaxy-list-format-options.yml diff --git a/changelogs/fragments/73474-galaxy-list-format-options.yml b/changelogs/fragments/73474-galaxy-list-format-options.yml new file mode 100644 index 00000000000..85826cd103d --- /dev/null +++ b/changelogs/fragments/73474-galaxy-list-format-options.yml @@ -0,0 +1,2 @@ +minor_changes: + - Add ``--format`` CLI option to ``ansible-galaxy collection list`` which allows for ``human`` (default), ``yaml``, or ``json``. (https://github.com/ansible/ansible/pull/73474) diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index a8351d06b91..dedceced7e4 100644 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -5,6 +5,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import json import os.path import re import shutil @@ -301,6 +302,10 @@ class GalaxyCLI(CLI): list_parser.add_argument(galaxy_type, help=galaxy_type.capitalize(), nargs='?', metavar=galaxy_type) + 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.") + def add_search_options(self, parser, parents=None): search_parser = parser.add_parser('search', parents=parents, help='Search the Galaxy database by tags, platforms, author and multiple ' @@ -1379,9 +1384,11 @@ class GalaxyCLI(CLI): :param artifacts_manager: Artifacts manager. """ + output_format = context.CLIARGS['output_format'] collections_search_paths = set(context.CLIARGS['collections_path']) collection_name = context.CLIARGS['collection'] default_collections_path = AnsibleCollectionConfig.collection_paths + collections_in_paths = {} warnings = [] path_found = False @@ -1428,6 +1435,13 @@ class GalaxyCLI(CLI): except ValueError as val_err: six.raise_from(AnsibleError(val_err), val_err) + if output_format in {'yaml', 'json'}: + collections_in_paths[collection_path] = { + collection.fqcn: {'version': collection.ver} + } + + continue + fqcn_width, version_width = _get_collection_widths([collection]) _display_header(collection_path, 'Collection', 'Version', fqcn_width, version_width) @@ -1451,6 +1465,13 @@ class GalaxyCLI(CLI): display.vvv("No collections found at {0}".format(collection_path)) continue + if output_format in {'yaml', 'json'}: + collections_in_paths[collection_path] = { + collection.fqcn: {'version': collection.ver} for collection in collections + } + + continue + # Display header fqcn_width, version_width = _get_collection_widths(collections) _display_header(collection_path, 'Collection', 'Version', fqcn_width, version_width) @@ -1469,6 +1490,11 @@ class GalaxyCLI(CLI): if not path_found: raise AnsibleOptionsError("- None of the provided paths were usable. Please specify a valid path with --{0}s-path".format(context.CLIARGS['type'])) + if output_format == 'json': + display.display(json.dumps(collections_in_paths)) + elif output_format == 'yaml': + display.display(yaml.safe_dump(collections_in_paths)) + return 0 def execute_publish(self): diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml index 0c54f2f7064..331e0a1c49c 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml @@ -31,6 +31,82 @@ - "'dev.collection2 placeholder' in list_result.stdout" - "'dev.collection3 *' in list_result.stdout" +- name: list collections in human format + command: ansible-galaxy collection list --format human + register: list_result_human + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + +- assert: + that: + - "'dev.collection1 *' in list_result_human.stdout" + # Note the version displayed is the 'placeholder' string rather than "*" since it is not falsey + - "'dev.collection2 placeholder' in list_result_human.stdout" + - "'dev.collection3 *' in list_result_human.stdout" + +- name: list collections in yaml format + command: ansible-galaxy collection list --format yaml + register: list_result_yaml + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + +- assert: + that: + - "item.value | length == 3" + - "item.value['dev.collection1'].version == '*'" + - "item.value['dev.collection2'].version == 'placeholder'" + - "item.value['dev.collection3'].version == '*'" + with_dict: "{{ list_result_yaml.stdout | from_yaml }}" + +- name: list collections in json format + command: ansible-galaxy collection list --format json + register: list_result_json + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + +- assert: + that: + - "item.value | length == 3" + - "item.value['dev.collection1'].version == '*'" + - "item.value['dev.collection2'].version == 'placeholder'" + - "item.value['dev.collection3'].version == '*'" + with_dict: "{{ list_result_json.stdout | from_json }}" + +- name: list single collection in json format + command: "ansible-galaxy collection list {{ item.key }} --format json" + register: list_single_result_json + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + with_dict: "{{ { 'dev.collection1': '*', 'dev.collection2': 'placeholder', 'dev.collection3': '*' } }}" + +- assert: + that: + - "(item.stdout | from_json)[galaxy_dir + '/dev/ansible_collections'][item.item.key].version == item.item.value" + with_items: "{{ list_single_result_json.results }}" + +- name: list single collection in yaml format + command: "ansible-galaxy collection list {{ item.key }} --format yaml" + register: list_single_result_yaml + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + with_dict: "{{ { 'dev.collection1': '*', 'dev.collection2': 'placeholder', 'dev.collection3': '*' } }}" + +- assert: + that: + - "(item.stdout | from_yaml)[galaxy_dir + '/dev/ansible_collections'][item.item.key].version == item.item.value" + with_items: "{{ list_single_result_json.results }}" + +- name: test that no json is emitted when no collection paths are usable + command: "ansible-galaxy collection list --format json" + register: list_result_error + ignore_errors: True + environment: + ANSIBLE_COLLECTIONS_PATH: "" + +- assert: + that: + - "'{}' not in list_result_error.stdout" + - name: install an artifact to the second collections path command: ansible-galaxy collection install namespace1.name1 -s galaxy_ng {{ galaxy_verbosity }} -p "{{ galaxy_dir }}/prod" environment: diff --git a/test/units/cli/galaxy/test_execute_list_collection.py b/test/units/cli/galaxy/test_execute_list_collection.py index be629a91b81..acd865b5f86 100644 --- a/test/units/cli/galaxy/test_execute_list_collection.py +++ b/test/units/cli/galaxy/test_execute_list_collection.py @@ -41,6 +41,7 @@ def cliargs(collections_paths=None, collection_name=None): 'collections_path': collections_paths, 'collection': collection_name, 'type': 'collection', + 'output_format': 'human' }