Allow restricting config values to enumerated list (#73162)

* Allow restricting config values to enumerated list
* dont document internal entries
* also ignore private defs for ansible-config
* remove invalid value from tests
* added porting entry
This commit is contained in:
Brian Coca 2021-01-14 15:11:30 -05:00 committed by GitHub
parent 88763c7347
commit 1202dd000f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 63 additions and 26 deletions

View file

@ -0,0 +1,3 @@
minor_changes:
- Now 'choices' keyword in config definitions also restricts valid values for the entry.
- Internal config entries will not be documented, to mark an entry as internal it must start with `_`.

View file

@ -31,6 +31,12 @@ Command Line
``~/.ansible/galaxy_token``) or (insecurely) via the ``--token`` argument to ``ansible-galaxy``. ``~/.ansible/galaxy_token``) or (insecurely) via the ``--token`` argument to ``ansible-galaxy``.
Other:
======
* The configuration system now validates the ``choices`` field, so any settings that currently violate it and are currently ignored will now cause an error.
For example, `ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH=0` will now cause an error (valid chioces are 'ignore', 'warn' or 'error'.
Deprecated Deprecated
========== ==========

View file

@ -26,7 +26,13 @@ DEFAULT_TEMPLATE_DIR = pathlib.Path(__file__).parents[4] / 'docs/templates'
def fix_description(config_options): def fix_description(config_options):
'''some descriptions are strings, some are lists. workaround it...''' '''some descriptions are strings, some are lists. workaround it...'''
for config_key in config_options: for config_key in list(config_options.keys()):
# drop internal entries
if config_key.startswith('_'):
del config_options[config_key]
continue
description = config_options[config_key].get('description', []) description = config_options[config_key].get('description', [])
if isinstance(description, list): if isinstance(description, list):
desc_list = description desc_list = description

View file

@ -159,7 +159,7 @@ class ConfigCLI(CLI):
''' '''
list all current configs reading lib/constants.py and shows env and config file setting names 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(), Dumper=AnsibleDumper), errors='surrogate_or_strict')) self.pager(to_text(yaml.dump(self.config.get_configuration_definitions(ignore_private=True), Dumper=AnsibleDumper), errors='surrogate_or_strict'))
def execute_dump(self): def execute_dump(self):
''' '''
@ -167,7 +167,7 @@ class ConfigCLI(CLI):
''' '''
# FIXME: deal with plugins, not just base config # FIXME: deal with plugins, not just base config
text = [] text = []
defaults = self.config.get_configuration_definitions().copy() defaults = self.config.get_configuration_definitions(ignore_private=True).copy()
for setting in self.config.data.get_settings(): for setting in self.config.data.get_settings():
if setting.name in defaults: if setting.name in defaults:
defaults[setting.name] = setting defaults[setting.name] = setting

View file

