Normalize config from environment as text strings
On Python3, these would be text strings already. On Python2, we need to convert them from bytes. Use a new helper function py3compat to do this. Fixes #43207
This commit is contained in:
parent
a452a92199
commit
d483d646eb
3 changed files with 73 additions and 2 deletions
5
changelogs/fragments/fix-config-from-environment.yaml
Normal file
5
changelogs/fragments/fix-config-from-environment.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
bugfixes:
|
||||||
|
- On Python2, loading config values from environment variables could lead to
|
||||||
|
a traceback if there were nonascii characters present. Converted them to
|
||||||
|
text strings so that no traceback will occur (https://github.com/ansible/ansible/pull/43468)
|
|
@ -28,6 +28,8 @@ from ansible.module_utils.parsing.convert_bool import boolean
|
||||||
from ansible.parsing.quoting import unquote
|
from ansible.parsing.quoting import unquote
|
||||||
from ansible.utils.path import unfrackpath
|
from ansible.utils.path import unfrackpath
|
||||||
from ansible.utils.path import makedirs_safe
|
from ansible.utils.path import makedirs_safe
|
||||||
|
from ansible.utils import py3compat
|
||||||
|
|
||||||
|
|
||||||
Plugin = namedtuple('Plugin', 'name type')
|
Plugin = namedtuple('Plugin', 'name type')
|
||||||
Setting = namedtuple('Setting', 'name value origin type')
|
Setting = namedtuple('Setting', 'name value origin type')
|
||||||
|
@ -315,7 +317,7 @@ class ConfigManager(object):
|
||||||
|
|
||||||
def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None, direct=None):
|
def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None, direct=None):
|
||||||
''' Given a config key figure out the actual value and report on the origin of the settings '''
|
''' Given a config key figure out the actual value and report on the origin of the settings '''
|
||||||
|
1/0
|
||||||
if cfile is None:
|
if cfile is None:
|
||||||
# use default config
|
# use default config
|
||||||
cfile = self._config_file
|
cfile = self._config_file
|
||||||
|
@ -351,7 +353,7 @@ class ConfigManager(object):
|
||||||
|
|
||||||
# env vars are next precedence
|
# env vars are next precedence
|
||||||
if value is None and defs[config].get('env'):
|
if value is None and defs[config].get('env'):
|
||||||
value, origin = self._loop_entries(os.environ, defs[config]['env'])
|
value, origin = self._loop_entries(py3compat.environ, defs[config]['env'])
|
||||||
origin = 'env: %s' % origin
|
origin = 'env: %s' % origin
|
||||||
|
|
||||||
# try config file entries next, if we have one
|
# try config file entries next, if we have one
|
||||||
|
|
64
lib/ansible/utils/py3compat.py
Normal file
64
lib/ansible/utils/py3compat.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# (c) 2018, Toshio Kuratomi <a.badger@gmail.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
#
|
||||||
|
# Note that the original author of this, Toshio Kuratomi, is trying to submit this to six. If
|
||||||
|
# successful, the code in six will be available under six's more liberal license:
|
||||||
|
# https://mail.python.org/pipermail/python-porting/2018-July/000539.html
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections import MutableMapping
|
||||||
|
|
||||||
|
from ansible.module_utils.six import PY3
|
||||||
|
from ansible.module_utils._text import to_bytes, to_text
|
||||||
|
|
||||||
|
__all__ = ('environ',)
|
||||||
|
|
||||||
|
|
||||||
|
class _TextEnviron(MutableMapping):
|
||||||
|
"""
|
||||||
|
Utility class to return text strings from the environment instead of byte strings
|
||||||
|
|
||||||
|
Mimics the behaviour of os.environ on Python3
|
||||||
|
"""
|
||||||
|
def __init__(self, env=None):
|
||||||
|
if env is None:
|
||||||
|
env = os.environ
|
||||||
|
self._raw_environ = env
|
||||||
|
self._value_cache = {}
|
||||||
|
# Since we're trying to mimic Python3's os.environ, use sys.getfilesystemencoding()
|
||||||
|
# instead of utf-8
|
||||||
|
self.encoding = sys.getfilesystemencoding()
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self._raw_environ[key]
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
value = self._raw_environ[key]
|
||||||
|
if PY3:
|
||||||
|
return value
|
||||||
|
# Cache keys off of the undecoded values to handle any environment variables which change
|
||||||
|
# during a run
|
||||||
|
if value not in self._value_cache:
|
||||||
|
self._value_cache[value] = to_text(value, encoding=self.encoding,
|
||||||
|
nonstring='passthru', errors='surrogate_or_strict')
|
||||||
|
return self._value_cache[value]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._raw_environ[key] = to_bytes(value, encoding=self.encoding, nonstring='strict',
|
||||||
|
errors='surrogate_or_strict')
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self._raw_environ.__iter__()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._raw_environ)
|
||||||
|
|
||||||
|
|
||||||
|
environ = _TextEnviron()
|
Loading…
Reference in a new issue