Add options to filter lists_mergeby (#4058)

* Update filter lists_mergeby #4057

* Added options 'recursive' and 'list_merge'. The functionality of the
  added options is the same as in the filter 'combine'.
* Allow the user to do [list1, list2, ...]|lists_mergeby('index')
* Use the function merge_hash from ansible.utils.vars

* Add merge_hash_wrapper to test Ansible version

* Enable Ansible 2.9 and lower versions with default options of
  lists_mergeby only.
* Non-default options of lists_mergeby trigger error in 2.9 and lower
  versions.
* Update messages and tests.

* Fix tests.

* Use LooseVersion instead of SpecifierSet.

* Update docs 'Filter Guide' section 'Merging lists of dictionaries'.

* Added changelog fragment.

* Update changelogs/fragments/4058-lists_mergeby-add-parameters.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Added examples; moved to rst/examples; fixes.

* Improve error message testing sequence.

* Removed .yamllint

* Update docs/docsite/rst/examples/lists_mergeby/example-003.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/example-004.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/example-005.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/example-006.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/example-007.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update tests/integration/targets/filter_list/tasks/lists_mergeby_default.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/example-008.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Fix docs. Antsibull only copies .rst files.

* Fix examples in-line.

* Update docs/docsite/rst/filter_guide.rst

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/examples.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/examples.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/examples.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/examples.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/examples.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs/docsite/rst/examples/lists_mergeby/examples.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update docs lists_mergeby. Remove rubbish.

* Emphasized labes of examples in filter_guide.rst
* Removed temporary file examples/lists_mergeby/examples.rst
* Removed tests/integration/targets/filter_list/runme.*

* Fix docs. Description of the lists_merge options.

* Move helper files out of rst/ directory.

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Vladimir Botka 2022-01-28 08:19:19 +01:00 committed by GitHub
commit 71fb3984db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1426 additions and 123 deletions

View file

@ -1,64 +0,0 @@
---
- name: Test lists merged by attribute name
assert:
that:
- "(list1 | community.general.lists_mergeby(list2, 'name') | list |
difference(list3) | length) == 0"
- name: Test list1 empty
assert:
that:
- "([] | community.general.lists_mergeby(list2, 'name') | list |
difference(list2) | length) == 0"
- name: Test list2 empty
assert:
that:
- "(list1 | community.general.lists_mergeby([], 'name') | list |
difference(list1) | length) == 0"
- name: Test all lists empty
assert:
that:
- "([] | community.general.lists_mergeby([], 'name') | list |
difference(list1) | length) == 0"
- name: First argument must be list
set_fact:
my_list: "{{ {'x': 'y'} | community.general.lists_mergeby(list2, 'name') }}"
register: result
ignore_errors: true
- assert:
that:
- result is failed
- '"First argument for community.general.lists_mergeby must be list." in result.msg'
- name: Second argument must be list
set_fact:
my_list: "{{ list1 | community.general.lists_mergeby({'x': 'y'}, 'name') }}"
register: result
ignore_errors: true
- assert:
that:
- result is failed
- '"Second argument for community.general.lists_mergeby must be list." in result.msg'
- name: Third argument must be string
set_fact:
my_list: "{{ list1 | community.general.lists_mergeby(list2, {'x': 'y'}) }}"
register: result
ignore_errors: true
- assert:
that:
- result is failed
- '"Third argument for community.general.lists_mergeby must be string." in result.msg'
- name: Elements of list must be dictionaries
set_fact:
my_list: "{{ list4 | community.general.lists_mergeby(list2, 'name') }}"
register: result
ignore_errors: true
- assert:
that:
- result is failed
- '"Elements of list arguments for lists_mergeby must be dictionaries." in result.msg'

View file

