diff --git a/changelogs/fragments/70784-vault-is-string.yml b/changelogs/fragments/70784-vault-is-string.yml new file mode 100644 index 00000000000..8dc1164a857 --- /dev/null +++ b/changelogs/fragments/70784-vault-is-string.yml @@ -0,0 +1,3 @@ +bugfixes: +- JSON Encoder - Ensure we treat single vault encrypted values as strings + (https://github.com/ansible/ansible/issues/70784) diff --git a/lib/ansible/module_utils/common/json.py b/lib/ansible/module_utils/common/json.py index 3018e9e238e..3ac9eecf2b4 100644 --- a/lib/ansible/module_utils/common/json.py +++ b/lib/ansible/module_utils/common/json.py @@ -15,15 +15,23 @@ from ansible.module_utils.common._collections_compat import Mapping from ansible.module_utils.common.collections import is_sequence +def _is_unsafe(value): + return getattr(value, '__UNSAFE__', False) and not getattr(value, '__ENCRYPTED__', False) + + +def _is_vault(value): + return getattr(value, '__ENCRYPTED__', False) + + 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): + if _is_unsafe(value): value = {'__ansible_unsafe': to_text(value, errors='surrogate_or_strict', nonstring='strict')} - elif is_sequence(value): + elif is_sequence(value) and not _is_vault(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()) diff --git a/test/units/parsing/test_ajson.py b/test/units/parsing/test_ajson.py index 929d19966d8..c38f43ea572 100644 --- a/test/units/parsing/test_ajson.py +++ b/test/units/parsing/test_ajson.py @@ -158,6 +158,7 @@ class TestAnsibleJSONEncoder: Test for passing AnsibleVaultEncryptedUnicode to AnsibleJSONEncoder.default(). """ assert ansible_json_encoder.default(test_input) == {'__ansible_vault': expected} + assert json.dumps(test_input, cls=AnsibleJSONEncoder, preprocess_unsafe=True) == '{"__ansible_vault": "%s"}' % expected.replace('\n', '\\n') @pytest.mark.parametrize( 'test_input,expected',