@ -275,15 +275,19 @@ COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH:
ini: [{key: collections_on_ansible_version_mismatch, section: defaults}] ini: [{key: collections_on_ansible_version_mismatch, section: defaults}]
choices: [error, warning, ignore] choices: [error, warning, ignore]
default: warning default: warning
_COLOR_DEFAULTS: &color
name: placeholder for color settings' defaults
choices: ['black', 'bright gray', 'blue', 'white', 'green', 'bright blue', 'cyan', 'bright green', 'red', 'bright cyan', 'purple', 'bright red', 'yellow', 'bright purple', 'dark gray', 'bright yellow', 'magenta', 'bright magenta', 'normal']
COLOR_CHANGED: COLOR_CHANGED:
<<: *color
name: Color for 'changed' task status name: Color for 'changed' task status
default: yellow default: yellow
description: Defines the color to use on 'Changed' task status description: Defines the color to use on 'Changed' task status
env: [{name: ANSIBLE_COLOR_CHANGED}] env: [{name: ANSIBLE_COLOR_CHANGED}]
ini: ini:
- {key: changed, section: colors} - {key: changed, section: colors}
yaml: {key: display.colors.changed}
COLOR_CONSOLE_PROMPT: COLOR_CONSOLE_PROMPT:
<<: *color
name: "Color for ansible-console's prompt task status" name: "Color for ansible-console's prompt task status"
default: white default: white
description: Defines the default color to use for ansible-console description: Defines the default color to use for ansible-console
@ -292,22 +296,23 @@ COLOR_CONSOLE_PROMPT:
- {key: console_prompt, section: colors} - {key: console_prompt, section: colors}
version_added: "2.7" version_added: "2.7"
COLOR_DEBUG: COLOR_DEBUG:
<<: *color
name: Color for debug statements name: Color for debug statements
default: dark gray default: dark gray
description: Defines the color to use when emitting debug messages description: Defines the color to use when emitting debug messages
env: [{name: ANSIBLE_COLOR_DEBUG}] env: [{name: ANSIBLE_COLOR_DEBUG}]
ini: ini:
- {key: debug, section: colors} - {key: debug, section: colors}
yaml: {key: display.colors.debug}
COLOR_DEPRECATE: COLOR_DEPRECATE:
<<: *color
name: Color for deprecation messages name: Color for deprecation messages
default: purple default: purple
description: Defines the color to use when emitting deprecation messages description: Defines the color to use when emitting deprecation messages
env: [{name: ANSIBLE_COLOR_DEPRECATE}] env: [{name: ANSIBLE_COLOR_DEPRECATE}]
ini: ini:
- {key: deprecate, section: colors} - {key: deprecate, section: colors}
yaml: {key: display.colors.deprecate}
COLOR_DIFF_ADD: COLOR_DIFF_ADD:
<<: *color
name: Color for diff added display name: Color for diff added display
default: green default: green
description: Defines the color to use when showing added lines in diffs description: Defines the color to use when showing added lines in diffs
@ -316,6 +321,7 @@ COLOR_DIFF_ADD:
- {key: diff_add, section: colors} - {key: diff_add, section: colors}
yaml: {key: display.colors.diff.add} yaml: {key: display.colors.diff.add}
COLOR_DIFF_LINES: COLOR_DIFF_LINES:
<<: *color
name: Color for diff lines display name: Color for diff lines display
default: cyan default: cyan
description: Defines the color to use when showing diffs description: Defines the color to use when showing diffs
@ -323,6 +329,7 @@ COLOR_DIFF_LINES:
ini: ini:
- {key: diff_lines, section: colors} - {key: diff_lines, section: colors}
COLOR_DIFF_REMOVE: COLOR_DIFF_REMOVE:
<<: *color
name: Color for diff removed display name: Color for diff removed display
default: red default: red
description: Defines the color to use when showing removed lines in diffs description: Defines the color to use when showing removed lines in diffs
@ -330,6 +337,7 @@ COLOR_DIFF_REMOVE:
ini: ini:
- {key: diff_remove, section: colors} - {key: diff_remove, section: colors}
COLOR_ERROR: COLOR_ERROR:
<<: *color
name: Color for error messages name: Color for error messages
default: red default: red
description: Defines the color to use when emitting error messages description: Defines the color to use when emitting error messages
@ -338,6 +346,7 @@ COLOR_ERROR:
- {key: error, section: colors} - {key: error, section: colors}
yaml: {key: colors.error} yaml: {key: colors.error}
COLOR_HIGHLIGHT: COLOR_HIGHLIGHT:
<<: *color
name: Color for highlighting name: Color for highlighting
default: white default: white
description: Defines the color to use for highlighting description: Defines the color to use for highlighting
@ -345,6 +354,7 @@ COLOR_HIGHLIGHT:
ini: ini:
- {key: highlight, section: colors} - {key: highlight, section: colors}
COLOR_OK: COLOR_OK:
<<: *color
name: Color for 'ok' task status name: Color for 'ok' task status
default: green default: green
description: Defines the color to use when showing 'OK' task status description: Defines the color to use when showing 'OK' task status
@ -352,6 +362,7 @@ COLOR_OK:
ini: ini:
- {key: ok, section: colors} - {key: ok, section: colors}
COLOR_SKIP: COLOR_SKIP:
<<: *color
name: Color for 'skip' task status name: Color for 'skip' task status
default: cyan default: cyan
description: Defines the color to use when showing 'Skipped' task status description: Defines the color to use when showing 'Skipped' task status
@ -359,6 +370,7 @@ COLOR_SKIP:
ini: ini:
- {key: skip, section: colors} - {key: skip, section: colors}
COLOR_UNREACHABLE: COLOR_UNREACHABLE:
<<: *color
name: Color for 'unreachable' host state name: Color for 'unreachable' host state
default: bright red default: bright red
description: Defines the color to use on 'Unreachable' status description: Defines the color to use on 'Unreachable' status
@ -366,6 +378,7 @@ COLOR_UNREACHABLE:
ini: ini:
- {key: unreachable, section: colors} - {key: unreachable, section: colors}
COLOR_VERBOSE: COLOR_VERBOSE:
<<: *color
name: Color for verbose messages name: Color for verbose messages
default: blue default: blue
description: Defines the color to use when emitting verbose messages. i.e those that show with '-v's. description: Defines the color to use when emitting verbose messages. i.e those that show with '-v's.
@ -373,6 +386,7 @@ COLOR_VERBOSE:
ini: ini:
- {key: verbose, section: colors} - {key: verbose, section: colors}
COLOR_WARN: COLOR_WARN:
<<: *color
name: Color for warning messages name: Color for warning messages
default: bright purple default: bright purple
description: Defines the color to use when emitting warning messages description: Defines the color to use when emitting warning messages

