ansilbe-doc list collections plugins (#67928)

Now -l and -F will list plugins from discover-able collections
This commit is contained in:
Brian Coca 2020-03-27 22:19:49 -04:00 committed by GitHub
parent 1570098e86
commit 0f5a63f1b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 86 additions and 3 deletions

View file

@ -26,7 +26,7 @@ from ansible.parsing.metadata import extract_metadata
from ansible.parsing.plugin_docs import read_docstub from ansible.parsing.plugin_docs import read_docstub
from ansible.parsing.yaml.dumper import AnsibleDumper from ansible.parsing.yaml.dumper import AnsibleDumper
from ansible.plugins.loader import action_loader, fragment_loader from ansible.plugins.loader import action_loader, fragment_loader
from ansible.utils.collection_loader import set_collection_playbook_paths from ansible.utils.collection_loader import set_collection_playbook_paths, list_collection_dirs, get_collection_name_from_path
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.utils.plugin_docs import BLACKLIST, get_docstring, get_versioned_doclink from ansible.utils.plugin_docs import BLACKLIST, get_docstring, get_versioned_doclink
display = Display() display = Display()
@ -36,6 +36,17 @@ def jdump(text):
display.display(json.dumps(text, sort_keys=True, indent=4)) display.display(json.dumps(text, sort_keys=True, indent=4))
def add_collection_plugins(plugin_list, plugin_type):
colldirs = list_collection_dirs()
for ns in colldirs.keys():
for path in colldirs[ns]:
collname = get_collection_name_from_path(path)
ptype = C.COLLECTION_PTYPE_COMPAT.get(plugin_type, plugin_type)
plugin_list.update(DocCLI.find_plugins(os.path.join(path, 'plugins', ptype), plugin_type, collname))
class RemovedPlugin(Exception): class RemovedPlugin(Exception):
pass pass
@ -125,6 +136,8 @@ class DocCLI(CLI):
for path in paths: for path in paths:
self.plugin_list.update(DocCLI.find_plugins(path, plugin_type)) self.plugin_list.update(DocCLI.find_plugins(path, plugin_type))
add_collection_plugins(self.plugin_list, plugin_type)
plugins = self._get_plugin_list_filenames(loader) plugins = self._get_plugin_list_filenames(loader)
if do_json: if do_json:
jdump(plugins) jdump(plugins)
@ -146,6 +159,8 @@ class DocCLI(CLI):
for path in paths: for path in paths:
self.plugin_list.update(DocCLI.find_plugins(path, plugin_type)) self.plugin_list.update(DocCLI.find_plugins(path, plugin_type))
add_collection_plugins(self.plugin_list, plugin_type)
descs = self._get_plugin_list_descriptions(loader) descs = self._get_plugin_list_descriptions(loader)
if do_json: if do_json:
jdump(descs) jdump(descs)
@ -349,7 +364,7 @@ class DocCLI(CLI):
return text return text
@staticmethod @staticmethod
def find_plugins(path, ptype): def find_plugins(path, ptype, collection=None):
display.vvvv("Searching %s for plugins" % path) display.vvvv("Searching %s for plugins" % path)
@ -386,6 +401,10 @@ class DocCLI(CLI):
plugin = plugin.lstrip('_') # remove underscore from deprecated plugins plugin = plugin.lstrip('_') # remove underscore from deprecated plugins
if plugin not in BLACKLIST.get(bkey, ()): if plugin not in BLACKLIST.get(bkey, ()):
if collection:
plugin = '%s.%s' % (collection, plugin)
plugin_list.add(plugin) plugin_list.add(plugin)
display.vvvv("Added %s" % plugin) display.vvvv("Added %s" % plugin)

View file

@ -97,6 +97,7 @@ BECOME_METHODS = _DeprecatedSequenceConstant(
# CONSTANTS ### yes, actual ones # CONSTANTS ### yes, actual ones
BLACKLIST_EXTS = ('.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst') BLACKLIST_EXTS = ('.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst')
BOOL_TRUE = BOOLEANS_TRUE BOOL_TRUE = BOOLEANS_TRUE
COLLECTION_PTYPE_COMPAT = {'module': 'modules'}
DEFAULT_BECOME_PASS = None DEFAULT_BECOME_PASS = None
DEFAULT_PASSWORD_CHARS = to_text(ascii_letters + digits + ".,:-_", errors='strict') # characters included in auto-generated passwords DEFAULT_PASSWORD_CHARS = to_text(ascii_letters + digits + ".,:-_", errors='strict') # characters included in auto-generated passwords
DEFAULT_REMOTE_PASS = None DEFAULT_REMOTE_PASS = None

View file

@ -6,17 +6,21 @@ __metaclass__ = type
import os import os
import os.path import os.path
import pkgutil
import re import re
import sys import sys
from types import ModuleType from types import ModuleType
from collections import defaultdict
from ansible import constants as C
from ansible.utils.display import Display
from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.compat.importlib import import_module from ansible.module_utils.compat.importlib import import_module
from ansible.module_utils.six import iteritems, string_types, with_metaclass from ansible.module_utils.six import iteritems, string_types, with_metaclass
from ansible.utils.singleton import Singleton from ansible.utils.singleton import Singleton
display = Display()
_SYNTHETIC_PACKAGES = { _SYNTHETIC_PACKAGES = {
# these provide fallback package definitions when there are no on-disk paths # these provide fallback package definitions when there are no on-disk paths
'ansible_collections': dict(type='pkg_only', allow_external_subpackages=True), 'ansible_collections': dict(type='pkg_only', allow_external_subpackages=True),
@ -28,6 +32,8 @@ _SYNTHETIC_PACKAGES = {
'ansible_collections.ansible.builtin.plugins.modules': dict(type='flatmap', flatmap='ansible.modules', graft=True), 'ansible_collections.ansible.builtin.plugins.modules': dict(type='flatmap', flatmap='ansible.modules', graft=True),
} }
FLAG_FILES = frozenset(['MANIFEST.json', 'galaxy.yml'])
# FIXME: exception handling/error logging # FIXME: exception handling/error logging
class AnsibleCollectionLoader(with_metaclass(Singleton, object)): class AnsibleCollectionLoader(with_metaclass(Singleton, object)):
@ -588,3 +594,60 @@ def get_collection_name_from_path(path):
def set_collection_playbook_paths(b_playbook_paths): def set_collection_playbook_paths(b_playbook_paths):
AnsibleCollectionLoader().set_playbook_paths(b_playbook_paths) AnsibleCollectionLoader().set_playbook_paths(b_playbook_paths)
def is_collection_path(path):
if os.path.isdir(path):
for flag in FLAG_FILES:
if os.path.exists(os.path.join(path, flag)):
return True
return False
def list_valid_collection_paths(search_paths=None, warn=False):
found_paths = []
if search_paths is None:
search_paths = C.COLLECTIONS_PATHS
for path in search_paths:
if not os.path.exists(path):
# warn for missing, but not if default
if warn:
display.warning("The configured collection path {0} does not exist.".format(path))
continue
if not os.path.isdir(path):
if warn:
display.warning("The configured collection path {0}, exists, but it is not a directory.".format(path))
continue
found_paths.append(path)
return found_paths
def list_collection_dirs(search_paths=None, namespace=None):
collections = defaultdict(list)
paths = list_valid_collection_paths(search_paths)
for path in paths:
if os.path.isdir(path):
coll_root = os.path.join(path, 'ansible_collections')
if os.path.exists(coll_root) and os.path.isdir(coll_root):
for namespace in os.listdir(coll_root):
namespace_dir = os.path.join(coll_root, namespace)
if os.path.isdir(namespace_dir):
for collection in os.listdir(namespace_dir):
coll_dir = os.path.join(namespace_dir, collection)
if is_collection_path(coll_dir):
collections[namespace].append(os.path.join(namespace_dir, collection))
return collections