From 1c05ed795175d89ed17d24498f792f60a2321b66 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi <a.badger@gmail.com> Date: Wed, 26 Apr 2017 15:09:36 -0700 Subject: [PATCH] Fix circular import with unsafe_proxy, template, and vars template/__init__.py imported unsafe_proxy from vars which caused vars/__init__.py to load. vars/__init__.py needed template/__init__.py which caused issues. Loading unsafe_proxy from another location fixes that. --- CHANGELOG.md | 3 + lib/ansible/executor/task_executor.py | 3 +- lib/ansible/parsing/yaml/constructor.py | 2 +- lib/ansible/parsing/yaml/dumper.py | 2 +- lib/ansible/plugins/action/__init__.py | 2 +- lib/ansible/plugins/callback/oneline.py | 4 +- lib/ansible/template/__init__.py | 2 +- lib/ansible/utils/unsafe_proxy.py | 124 ++++++++++++++++++++++ lib/ansible/vars/__init__.py | 2 +- lib/ansible/vars/unsafe_proxy.py | 133 ++++-------------------- test/sanity/pep8/legacy-files.txt | 1 - test/units/template/test_templar.py | 3 +- 12 files changed, 157 insertions(+), 124 deletions(-) create mode 100644 lib/ansible/utils/unsafe_proxy.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e7004761c7e..d1a76189b90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ Ansible Changes By Release * The fetch module's validate_md5 parameter has been deprecated and will be removed in 2.8. If you wish to disable post-validation of the downloaded file, use validate_checksum instead. +* Those using ansible as a library should note that the ansible.vars.unsafe_proxy + module is deprecated and slated to go away in 2.8. The functionality has been + moved to ansible.utils.unsafe_proxy to avoid a circular import. ### Minor Changes * removed previously deprecated config option 'hostfile' and env var 'ANSIBLE_HOSTS' diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index b19b8b6c6a9..a983c8aec6f 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -36,7 +36,7 @@ from ansible.template import Templar from ansible.utils.encrypt import key_for_hostname from ansible.utils.listify import listify_lookup_plugin_terms from ansible.utils.ssh_functions import check_for_controlpersist -from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var +from ansible.utils.unsafe_proxy import UnsafeProxy, wrap_var try: from __main__ import display @@ -229,7 +229,6 @@ class TaskExecutor: del self._job_vars[k] if items: - from ansible.vars.unsafe_proxy import UnsafeProxy for idx, item in enumerate(items): if item is not None and not isinstance(item, UnsafeProxy): items[idx] = UnsafeProxy(item) diff --git a/lib/ansible/parsing/yaml/constructor.py b/lib/ansible/parsing/yaml/constructor.py index 44167573204..02beca89555 100644 --- a/lib/ansible/parsing/yaml/constructor.py +++ b/lib/ansible/parsing/yaml/constructor.py @@ -26,7 +26,7 @@ from ansible.module_utils._text import to_bytes from ansible.parsing.vault import VaultLib from ansible.parsing.yaml.objects import AnsibleMapping, AnsibleSequence, AnsibleUnicode from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode -from ansible.vars.unsafe_proxy import wrap_var +from ansible.utils.unsafe_proxy import wrap_var try: from __main__ import display diff --git a/lib/ansible/parsing/yaml/dumper.py b/lib/ansible/parsing/yaml/dumper.py index 6060f1647c4..313169490ad 100644 --- a/lib/ansible/parsing/yaml/dumper.py +++ b/lib/ansible/parsing/yaml/dumper.py @@ -24,8 +24,8 @@ import yaml from ansible.module_utils.six import PY3 from ansible.parsing.yaml.objects import AnsibleUnicode, AnsibleSequence, AnsibleMapping from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode +from ansible.utils.unsafe_proxy import AnsibleUnsafeText from ansible.vars.hostvars import HostVars -from ansible.vars.unsafe_proxy import AnsibleUnsafeText class AnsibleDumper(yaml.SafeDumper): diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index 37f423dc878..b3df22061ee 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -39,7 +39,7 @@ from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.parsing.utils.jsonify import jsonify from ansible.playbook.play_context import MAGIC_VARIABLE_MAPPING from ansible.release import __version__ -from ansible.vars.unsafe_proxy import wrap_var +from ansible.utils.unsafe_proxy import wrap_var try: diff --git a/lib/ansible/plugins/callback/oneline.py b/lib/ansible/plugins/callback/oneline.py index 4930edf7219..0985f51d0b2 100644 --- a/lib/ansible/plugins/callback/oneline.py +++ b/lib/ansible/plugins/callback/oneline.py @@ -35,9 +35,9 @@ class CallbackModule(CallbackBase): CALLBACK_NAME = 'oneline' def _command_generic_msg(self, hostname, result, caption): - stdout = result.get('stdout','').replace('\n', '\\n') + stdout = result.get('stdout','').replace('\n', '\\n').replace('\r', '\\r') if 'stderr' in result and result['stderr']: - stderr = result.get('stderr','').replace('\n', '\\n') + stderr = result.get('stderr','').replace('\n', '\\n').replace('\r', '\\r') return "%s | %s | rc=%s | (stdout) %s (stderr) %s" % (hostname, caption, result.get('rc', -1), stdout, stderr) else: return "%s | %s | rc=%s | (stdout) %s" % (hostname, caption, result.get('rc', -1), stdout) diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index 607d9ab0fc7..5d551d7b2f2 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -49,7 +49,7 @@ from ansible.plugins import filter_loader, lookup_loader, test_loader from ansible.template.safe_eval import safe_eval from ansible.template.template import AnsibleJ2Template from ansible.template.vars import AnsibleJ2Vars -from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var +from ansible.utils.unsafe_proxy import UnsafeProxy, wrap_var try: from __main__ import display diff --git a/lib/ansible/utils/unsafe_proxy.py b/lib/ansible/utils/unsafe_proxy.py new file mode 100644 index 00000000000..6f01660cee0 --- /dev/null +++ b/lib/ansible/utils/unsafe_proxy.py @@ -0,0 +1,124 @@ +# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +# -------------------------------------------- +# +# 1. This LICENSE AGREEMENT is between the Python Software Foundation +# ("PSF"), and the Individual or Organization ("Licensee") accessing and +# otherwise using this software ("Python") in source or binary form and +# its associated documentation. +# +# 2. Subject to the terms and conditions of this License Agreement, PSF hereby +# grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +# analyze, test, perform and/or display publicly, prepare derivative works, +# distribute, and otherwise use Python alone or in any derivative version, +# provided, however, that PSF's License Agreement and PSF's notice of copyright, +# i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are +# retained in Python alone or in any derivative version prepared by Licensee. +# +# 3. In the event Licensee prepares a derivative work that is based on +# or incorporates Python or any part thereof, and wants to make +# the derivative work available to others as provided herein, then +# Licensee hereby agrees to include in any such work a brief summary of +# the changes made to Python. +# +# 4. PSF is making Python available to Licensee on an "AS IS" +# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +# INFRINGE ANY THIRD PARTY RIGHTS. +# +# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +# +# 6. This License Agreement will automatically terminate upon a material +# breach of its terms and conditions. +# +# 7. Nothing in this License Agreement shall be deemed to create any +# relationship of agency, partnership, or joint venture between PSF and +# Licensee. This License Agreement does not grant permission to use PSF +# trademarks or trade name in a trademark sense to endorse or promote +# products or services of Licensee, or any third party. +# +# 8. By copying, installing or otherwise using Python, Licensee +# agrees to be bound by the terms and conditions of this License +# Agreement. +# +# Original Python Recipe for Proxy: +# http://code.activestate.com/recipes/496741-object-proxying/ +# Author: Tomer Filiba + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible.module_utils.six import string_types, text_type +from ansible.module_utils._text import to_text + + +__all__ = ['UnsafeProxy', 'AnsibleUnsafe', 'AnsibleJSONUnsafeEncoder', 'AnsibleJSONUnsafeDecoder', 'wrap_var'] + + +class AnsibleUnsafe(object): + __UNSAFE__ = True + + +class AnsibleUnsafeText(text_type, AnsibleUnsafe): + pass + + +class UnsafeProxy(object): + def __new__(cls, obj, *args, **kwargs): + # In our usage we should only receive unicode strings. + # This conditional and conversion exists to sanity check the values + # we're given but we may want to take it out for testing and sanitize + # our input instead. + if isinstance(obj, string_types): + obj = to_text(obj, errors='surrogate_or_strict') + return AnsibleUnsafeText(obj) + return obj + + +class AnsibleJSONUnsafeEncoder(json.JSONEncoder): + def encode(self, obj): + if isinstance(obj, AnsibleUnsafe): + return super(AnsibleJSONUnsafeEncoder, self).encode(dict(__ansible_unsafe=True, value=unicode(obj))) + else: + return super(AnsibleJSONUnsafeEncoder, self).encode(obj) + + +class AnsibleJSONUnsafeDecoder(json.JSONDecoder): + def decode(self, obj): + value = super(AnsibleJSONUnsafeDecoder, self).decode(obj) + if isinstance(value, dict) and '__ansible_unsafe' in value: + return UnsafeProxy(value.get('value', '')) + else: + return value + + +def _wrap_dict(v): + for k in v.keys(): + if v[k] is not None: + v[wrap_var(k)] = wrap_var(v[k]) + return v + + +def _wrap_list(v): + for idx, item in enumerate(v): + if item is not None: + v[idx] = wrap_var(item) + return v + + +def wrap_var(v): + if isinstance(v, dict): + v = _wrap_dict(v) + elif isinstance(v, list): + v = _wrap_list(v) + else: + if v is not None and not isinstance(v, AnsibleUnsafe): + v = UnsafeProxy(v) + return v diff --git a/lib/ansible/vars/__init__.py b/lib/ansible/vars/__init__.py index 57c8341559b..46ad022e379 100644 --- a/lib/ansible/vars/__init__.py +++ b/lib/ansible/vars/__init__.py @@ -41,7 +41,7 @@ from ansible.plugins.cache import FactCache from ansible.template import Templar from ansible.utils.listify import listify_lookup_plugin_terms from ansible.utils.vars import combine_vars -from ansible.vars.unsafe_proxy import wrap_var +from ansible.utils.unsafe_proxy import wrap_var from ansible.module_utils._text import to_native try: diff --git a/lib/ansible/vars/unsafe_proxy.py b/lib/ansible/vars/unsafe_proxy.py index 00702dba054..7951865d0cf 100644 --- a/lib/ansible/vars/unsafe_proxy.py +++ b/lib/ansible/vars/unsafe_proxy.py @@ -1,122 +1,31 @@ -# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -# -------------------------------------------- +# (c) 2017, Toshio Kuratomi <tkuratomi@ansible.com> # -# 1. This LICENSE AGREEMENT is between the Python Software Foundation -# ("PSF"), and the Individual or Organization ("Licensee") accessing and -# otherwise using this software ("Python") in source or binary form and -# its associated documentation. +# This file is part of Ansible # -# 2. Subject to the terms and conditions of this License Agreement, PSF hereby -# grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -# analyze, test, perform and/or display publicly, prepare derivative works, -# distribute, and otherwise use Python alone or in any derivative version, -# provided, however, that PSF's License Agreement and PSF's notice of copyright, -# i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -# 2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are -# retained in Python alone or in any derivative version prepared by Licensee. +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# 3. In the event Licensee prepares a derivative work that is based on -# or incorporates Python or any part thereof, and wants to make -# the derivative work available to others as provided herein, then -# Licensee hereby agrees to include in any such work a brief summary of -# the changes made to Python. +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# 4. PSF is making Python available to Licensee on an "AS IS" -# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -# INFRINGE ANY THIRD PARTY RIGHTS. -# -# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. -# -# 6. This License Agreement will automatically terminate upon a material -# breach of its terms and conditions. -# -# 7. Nothing in this License Agreement shall be deemed to create any -# relationship of agency, partnership, or joint venture between PSF and -# Licensee. This License Agreement does not grant permission to use PSF -# trademarks or trade name in a trademark sense to endorse or promote -# products or services of Licensee, or any third party. -# -# 8. By copying, installing or otherwise using Python, Licensee -# agrees to be bound by the terms and conditions of this License -# Agreement. -# -# Original Python Recipe for Proxy: -# http://code.activestate.com/recipes/496741-object-proxying/ -# Author: Tomer Filiba +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# Make coding more python3-ish from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json -from ansible.module_utils.six import string_types, text_type -from ansible.module_utils._text import to_text +# This is backwards compat. unsafe_proxy was moved to avoid circular imports. +from ansible.utils.unsafe_proxy import * +try: + from __main__ import display +except: + from ansible.utils.display import Display + display = Display() -__all__ = ['UnsafeProxy', 'AnsibleUnsafe', 'AnsibleJSONUnsafeEncoder', 'AnsibleJSONUnsafeDecoder', 'wrap_var'] - - -class AnsibleUnsafe(object): - __UNSAFE__ = True - -class AnsibleUnsafeText(text_type, AnsibleUnsafe): - pass - - -class UnsafeProxy(object): - def __new__(cls, obj, *args, **kwargs): - # In our usage we should only receive unicode strings. - # This conditional and conversion exists to sanity check the values - # we're given but we may want to take it out for testing and sanitize - # our input instead. - if isinstance(obj, string_types): - obj = to_text(obj, errors='surrogate_or_strict') - return AnsibleUnsafeText(obj) - return obj - - -class AnsibleJSONUnsafeEncoder(json.JSONEncoder): - def encode(self, obj): - if isinstance(obj, AnsibleUnsafe): - return super(AnsibleJSONUnsafeEncoder, self).encode(dict(__ansible_unsafe=True, value=unicode(obj))) - else: - return super(AnsibleJSONUnsafeEncoder, self).encode(obj) - - -class AnsibleJSONUnsafeDecoder(json.JSONDecoder): - def decode(self, obj): - value = super(AnsibleJSONUnsafeDecoder, self).decode(obj) - if isinstance(value, dict) and '__ansible_unsafe' in value: - return UnsafeProxy(value.get('value', '')) - else: - return value - - -def _wrap_dict(v): - for k in v.keys(): - if v[k] is not None: - v[wrap_var(k)] = wrap_var(v[k]) - return v - - -def _wrap_list(v): - for idx, item in enumerate(v): - if item is not None: - v[idx] = wrap_var(item) - return v - - -def wrap_var(v): - if isinstance(v, dict): - v = _wrap_dict(v) - elif isinstance(v, list): - v = _wrap_list(v) - else: - if v is not None and not isinstance(v, AnsibleUnsafe): - v = UnsafeProxy(v) - return v +display.deprecated('ansible.vars.unsafe_proxy is deprecated. Use ansible.utils.unsafe_proxy instead.', version='2.8') diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index 49380a98fb8..8621913da6d 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -975,7 +975,6 @@ lib/ansible/utils/vars.py lib/ansible/vars/__init__.py lib/ansible/vars/hostvars.py lib/ansible/vars/reserved.py -lib/ansible/vars/unsafe_proxy.py setup.py test/integration/cleanup_azure.py test/integration/cleanup_ec2.py diff --git a/test/units/template/test_templar.py b/test/units/template/test_templar.py index cf49e75a022..79042a4e959 100644 --- a/test/units/template/test_templar.py +++ b/test/units/template/test_templar.py @@ -28,8 +28,7 @@ from ansible import constants as C from ansible.errors import AnsibleError, AnsibleUndefinedVariable from ansible.module_utils.six import string_types from ansible.template import Templar, AnsibleContext, AnsibleEnvironment -from ansible.vars.unsafe_proxy import AnsibleUnsafe, wrap_var -#from ansible.unsafe_proxy import AnsibleUnsafe, wrap_var +from ansible.utils.unsafe_proxy import AnsibleUnsafe, wrap_var from units.mock.loader import DictDataLoader