@ -0,0 +1,140 @@
---
- name: 101.Merge 2 lists by attribute name. list_merge='keep'
block:
- name: Merge 2 lists by attribute name. list_merge='keep'. set
set_fact:
my_list: "{{ [list100, list101]|
community.general.lists_mergeby('name', list_merge='keep') }}"
- name: Merge 2 lists by attribute name. list_merge='keep'. debug
debug:
msg: |-
my_list:
{{ my_list|to_nice_yaml|indent(2) }}
my_list|difference(result101):
{{ my_list|difference(result101)|to_nice_yaml|indent(2) }}
when: debug_test|d(false)|bool
- name: Merge 2 lists by attribute name. list_merge='keep'. assert
assert:
that: my_list | difference(result101) | length == 0
tags: t101
- name: 102.Merge 2 lists by attribute name. list_merge='append'
block:
- name: Merge 2 lists by attribute name. list_merge='append'. set
set_fact:
my_list: "{{ [list100, list101]|
community.general.lists_mergeby('name', list_merge='append') }}"
- name: Merge 2 lists by attribute name. list_merge='append'. debug
debug:
msg: |-
my_list:
{{ my_list|to_nice_yaml|indent(2) }}
my_list|difference(result102):
{{ my_list|difference(result102)|to_nice_yaml|indent(2) }}
when: debug_test|d(false)|bool
- name: Merge 2 lists by attribute name. list_merge='append'. assert
assert:
that: my_list | difference(result102) | length == 0
tags: t102
- name: 103.Merge 2 lists by attribute name. list_merge='prepend'
block:
- name: Merge 2 lists by attribute name. list_merge='prepend'. set
set_fact:
my_list: "{{ [list100, list101]|
community.general.lists_mergeby('name', list_merge='prepend') }}"
- name: Merge 2 lists by attribute name. list_merge='prepend'. debug
debug:
msg: |-
my_list:
{{ my_list|to_nice_yaml|indent(2) }}
my_list|difference(result103):
{{ my_list|difference(result103)|to_nice_yaml|indent(2) }}
when: debug_test|d(false)|bool
- name: Merge 2 lists by attribute name. list_merge='prepend'. assert
assert:
that: my_list | difference(result103) | length == 0
tags: t103
- name: 104.Merge 2 lists by attribute name. list_merge='append_rp'
block:
- name: Merge 2 lists by attribute name. list_merge='append_rp'. set
set_fact:
my_list: "{{ [list102, list103]|
community.general.lists_mergeby('name', list_merge='append_rp') }}"
- name: Merge 2 lists by attribute name. list_merge='append_rp'. debug
debug:
msg: |-
my_list:
{{ my_list|to_nice_yaml|indent(2) }}
my_list|difference(result104):
{{ my_list|difference(result104)|to_nice_yaml|indent(2) }}
when: debug_test|d(false)|bool
- name: Merge 2 lists by attribute name. list_merge='append_rp'. assert
assert:
that: my_list | difference(result104) | length == 0
tags: t104
- name: 105.Merge 2 lists by attribute name. list_merge='prepend_rp'
block:
- name: Merge 2 lists by attribute name. list_merge='prepend_rp'. set
set_fact:
my_list: "{{ [list102, list103]|
community.general.lists_mergeby('name', list_merge='prepend_rp') }}"
- name: Merge 2 lists by attribute name. list_merge='prepend_rp'. debug
debug:
msg: |-
my_list:
{{ my_list|to_nice_yaml|indent(2) }}
my_list|difference(result105):
{{ my_list|difference(result105)|to_nice_yaml|indent(2) }}
when: debug_test|d(false)|bool
- name: Merge 2 lists by attribute name. list_merge='prepend_rp'. assert
assert:
that: my_list | difference(result105) | length == 0
tags: t105
# Test recursive
- name: 200.Merge by name. recursive=True list_merge='append_rp'
block:
- name: Merge by name. recursive=True list_merge='append_rp'. set
set_fact:
my_list: "{{ [list200, list201]|
community.general.lists_mergeby('name',
recursive=True,
list_merge='append_rp') }}"
- name: Merge by name. recursive=True list_merge='append_rp'. debug
debug:
msg: |-
my_list:
{{ my_list|to_nice_yaml|indent(2) }}
my_list|difference(result200):
{{ my_list|difference(result200)|to_nice_yaml|indent(2) }}
when: debug_test|d(false)|bool
- name: Merge by name. recursive=True list_merge='append_rp'. assert
assert:
that: my_list | difference(result200) | length == 0
tags: t200
- name: 201.Merge by name. recursive=False list_merge='append_rp'
block:
- name: Merge by name. recursive=False list_merge='append_rp'. set
set_fact:
my_list: "{{ [list200, list201]|
community.general.lists_mergeby('name',
recursive=False,
list_merge='append_rp') }}"
- name: Merge by name. recursive=False list_merge='append_rp'. debug
debug:
msg: |-
my_list:
{{ my_list|to_nice_yaml|indent(2) }}
my_list|difference(result201):
{{ my_list|difference(result201)|to_nice_yaml|indent(2) }}
when: debug_test|d(false)|bool
- name: Merge by name. recursive=False list_merge='append_rp'. assert
assert:
that: my_list | difference(result201) | length == 0
tags: t201

