ansible-config added json/yaml output to list/dump (#77447)

fixes #733644
pull/77689/head
Brian Coca 2 years ago committed by GitHub
parent a3cc6a581e
commit a12e0a0e87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
minor_changes:
- ansible-config adds JSON and YAML output formats for list and dump actions.

@ -10,13 +10,12 @@ __metaclass__ = type
from ansible.cli import CLI from ansible.cli import CLI
import os import os
import yaml
import shlex import shlex
import subprocess import subprocess
from collections.abc import Mapping from collections.abc import Mapping
import yaml
from ansible import context from ansible import context
import ansible.plugins.loader as plugin_loader import ansible.plugins.loader as plugin_loader
@ -25,6 +24,7 @@ from ansible.cli.arguments import option_helpers as opt_help
from ansible.config.manager import ConfigManager, Setting from ansible.config.manager import ConfigManager, Setting
from ansible.errors import AnsibleError, AnsibleOptionsError from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.module_utils._text import to_native, to_text, to_bytes from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible.module_utils.common.json import json_dump
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
from ansible.parsing.quoting import is_quoted from ansible.parsing.quoting import is_quoted
from ansible.parsing.yaml.dumper import AnsibleDumper from ansible.parsing.yaml.dumper import AnsibleDumper
@ -35,6 +35,10 @@ from ansible.utils.path import unfrackpath
display = Display() display = Display()
def yaml_dump(data, default_flow_style=True):
return yaml.dump(data, Dumper=AnsibleDumper, default_flow_style=default_flow_style)
def get_constants(): def get_constants():
''' helper method to ensure we can template based on existing constants ''' ''' helper method to ensure we can template based on existing constants '''
if not hasattr(get_constants, 'cvars'): if not hasattr(get_constants, 'cvars'):
@ -72,11 +76,15 @@ class ConfigCLI(CLI):
list_parser = subparsers.add_parser('list', help='Print all config options', parents=[common]) list_parser = subparsers.add_parser('list', help='Print all config options', parents=[common])
list_parser.set_defaults(func=self.execute_list) list_parser.set_defaults(func=self.execute_list)
list_parser.add_argument('--format', '-f', dest='format', action='store', choices=['json', 'yaml'], default='yaml',
help='Output format for list')
dump_parser = subparsers.add_parser('dump', help='Dump configuration', parents=[common]) dump_parser = subparsers.add_parser('dump', help='Dump configuration', parents=[common])
dump_parser.set_defaults(func=self.execute_dump) dump_parser.set_defaults(func=self.execute_dump)
dump_parser.add_argument('--only-changed', '--changed-only', 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") help="Only show configurations that have changed from the default")
dump_parser.add_argument('--format', '-f', dest='format', action='store', choices=['json', 'yaml', 'display'], default='display',
help='Output format for dump')
view_parser = subparsers.add_parser('view', help='View configuration file', parents=[common]) view_parser = subparsers.add_parser('view', help='View configuration file', parents=[common])
view_parser.set_defaults(func=self.execute_view) view_parser.set_defaults(func=self.execute_view)
@ -238,7 +246,12 @@ class ConfigCLI(CLI):
''' '''
config_entries = self._list_entries_from_args() config_entries = self._list_entries_from_args()
self.pager(to_text(yaml.dump(config_entries, Dumper=AnsibleDumper), errors='surrogate_or_strict')) if context.CLIARGS['format'] == 'yaml':
output = yaml_dump(config_entries)
elif context.CLIARGS['format'] == 'json':
output = json_dump(config_entries)
self.pager(to_text(output, errors='surrogate_or_strict'))
def _get_settings_vars(self, settings, subkey): def _get_settings_vars(self, settings, subkey):
@ -288,7 +301,7 @@ class ConfigCLI(CLI):
if subkey == 'env': if subkey == 'env':
data.append('%s%s=%s' % (prefix, entry, default)) data.append('%s%s=%s' % (prefix, entry, default))
elif subkey == 'vars': elif subkey == 'vars':
data.append(prefix + to_text(yaml.dump({entry: default}, Dumper=AnsibleDumper, default_flow_style=False), errors='surrogate_or_strict')) data.append(prefix + to_text(yaml_dump({entry: default}, default_flow_style=False), errors='surrogate_or_strict'))
data.append('') data.append('')
return data return data
@ -377,28 +390,35 @@ class ConfigCLI(CLI):
def _render_settings(self, config): def _render_settings(self, config):
text = [] entries = []
for setting in sorted(config): for setting in sorted(config):
changed = False changed = (config[setting].origin != 'default')
if isinstance(config[setting], Setting):
# proceed normally if context.CLIARGS['format'] == 'display':
if config[setting].origin == 'default': if isinstance(config[setting], Setting):
color = 'green' # proceed normally
elif config[setting].origin == 'REQUIRED': if config[setting].origin == 'default':
# should include '_terms', '_input', etc color = 'green'
color = 'red' elif config[setting].origin == 'REQUIRED':
# should include '_terms', '_input', etc
color = 'red'
else:
color = 'yellow'
msg = "%s(%s) = %s" % (setting, config[setting].origin, config[setting].value)
else: else:
color = 'yellow' color = 'green'
changed = True msg = "%s(%s) = %s" % (setting, 'default', config[setting].get('default'))
msg = "%s(%s) = %s" % (setting, config[setting].origin, config[setting].value)
entry = stringc(msg, color)
else: else:
color = 'green' entry = {}
msg = "%s(%s) = %s" % (setting, 'default', config[setting].get('default')) for key in config[setting]._fields:
entry[key] = getattr(config[setting], key)
if not context.CLIARGS['only_changed'] or changed: if not context.CLIARGS['only_changed'] or changed:
text.append(stringc(msg, color)) entries.append(entry)
return text return entries
def _get_global_configs(self): def _get_global_configs(self):
config = self.config.get_configuration_definitions(ignore_private=True).copy() config = self.config.get_configuration_definitions(ignore_private=True).copy()
@ -414,7 +434,7 @@ class ConfigCLI(CLI):
loader = getattr(plugin_loader, '%s_loader' % ptype) loader = getattr(plugin_loader, '%s_loader' % ptype)
# acumulators # acumulators
text = [] output = []
config_entries = {} config_entries = {}
# build list # build list
@ -469,10 +489,14 @@ class ConfigCLI(CLI):
# pretty please! # pretty please!
results = self._render_settings(config_entries[finalname]) results = self._render_settings(config_entries[finalname])
if results: if results:
# avoid header for empty lists (only changed!) if context.CLIARGS['format'] == 'display':
text.append('\n%s:\n%s' % (finalname, '_' * len(finalname))) # avoid header for empty lists (only changed!)
text.extend(results) output.append('\n%s:\n%s' % (finalname, '_' * len(finalname)))
return text output.extend(results)
else:
output.append({finalname: results})
return output
def execute_dump(self): def execute_dump(self):
''' '''
@ -480,19 +504,34 @@ class ConfigCLI(CLI):
''' '''
if context.CLIARGS['type'] == 'base': if context.CLIARGS['type'] == 'base':
# deal with base # deal with base
text = self._get_global_configs() output = self._get_global_configs()
elif context.CLIARGS['type'] == 'all': elif context.CLIARGS['type'] == 'all':
# deal with base # deal with base
text = self._get_global_configs() output = self._get_global_configs()
# deal with plugins # deal with plugins
for ptype in C.CONFIGURABLE_PLUGINS: for ptype in C.CONFIGURABLE_PLUGINS:
text.append('\n%s:\n%s' % (ptype.upper(), '=' * len(ptype))) plugin_list = self._get_plugin_configs(ptype, context.CLIARGS['args'])
text.extend(self._get_plugin_configs(ptype, context.CLIARGS['args'])) if context.CLIARGS['format'] == 'display':
output.append('\n%s:\n%s' % (ptype.upper(), '=' * len(ptype)))
output.extend(plugin_list)
else:
if ptype in ('modules', 'doc_fragments'):
pname = ptype.upper()
else:
pname = '%s_PLUGINS' % ptype.upper()
output.append({pname: plugin_list})
else: else:
# deal with plugins # deal with plugins
text = self._get_plugin_configs(context.CLIARGS['type'], context.CLIARGS['args']) output = self._get_plugin_configs(context.CLIARGS['type'], context.CLIARGS['args'])
if context.CLIARGS['format'] == 'display':
text = '\n'.join(output)
if context.CLIARGS['format'] == 'yaml':
text = yaml_dump(output)
elif context.CLIARGS['format'] == 'json':
text = json_dump(output)
self.pager(to_text('\n'.join(text), errors='surrogate_or_strict')) self.pager(to_text(text, errors='surrogate_or_strict'))
def main(args=None): def main(args=None):

@ -10,7 +10,6 @@ __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first # ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI from ansible.cli import CLI
import json
import pkgutil import pkgutil
import os import os
import os.path import os.path
@ -29,7 +28,7 @@ from ansible.cli.arguments import option_helpers as opt_help
from ansible.collections.list import list_collection_dirs from ansible.collections.list import list_collection_dirs
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
from ansible.module_utils._text import to_native, to_text from ansible.module_utils._text import to_native, to_text
from ansible.module_utils.common.json import AnsibleJSONEncoder from ansible.module_utils.common.json import json_dump
from ansible.module_utils.common.yaml import yaml_dump from ansible.module_utils.common.yaml import yaml_dump
from ansible.module_utils.compat import importlib from ansible.module_utils.compat import importlib
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
@ -62,7 +61,7 @@ def add_collection_plugins(plugin_list, plugin_type, coll_filter=None):
def jdump(text): def jdump(text):
try: try:
display.display(json.dumps(text, cls=AnsibleJSONEncoder, sort_keys=True, indent=4)) display.display(json_dump(text))
except TypeError as e: except TypeError as e:
display.vvv(traceback.format_exc()) display.vvv(traceback.format_exc())
raise AnsibleError('We could not convert all the documentation into JSON as there was a conversion issue: %s' % to_native(e)) raise AnsibleError('We could not convert all the documentation into JSON as there was a conversion issue: %s' % to_native(e))
@ -700,7 +699,7 @@ class DocCLI(CLI, RoleMixin):
if not fail_on_errors: if not fail_on_errors:
# Check whether JSON serialization would break # Check whether JSON serialization would break
try: try:
json.dumps(docs, cls=AnsibleJSONEncoder) json_dump(docs)
except Exception as e: # pylint:disable=broad-except except Exception as e: # pylint:disable=broad-except
plugin_docs[plugin] = { plugin_docs[plugin] = {
'error': 'Cannot serialize documentation as JSON: %s' % to_native(e), 'error': 'Cannot serialize documentation as JSON: %s' % to_native(e),

@ -39,6 +39,10 @@ def _preprocess_unsafe_encode(value):
return value return value
def json_dump(structure):
return json.dumps(structure, cls=AnsibleJSONEncoder, sort_keys=True, indent=4)
class AnsibleJSONEncoder(json.JSONEncoder): class AnsibleJSONEncoder(json.JSONEncoder):
''' '''
Simple encoder class to deal with JSON encoding of Ansible internal types Simple encoder class to deal with JSON encoding of Ansible internal types

Loading…
Cancel
Save