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
import os
import yaml
import shlex
import subprocess
from collections.abc import Mapping
import yaml
from ansible import context
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.errors import AnsibleError, AnsibleOptionsError
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.parsing.quoting import is_quoted
from ansible.parsing.yaml.dumper import AnsibleDumper
@ -35,6 +35,10 @@ from ansible.utils.path import unfrackpath
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():
''' helper method to ensure we can template based on existing constants '''
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.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.set_defaults(func=self.execute_dump)
dump_parser.add_argument('--only-changed', '--changed-only', dest='only_changed', action='store_true',
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.set_defaults(func=self.execute_view)
@ -238,7 +246,12 @@ class ConfigCLI(CLI):
'''
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):
@ -288,7 +301,7 @@ class ConfigCLI(CLI):
if subkey == 'env':
data.append('%s%s=%s' % (prefix, entry, default))
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('')
return data
@ -377,28 +390,35 @@ class ConfigCLI(CLI):
def _render_settings(self, config):
text = []
entries = []
for setting in sorted(config):
changed = False
if isinstance(config[setting], Setting):
# proceed normally
if config[setting].origin == 'default':
color = 'green'
elif config[setting].origin == 'REQUIRED':
# should include '_terms', '_input', etc
color = 'red'
changed = (config[setting].origin != 'default')
if context.CLIARGS['format'] == 'display':
if isinstance(config[setting], Setting):
# proceed normally
if config[setting].origin == 'default':
color = 'green'
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:
color = 'yellow'
changed = True
msg = "%s(%s) = %s" % (setting, config[setting].origin, config[setting].value)
color = 'green'
msg = "%s(%s) = %s" % (setting, 'default', config[setting].get('default'))
entry = stringc(msg, color)
else:
color = 'green'
msg = "%s(%s) = %s" % (setting, 'default', config[setting].get('default'))
entry = {}
for key in config[setting]._fields:
entry[key] = getattr(config[setting], key)
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):
config = self.config.get_configuration_definitions(ignore_private=True).copy()
@ -414,7 +434,7 @@ class ConfigCLI(CLI):
loader = getattr(plugin_loader, '%s_loader' % ptype)
# acumulators
text = []
output = []
config_entries = {}
# build list
@ -469,10 +489,14 @@ class ConfigCLI(CLI):
# 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
if context.CLIARGS['format'] == 'display':
# avoid header for empty lists (only changed!)
output.append('\n%s:\n%s' % (finalname, '_' * len(finalname)))
output.extend(results)
else:
output.append({finalname: results})
return output
def execute_dump(self):
'''
@ -480,19 +504,34 @@ class ConfigCLI(CLI):
'''
if context.CLIARGS['type'] == 'base':
# deal with base
text = self._get_global_configs()
output = self._get_global_configs()
elif context.CLIARGS['type'] == 'all':
# deal with base
text = self._get_global_configs()
output = 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, context.CLIARGS['args']))
plugin_list = 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:
# 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):

@ -10,7 +10,6 @@ __metaclass__ = type
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
from ansible.cli import CLI
import json
import pkgutil
import os
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.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
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.compat import importlib
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):
try:
display.display(json.dumps(text, cls=AnsibleJSONEncoder, sort_keys=True, indent=4))
display.display(json_dump(text))
except TypeError as e:
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))
@ -700,7 +699,7 @@ class DocCLI(CLI, RoleMixin):
if not fail_on_errors:
# Check whether JSON serialization would break
try:
json.dumps(docs, cls=AnsibleJSONEncoder)
json_dump(docs)
except Exception as e: # pylint:disable=broad-except
plugin_docs[plugin] = {
'error': 'Cannot serialize documentation as JSON: %s' % to_native(e),

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

Loading…
Cancel
Save