Add plugin config lists (#49627)

* add plugin config lists
* and dump config for plugins
*  also list configs under PLUGINS for 'all' list
This commit is contained in:
Brian Coca 2021-04-29 15:01:05 -04:00 committed by GitHub
parent aa12af1d34
commit 8a2fc854f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 134 additions and 24 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- ansible-config now supports displaying plugin configuration info.

View file

@ -10,9 +10,12 @@ import subprocess
import yaml
from ansible import context
import ansible.plugins.loader as plugin_loader
from ansible import constants as C
from ansible.cli import CLI
from ansible.cli.arguments import option_helpers as opt_help
from ansible.config.manager import ConfigManager, Setting, find_ini_config_file
from ansible.config.manager import ConfigManager, Setting
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible.parsing.yaml.dumper import AnsibleDumper
@ -42,6 +45,8 @@ class ConfigCLI(CLI):
opt_help.add_verbosity_options(common)
common.add_argument('-c', '--config', dest='config_file',
help="path to configuration file, defaults to first file found in precedence.")
common.add_argument("-t", "--type", action="store", default='base', dest='type', choices=['all', 'base'] + list(C.CONFIGURABLE_PLUGINS),
help="Show configuration for a plugin type, for a specific plugin's options see ansible-doc.")
subparsers = self.parser.add_subparsers(dest='action')
subparsers.required = True
@ -51,7 +56,7 @@ class ConfigCLI(CLI):
dump_parser = subparsers.add_parser('dump', help='Dump configuration', parents=[common])
dump_parser.set_defaults(func=self.execute_dump)
dump_parser.add_argument('--only-changed', dest='only_changed', action='store_true',
dump_parser.add_argument('--only-changed', '--changed-only', dest='only_changed', action='store_true',
help="Only show configurations that have changed from the default")
view_parser = subparsers.add_parser('view', help='View configuration file', parents=[common])
@ -85,8 +90,8 @@ class ConfigCLI(CLI):
else:
raise AnsibleOptionsError('The provided configuration file is missing or not accessible: %s' % to_native(self.config_file))
else:
self.config = ConfigManager()
self.config_file = find_ini_config_file()
self.config = C.config
self.config_file = self.config._config_file
if self.config_file:
try:
@ -155,34 +160,137 @@ class ConfigCLI(CLI):
except Exception as e:
raise AnsibleError("Failed to open editor: %s" % to_native(e))
def _list_plugin_settings(self, ptype):
entries = {}
loader = getattr(plugin_loader, '%s_loader' % ptype)
for plugin in loader.all(class_only=True):
finalname = name = plugin._load_name
if name.startswith('_'):
# alias or deprecated
if os.path.islink(plugin._original_path):
continue
else:
finalname = name.replace('_', '', 1) + ' (DEPRECATED)'
entries[finalname] = self.config.get_configuration_definitions(ptype, name)
return entries
def execute_list(self):
'''
list all current configs reading lib/constants.py and shows env and config file setting names
'''
self.pager(to_text(yaml.dump(self.config.get_configuration_definitions(ignore_private=True), Dumper=AnsibleDumper), errors='surrogate_or_strict'))
config_entries = {}
if context.CLIARGS['type'] == 'base':
# this dumps main/common configs
config_entries = self.config.get_configuration_definitions(ignore_private=True)
elif context.CLIARGS['type'] == 'all':
# get global
config_entries = self.config.get_configuration_definitions(ignore_private=True)
config_entries['PLUGINS'] = {}
# now each plugin type
for ptype in C.CONFIGURABLE_PLUGINS:
config_entries['PLUGINS'][ptype.upper()] = self._list_plugin_settings(ptype)
else:
config_entries = self._list_plugin_settings(context.CLIARGS['type'])
self.pager(to_text(yaml.dump(config_entries, Dumper=AnsibleDumper), errors='surrogate_or_strict'))
def _render_settings(self, config):
text = []
for setting in sorted(config):
if isinstance(config[setting], Setting):
if config[setting].origin == 'default':
color = 'green'
elif config[setting].origin == 'REQUIRED':
color = 'red'
else:
color = 'yellow'
msg = "%s(%s) = %s" % (setting, config[setting].origin, config[setting].value)
else:
color = 'green'
msg = "%s(%s) = %s" % (setting, 'default', config[setting].get('default'))
if not context.CLIARGS['only_changed'] or color == 'yellow':
text.append(stringc(msg, color))
return text
def _get_global_configs(self):
config = self.config.get_configuration_definitions(ignore_private=True).copy()
for setting in self.config.data.get_settings():
if setting.name in config:
config[setting.name] = setting
return self._render_settings(config)
def _get_plugin_configs(self, ptype):
# prep loading
loader = getattr(plugin_loader, '%s_loader' % ptype)
# acumulators
text = []
config_entries = {}
for plugin in loader.all(class_only=True):
# in case of deprecastion they diverge
finalname = name = plugin._load_name
if name.startswith('_'):
if os.path.islink(plugin._original_path):
# skip alias
continue
# deprecated, but use 'nice name'
finalname = name.replace('_', '', 1) + ' (DEPRECATED)'
# default entries per plugin
config_entries[finalname] = self.config.get_configuration_definitions(ptype, name)
try:
# populate config entries by loading plugin
dump = loader.get(name, class_only=True)
except Exception as e:
display.warning('Skipping "%s" %s plugin, as we cannot load plugin to check config due to : %s' % (name, ptype, to_native(e)))
continue
# actually get the values
for setting in config_entries[finalname].keys():
try:
v, o = C.config.get_config_value_and_origin(setting, plugin_type=ptype, plugin_name=name)
except AnsibleError as e:
if to_text(e).startswith('No setting was provided for required configuration'):
v = None
o = 'REQUIRED'
else:
raise e
config_entries[finalname][setting] = Setting(setting, v, o, None)
# pretty please!
results = self._render_settings(config_entries[finalname])
if results:
# avoid header for empty lists (only changed!)
text.append('\n%s:\n%s' % (finalname, '_' * len(finalname)))
text.extend(results)
return text
def execute_dump(self):
'''
Shows the current settings, merges ansible.cfg if specified
'''
# FIXME: deal with plugins, not just base config
text = []
defaults = self.config.get_configuration_definitions(ignore_private=True).copy()
for setting in self.config.data.get_settings():
if setting.name in defaults:
defaults[setting.name] = setting
for setting in sorted(defaults):
if isinstance(defaults[setting], Setting):
if defaults[setting].origin == 'default':
color = 'green'
else:
color = 'yellow'
msg = "%s(%s) = %s" % (setting, defaults[setting].origin, defaults[setting].value)
else:
color = 'green'
msg = "%s(%s) = %s" % (setting, 'default', defaults[setting].get('default'))
if not context.CLIARGS['only_changed'] or color == 'yellow':
text.append(stringc(msg, color))
if context.CLIARGS['type'] == 'base':
# deal with base
text = self._get_global_configs()
elif context.CLIARGS['type'] == 'all':
# deal with base
text = self._get_global_configs()
# deal with plugins
for ptype in C.CONFIGURABLE_PLUGINS:
text.append('\n%s:\n%s' % (ptype.upper(), '=' * len(ptype)))
text.extend(self._get_plugin_configs(ptype))
else:
# deal with plugins
text = self._get_plugin_configs(context.CLIARGS['type'])
self.pager(to_text('\n'.join(text), errors='surrogate_or_strict'))