diff --git a/docs/docsite/rst/user_guide/playbooks_filters.rst b/docs/docsite/rst/user_guide/playbooks_filters.rst index eb40dcebf9425f..8536af728cc780 100644 --- a/docs/docsite/rst/user_guide/playbooks_filters.rst +++ b/docs/docsite/rst/user_guide/playbooks_filters.rst @@ -184,6 +184,65 @@ into:: - key: Environment value: dev +subelements Filter +`````````````````` + +.. versionadded:: 2.7 + +Produces a product of an object, and subelement values of that object, similar to the ``subelements`` lookup:: + + {{ users|subelements('groups', skip_missing=True) }} + +Which turns:: + + users: + - name: alice + authorized: + - /tmp/alice/onekey.pub + - /tmp/alice/twokey.pub + groups: + - wheel + - docker + - name: bob + authorized: + - /tmp/bob/id_rsa.pub + groups: + - docker + +Into:: + + - + - name: alice + groups: + - wheel + - docker + authorized: + - /tmp/alice/onekey.pub + - wheel + - + - name: alice + groups: + - wheel + - docker + authorized: + - /tmp/alice/onekey.pub + - docker + - + - name: bob + authorized: + - /tmp/bob/id_rsa.pub + groups: + - docker + - docker + +An example of using this filter with ``loop``:: + + - name: Set authorized ssh key, extracting just that data from 'users' + authorized_key: + user: "{{ item.0.name }}" + key: "{{ lookup('file', item.1) }}" + loop: "{{ users|subelements('authorized') }}" + .. _random_filter: Random Number Filter diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index f9d1f9f8a9d674..f6f6e28cde443a 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -473,6 +473,52 @@ def flatten(mylist, levels=None): return ret +def subelements(obj, subelements, skip_missing=False): + '''Accepts a dict or list of dicts, and a dotted accessor and produces a product + of the element and the results of the dotted accessor + + >>> obj = [{"name": "alice", "groups": ["wheel"], "authorized": ["/tmp/alice/onekey.pub"]}] + >>> subelements(obj, 'groups') + [({'name': 'alice', 'groups': ['wheel'], 'authorized': ['/tmp/alice/onekey.pub']}, 'wheel')] + + ''' + if isinstance(obj, dict): + element_list = list(obj.values()) + elif isinstance(obj, list): + element_list = obj[:] + else: + raise AnsibleFilterError('obj must be a list of dicts or a nested dict') + + if isinstance(subelements, list): + subelement_list = subelements[:] + elif isinstance(subelements, string_types): + subelement_list = subelements.split('.') + else: + raise AnsibleFilterError('subelements must be a list or a string') + + results = [] + + for element in element_list: + values = element + for subelement in subelement_list: + try: + values = values[subelement] + except KeyError: + if skip_missing: + values = [] + break + raise AnsibleFilterError("could not find %r key in iterated item %r" % (subelement, values)) + except TypeError: + raise AnsibleFilterError("the key %s should point to a dictionary, got '%s'" % (subelement, values)) + if not isinstance(values, list): + raise AnsibleFilterError("the key %r should point to a list, got %r" % (subelement, values)) + + for value in values: + results.append((element, value)) + + return results + + def dict_to_list_of_dict_key_value_elements(mydict): ''' takes a dictionary and transforms it into a list of dictionaries, with each having a 'key' and 'value' keys that correspond to the keys and values of the original ''' @@ -574,4 +620,5 @@ def filters(self): 'extract': extract, 'flatten': flatten, 'dict2items': dict_to_list_of_dict_key_value_elements, + 'subelements': subelements, }