diff options
author | Matt Martz <matt@sivel.net> | 2019-08-26 09:08:22 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-26 09:08:22 -0500 |
commit | 5941e4c843df9574802a9dba552bb27dfc65b406 (patch) | |
tree | 89e8953447270bf816f335a270a02f7df0cbfd6b /lib/ansible/module_utils/common/json.py | |
parent | 1d405fdd60dc578fa2b3ea24f87dd7b4a9ebea5b (diff) | |
download | ansible-5941e4c843df9574802a9dba552bb27dfc65b406.tar.gz |
Properly JSON encode AnsibleUnsafe, using a pre-processor (#60602)
* Properly JSON encode AnsibleUnsafe, using a pre-processor. Fixes #47295
* Add AnsibleUnsafe json tests
* Require preprocess_unsafe to be enabled for that functionality
* Support older json
* sort keys in tests
* Decouple AnsibleJSONEncoder from isinstance checks in preparation to move to module_utils
* Move AnsibleJSONEncoder to module_utils, consolidate instances
* add missing boilerplate
* remove removed.py from ignore
Diffstat (limited to 'lib/ansible/module_utils/common/json.py')
-rw-r--r-- | lib/ansible/module_utils/common/json.py | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/lib/ansible/module_utils/common/json.py b/lib/ansible/module_utils/common/json.py new file mode 100644 index 0000000000..7da31f4598 --- /dev/null +++ b/lib/ansible/module_utils/common/json.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 Ansible Project +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +import datetime + +from ansible.module_utils._text import to_text +from ansible.module_utils.common._collections_compat import Mapping +from ansible.module_utils.common.collections import is_sequence + + +def _preprocess_unsafe_encode(value): + """Recursively preprocess a data structure converting instances of ``AnsibleUnsafe`` + into their JSON dict representations + + Used in ``AnsibleJSONEncoder.iterencode`` + """ + if getattr(value, '__UNSAFE__', False) and not getattr(value, '__ENCRYPTED__', False): + value = {'__ansible_unsafe': to_text(value, errors='surrogate_or_strict', nonstring='strict')} + elif is_sequence(value): + value = [_preprocess_unsafe_encode(v) for v in value] + elif isinstance(value, Mapping): + value = dict((k, _preprocess_unsafe_encode(v)) for k, v in value.items()) + + return value + + +class AnsibleJSONEncoder(json.JSONEncoder): + ''' + Simple encoder class to deal with JSON encoding of Ansible internal types + ''' + + def __init__(self, preprocess_unsafe=False, **kwargs): + self._preprocess_unsafe = preprocess_unsafe + super(AnsibleJSONEncoder, self).__init__(**kwargs) + + # NOTE: ALWAYS inform AWS/Tower when new items get added as they consume them downstream via a callback + def default(self, o): + if getattr(o, '__ENCRYPTED__', False): + # vault object + value = {'__ansible_vault': to_text(o._ciphertext, errors='surrogate_or_strict', nonstring='strict')} + elif getattr(o, '__UNSAFE__', False): + # unsafe object, this will never be triggered, see ``AnsibleJSONEncoder.iterencode`` + value = {'__ansible_unsafe': to_text(o, errors='surrogate_or_strict', nonstring='strict')} + elif isinstance(o, Mapping): + # hostvars and other objects + value = dict(o) + elif isinstance(o, (datetime.date, datetime.datetime)): + # date object + value = o.isoformat() + else: + # use default encoder + value = super(AnsibleJSONEncoder, self).default(o) + return value + + def iterencode(self, o, **kwargs): + """Custom iterencode, primarily design to handle encoding ``AnsibleUnsafe`` + as the ``AnsibleUnsafe`` subclasses inherit from string types and + ``json.JSONEncoder`` does not support custom encoders for string types + """ + if self._preprocess_unsafe: + o = _preprocess_unsafe_encode(o) + + return super(AnsibleJSONEncoder, self).iterencode(o, **kwargs) |