View file

@ -384,7 +384,7 @@ class ConfigManager(object):
return ret return ret
def get_configuration_definitions(self, plugin_type=None, name=None): def get_configuration_definitions(self, plugin_type=None, name=None, ignore_private=False):
''' just list the possible settings, either base or for specific plugins or plugin ''' ''' just list the possible settings, either base or for specific plugins or plugin '''
ret = {} ret = {}
@ -395,6 +395,11 @@ class ConfigManager(object):
else: else:
ret = self._plugins.get(plugin_type, {}).get(name, {}) ret = self._plugins.get(plugin_type, {}).get(name, {})
if ignore_private:
for cdef in list(ret.keys()):
if cdef.startswith('_'):
del ret[cdef]
return ret return ret
def _loop_entries(self, container, entry_list): def _loop_entries(self, container, entry_list):
@ -536,6 +541,12 @@ class ConfigManager(object):
raise AnsibleOptionsError('Invalid type for configuration option %s: %s' % raise AnsibleOptionsError('Invalid type for configuration option %s: %s' %
(to_native(_get_entry(plugin_type, plugin_name, config)), to_native(e))) (to_native(_get_entry(plugin_type, plugin_name, config)), to_native(e)))
# deal with restricted values
if value is not None and 'choices' in defs[config] and defs[config]['choices'] is not None:
if value not in defs[config]['choices']:
raise AnsibleOptionsError('Invalid value "%s" for configuration option "%s", valid values are: %s' %
(value, to_native(_get_entry(plugin_type, plugin_name, config)), defs[config]['choices']))
# deal with deprecation of the setting # deal with deprecation of the setting
if 'deprecated' in defs[config] and origin != 'default': if 'deprecated' in defs[config] and origin != 'default':
self.DEPRECATED.append((config, defs[config].get('deprecated'))) self.DEPRECATED.append((config, defs[config].get('deprecated')))

View file