View file

@ -0,0 +1,166 @@
---
- name: Debug ansible_version
debug:
var: ansible_version
when: debug_test|d(false)|bool
tags: t0
- name: 1. Test lists merged by attribute name
block:
- name: Test lists merged by attribute name debug
debug:
msg: "{{ list1 | community.general.lists_mergeby(list2, 'name') }}"
when: debug_test|d(false)|bool
- name: Test lists merged by attribute name assert
assert:
that:
- "(list1 | community.general.lists_mergeby(list2, 'name') | list |
difference(list3) | length) == 0"
tags: t1
- name: 2.Test list1 empty
block:
- name: Test list1 empty debug
debug:
msg: "{{ [] | community.general.lists_mergeby(list2, 'name') }}"
when: debug_test|d(false)|bool
- name: Test list1 empty assert
assert:
that:
- "([] | community.general.lists_mergeby(list2, 'name') | list |
difference(list2) | length) == 0"
tags: t2
- name: 3.Test all lists empty
block:
- name: Test all lists empty debug
debug:
msg: "{{ [] | community.general.lists_mergeby([], 'name') }}"
when: debug_test|d(false)|bool
- name: Test all lists empty assert
assert:
that:
- "([] | community.general.lists_mergeby([], 'name') | list |
length) == 0"
tags: t3
- name: 4.First argument must be list
block:
- name: First argument must be list set
set_fact:
my_list: "{{ {'x': 'y'} | community.general.lists_mergeby(list2, 'name') }}"
register: result
ignore_errors: true
- name: First argument must be list debug
debug:
var: my_list
when: debug_test|d(false)|bool
- name: First argument must be list assert
assert:
that:
- result is failed
- '"All arguments before the argument index for community.general.lists_mergeby must be lists." in result.msg'
tags: t4
- name: 5.Second argument must be list
block:
- name: Second argument must be list set
set_fact:
my_list: "{{ list1 | community.general.lists_mergeby({'x': 'y'}, 'name') }}"
register: result
ignore_errors: true
- name: Second argument must be list set debug
debug:
var: my_list
when: debug_test|d(false)|bool
- name: Second argument must be list set assert
assert:
that:
- result is failed
- '"All arguments before the argument index for community.general.lists_mergeby must be lists." in result.msg'
tags: t5
- name: 6.First arguments after the lists must be string
block:
- name: First arguments after the lists must be string set
set_fact:
my_list: "{{ list1 | community.general.lists_mergeby(list2, {'x': 'y'}) }}"
register: result
ignore_errors: true
- name: First arguments after the lists must be string debug
debug:
var: my_list
when: debug_test|d(false)|bool
- name: First arguments after the lists must be string assert
assert:
that:
- result is failed
- '"First argument after the lists for community.general.lists_mergeby must be string." in result.msg'
tags: t6
- name: 7.Elements of list must be dictionaries
block:
- name: Elements of list must be dictionaries set
set_fact:
my_list: "{{ list4 | community.general.lists_mergeby(list2, 'name') }}"
register: result
ignore_errors: true
- name: Elements of list must be dictionaries debug
debug:
var: my_list
when: debug_test|d(false)|bool
- name: Elements of list must be dictionaries assert
assert:
that:
- result is failed
- '"Elements of list arguments for lists_mergeby must be dictionaries." in result.msg'
tags: t7
- name: 8.Merge 3 lists by attribute name. 1 list in params.
block:
- name: Merge 3 lists by attribute name. 1 list in params. set
set_fact:
my_list: "{{ [list1, list2] | community.general.lists_mergeby(list5, 'name') }}"
- name: Merge 3 lists by attribute name. 1 list in params. debug
debug:
var: my_list
when: debug_test|d(false)|bool
- name: Merge 3 lists by attribute name. 1 list in params. assert
assert:
that: my_list | difference(result1) | length == 0
tags: t8
- name: 9.Merge 3 lists by attribute name. No list in the params.
block:
- name: Merge 3 lists by attribute name. No list in the params. set
set_fact:
my_list: "{{ [list1, list2, list5] | community.general.lists_mergeby('name') }}"
- name: Merge 3 lists by attribute name. No list in the params. debug
debug:
var: my_list
when: debug_test|d(false)|bool
- name: Merge 3 lists by attribute name. No list in the params. asset
assert:
that: my_list | difference(result1) | length == 0
tags: t9
# Test list_merge default options
- name: 100.Merge 2 lists by attribute name. list_merge='replace'
block:
- name: Merge 2 lists by attribute name. list_merge='replace'. set
set_fact:
my_list: "{{ [list100, list101] | community.general.lists_mergeby('name') }}"
- name: Merge 2 lists by attribute name. list_merge='replace'. debug
debug:
msg: |-
my_list:
{{ my_list|to_nice_yaml|indent(2) }}
my_list|difference(result100):
{{ my_list|difference(result100)|to_nice_yaml|indent(2) }}
when: debug_test|d(false)|bool
- name: Merge 2 lists by attribute name. list_merge='replace'. assert
assert:
that: my_list | difference(result100) | length == 0
tags: t100

