From 80a1262252db951e40749d37f5348aa8907039c6 Mon Sep 17 00:00:00 2001 From: Nolan Brubaker Date: Thu, 6 Jul 2017 17:09:14 -0400 Subject: [PATCH] Add a get_nested filter When accessing nested facts, such as in ansible_local sections, guard statements in the when clause can get quite long, due to having to repeate the 'in/not in' logic for every key at every level. The get_nested filter attempts to instead to move the guard into a single line, making the conditions easier to write and maintain. As example, ('openstack_ansible' not in ansible_local or 'swift' not in ansible_local['openstack_ansible'] or 'venv_tag' not in ansible_local['openstack_ansible']['swift'] or ansible_local['openstack_ansible']['swift']['venv_tag'] == swift_venv_tag) could be rewritten as get_nested(ansible_local, 'openstack_ansible.swift.venv_tag') == swift_venv_tag Change-Id: I3b43c25c8783c43cf5285f2b3e7267b2c5712ea0 --- filter/osa-filters.py | 31 ++++++++++++++++++- .../get_nested_filter-b89828586d7e2520.yaml | 7 +++++ tests/test-filters.yml | 15 +++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/get_nested_filter-b89828586d7e2520.yaml diff --git a/filter/osa-filters.py b/filter/osa-filters.py index d624a52..1071a9c 100644 --- a/filter/osa-filters.py +++ b/filter/osa-filters.py @@ -317,6 +317,34 @@ def git_link_parse_name(repo): return git_link_parse(repo)['name'] +def get_nested(target_dict, keys): + """Retrieves values through a nested dictionary. + + If any key on the path is missing, return None + + This helps solves convoluted guards in roles/plays such as the following: + + ('openstack_ansible' not in ansible_local or + 'swift' not in ansible_local['openstack_ansible'] or + 'venv_tag' not in ansible_local['openstack_ansible']['swift'] or + ansible_local['openstack_ansible']['swift']['venv_tag'] == swift_venv_tag) + + With this filter, it could be instead written: + ansible_local|get_nested('openstack_ansible.swift.venv_tag') == swift_venv_tag + + """ + + try: + key, next_keys = keys.split('.', 1) + except ValueError: + return target_dict.get(keys, None) + + try: + next_dict = target_dict[key] + except KeyError: + return None + return get_nested(next_dict, next_keys) + class FilterModule(object): """Ansible jinja2 filters.""" @@ -335,5 +363,6 @@ class FilterModule(object): 'filtered_list': filtered_list, 'git_link_parse': git_link_parse, 'git_link_parse_name': git_link_parse_name, - 'deprecated': _deprecated + 'deprecated': _deprecated, + 'get_nested': get_nested } diff --git a/releasenotes/notes/get_nested_filter-b89828586d7e2520.yaml b/releasenotes/notes/get_nested_filter-b89828586d7e2520.yaml new file mode 100644 index 0000000..e4a6a46 --- /dev/null +++ b/releasenotes/notes/get_nested_filter-b89828586d7e2520.yaml @@ -0,0 +1,7 @@ +--- +features: + - The `get_nested` filter has been added, allowing for simplified + value lookups inside of nested dictionaries. + + `ansible_local|get_nested('openstack_ansible.swift')`, for example, + will look 2 levels down and return the result. diff --git a/tests/test-filters.yml b/tests/test-filters.yml index 5ef9bf3..639ef9d 100644 --- a/tests/test-filters.yml +++ b/tests/test-filters.yml @@ -120,3 +120,18 @@ - name: Validate deprecated filter assert: that: "deprecated_value == old_var" + + - name: Set test_dict fact + set_fact: + test_dict: + a: + b: + c: d + + - name: Validate get_nested returns value + assert: + that: "{{ test_dict|get_nested('a.b.c') == 'd' }}" + + - name: Validate get_nested returns None on missing key + assert: + that: "{{ test_dict|get_nested('a.c') == None }}"