ansible-doc: allow to filter by more than one collection (#81450)

Make collection filters more flexible for listing collections.
Co-authored-by: Maxwell G <maxwell@gtmx.me>
pull/81677/head
Felix Fontein 1 year ago committed by GitHub
parent 8034651cd2
commit b1b029c6b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,4 @@
bugfixes:
- "ansible-console - fix filtering by collection names when a collection search path was set (https://github.com/ansible/ansible/pull/81450)."
minor_changes:
- "ansible-doc - allow to filter listing of collections and metadata dump by more than one collection (https://github.com/ansible/ansible/pull/81450)."

@ -163,8 +163,8 @@ class RoleMixin(object):
might be fully qualified with the collection name (e.g., community.general.roleA) might be fully qualified with the collection name (e.g., community.general.roleA)
or not (e.g., roleA). or not (e.g., roleA).
:param collection_filter: A string containing the FQCN of a collection which will be :param collection_filter: A list of strings containing the FQCN of a collection which will
used to limit results. This filter will take precedence over the name_filters. be used to limit results. This filter will take precedence over the name_filters.
:returns: A set of tuples consisting of: role name, collection name, collection path :returns: A set of tuples consisting of: role name, collection name, collection path
""" """
@ -678,12 +678,11 @@ class DocCLI(CLI, RoleMixin):
def _get_collection_filter(self): def _get_collection_filter(self):
coll_filter = None coll_filter = None
if len(context.CLIARGS['args']) == 1: if len(context.CLIARGS['args']) >= 1:
coll_filter = context.CLIARGS['args'][0] coll_filter = context.CLIARGS['args']
if not AnsibleCollectionRef.is_valid_collection_name(coll_filter): for coll_name in coll_filter:
raise AnsibleError('Invalid collection name (must be of the form namespace.collection): {0}'.format(coll_filter)) if not AnsibleCollectionRef.is_valid_collection_name(coll_name):
elif len(context.CLIARGS['args']) > 1: raise AnsibleError('Invalid collection name (must be of the form namespace.collection): {0}'.format(coll_name))
raise AnsibleOptionsError("Only a single collection filter is supported.")
return coll_filter return coll_filter

@ -32,16 +32,31 @@ def list_collection_dirs(search_paths=None, coll_filter=None, artifacts_manager=
namespace_filter = None namespace_filter = None
collection_filter = None collection_filter = None
has_pure_namespace_filter = False # whether at least one coll_filter is a namespace-only filter
if coll_filter is not None: if coll_filter is not None:
if '.' in coll_filter: if isinstance(coll_filter, str):
try: coll_filter = [coll_filter]
namespace_filter, collection_filter = coll_filter.split('.') namespace_filter = set()
except ValueError: for coll_name in coll_filter:
raise AnsibleError("Invalid collection pattern supplied: %s" % coll_filter) if '.' in coll_name:
else: try:
namespace_filter = coll_filter namespace, collection = coll_name.split('.')
except ValueError:
raise AnsibleError("Invalid collection pattern supplied: %s" % coll_name)
namespace_filter.add(namespace)
if not has_pure_namespace_filter:
if collection_filter is None:
collection_filter = []
collection_filter.append(collection)
else:
namespace_filter.add(coll_name)
has_pure_namespace_filter = True
collection_filter = None
namespace_filter = sorted(namespace_filter)
for req in find_existing_collections(search_paths, artifacts_manager, namespace_filter=namespace_filter, for req in find_existing_collections(search_paths, artifacts_manager, namespace_filter=namespace_filter,
collection_filter=collection_filter, dedupe=dedupe): collection_filter=collection_filter, dedupe=dedupe):
if not has_pure_namespace_filter and coll_filter is not None and req.fqcn not in coll_filter:
continue
yield to_bytes(req.src) yield to_bytes(req.src)

@ -1420,6 +1420,10 @@ def find_existing_collections(path_filter, artifacts_manager, namespace_filter=N
if path_filter and not is_sequence(path_filter): if path_filter and not is_sequence(path_filter):
path_filter = [path_filter] path_filter = [path_filter]
if namespace_filter and not is_sequence(namespace_filter):
namespace_filter = [namespace_filter]
if collection_filter and not is_sequence(collection_filter):
collection_filter = [collection_filter]
paths = set() paths = set()
for path in files('ansible_collections').glob('*/*/'): for path in files('ansible_collections').glob('*/*/'):
@ -1441,9 +1445,9 @@ def find_existing_collections(path_filter, artifacts_manager, namespace_filter=N
for path in paths: for path in paths:
namespace = path.parent.name namespace = path.parent.name
name = path.name name = path.name
if namespace_filter and namespace != namespace_filter: if namespace_filter and namespace not in namespace_filter:
continue continue
if collection_filter and name != collection_filter: if collection_filter and name not in collection_filter:
continue continue
if dedupe: if dedupe:

@ -171,28 +171,32 @@ def list_collection_plugins(ptype, collections, search_paths=None):
return plugins return plugins
def list_plugins(ptype, collection=None, search_paths=None): def list_plugins(ptype, collections=None, search_paths=None):
if isinstance(collections, str):
collections = [collections]
# {plugin_name: (filepath, class), ...} # {plugin_name: (filepath, class), ...}
plugins = {} plugins = {}
collections = {} plugin_collections = {}
if collection is None: if collections is None:
# list all collections, add synthetic ones # list all collections, add synthetic ones
collections['ansible.builtin'] = b'' plugin_collections['ansible.builtin'] = b''
collections['ansible.legacy'] = b'' plugin_collections['ansible.legacy'] = b''
collections.update(list_collections(search_paths=search_paths, dedupe=True)) plugin_collections.update(list_collections(search_paths=search_paths, dedupe=True))
elif collection == 'ansible.legacy':
# add builtin, since legacy also resolves to these
collections[collection] = b''
collections['ansible.builtin'] = b''
else: else:
try: for collection in collections:
collections[collection] = to_bytes(_get_collection_path(collection)) if collection == 'ansible.legacy':
except ValueError as e: # add builtin, since legacy also resolves to these
raise AnsibleError("Cannot use supplied collection {0}: {1}".format(collection, to_native(e)), orig_exc=e) plugin_collections[collection] = b''
plugin_collections['ansible.builtin'] = b''
else:
try:
plugin_collections[collection] = to_bytes(_get_collection_path(collection))
except ValueError as e:
raise AnsibleError("Cannot use supplied collection {0}: {1}".format(collection, to_native(e)), orig_exc=e)
if collections: if plugin_collections:
plugins.update(list_collection_plugins(ptype, collections)) plugins.update(list_collection_plugins(ptype, plugin_collections))
return plugins return plugins

@ -0,0 +1,6 @@
namespace: testns
name: testcol3
version: 0.1.0
readme: README.md
authors:
- me

@ -0,0 +1,27 @@
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
module: test1
short_description: Foo module in testcol3
description:
- This is a foo module.
author:
- me
"""
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(
argument_spec=dict(),
)
module.exit_json()
if __name__ == '__main__':
main()

@ -0,0 +1,6 @@
namespace: testns
name: testcol4
version: 1.0.0
readme: README.md
authors:
- me

@ -0,0 +1,27 @@
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
module: test2
short_description: Foo module in testcol4
description:
- This is a foo module.
author:
- me
"""
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(
argument_spec=dict(),
)
module.exit_json()
if __name__ == '__main__':
main()

@ -60,6 +60,14 @@ ansible-doc --list testns.testcol --playbook-dir ./ 2>&1 | grep $GREP_OPTS -v "I
echo "ensure we dont break on invalid collection name for list" echo "ensure we dont break on invalid collection name for list"
ansible-doc --list testns.testcol.fakemodule --playbook-dir ./ 2>&1 | grep $GREP_OPTS "Invalid collection name" ansible-doc --list testns.testcol.fakemodule --playbook-dir ./ 2>&1 | grep $GREP_OPTS "Invalid collection name"
echo "filter list with more than one collection (1/2)"
output=$(ansible-doc --list testns.testcol3 testns.testcol4 --playbook-dir ./ 2>&1 | wc -l)
test "$output" -eq 2
echo "filter list with more than one collection (2/2)"
output=$(ansible-doc --list testns.testcol testns.testcol4 --playbook-dir ./ 2>&1 | wc -l)
test "$output" -eq 5
echo "testing ansible-doc output for various plugin types" echo "testing ansible-doc output for various plugin types"
for ptype in cache inventory lookup vars filter module for ptype in cache inventory lookup vars filter module
do do
@ -114,6 +122,11 @@ echo "testing multiple role entrypoints"
output=$(ansible-doc -t role -l --playbook-dir . testns.testcol | wc -l) output=$(ansible-doc -t role -l --playbook-dir . testns.testcol | wc -l)
test "$output" -eq 2 test "$output" -eq 2
echo "test listing roles with multiple collection filters"
# Two collection roles are defined, but only 1 has a role arg spec with 2 entry points
output=$(ansible-doc -t role -l --playbook-dir . testns.testcol2 testns.testcol | wc -l)
test "$output" -eq 2
echo "testing standalone roles" echo "testing standalone roles"
# Include normal roles (no collection filter) # Include normal roles (no collection filter)
output=$(ansible-doc -t role -l --playbook-dir . | wc -l) output=$(ansible-doc -t role -l --playbook-dir . | wc -l)

Loading…
Cancel
Save