View file

@ -1,2 +1,7 @@
---
- include_tasks: lists_mergeby.yml
- name: Test list_merge default options
import_tasks: lists_mergeby_default.yml
- name: Test list_merge non-default options in Ansible 2.10 and higher
import_tasks: lists_mergeby_2-10.yml
when: ansible_version.full is version('2.10', '>=')

View file

@ -1,3 +1,4 @@
---
list1:
- name: myname01
param01: myparam01
@ -25,3 +26,180 @@ list4:
- name: myname01
param01: myparam01
- myname02
list5:
- name: myname01
param01: myparam05
- name: myname02
param01: myparam06
result1:
- name: myname01
param01: myparam05
- name: myname02
param01: myparam06
param02: myparam04
- name: myname03
param03: myparam03
# Test list_merge
list100:
- name: myname01
param01:
- default1
- name: myname02
param01:
- default2
list101:
- name: myname01
param01:
- patch1
- name: myname02
param01:
- patch2
list102:
- name: myname01
param01:
- patch1a
- patch1b
- patch1c
- name: myname02
param01:
- patch2a
- patch2b
- patch2d
list103:
- name: myname01
param01:
- patch1c
- patch1d
- name: myname02
param01:
- patch2c
- patch2d
result100:
- name: myname01
param01:
- patch1
- name: myname02
param01:
- patch2
result101:
- name: myname01
param01:
- default1
- name: myname02
param01:
- default2
result102:
- name: myname01
param01:
- default1
- patch1
- name: myname02
param01:
- default2
- patch2
result103:
- name: myname01
param01:
- patch1
- default1
- name: myname02
param01:
- patch2
- default2
result104:
- name: myname01
param01:
- patch1a
- patch1b
- patch1c
- patch1d
- name: myname02
param01:
- patch2a
- patch2b
- patch2c
- patch2d
result105:
- name: myname01
param01:
- patch1c
- patch1d
- patch1a
- patch1b
- name: myname02
param01:
- patch2c
- patch2d
- patch2a
- patch2b
# Test recursive
list200:
- name: myname01
param01:
x: default_value
y: default_value
list:
- default_value
- name: myname02
param01: [1, 1, 2, 3]
list201:
- name: myname01
param01:
y: patch_value
z: patch_value
list:
- patch_value
- name: myname02
param01: [3, 4, 4, {key: value}]
result200:
- name: myname01
param01:
list:
- default_value
- patch_value
x: default_value
y: patch_value
z: patch_value
- name: myname02
param01:
- 1
- 1
- 2
- 3
- 4
- 4
- key: value
result201:
- name: myname01
param01:
list:
- patch_value
y: patch_value
z: patch_value
- name: myname02
param01:
- 1
- 1
- 2
- 3
- 4
- 4
- key: value