diff --git a/changelogs/fragments/68518-to_nice_json-cleanup.yaml b/changelogs/fragments/68518-to_nice_json-cleanup.yaml new file mode 100644 index 00000000000..fa3c4923b0e --- /dev/null +++ b/changelogs/fragments/68518-to_nice_json-cleanup.yaml @@ -0,0 +1,2 @@ +minor_changes: + - to_nice_json filter - Removed now-useless exception handler diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index 52721e53808..48fab01174c 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -80,12 +80,7 @@ def to_json(a, *args, **kw): def to_nice_json(a, indent=4, sort_keys=True, *args, **kw): '''Make verbose, human readable JSON''' - try: - return json.dumps(a, indent=indent, sort_keys=sort_keys, separators=(',', ': '), cls=AnsibleJSONEncoder, *args, **kw) - except Exception as e: - # Fallback to the to_json filter - display.warning(u'Unable to convert data using to_nice_json, falling back to to_json: %s' % to_text(e)) - return to_json(a, *args, **kw) + return to_json(a, indent=indent, sort_keys=sort_keys, separators=(',', ': '), *args, **kw) def to_bool(a): diff --git a/test/integration/targets/filter_core/tasks/main.yml b/test/integration/targets/filter_core/tasks/main.yml index 230b7b4f62d..cfc04bad5c3 100644 --- a/test/integration/targets/filter_core/tasks/main.yml +++ b/test/integration/targets/filter_core/tasks/main.yml @@ -3,6 +3,8 @@ # Copyright: (c) 2019, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# Note: |groupby is already tested by the `groupby_filter` target. + - set_fact: output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}" @@ -108,6 +110,7 @@ that: - '"{{ "hash" | hash("sha1") }}" == "2346ad27d7568ba9896f1b7da6b5991251debdf2"' - '"{{ "café" | hash("sha1") }}" == "f424452a9673918c6f09b0cdd35b20be8e6ae7d7"' + - '"corned beef"|hash("haha, get it?") == None' - name: Flatten tests block: @@ -190,6 +193,12 @@ - 4 - key: value +- name: Verify combine fails with extra kwargs + set_fact: + foo: "{{[1] | combine(foo='bar')}}" + ignore_errors: yes + register: combine_fail + - name: Verify combine filter assert: that: @@ -198,9 +207,12 @@ - "(x | combine(y, z)) == {'x': 'x', 'y': 'y', 'z': 'z', 'key': 'z'}" - "([x, y, z] | combine) == {'x': 'x', 'y': 'y', 'z': 'z', 'key': 'z'}" - "([x, y] | combine(z)) == {'x': 'x', 'y': 'y', 'z': 'z', 'key': 'z'}" - # more advance dicts combination tests are done in "merge_hash" function unit tests - # but even it's redundant with those unit tests, we do at least the most complicated exemple of the documentation here + - "None|combine == {}" + # more advanced dict combination tests are done in the "merge_hash" function unit tests + # but even though it's redundant with those unit tests, we do at least the most complicated example of the documentation here - "(default | combine(patch, recursive=True, list_merge='append_rp')) == result" + - combine_fail is failed + - "combine_fail.msg == \"'recursive' and 'list_merge' are the only valid keyword arguments\"" - set_fact: combine: "{{[x, [y]] | combine(z)}}" @@ -261,3 +273,287 @@ - named_groups == ['bye', 'good'] - numbered_groups == ['bye', 'good'] - failure is failed + +- name: Verify to_bool + assert: + that: + - 'None|bool == None' + - 'False|bool == False' + - '"TrUe"|bool == True' + - '"FalSe"|bool == False' + - '7|bool == False' + +- name: Verify to_datetime + assert: + that: + - '"1993-03-26 01:23:45"|to_datetime < "1994-03-26 01:23:45"|to_datetime' + +- name: strftime invalid argument (failure expected) + set_fact: + foo: "{{ '%Y' | strftime('foo') }}" + ignore_errors: yes + register: strftime_fail + +- name: Verify strftime + assert: + that: + - '"%Y-%m-%d"|strftime(1585247522) == "2020-03-26"' + - '("%Y"|strftime(None)).startswith("20")' # Current date, can't check much there. + - strftime_fail is failed + - '"Invalid value for epoch value" in strftime_fail.msg' + +- name: Verify case-insensitive regex_replace + assert: + that: + - '"hElLo there"|regex_replace("hello", "hi", ignorecase=True) == "hi there"' + +- name: Verify case-insensitive regex_findall + assert: + that: + - '"hEllo there heLlo haha HELLO there"|regex_findall("h.... ", ignorecase=True)|length == 3' + +- name: Verify ternary + assert: + that: + - 'True|ternary("seven", "eight") == "seven"' + - 'None|ternary("seven", "eight") == "eight"' + - 'None|ternary("seven", "eight", "nine") == "nine"' + - 'False|ternary("seven", "eight") == "eight"' + - '123|ternary("seven", "eight") == "seven"' + - '"haha"|ternary("seven", "eight") == "seven"' + +- name: Verify regex_escape raises on posix_extended (failure expected) + set_fact: + foo: '{{"]]^"|regex_escape(re_type="posix_extended")}}' + ignore_errors: yes + register: regex_escape_fail_1 + +- name: Verify regex_escape raises on other re_type (failure expected) + set_fact: + foo: '{{"]]^"|regex_escape(re_type="haha")}}' + ignore_errors: yes + register: regex_escape_fail_2 + +- name: Verify regex_escape with re_type other than 'python' + assert: + that: + - '"]]^"|regex_escape(re_type="posix_basic") == "\\]\\]\\^"' + - regex_escape_fail_1 is failed + - 'regex_escape_fail_1.msg == "Regex type (posix_extended) not yet implemented"' + - regex_escape_fail_2 is failed + - 'regex_escape_fail_2.msg == "Invalid regex type (haha)"' + +- name: Verify from_yaml and from_yaml_all + assert: + that: + - "'---\nbananas: yellow\napples: red'|from_yaml == {'bananas': 'yellow', 'apples': 'red'}" + - "2|from_yaml == 2" + - "'---\nbananas: yellow\n---\napples: red'|from_yaml_all|list == [{'bananas': 'yellow'}, {'apples': 'red'}]" + - "2|from_yaml_all == 2" + +- name: Verify random raises on non-iterable input (failure expected) + set_fact: + foo: '{{None|random}}' + ignore_errors: yes + register: random_fail_1 + +- name: Verify random raises on iterable input with start (failure expected) + set_fact: + foo: '{{[1,2,3]|random(start=2)}}' + ignore_errors: yes + register: random_fail_2 + +- name: Verify random raises on iterable input with step (failure expected) + set_fact: + foo: '{{[1,2,3]|random(step=2)}}' + ignore_errors: yes + register: random_fail_3 + +- name: Verify random + assert: + that: + - '2|random in [0,1]' + - '2|random(seed=1337) in [0,1]' + - '["a", "b"]|random in ["a", "b"]' + - '20|random(start=10) in range(10, 20)' + - '20|random(start=10, step=2) % 2 == 0' + - random_fail_1 is failure + - '"random can only be used on" in random_fail_1.msg' + - random_fail_2 is failure + - '"start and step can only be used" in random_fail_2.msg' + - random_fail_3 is failure + - '"start and step can only be used" in random_fail_3.msg' + +# It's hard to actually verify much here since the result is, well, random. +- name: Verify randomize_list + assert: + that: + - '[1,3,5,7,9]|shuffle|length == 5' + - '[1,3,5,7,9]|shuffle(seed=1337)|length == 5' + - '22|shuffle == 22' + +- name: Verify password_hash throws on weird salt_size type + set_fact: + foo: '{{"hey"|password_hash(salt_size=[999])}}' + ignore_errors: yes + register: password_hash_1 + +- name: Verify password_hash throws on weird hashtype + set_fact: + foo: '{{"hey"|password_hash(hashtype="supersecurehashtype")}}' + ignore_errors: yes + register: password_hash_2 + +- name: Verify password_hash + assert: + that: + - "'what in the WORLD is up?'|password_hash|length == 106" + # This throws a vastly different error on py2 vs py3, so we just check + # that it's a failure, not a substring of the exception. + - password_hash_1 is failed + - password_hash_2 is failed + - "'not support' in password_hash_2.msg" + +- name: Verify to_uuid throws on weird namespace + set_fact: + foo: '{{"hey"|to_uuid(namespace=22)}}' + ignore_errors: yes + register: to_uuid_1 + +- name: Verify to_uuid + assert: + that: + - '"monkeys"|to_uuid == "0d03a178-da0f-5b51-934e-cda9c76578c3"' + - to_uuid_1 is failed + - '"Invalid value" in to_uuid_1.msg' + +- name: Verify mandatory throws on undefined variable + set_fact: + foo: '{{hey|mandatory}}' + ignore_errors: yes + register: mandatory_1 + +- name: Verify mandatory throws on undefined variable with custom message + set_fact: + foo: '{{hey|mandatory("You did not give me a variable. I am a sad wolf.")}}' + ignore_errors: yes + register: mandatory_2 + +- name: Set a variable + set_fact: + mandatory_demo: 123 + +- name: Verify mandatory + assert: + that: + - '{{mandatory_demo|mandatory}} == 123' + - mandatory_1 is failed + - "mandatory_1.msg == \"Mandatory variable 'hey' not defined.\"" + - mandatory_2 is failed + - "mandatory_2.msg == 'You did not give me a variable. I am a sad wolf.'" + +- name: Verify comment + assert: + that: + - '"boo!"|comment == "#\n# boo!\n#"' + - '"boo!"|comment(decoration="-- ") == "--\n-- boo!\n--"' + - '"boo!"|comment(style="cblock") == "/*\n *\n * boo!\n *\n */"' + - '"boo!"|comment(decoration="") == "boo!\n"' + - '"boo!"|comment(prefix="\n", prefix_count=20) == "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n# boo!\n#"' + +- name: Verify subelements throws on invalid obj + set_fact: + foo: '{{True|subelements("foo")}}' + ignore_errors: yes + register: subelements_1 + +- name: Verify subelements throws on invalid subelements arg + set_fact: + foo: '{{{}|subelements(17)}}' + ignore_errors: yes + register: subelements_2 + +- name: Set demo data for subelements + set_fact: + subelements_demo: '{{ [{"name": "alice", "groups": ["wheel"], "authorized": ["/tmp/alice/onekey.pub"]}] }}' + +- name: Verify subelements throws on bad key + set_fact: + foo: '{{subelements_demo | subelements("does not compute")}}' + ignore_errors: yes + register: subelements_3 + +- name: Verify subelements throws on key pointing to bad value + set_fact: + foo: '{{subelements_demo | subelements("name")}}' + ignore_errors: yes + register: subelements_4 + +- name: Verify subelements throws on list of keys ultimately pointing to bad value + set_fact: + foo: '{{subelements_demo | subelements(["groups", "authorized"])}}' + ignore_errors: yes + register: subelements_5 + +- name: Verify subelements + assert: + that: + - subelements_1 is failed + - 'subelements_1.msg == "obj must be a list of dicts or a nested dict"' + - subelements_2 is failed + - 'subelements_2.msg == "subelements must be a list or a string"' + - 'subelements_demo|subelements("does not compute", skip_missing=True) == []' + - subelements_3 is failed + - '"could not find" in subelements_3.msg' + - subelements_4 is failed + - '"should point to a list" in subelements_4.msg' + - subelements_5 is failed + - '"should point to a dictionary" in subelements_5.msg' + - 'subelements_demo|subelements("groups") == [({"name": "alice", "groups": ["wheel"], "authorized": ["/tmp/alice/onekey.pub"]}, "wheel")]' + - 'subelements_demo|subelements(["groups"]) == [({"name": "alice", "groups": ["wheel"], "authorized": ["/tmp/alice/onekey.pub"]}, "wheel")]' + + +- name: Verify dict2items throws on non-Mapping + set_fact: + foo: '{{True|dict2items}}' + ignore_errors: yes + register: dict2items_fail + +- name: Verify dict2items + assert: + that: + - '{"foo": "bar", "banana": "fruit"}|dict2items == [{"key": "foo", "value": "bar"}, {"key": "banana", "value": "fruit"}]' + - dict2items_fail is failed + - '"dict2items requires a dictionary" in dict2items_fail.msg' + +- name: Verify items2dict throws on non-Mapping + set_fact: + foo: '{{True|items2dict}}' + ignore_errors: yes + register: items2dict_fail + +- name: Verify items2dict + assert: + that: + - '[{"key": "foo", "value": "bar"}, {"key": "banana", "value": "fruit"}]|items2dict == {"foo": "bar", "banana": "fruit"}' + - items2dict_fail is failed + - '"items2dict requires a list" in items2dict_fail.msg' + +- name: Verify path_join throws on non-string and non-sequence + set_fact: + foo: '{{True|path_join}}' + ignore_errors: yes + register: path_join_fail + +- name: Verify path_join + assert: + that: + - '"foo"|path_join == "foo"' + - '["foo", "bar"]|path_join in ["foo/bar", "foo\bar"]' + - path_join_fail is failed + - '"expects string or sequence" in path_join_fail.msg' + +- name: Verify type_debug + assert: + that: + - '"foo"|type_debug == "str"' diff --git a/test/integration/targets/filter_core/templates/foo.j2 b/test/integration/targets/filter_core/templates/foo.j2 index 9d895fdcedc..1e0c4a6a3e7 100644 --- a/test/integration/targets/filter_core/templates/foo.j2 +++ b/test/integration/targets/filter_core/templates/foo.j2 @@ -1,4 +1,4 @@ -This is a test of various filter plugins found in Ansible (ex: core.py), and +This is a test of various filter plugins found in Ansible (ex: core.py), and not so much a test of the core filters in Jinja2. Dumping the same structure to YAML