Case-insensitive set theory filters (#74256)
Fixes #74255 * Fix call to 'unique(case_sensitive=False)' triggering error when falling back to Ansible's version which **is** case-sensitive * Test multiple situations of 'unique' filter errors with fallback not handling specific parameters Signed-off-by: Rick Elrod <rick@elrod.me> Co-authored-by: Rick Elrod <rick@elrod.me>
This commit is contained in:
parent
e6a5245d60
commit
8698855ffd
6 changed files with 61 additions and 11 deletions
|
@ -0,0 +1,6 @@
|
||||||
|
breaking_changes:
|
||||||
|
- intersect, difference, symmetric_difference, union filters - the default behavior
|
||||||
|
is now to be case-sensitive (https://github.com/ansible/ansible/issues/74255)
|
||||||
|
- unique filter - the default behavior is now to fail if Jinja2's filter fails and
|
||||||
|
explicit ``case_sensitive=False`` as the Ansible's fallback is case-sensitive
|
||||||
|
(https://github.com/ansible/ansible/pull/74256)
|
|
@ -77,7 +77,8 @@ No notable changes
|
||||||
Plugins
|
Plugins
|
||||||
=======
|
=======
|
||||||
|
|
||||||
No notable changes
|
* ``unique`` filter with Jinja2 < 2.10 is case-sensitive and now raise coherently an error if ``case_sensitive=False`` instead of when ``case_sensitive=True``.
|
||||||
|
* Set theory filters (``intersect``, ``difference``, ``symmetric_difference`` and ``union``) are now case-sensitive. Explicitly use ``case_sensitive=False`` to keep previous behavior. Note: with Jinja2 < 2.10, the filters were already case-sensitive by default.
|
||||||
|
|
||||||
|
|
||||||
Porting custom scripts
|
Porting custom scripts
|
||||||
|
|
|
@ -52,17 +52,19 @@ display = Display()
|
||||||
|
|
||||||
|
|
||||||
@environmentfilter
|
@environmentfilter
|
||||||
def unique(environment, a, case_sensitive=False, attribute=None):
|
# Use case_sensitive=None as a sentinel value, so we raise an error only when
|
||||||
|
# explicitly set and cannot be handle (by Jinja2 w/o 'unique' or fallback version)
|
||||||
|
def unique(environment, a, case_sensitive=None, attribute=None):
|
||||||
|
|
||||||
def _do_fail(e):
|
def _do_fail(e):
|
||||||
if case_sensitive or attribute:
|
if case_sensitive is False or attribute:
|
||||||
raise AnsibleFilterError("Jinja2's unique filter failed and we cannot fall back to Ansible's version "
|
raise AnsibleFilterError("Jinja2's unique filter failed and we cannot fall back to Ansible's version "
|
||||||
"as it does not support the parameters supplied", orig_exc=e)
|
"as it does not support the parameters supplied", orig_exc=e)
|
||||||
|
|
||||||
error = e = None
|
error = e = None
|
||||||
try:
|
try:
|
||||||
if HAS_UNIQUE:
|
if HAS_UNIQUE:
|
||||||
c = list(do_unique(environment, a, case_sensitive=case_sensitive, attribute=attribute))
|
c = list(do_unique(environment, a, case_sensitive=bool(case_sensitive), attribute=attribute))
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
error = e
|
error = e
|
||||||
_do_fail(e)
|
_do_fail(e)
|
||||||
|
@ -74,8 +76,8 @@ def unique(environment, a, case_sensitive=False, attribute=None):
|
||||||
if not HAS_UNIQUE or error:
|
if not HAS_UNIQUE or error:
|
||||||
|
|
||||||
# handle Jinja2 specific attributes when using Ansible's version
|
# handle Jinja2 specific attributes when using Ansible's version
|
||||||
if case_sensitive or attribute:
|
if case_sensitive is False or attribute:
|
||||||
raise AnsibleFilterError("Ansible's unique filter does not support case_sensitive nor attribute parameters, "
|
raise AnsibleFilterError("Ansible's unique filter does not support case_sensitive=False nor attribute parameters, "
|
||||||
"you need a newer version of Jinja2 that provides their version of the filter.")
|
"you need a newer version of Jinja2 that provides their version of the filter.")
|
||||||
|
|
||||||
c = []
|
c = []
|
||||||
|
@ -91,7 +93,7 @@ def intersect(environment, a, b):
|
||||||
if isinstance(a, Hashable) and isinstance(b, Hashable):
|
if isinstance(a, Hashable) and isinstance(b, Hashable):
|
||||||
c = set(a) & set(b)
|
c = set(a) & set(b)
|
||||||
else:
|
else:
|
||||||
c = unique(environment, [x for x in a if x in b])
|
c = unique(environment, [x for x in a if x in b], True)
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,7 +102,7 @@ def difference(environment, a, b):
|
||||||
if isinstance(a, Hashable) and isinstance(b, Hashable):
|
if isinstance(a, Hashable) and isinstance(b, Hashable):
|
||||||
c = set(a) - set(b)
|
c = set(a) - set(b)
|
||||||
else:
|
else:
|
||||||
c = unique(environment, [x for x in a if x not in b])
|
c = unique(environment, [x for x in a if x not in b], True)
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,7 +121,7 @@ def union(environment, a, b):
|
||||||
if isinstance(a, Hashable) and isinstance(b, Hashable):
|
if isinstance(a, Hashable) and isinstance(b, Hashable):
|
||||||
c = set(a) | set(b)
|
c = set(a) | set(b)
|
||||||
else:
|
else:
|
||||||
c = unique(environment, a + b)
|
c = unique(environment, a + b, True)
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
|
17
test/integration/targets/filter_mathstuff/runme.sh
Executable file
17
test/integration/targets/filter_mathstuff/runme.sh
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
export ANSIBLE_ROLES_PATH=../
|
||||||
|
|
||||||
|
ansible-playbook runme.yml "$@"
|
||||||
|
|
||||||
|
source virtualenv.sh
|
||||||
|
|
||||||
|
# Install Jinja < 2.10 since we want to test the fallback to Ansible's custom
|
||||||
|
# unique filter. Jinja < 2.10 does not have do_unique so we will trigger the
|
||||||
|
# fallback.
|
||||||
|
pip install 'jinja2 < 2.10'
|
||||||
|
|
||||||
|
# Run the playbook again in the venv with Jinja < 2.10
|
||||||
|
ansible-playbook runme.yml "$@"
|
4
test/integration/targets/filter_mathstuff/runme.yml
Normal file
4
test/integration/targets/filter_mathstuff/runme.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
- hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
roles:
|
||||||
|
- { role: filter_mathstuff }
|
|
@ -1,6 +1,6 @@
|
||||||
- name: Verify unique's fallback's exception throwing for case_sensitive=True
|
- name: Verify unique's fallback's exception throwing for case_sensitive=False
|
||||||
set_fact:
|
set_fact:
|
||||||
unique_fallback_exc1: '{{ [{"foo": "bar", "moo": "cow"}]|unique(case_sensitive=True) }}'
|
unique_fallback_exc1: '{{ [{"foo": "bar", "moo": "cow"}]|unique(case_sensitive=False) }}'
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
tags: unique
|
tags: unique
|
||||||
register: unique_fallback_exc1_res
|
register: unique_fallback_exc1_res
|
||||||
|
@ -67,6 +67,11 @@
|
||||||
- '[1,2,3]|intersect([3,2,1]) == [1,2,3]'
|
- '[1,2,3]|intersect([3,2,1]) == [1,2,3]'
|
||||||
- '(1,2,3)|intersect((4,5,6))|list == []'
|
- '(1,2,3)|intersect((4,5,6))|list == []'
|
||||||
- '(1,2,3)|intersect((3,4,5,6))|list == [3]'
|
- '(1,2,3)|intersect((3,4,5,6))|list == [3]'
|
||||||
|
- '["a","A","b"]|intersect(["B","c","C"]) == []'
|
||||||
|
- '["a","A","b"]|intersect(["b","B","c","C"]) == ["b"]'
|
||||||
|
- '["a","A","b"]|intersect(["b","A","a"]) == ["a","A","b"]'
|
||||||
|
- '("a","A","b")|intersect(("B","c","C"))|list == []'
|
||||||
|
- '("a","A","b")|intersect(("b","B","c","C"))|list == ["b"]'
|
||||||
|
|
||||||
- name: Verify difference
|
- name: Verify difference
|
||||||
tags: difference
|
tags: difference
|
||||||
|
@ -77,6 +82,11 @@
|
||||||
- '[1,2,3]|difference([3,2,1]) == []'
|
- '[1,2,3]|difference([3,2,1]) == []'
|
||||||
- '(1,2,3)|difference((4,5,6))|list == [1,2,3]'
|
- '(1,2,3)|difference((4,5,6))|list == [1,2,3]'
|
||||||
- '(1,2,3)|difference((3,4,5,6))|list == [1,2]'
|
- '(1,2,3)|difference((3,4,5,6))|list == [1,2]'
|
||||||
|
- '["a","A","b"]|difference(["B","c","C"]) == ["a","A","b"]'
|
||||||
|
- '["a","A","b"]|difference(["b","B","c","C"]) == ["a","A"]'
|
||||||
|
- '["a","A","b"]|difference(["b","A","a"]) == []'
|
||||||
|
- '("a","A","b")|difference(("B","c","C"))|list|sort(case_sensitive=True) == ["A","a","b"]'
|
||||||
|
- '("a","A","b")|difference(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","a"]'
|
||||||
|
|
||||||
- name: Verify symmetric_difference
|
- name: Verify symmetric_difference
|
||||||
tags: symmetric_difference
|
tags: symmetric_difference
|
||||||
|
@ -87,6 +97,11 @@
|
||||||
- '[1,2,3]|symmetric_difference([3,2,1]) == []'
|
- '[1,2,3]|symmetric_difference([3,2,1]) == []'
|
||||||
- '(1,2,3)|symmetric_difference((4,5,6))|list == [1,2,3,4,5,6]'
|
- '(1,2,3)|symmetric_difference((4,5,6))|list == [1,2,3,4,5,6]'
|
||||||
- '(1,2,3)|symmetric_difference((3,4,5,6))|list == [1,2,4,5,6]'
|
- '(1,2,3)|symmetric_difference((3,4,5,6))|list == [1,2,4,5,6]'
|
||||||
|
- '["a","A","b"]|symmetric_difference(["B","c","C"]) == ["a","A","b","B","c","C"]'
|
||||||
|
- '["a","A","b"]|symmetric_difference(["b","B","c","C"]) == ["a","A","B","c","C"]'
|
||||||
|
- '["a","A","b"]|symmetric_difference(["b","A","a"]) == []'
|
||||||
|
- '("a","A","b")|symmetric_difference(("B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
|
||||||
|
- '("a","A","b")|symmetric_difference(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","c"]'
|
||||||
|
|
||||||
- name: Verify union
|
- name: Verify union
|
||||||
tags: union
|
tags: union
|
||||||
|
@ -97,6 +112,11 @@
|
||||||
- '[1,2,3]|union([3,2,1]) == [1,2,3]'
|
- '[1,2,3]|union([3,2,1]) == [1,2,3]'
|
||||||
- '(1,2,3)|union((4,5,6))|list == [1,2,3,4,5,6]'
|
- '(1,2,3)|union((4,5,6))|list == [1,2,3,4,5,6]'
|
||||||
- '(1,2,3)|union((3,4,5,6))|list == [1,2,3,4,5,6]'
|
- '(1,2,3)|union((3,4,5,6))|list == [1,2,3,4,5,6]'
|
||||||
|
- '["a","A","b"]|union(["B","c","C"]) == ["a","A","b","B","c","C"]'
|
||||||
|
- '["a","A","b"]|union(["b","B","c","C"]) == ["a","A","b","B","c","C"]'
|
||||||
|
- '["a","A","b"]|union(["b","A","a"]) == ["a","A","b"]'
|
||||||
|
- '("a","A","b")|union(("B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
|
||||||
|
- '("a","A","b")|union(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
|
||||||
|
|
||||||
- name: Verify min
|
- name: Verify min
|
||||||
tags: min
|
tags: min
|
||||||
|
|
Loading…
Reference in a new issue