@ -61,6 +61,19 @@ class _DeprecatedSequenceConstant(Sequence):
# CONSTANTS ### yes, actual ones # CONSTANTS ### yes, actual ones
# http://nezzen.net/2008/06/23/colored-text-in-python-using-ansi-escape-sequences/
COLOR_CODES = {
'black': u'0;30', 'bright gray': u'0;37',
'blue': u'0;34', 'white': u'1;37',
'green': u'0;32', 'bright blue': u'1;34',
'cyan': u'0;36', 'bright green': u'1;32',
'red': u'0;31', 'bright cyan': u'1;36',
'purple': u'0;35', 'bright red': u'1;31',
'yellow': u'0;33', 'bright purple': u'1;35',
'dark gray': u'1;30', 'bright yellow': u'1;33',
'magenta': u'0;35', 'bright magenta': u'1;35',
'normal': u'0',
}
REJECT_EXTS = ('.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst') REJECT_EXTS = ('.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst')
BOOL_TRUE = BOOLEANS_TRUE BOOL_TRUE = BOOLEANS_TRUE
COLLECTION_PTYPE_COMPAT = {'module': 'modules'} COLLECTION_PTYPE_COMPAT = {'module': 'modules'}

View file

@ -38,8 +38,7 @@ options:
- Windows only supports C(no) as an option. - Windows only supports C(no) as an option.
type: bool type: bool
default: 'no' default: 'no'
choices: choices: ['no', False]
- 'no'
environment: environment:
description: description:
- List of dictionaries of environment variables and their values to use when - List of dictionaries of environment variables and their values to use when

View file

@ -51,21 +51,6 @@ if C.ANSIBLE_FORCE_COLOR:
# domain - there is no license except that you must leave this header. # domain - there is no license except that you must leave this header.
# #
# Copyright (C) 2008 Brian Nez <thedude at bri1 dot com> # Copyright (C) 2008 Brian Nez <thedude at bri1 dot com>
#
# http://nezzen.net/2008/06/23/colored-text-in-python-using-ansi-escape-sequences/
codeCodes = {
'black': u'0;30', 'bright gray': u'0;37',
'blue': u'0;34', 'white': u'1;37',
'green': u'0;32', 'bright blue': u'1;34',
'cyan': u'0;36', 'bright green': u'1;32',
'red': u'0;31', 'bright cyan': u'1;36',
'purple': u'0;35', 'bright red': u'1;31',
'yellow': u'0;33', 'bright purple': u'1;35',
'dark gray': u'1;30', 'bright yellow': u'1;33',
'magenta': u'0;35', 'bright magenta': u'1;35',
'normal': u'0',
}
def parsecolor(color): def parsecolor(color):
@ -74,7 +59,7 @@ def parsecolor(color):
r"|(?P<rgb>rgb(?P<red>[0-5])(?P<green>[0-5])(?P<blue>[0-5]))" r"|(?P<rgb>rgb(?P<red>[0-5])(?P<green>[0-5])(?P<blue>[0-5]))"
r"|gray(?P<gray>[0-9]+)", color) r"|gray(?P<gray>[0-9]+)", color)
if not matches: if not matches:
return codeCodes[color] return C.COLOR_CODES[color]
if matches.group('color'): if matches.group('color'):
return u'38;5;%d' % int(matches.group('color')) return u'38;5;%d' % int(matches.group('color'))
if matches.group('rgb'): if matches.group('rgb'):

View file

@ -6,7 +6,7 @@ export ANSIBLE_COLLECTIONS_PATH=$PWD/collection_root_user:$PWD/collection_root_s
export ANSIBLE_GATHERING=explicit export ANSIBLE_GATHERING=explicit
export ANSIBLE_GATHER_SUBSET=minimal export ANSIBLE_GATHER_SUBSET=minimal
export ANSIBLE_HOST_PATTERN_MISMATCH=error export ANSIBLE_HOST_PATTERN_MISMATCH=error
export ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH=0 unset ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH
# FUTURE: just use INVENTORY_PATH as-is once ansible-test sets the right dir # FUTURE: just use INVENTORY_PATH as-is once ansible-test sets the right dir
ipath=../../$(basename "${INVENTORY_PATH:-../../inventory}") ipath=../../$(basename "${INVENTORY_PATH:-../../inventory}")