From 3c42f13b30e364557784603964931df494a36f4b Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Wed, 21 Oct 2020 10:45:34 -0700 Subject: [PATCH] Split filter docs (#17) * Dedicated DOCUMENTATION for get_path * Comment out cross collection example * Remove sanity exception, unneeded * Remove extra empty lines * Dedicated DOCUMENTATION for index_of filter plugin * Dedicated DOCUMENTATION for to_paths filter plugin * Add rst docs for filter plugins, update README * lint and pep8 fixes * filter plugin doc update for wantlist Co-authored-by: cidrblock --- README.md | 6 +- docs/ansible.utils.get_path_filter.rst | 225 ++++++++++ docs/ansible.utils.get_path_lookup.rst | 45 +- docs/ansible.utils.index_of_filter.rst | 415 ++++++++++++++++++ docs/ansible.utils.index_of_lookup.rst | 211 ++++----- docs/ansible.utils.to_paths_filter.rst | 194 ++++++++ docs/ansible.utils.to_paths_lookup.rst | 35 +- plugins/filter/get_path.py | 141 +++++- plugins/filter/index_of.py | 287 +++++++++++- plugins/filter/to_paths.py | 109 ++++- plugins/lookup/get_path.py | 45 +- plugins/lookup/index_of.py | 214 ++++----- plugins/lookup/to_paths.py | 36 +- .../tasks/include/examples_filter.yaml | 101 +++++ .../tasks/include/examples_lookup.yaml | 101 +++++ .../{examples.yaml => examples_filter.yaml} | 106 ++--- .../tasks/include/examples_lookup.yaml | 243 ++++++++++ .../tasks/include/examples_filter.yaml | 70 +++ .../tasks/include/examples_lookup.yaml | 70 +++ tests/sanity/ignore-2.10.txt | 3 - tests/sanity/ignore-2.11.txt | 3 - tests/sanity/ignore-2.9.txt | 3 - 22 files changed, 2232 insertions(+), 431 deletions(-) create mode 100644 docs/ansible.utils.get_path_filter.rst create mode 100644 docs/ansible.utils.index_of_filter.rst create mode 100644 docs/ansible.utils.to_paths_filter.rst create mode 100644 tests/integration/targets/get_path/tasks/include/examples_filter.yaml create mode 100644 tests/integration/targets/get_path/tasks/include/examples_lookup.yaml rename tests/integration/targets/index_of/tasks/include/{examples.yaml => examples_filter.yaml} (64%) create mode 100644 tests/integration/targets/index_of/tasks/include/examples_lookup.yaml create mode 100644 tests/integration/targets/to_paths/tasks/include/examples_filter.yaml create mode 100644 tests/integration/targets/to_paths/tasks/include/examples_lookup.yaml diff --git a/README.md b/README.md index 9d90049..8d8df0c 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ PEP440 is the schema used to describe the versions of Ansible. ### Filter plugins Name | Description --- | --- -ansible.utils.get_path|Retrieve the value in a variable using a path. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.get_path_lookup.rst) -ansible.utils.index_of|Find the indicies of items in a list matching some criteria. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.index_of_lookup.rst) -ansible.utils.to_paths|Flatten a complex object into a dictionary of paths and values. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_paths_lookup.rst) +[ansible.utils.get_path](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.get_path_filter.rst)|Retrieve the value in a variable using a path +[ansible.utils.index_of](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.index_of_filter.rst)|Find the indicies of items in a list matching some criteria +[ansible.utils.to_paths](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_paths_filter.rst)|Flatten a complex object into a dictionary of paths and values ### Lookup plugins Name | Description diff --git a/docs/ansible.utils.get_path_filter.rst b/docs/ansible.utils.get_path_filter.rst new file mode 100644 index 0000000..02d5066 --- /dev/null +++ b/docs/ansible.utils.get_path_filter.rst @@ -0,0 +1,225 @@ +.. _ansible.utils.get_path_filter: + + +********************** +ansible.utils.get_path +********************** + +**Retrieve the value in a variable using a path** + + +Version added: 1.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Use a ``path`` to retreive a nested value from a ``var`` +- ``get_path`` is also available as a ``lookup plugin`` for convenience + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ path + +
+ string + / required +
+
+ + +
The path in the var to retrieve the value of.
+
The path needs to a be a valid jinja path
+
+
+ var + +
+ raw + / required +
+
+ + +
The variable from which the value should be extraced
+
This option represents the value that is passed to filter plugin in pipe format.
+
For example config_data|ansible.utils.get_path(), in this case config_data represents this option.
+
+
+ wantlist + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+ +
If set to True, the return value will always be a list
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + - ansible.builtin.set_fact: + a: + b: + c: + d: + - 0 + - 1 + e: + - True + - False + + - name: Retrieve a value deep inside a using a path + ansible.builtin.set_fact: + value: "{{ a|ansible.utils.get_path(path) }}" + vars: + path: b.c.d[0] + + # TASK [Retrieve a value deep inside a using a path] ****************** + # ok: [localhost] => changed=false + # ansible_facts: + # value: '0' + + + #### Working with hostvars + + - name: Retrieve a value deep inside all of the host's vars + ansible.builtin.set_fact: + value: "{{ look_in|ansible.utils.get_path(look_for) }}" + vars: + look_in: "{{ hostvars[inventory_hostname] }}" + look_for: a.b.c.d[0] + + # TASK [Retrieve a value deep inside all of the host's vars] ******** + # ok: [nxos101] => changed=false + # ansible_facts: + # as_filter: '0' + # as_lookup: '0' + + + #### Used alongside ansible.utils.to_paths + + - name: Get the paths for the object + ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths(prepend='a') }}" + + - name: Retrieve the value of each path from vars + ansible.builtin.debug: + msg: "The value of path {{ path }} in vars is {{ value }}" + loop: "{{ paths.keys()|list }}" + loop_control: + label: "{{ item }}" + vars: + path: "{{ item }}" + value: "{{ vars|ansible.utils.get_path(item) }}" + + # TASK [Get the paths for the object] ******************************* + # ok: [nxos101] => changed=false + # ansible_facts: + # paths: + # a.b.c.d[0]: 0 + # a.b.c.d[1]: 1 + # a.b.c.e[0]: true + # a.b.c.e[1]: false + + # TASK [Retrieve the value of each path from vars] ****************** + # ok: [nxos101] => (item=a.b.c.d[0]) => + # msg: The value of path a.b.c.d[0] in vars is 0 + # ok: [nxos101] => (item=a.b.c.d[1]) => + # msg: The value of path a.b.c.d[1] in vars is 1 + # ok: [nxos101] => (item=a.b.c.e[0]) => + # msg: The value of path a.b.c.e[0] in vars is True + # ok: [nxos101] => (item=a.b.c.e[1]) => + # msg: The value of path a.b.c.e[1] in vars is False + + + #### Working with complex structures and transforming results + + - name: Retrieve the current interface config + cisco.nxos.nxos_interfaces: + state: gathered + register: interfaces + + - name: Get the description of several interfaces + ansible.builtin.debug: + msg: "{{ rekeyed|ansible.utils.get_path(item) }}" + vars: + rekeyed: + by_name: "{{ interfaces.gathered|ansible.builtin.rekey_on_member('name') }}" + loop: + - by_name['Ethernet1/1'].description + - by_name['Ethernet1/2'].description|upper + - by_name['Ethernet1/3'].description|default('') + + + # TASK [Get the description of several interfaces] ****************** + # ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false + # msg: Configured by ansible + # ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false + # msg: CONFIGURED BY ANSIBLE + # ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false + # msg: '' + + + + +Status +------ + + +Authors +~~~~~~~ + +- Bradley Thornton (@cidrblock) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/docs/ansible.utils.get_path_lookup.rst b/docs/ansible.utils.get_path_lookup.rst index ddd76f3..dee4f6b 100644 --- a/docs/ansible.utils.get_path_lookup.rst +++ b/docs/ansible.utils.get_path_lookup.rst @@ -18,7 +18,7 @@ Version added: 1.0 Synopsis -------- - Use a ``path`` to retreive a nested value from a ``var`` -- ``get_path`` is also available as a ``filter_plugin`` for convenience +- ``get_path`` is also available as a ``filter plugin`` for convenience @@ -116,29 +116,26 @@ Examples - name: Retrieve a value deep inside a using a path ansible.builtin.set_fact: - as_lookup: "{{ lookup('ansible.utils.get_path', a, path) }}" - as_filter: "{{ a|ansible.utils.get_path(path) }}" + value: "{{ lookup('ansible.utils.get_path', a, path) }}" vars: path: b.c.d[0] - # TASK [ansible.builtin.set_fact] ************************************* - # ok: [nxos101] => changed=false + # TASK [Retrieve a value deep inside a using a path] ****************** + # ok: [localhost] => changed=false # ansible_facts: - # as_filter: '0' - # as_lookup: '0' + # value: '0' #### Working with hostvars - name: Retrieve a value deep inside all of the host's vars ansible.builtin.set_fact: - as_lookup: "{{ lookup('ansible.utils.get_path', look_in, look_for) }}" - as_filter: "{{ look_in|ansible.utils.get_path(look_for) }}" + value: "{{ lookup('ansible.utils.get_path', look_in, look_for) }}" vars: look_in: "{{ hostvars[inventory_hostname] }}" look_for: a.b.c.d[0] - # TASK [Retrieve a value deep inside all of the host's vars] ********** + # TASK [Retrieve a value deep inside all of the host's vars] ******** # ok: [nxos101] => changed=false # ansible_facts: # as_filter: '0' @@ -149,7 +146,7 @@ Examples - name: Get the paths for the object ansible.builtin.set_fact: - paths: "{{ a|ansible.utils.to_paths(prepend='a') }}" + paths: "{{ lookup('ansible.utils.to_paths', a, prepend='a') }}" - name: Retrieve the value of each path from vars ansible.builtin.debug: @@ -159,9 +156,9 @@ Examples label: "{{ item }}" vars: path: "{{ item }}" - value: "{{ vars|ansible.utils.get_path(item) }}" + value: "{{ lookup('ansible.utils.get_path', hostvars[inventory_hostname], item) }}" - # TASK [Get the paths for the object] ********************************* + # TASK [Get the paths for the object] ******************************* # ok: [nxos101] => changed=false # ansible_facts: # paths: @@ -170,7 +167,7 @@ Examples # a.b.c.e[0]: true # a.b.c.e[1]: false - # TASK [Retrieve the value of each path from vars] ******************** + # TASK [Retrieve the value of each path from vars] ****************** # ok: [nxos101] => (item=a.b.c.d[0]) => # msg: The value of path a.b.c.d[0] in vars is 0 # ok: [nxos101] => (item=a.b.c.d[1]) => @@ -181,7 +178,7 @@ Examples # msg: The value of path a.b.c.e[1] in vars is False - #### Working with complex structures + #### Working with complex structures and transforming results - name: Retrieve the current interface config cisco.nxos.nxos_interfaces: @@ -190,19 +187,23 @@ Examples - name: Get the description of several interfaces ansible.builtin.debug: - msg: "{{ rekeyed|ansible.utils.get_path(item) }}" + msg: "{{ lookup('ansible.utils.get_path', rekeyed, item) }}" vars: rekeyed: by_name: "{{ interfaces.gathered|ansible.builtin.rekey_on_member('name') }}" loop: - by_name['Ethernet1/1'].description - - by_name['Ethernet1/2'].description + - by_name['Ethernet1/2'].description|upper + - by_name['Ethernet1/3'].description|default('') - # TASK [Get the description of several interfaces] ******************** - # ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => - # msg: Configured by Ansible - # ok: [nxos101] => (item=by_name['Ethernet1/2'].description) => - # msg: Configured by Ansible Network + + # TASK [Get the description of several interfaces] ****************** + # ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false + # msg: Configured by ansible + # ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false + # msg: CONFIGURED BY ANSIBLE + # ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false + # msg: '' diff --git a/docs/ansible.utils.index_of_filter.rst b/docs/ansible.utils.index_of_filter.rst new file mode 100644 index 0000000..c0db31d --- /dev/null +++ b/docs/ansible.utils.index_of_filter.rst @@ -0,0 +1,415 @@ +.. _ansible.utils.index_of_filter: + + +********************** +ansible.utils.index_of +********************** + +**Find the indicies of items in a list matching some criteria** + + +Version added: 1.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This plugin returns the indicies of items matching some criteria in a list +- When working with a list of dictionaries, the key to evaluate can be specified +- ``index_of`` is also available as a ``lookup plugin`` for convenience + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ data + +
+ list + / required +
+
+ + +
A list of items to enumerate and test against
+
This option represents the value that is passed to filter plugin in pipe format.
+
For example config_data|ansible.utils.index_of('x'), in this case config_data represents this option.
+
+
+ fail_on_missing + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+ +
When provided a list of dictionaries, fail if the key is missing from one or more of the dictionaries
+
+
+ key + +
+ string +
+
+ + +
When the data provided is a list of dictionaries, run the test againt this dictionary key
+
When using a key, the data must only contain dictionaries
+
See fail_on_missing below to determine the behaviour when the key is missing from a dictionary in the data
+
+
+ test + +
+ string + / required +
+
+ + +
The name of the test to run against the list, a valid jinja2 test or ansible test plugin.
+ + +
+
+ value + +
+ raw +
+
+ + +
The value used to test each list item against
+
{'Not required for simple tests (eg': 'true, false, even, odd)'}
+
May be a string, boolean, number, regular expesion dict etc, depending on the test used
+
+
+ wantlist + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+ +
When only a single entry in the data is matched, that entries index is returned as an integer
+
If set to True, the return value will always be a list, even if only a single entry is matched
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + #### Simple examples + + - set_fact: + data: + - 1 + - 2 + - 3 + + - name: Find the index of 2 + set_fact: + indices: "{{ data|ansible.utils.index_of('eq', 2) }}" + + # TASK [Find the index of 2] ************************************************* + # ok: [nxos101] => changed=false + # ansible_facts: + # indices: '1' + + + - name: Find the index of 2, ensure list is returned + set_fact: + indices: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" + + # TASK [Find the index of 2, ensure list is returned] ************************ + # ok: [nxos101] => changed=false + # ansible_facts: + # indices: + # - 1 + + + - name: Find the index of 3 using the long format + set_fact: + indices: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" + vars: + value: 3 + + # TASK [Find the index of 3 using the long format] *************************** + # ok: [nxos101] => changed=false + # ansible_facts: + # indices: + # - 2 + + + - name: Find numbers greater than 1, using loop + debug: + msg: "{{ data[item] }} is {{ test }} than {{ value }}" + loop: "{{ data|ansible.utils.index_of(test, value) }}" + vars: + test: '>' + value: 1 + + # TASK [Find numbers great than 1, using loop] ******************************* + # ok: [sw01] => (item=1) => + # msg: 2 is > than 1 + # ok: [sw01] => (item=2) => + # msg: 3 is > than 1 + + + #### Working with lists of dictionaries + + - set_fact: + data: + - name: sw01.example.lan + type: switch + - name: rtr01.example.lan + type: router + - name: fw01.example.corp + type: firewall + - name: fw02.example.corp + type: firewall + + - name: Find the index of all firewalls using the type key + set_fact: + firewalls: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" + + # TASK [Find the index of all firewalls using the type key] ****************** + # ok: [nxos101] => changed=false + # ansible_facts: + # firewalls: + # - 2 + # - 3 + + - name: Find the index of all firewalls, use in a loop + debug: + msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." + loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" + vars: + device_type: firewall + + # TASK [Find the index of all firewalls, use in a loop, as a filter] ********* + # ok: [nxos101] => (item=2) => + # msg: The type of firewall at index 2 has name fw01.example.corp. + # ok: [nxos101] => (item=3) => + # msg: The type of firewall at index 3 has name fw02.example.corp. + + - name: Find the index of all devices with a .corp name + debug: + msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" + loop: "{{ data|ansible.utils.index_of('regex', expression, 'name') }}" + vars: + expression: '\.corp$' # ends with .corp + + # TASK [Find the index of all devices with a .corp name] ********************* + # ok: [nxos101] => (item=2) => + # msg: The device named fw01.example.corp is a firewall + # ok: [nxos101] => (item=3) => + # msg: The device named fw02.example.corp is a firewall + + + #### Working with complex structures from resource modules + + - name: Retrieve the current L3 interface configuration + cisco.nxos.nxos_l3_interfaces: + state: gathered + register: current_l3 + + # TASK [Retrieve the current L3 interface configuration] ********************* + # ok: [sw01] => changed=false + # gathered: + # - name: Ethernet1/1 + # - name: Ethernet1/2 + # <...> + # - name: Ethernet1/128 + # - ipv4: + # - address: 192.168.101.14/24 + # name: mgmt0 + + - name: Find the indices interfaces with a 192.168.101.xx ip address + set_fact: + found: "{{ found + entry }}" + with_indexed_items: "{{ current_l3.gathered }}" + vars: + found: [] + ip: '192.168.101.' + address: "{{ item.1.ipv4|d([])|ansible.utils.index_of('search', ip, 'address', wantlist=True) }}" + entry: + - interface_idx: "{{ item.0 }}" + address_idxs: "{{ address }}" + when: address + + # TASK [debug] *************************************************************** + # ok: [sw01] => + # found: + # - address_idxs: + # - 0 + # interface_idx: '128' + + - name: Show all interfaces and their address + debug: + msg: "{{ interface.name }} has ip {{ address }}" + loop: "{{ found|subelements('address_idxs') }}" + vars: + interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" + address: "{{ interface.ipv4[item.1].address }}" + + # TASK [Show all interfaces and their address] ******************************* + # ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => + # msg: mgmt0 has ip 192.168.101.14/24 + + + #### Working with deeply nested data + + - set_fact: + data: + interfaces: + interface: + - config: + description: configured by Ansible - 1 + enabled: True + loopback-mode: False + mtu: 1024 + name: loopback0000 + type: eth + name: loopback0000 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 1 + enabled: True + index: 5 + index: 5 + - config: + description: subinterface configured by Ansible - 2 + enabled: False + index: 2 + index: 2 + - config: + description: configured by Ansible - 2 + enabled: False + loopback-mode: False + mtu: 2048 + name: loopback1111 + type: virt + name: loopback1111 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 3 + enabled: True + index: 10 + index: 10 + - config: + description: subinterface configured by Ansible - 4 + enabled: False + index: 3 + index: 3 + + + - name: Find the description of loopback111, subinterface index 10 + debug: + msg: |- + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface[subint_idx|int] + .config.description }} + vars: + # the values to search for + int_name: loopback1111 + sub_index: 10 + # retrieve the index in each nested list + int_idx: | + {{ data.interfaces.interface| + ansible.utils.index_of('eq', int_name, 'name') }} + subint_idx: | + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface| + ansible.utils.index_of('eq', sub_index, 'index') }} + + # TASK [Find the description of loopback111, subinterface index 10] ************ + # ok: [sw01] => + # msg: subinterface configured by Ansible - 3 + + + + +Status +------ + + +Authors +~~~~~~~ + +- Bradley Thornton (@cidrblock) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/docs/ansible.utils.index_of_lookup.rst b/docs/ansible.utils.index_of_lookup.rst index 43fe80b..d425b39 100644 --- a/docs/ansible.utils.index_of_lookup.rst +++ b/docs/ansible.utils.index_of_lookup.rst @@ -17,9 +17,9 @@ Version added: 1.0 Synopsis -------- -- This lookup returns the indicies of items matching some criteria in a list +- This plugin returns the indicies of items matching some criteria in a list - When working with a list of dictionaries, the key to evaluate can be specified -- ``index_of`` is also available as a ``filter_plugin`` for convenience +- ``index_of`` is also available as a ``filter plugin`` for convenience @@ -159,7 +159,7 @@ Examples .. code-block:: yaml - #### Simple examples using a list of values + #### Simple examples - set_fact: data: @@ -167,66 +167,41 @@ Examples - 2 - 3 - - name: Find the index of 2, lookup or filter + - name: Find the index of 2 set_fact: - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 2) }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 2) }}" + indices: "{{ lookup('ansible.utils.index_of', data, 'eq', 2) }}" - # TASK [Find the index of 2, lookup or filter] ******************************* - # ok: [sw01] => changed=false + # TASK [Find the index of 2] ************************************************* + # ok: [nxos101] => changed=false # ansible_facts: - # as_filter: '1' - # as_lookup: '1' + # indices: '1' - - name: Any test can be negated using not or ! + - name: Find the index of 2, ensure list is returned set_fact: - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'not in', [1,2]) }}" - as_filter: "{{ data|ansible.utils.index_of('!in', [1,2]) }}" + indices: "{{ lookup('ansible.utils.index_of', data, 'eq', 2, wantlist=True) }}" - # TASK [Any test can be negated using not or !] ****************************** - # ok: [localhost] => changed=false + # TASK [Find the index of 2, ensure list is returned] ************************ + # ok: [nxos101] => changed=false # ansible_facts: - # as_filter: '2' - # as_lookup: '2' - - - name: Find the index of 2, lookup or filter, ensure list is returned - set_fact: - as_query: "{{ query('ansible.utils.index_of', data, 'eq', 2) }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 2, wantlist=True) }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" - - # TASK [Find the index of 2, lookup or filter, ensure list is returned] ****** - # ok: [sw01] => changed=false - # ansible_facts: - # as_filter: - # - 1 - # as_lookup: - # - 1 - # as_query: + # indices: # - 1 - name: Find the index of 3 using the long format set_fact: - as_query: "{{ query('ansible.utils.index_of', data=data, test='eq', value=value) }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data=data, test='eq',value =value, wantlist=True) }}" - as_filter: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" + indices: "{{ lookup('ansible.utils.index_of', data=data, test='eq', value=value, wantlist=True) }}" vars: value: 3 # TASK [Find the index of 3 using the long format] *************************** - # ok: [sw01] => changed=false + # ok: [nxos101] => changed=false # ansible_facts: - # as_filter: - # - 2 - # as_lookup: - # - 2 - # as_query: + # indices: # - 2 - name: Find numbers greater than 1, using loop debug: msg: "{{ data[item] }} is {{ test }} than {{ value }}" - loop: "{{ data|ansible.utils.index_of(test, value) }}" + loop: "{{ lookup('ansible.utils.index_of', data, test, value) }}" vars: test: '>' value: 1 @@ -248,13 +223,12 @@ Examples value: 1 # TASK [Find numbers greater than 1, using with] ***************************** - # ok: [sw01] => (item=1) => + # ok: [nxos101] => (item=1) => # msg: 2 is > than 1 - # ok: [sw01] => (item=2) => + # ok: [nxos101] => (item=2) => # msg: 3 is > than 1 - #### Working with lists of dictionaries - set_fact: @@ -270,52 +244,43 @@ Examples - name: Find the index of all firewalls using the type key set_fact: - as_query: "{{ query('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" + firewalls: "{{ lookup('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" # TASK [Find the index of all firewalls using the type key] ****************** - # ok: [sw01] => changed=false + # ok: [nxos101] => changed=false # ansible_facts: - # as_filter: - # - 2 - # - 3 - # as_lookup: - # - 2 - # - 3 - # as_query: + # firewalls: # - 2 # - 3 - - name: Find the index of all firewalls, use in a loop, as a filter + - name: Find the index of all firewalls, use in a loop debug: msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." - loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" + loop: "{{ lookup('ansible.utils.index_of', data, 'eq', device_type, 'type') }}" vars: device_type: firewall - # TASK [Find the index of all firewalls, use in a loop] ********************** - # ok: [sw01] => (item=2) => - # msg: The type of firewall at index 2 has name fw01.example.corp - # ok: [sw01] => (item=3) => - # msg: The type of firewall at index 3 has name fw02.example.corp + # TASK [Find the index of all firewalls, use in a loop, as a filter] ********* + # ok: [nxos101] => (item=2) => + # msg: The type of firewall at index 2 has name fw01.example.corp. + # ok: [nxos101] => (item=3) => + # msg: The type of firewall at index 3 has name fw02.example.corp. - - name: Find the index of all devices with a .corp name, as a lookup + - name: Find the index of all devices with a .corp name debug: msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" - loop: "{{ lookup('ansible.utils.index_of', data, 'regex', regex, 'name') }}" + loop: "{{ lookup('ansible.utils.index_of', data, 'regex', expression, 'name') }}" vars: - regex: '\.corp$' # ends with .corp + expression: '\.corp$' # ends with .corp - # TASK [Find the index of all devices with a .corp name, as a lookup] ********** - # ok: [sw01] => (item=2) => + # TASK [Find the index of all devices with a .corp name] ********************* + # ok: [nxos101] => (item=2) => # msg: The device named fw01.example.corp is a firewall - # ok: [sw01] => (item=3) => + # ok: [nxos101] => (item=3) => # msg: The device named fw02.example.corp is a firewall - - #### Working with data from resource modules + #### Working with complex structures from resource modules - name: Retrieve the current L3 interface configuration cisco.nxos.nxos_l3_interfaces: @@ -333,17 +298,17 @@ Examples # - address: 192.168.101.14/24 # name: mgmt0 - - name: Find the index of the interface and address with a 192.168.101.xx ip address + - name: Find the indices interfaces with a 192.168.101.xx ip address set_fact: found: "{{ found + entry }}" with_indexed_items: "{{ current_l3.gathered }}" vars: found: [] ip: '192.168.101.' - address: "{{ item.1.ipv4|d([])|ansible.utils.index_of('search', ip, 'address', wantlist=True) }}" + address: "{{ lookup('ansible.utils.index_of', item.1.ipv4|d([]), 'search', ip, 'address', wantlist=True) }}" entry: - interface_idx: "{{ item.0 }}" - address_idxs: "{{ address }}" + address_idxs: "{{ address }}" when: address # TASK [debug] *************************************************************** @@ -361,78 +326,78 @@ Examples interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" address: "{{ interface.ipv4[item.1].address }}" - # TASK [debug] *************************************************************** - # ok: [sw01] => (item=[{'interface_idx': '128', 'address_idx': [0]}, 0]) => + # TASK [Show all interfaces and their address] ******************************* + # ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => # msg: mgmt0 has ip 192.168.101.14/24 - - #### Working with complex structures + #### Working with deeply nested data - set_fact: data: interfaces: interface: - - config: - description: configured by Ansible - 1 - enabled: True - loopback-mode: False - mtu: 1024 + - config: + description: configured by Ansible - 1 + enabled: True + loopback-mode: False + mtu: 1024 + name: loopback0000 + type: eth name: loopback0000 - type: eth - name: loopback0000 - subinterfaces: - subinterface: - - config: - description: subinterface configured by Ansible - 1 - enabled: True - index: 5 - index: 5 - - config: - description: subinterface configured by Ansible - 2 - enabled: False - index: 2 - index: 2 - - config: - description: configured by Ansible - 2 - enabled: False - loopback-mode: False - mtu: 2048 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 1 + enabled: True + index: 5 + index: 5 + - config: + description: subinterface configured by Ansible - 2 + enabled: False + index: 2 + index: 2 + - config: + description: configured by Ansible - 2 + enabled: False + loopback-mode: False + mtu: 2048 + name: loopback1111 + type: virt name: loopback1111 - type: virt - name: loopback1111 - subinterfaces: - subinterface: - - config: - description: subinterface configured by Ansible - 3 - enabled: True - index: 10 - index: 10 - - config: - description: subinterface configured by Ansible - 4 - enabled: False - index: 3 - index: 3 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 3 + enabled: True + index: 10 + index: 10 + - config: + description: subinterface configured by Ansible - 4 + enabled: False + index: 3 + index: 3 - name: Find the description of loopback111, subinterface index 10 debug: msg: |- {{ data.interfaces.interface[int_idx|int] - .subinterfaces.subinterface[subint_idx|int] - .config.description }} + .subinterfaces.subinterface[subint_idx|int] + .config.description }} vars: # the values to search for int_name: loopback1111 sub_index: 10 # retrieve the index in each nested list int_idx: | - {{ data.interfaces.interface| - ansible.utils.index_of('eq', int_name, 'name') }} + {{ lookup('ansible.utils.index_of', + data.interfaces.interface, + 'eq', int_name, 'name') }} subint_idx: | - {{ data.interfaces.interface[int_idx|int] - .subinterfaces.subinterface| - ansible.utils.index_of('eq', sub_index, 'index') }} + {{ lookup('ansible.utils.index_of', + data.interfaces.interface[int_idx|int].subinterfaces.subinterface, + 'eq', sub_index, 'index') }} # TASK [Find the description of loopback111, subinterface index 10] ************ # ok: [sw01] => diff --git a/docs/ansible.utils.to_paths_filter.rst b/docs/ansible.utils.to_paths_filter.rst new file mode 100644 index 0000000..064292d --- /dev/null +++ b/docs/ansible.utils.to_paths_filter.rst @@ -0,0 +1,194 @@ +.. _ansible.utils.to_paths_filter: + + +********************** +ansible.utils.to_paths +********************** + +**Flatten a complex object into a dictionary of paths and values** + + +Version added: 1.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Flatten a complex object into a dictionary of paths and values. +- Paths are dot delimited whenever possible +- Brakets are used for list indicies and keys that contain special characters +- ``to_paths`` is also available as a ``lookup plugin`` for convenience + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ prepend + +
+ string +
+
+ + +
Prepend each path entry. Useful to add the initial var name.
+
+
+ var + +
+ raw + / required +
+
+ + +
The value of var will be will be used.
+
This option represents the value that is passed to filter plugin in pipe format.
+
For example config_data|ansible.utils.to_paths(), in this case config_data represents this option.
+
+
+ wantlist + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+ +
If set to True, the return value will always be a list.
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + #### Simple examples + + - ansible.builtin.set_fact: + a: + b: + c: + d: + - 0 + - 1 + e: + - True + - False + + - ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths }}" + + # TASK [ansible.builtin.set_fact] ******************************************** + # ok: [nxos101] => changed=false + # ansible_facts: + # paths: + # b.c.d[0]: 0 + # b.c.d[1]: 1 + # b.c.e[0]: true + # b.c.e[1]: false + + - name: Use prepend to add the initial variable name + ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths(prepend='a') }}" + + # TASK [Use prepend to add the initial variable name] ************************** + # ok: [nxos101] => changed=false + # ansible_facts: + # paths: + # a.b.c.d[0]: 0 + # a.b.c.d[1]: 1 + # a.b.c.e[0]: true + # a.b.c.e[1]: false + + + #### Using a complex object + + - name: Make an API call + uri: + url: "https://nxos101/restconf/data/openconfig-interfaces:interfaces" + headers: + accept: "application/yang.data+json" + url_password: password + url_username: admin + validate_certs: False + register: result + delegate_to: localhost + + - name: Flatten the complex object + set_fact: + paths: "{{ result.json|ansible.utils.to_paths }}" + + # TASK [Flatten the complex object] ****************************************** + # ok: [nxos101] => changed=false + # ansible_facts: + # paths: + # interfaces.interface[0].config.enabled: 'true' + # interfaces.interface[0].config.mtu: '1500' + # interfaces.interface[0].config.name: eth1/71 + # interfaces.interface[0].config.type: ethernetCsmacd + # interfaces.interface[0].ethernet.config['auto-negotiate']: 'true' + # interfaces.interface[0].ethernet.state.counters['in-crc-errors']: '0' + # interfaces.interface[0].ethernet.state.counters['in-fragment-frames']: '0' + # interfaces.interface[0].ethernet.state.counters['in-jabber-frames']: '0' + # interfaces.interface[0].ethernet.state.counters['in-mac-control-frames']: '0' + # <...> + + + + +Status +------ + + +Authors +~~~~~~~ + +- Bradley Thornton (@cidrblock) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/docs/ansible.utils.to_paths_lookup.rst b/docs/ansible.utils.to_paths_lookup.rst index 43312ed..5021e7d 100644 --- a/docs/ansible.utils.to_paths_lookup.rst +++ b/docs/ansible.utils.to_paths_lookup.rst @@ -118,19 +118,12 @@ Examples - False - ansible.builtin.set_fact: - as_lookup: "{{ lookup('ansible.utils.to_paths', a) }}" - as_filter: "{{ a|ansible.utils.to_paths }}" + paths: "{{ lookup('ansible.utils.to_paths', a) }}" - # TASK [set_fact] ***************************************************** - # task path: /home/brad/github/dotbracket/site.yaml:17 - # ok: [localhost] => changed=false + # TASK [ansible.builtin.set_fact] ******************************************** + # ok: [nxos101] => changed=false # ansible_facts: - # as_filter: - # b.c.d[0]: 0 - # b.c.d[1]: 1 - # b.c.e[0]: true - # b.c.e[1]: false - # as_lookup: + # paths: # b.c.d[0]: 0 # b.c.d[1]: 1 # b.c.e[0]: true @@ -138,18 +131,12 @@ Examples - name: Use prepend to add the initial variable name ansible.builtin.set_fact: - as_lookup: "{{ lookup('ansible.utils.to_paths', a, prepend=('a')) }}" - as_filter: "{{ a|ansible.utils.to_paths(prepend='a') }}" + paths: "{{ lookup('ansible.utils.to_paths', a, prepend='a') }}" - # TASK [Use prepend to add the initial variable name] ***************** + # TASK [Use prepend to add the initial variable name] ************************** # ok: [nxos101] => changed=false # ansible_facts: - # as_filter: - # a.b.c.d[0]: 0 - # a.b.c.d[1]: 1 - # a.b.c.e[0]: true - # a.b.c.e[1]: false - # as_lookup: + # paths: # a.b.c.d[0]: 0 # a.b.c.d[1]: 1 # a.b.c.e[0]: true @@ -171,12 +158,12 @@ Examples - name: Flatten the complex object set_fact: - flattened: "{{ result.json|ansible.utils.to_paths }}" + paths: "{{ lookup('ansible.utils.to_paths', result.json) }}" - # TASK [Flatten the complex object] ******************** + # TASK [Flatten the complex object] ****************************************** # ok: [nxos101] => changed=false # ansible_facts: - # flattened: + # paths: # interfaces.interface[0].config.enabled: 'true' # interfaces.interface[0].config.mtu: '1500' # interfaces.interface[0].config.name: eth1/71 @@ -185,6 +172,8 @@ Examples # interfaces.interface[0].ethernet.state.counters['in-crc-errors']: '0' # interfaces.interface[0].ethernet.state.counters['in-fragment-frames']: '0' # interfaces.interface[0].ethernet.state.counters['in-jabber-frames']: '0' + # interfaces.interface[0].ethernet.state.counters['in-mac-control-frames']: '0' + # <...> diff --git a/plugins/filter/get_path.py b/plugins/filter/get_path.py index 02f46b4..e01962c 100644 --- a/plugins/filter/get_path.py +++ b/plugins/filter/get_path.py @@ -11,15 +11,148 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +DOCUMENTATION = """ + filter: get_path + author: Bradley Thornton (@cidrblock) + version_added: "1.0" + short_description: Retrieve the value in a variable using a path + description: + - Use a C(path) to retreive a nested value from a C(var) + - C(get_path) is also available as a C(lookup plugin) for convenience + options: + var: + description: + - The variable from which the value should be extraced + - This option represents the value that is passed to filter plugin in pipe format. + - For example I(config_data|ansible.utils.get_path()), in this case I(config_data) represents this option. + type: raw + required: True + path: + description: + - The C(path) in the C(var) to retrieve the value of. + - The C(path) needs to a be a valid jinja path + type: str + required: True + wantlist: + description: + - If set to C(True), the return value will always be a list + type: bool + + notes: +""" + +EXAMPLES = r""" +- ansible.builtin.set_fact: + a: + b: + c: + d: + - 0 + - 1 + e: + - True + - False + +- name: Retrieve a value deep inside a using a path + ansible.builtin.set_fact: + value: "{{ a|ansible.utils.get_path(path) }}" + vars: + path: b.c.d[0] + +# TASK [Retrieve a value deep inside a using a path] ****************** +# ok: [localhost] => changed=false +# ansible_facts: +# value: '0' + + +#### Working with hostvars + +- name: Retrieve a value deep inside all of the host's vars + ansible.builtin.set_fact: + value: "{{ look_in|ansible.utils.get_path(look_for) }}" + vars: + look_in: "{{ hostvars[inventory_hostname] }}" + look_for: a.b.c.d[0] + +# TASK [Retrieve a value deep inside all of the host's vars] ******** +# ok: [nxos101] => changed=false +# ansible_facts: +# as_filter: '0' +# as_lookup: '0' + + +#### Used alongside ansible.utils.to_paths + +- name: Get the paths for the object + ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths(prepend='a') }}" + +- name: Retrieve the value of each path from vars + ansible.builtin.debug: + msg: "The value of path {{ path }} in vars is {{ value }}" + loop: "{{ paths.keys()|list }}" + loop_control: + label: "{{ item }}" + vars: + path: "{{ item }}" + value: "{{ vars|ansible.utils.get_path(item) }}" + +# TASK [Get the paths for the object] ******************************* +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# a.b.c.d[0]: 0 +# a.b.c.d[1]: 1 +# a.b.c.e[0]: true +# a.b.c.e[1]: false + +# TASK [Retrieve the value of each path from vars] ****************** +# ok: [nxos101] => (item=a.b.c.d[0]) => +# msg: The value of path a.b.c.d[0] in vars is 0 +# ok: [nxos101] => (item=a.b.c.d[1]) => +# msg: The value of path a.b.c.d[1] in vars is 1 +# ok: [nxos101] => (item=a.b.c.e[0]) => +# msg: The value of path a.b.c.e[0] in vars is True +# ok: [nxos101] => (item=a.b.c.e[1]) => +# msg: The value of path a.b.c.e[1] in vars is False + + +#### Working with complex structures and transforming results + +- name: Retrieve the current interface config + cisco.nxos.nxos_interfaces: + state: gathered + register: interfaces + +- name: Get the description of several interfaces + ansible.builtin.debug: + msg: "{{ rekeyed|ansible.utils.get_path(item) }}" + vars: + rekeyed: + by_name: "{{ interfaces.gathered|ansible.builtin.rekey_on_member('name') }}" + loop: + - by_name['Ethernet1/1'].description + - by_name['Ethernet1/2'].description|upper + - by_name['Ethernet1/3'].description|default('') + + +# TASK [Get the description of several interfaces] ****************** +# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false +# msg: Configured by ansible +# ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false +# msg: CONFIGURED BY ANSIBLE +# ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false +# msg: '' + +""" + from ansible.errors import AnsibleFilterError from jinja2.filters import environmentfilter from ansible_collections.ansible.utils.plugins.module_utils.common.get_path import ( get_path, ) -from ansible_collections.ansible.utils.plugins.lookup.get_path import ( - DOCUMENTATION, -) + from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( AnsibleArgSpecValidator, ) @@ -27,7 +160,7 @@ from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_valid @environmentfilter def _get_path(*args, **kwargs): - """Retrieve the value in a variable using a path. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.get_path_lookup.rst)""" + """Retrieve the value in a variable using a path.""" keys = ["environment", "var", "path"] data = dict(zip(keys, args)) data.update(kwargs) diff --git a/plugins/filter/index_of.py b/plugins/filter/index_of.py index a94b106..a3d7d55 100644 --- a/plugins/filter/index_of.py +++ b/plugins/filter/index_of.py @@ -11,14 +11,293 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +DOCUMENTATION = """ + filter: index_of + author: Bradley Thornton (@cidrblock) + version_added: "1.0" + short_description: Find the indicies of items in a list matching some criteria + description: + - This plugin returns the indicies of items matching some criteria in a list + - When working with a list of dictionaries, the key to evaluate can be specified + - C(index_of) is also available as a C(lookup plugin) for convenience + options: + data: + description: + - A list of items to enumerate and test against + - This option represents the value that is passed to filter plugin in pipe format. + - For example I(config_data|ansible.utils.index_of('x')), in this case I(config_data) represents this option. + type: list + required: True + test: + description: + - The name of the test to run against the list, a valid jinja2 test or ansible test plugin. + - Jinja2 includes the following tests U(http://jinja.palletsprojects.com/templates/#builtin-tests). + - An overview of tests included in ansible U(https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html) + type: str + required: True + value: + description: + - The value used to test each list item against + - Not required for simple tests (eg: C(true), C(false), C(even), C(odd)) + - May be a C(string), C(boolean), C(number), C(regular expesion) C(dict) etc, depending on the C(test) used + type: raw + key: + description: + - When the data provided is a list of dictionaries, run the test againt this dictionary key + - When using a C(key), the C(data) must only contain dictionaries + - See C(fail_on_missing) below to determine the behaviour when the C(key) is missing from a dictionary in the C(data) + type: str + fail_on_missing: + description: When provided a list of dictionaries, fail if the key is missing from one or more of the dictionaries + type: bool + wantlist: + description: + - When only a single entry in the C(data) is matched, that entries index is returned as an integer + - If set to C(True), the return value will always be a list, even if only a single entry is matched + type: bool + + notes: +""" + +EXAMPLES = r""" + +#### Simple examples + +- set_fact: + data: + - 1 + - 2 + - 3 + +- name: Find the index of 2 + set_fact: + indices: "{{ data|ansible.utils.index_of('eq', 2) }}" + +# TASK [Find the index of 2] ************************************************* +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: '1' + + +- name: Find the index of 2, ensure list is returned + set_fact: + indices: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" + +# TASK [Find the index of 2, ensure list is returned] ************************ +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: +# - 1 + + +- name: Find the index of 3 using the long format + set_fact: + indices: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" + vars: + value: 3 + +# TASK [Find the index of 3 using the long format] *************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: +# - 2 + + +- name: Find numbers greater than 1, using loop + debug: + msg: "{{ data[item] }} is {{ test }} than {{ value }}" + loop: "{{ data|ansible.utils.index_of(test, value) }}" + vars: + test: '>' + value: 1 + +# TASK [Find numbers great than 1, using loop] ******************************* +# ok: [sw01] => (item=1) => +# msg: 2 is > than 1 +# ok: [sw01] => (item=2) => +# msg: 3 is > than 1 + + +#### Working with lists of dictionaries + +- set_fact: + data: + - name: sw01.example.lan + type: switch + - name: rtr01.example.lan + type: router + - name: fw01.example.corp + type: firewall + - name: fw02.example.corp + type: firewall + +- name: Find the index of all firewalls using the type key + set_fact: + firewalls: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" + +# TASK [Find the index of all firewalls using the type key] ****************** +# ok: [nxos101] => changed=false +# ansible_facts: +# firewalls: +# - 2 +# - 3 + +- name: Find the index of all firewalls, use in a loop + debug: + msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." + loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" + vars: + device_type: firewall + +# TASK [Find the index of all firewalls, use in a loop, as a filter] ********* +# ok: [nxos101] => (item=2) => +# msg: The type of firewall at index 2 has name fw01.example.corp. +# ok: [nxos101] => (item=3) => +# msg: The type of firewall at index 3 has name fw02.example.corp. + +- name: Find the index of all devices with a .corp name + debug: + msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" + loop: "{{ data|ansible.utils.index_of('regex', expression, 'name') }}" + vars: + expression: '\.corp$' # ends with .corp + +# TASK [Find the index of all devices with a .corp name] ********************* +# ok: [nxos101] => (item=2) => +# msg: The device named fw01.example.corp is a firewall +# ok: [nxos101] => (item=3) => +# msg: The device named fw02.example.corp is a firewall + + +#### Working with complex structures from resource modules + +- name: Retrieve the current L3 interface configuration + cisco.nxos.nxos_l3_interfaces: + state: gathered + register: current_l3 + +# TASK [Retrieve the current L3 interface configuration] ********************* +# ok: [sw01] => changed=false +# gathered: +# - name: Ethernet1/1 +# - name: Ethernet1/2 +# <...> +# - name: Ethernet1/128 +# - ipv4: +# - address: 192.168.101.14/24 +# name: mgmt0 + +- name: Find the indices interfaces with a 192.168.101.xx ip address + set_fact: + found: "{{ found + entry }}" + with_indexed_items: "{{ current_l3.gathered }}" + vars: + found: [] + ip: '192.168.101.' + address: "{{ item.1.ipv4|d([])|ansible.utils.index_of('search', ip, 'address', wantlist=True) }}" + entry: + - interface_idx: "{{ item.0 }}" + address_idxs: "{{ address }}" + when: address + +# TASK [debug] *************************************************************** +# ok: [sw01] => +# found: +# - address_idxs: +# - 0 +# interface_idx: '128' + +- name: Show all interfaces and their address + debug: + msg: "{{ interface.name }} has ip {{ address }}" + loop: "{{ found|subelements('address_idxs') }}" + vars: + interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" + address: "{{ interface.ipv4[item.1].address }}" + +# TASK [Show all interfaces and their address] ******************************* +# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => +# msg: mgmt0 has ip 192.168.101.14/24 + + +#### Working with deeply nested data + +- set_fact: + data: + interfaces: + interface: + - config: + description: configured by Ansible - 1 + enabled: True + loopback-mode: False + mtu: 1024 + name: loopback0000 + type: eth + name: loopback0000 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 1 + enabled: True + index: 5 + index: 5 + - config: + description: subinterface configured by Ansible - 2 + enabled: False + index: 2 + index: 2 + - config: + description: configured by Ansible - 2 + enabled: False + loopback-mode: False + mtu: 2048 + name: loopback1111 + type: virt + name: loopback1111 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 3 + enabled: True + index: 10 + index: 10 + - config: + description: subinterface configured by Ansible - 4 + enabled: False + index: 3 + index: 3 + + +- name: Find the description of loopback111, subinterface index 10 + debug: + msg: |- + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface[subint_idx|int] + .config.description }} + vars: + # the values to search for + int_name: loopback1111 + sub_index: 10 + # retrieve the index in each nested list + int_idx: | + {{ data.interfaces.interface| + ansible.utils.index_of('eq', int_name, 'name') }} + subint_idx: | + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface| + ansible.utils.index_of('eq', sub_index, 'index') }} + +# TASK [Find the description of loopback111, subinterface index 10] ************ +# ok: [sw01] => +# msg: subinterface configured by Ansible - 3 + +""" + from ansible.errors import AnsibleFilterError from jinja2.filters import environmentfilter from ansible_collections.ansible.utils.plugins.module_utils.common.index_of import ( index_of, ) -from ansible_collections.ansible.utils.plugins.lookup.index_of import ( - DOCUMENTATION, -) from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( AnsibleArgSpecValidator, ) @@ -26,7 +305,7 @@ from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_valid @environmentfilter def _index_of(*args, **kwargs): - """Find the indicies of items in a list matching some criteria. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.index_of_lookup.rst)""" + """Find the indicies of items in a list matching some criteria.""" keys = [ "environment", diff --git a/plugins/filter/to_paths.py b/plugins/filter/to_paths.py index 552db93..deb0fb4 100644 --- a/plugins/filter/to_paths.py +++ b/plugins/filter/to_paths.py @@ -12,20 +12,121 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +DOCUMENTATION = """ + filter: to_paths + author: Bradley Thornton (@cidrblock) + version_added: "1.0" + short_description: Flatten a complex object into a dictionary of paths and values + description: + - Flatten a complex object into a dictionary of paths and values. + - Paths are dot delimited whenever possible + - Brakets are used for list indicies and keys that contain special characters + - C(to_paths) is also available as a C(lookup plugin) for convenience + options: + var: + description: + - The value of C(var) will be will be used. + - This option represents the value that is passed to filter plugin in pipe format. + - For example I(config_data|ansible.utils.to_paths()), in this case I(config_data) represents this option. + type: raw + required: True + prepend: + description: Prepend each path entry. Useful to add the initial C(var) name. + type: str + required: False + wantlist: + description: + - If set to C(True), the return value will always be a list. + type: bool + + notes: +""" + +EXAMPLES = r""" + +#### Simple examples + +- ansible.builtin.set_fact: + a: + b: + c: + d: + - 0 + - 1 + e: + - True + - False + +- ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths }}" + +# TASK [ansible.builtin.set_fact] ******************************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# b.c.d[0]: 0 +# b.c.d[1]: 1 +# b.c.e[0]: true +# b.c.e[1]: false + +- name: Use prepend to add the initial variable name + ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths(prepend='a') }}" + +# TASK [Use prepend to add the initial variable name] ************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# a.b.c.d[0]: 0 +# a.b.c.d[1]: 1 +# a.b.c.e[0]: true +# a.b.c.e[1]: false + + +#### Using a complex object + +- name: Make an API call + uri: + url: "https://nxos101/restconf/data/openconfig-interfaces:interfaces" + headers: + accept: "application/yang.data+json" + url_password: password + url_username: admin + validate_certs: False + register: result + delegate_to: localhost + +- name: Flatten the complex object + set_fact: + paths: "{{ result.json|ansible.utils.to_paths }}" + +# TASK [Flatten the complex object] ****************************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# interfaces.interface[0].config.enabled: 'true' +# interfaces.interface[0].config.mtu: '1500' +# interfaces.interface[0].config.name: eth1/71 +# interfaces.interface[0].config.type: ethernetCsmacd +# interfaces.interface[0].ethernet.config['auto-negotiate']: 'true' +# interfaces.interface[0].ethernet.state.counters['in-crc-errors']: '0' +# interfaces.interface[0].ethernet.state.counters['in-fragment-frames']: '0' +# interfaces.interface[0].ethernet.state.counters['in-jabber-frames']: '0' +# interfaces.interface[0].ethernet.state.counters['in-mac-control-frames']: '0' +# <...> +""" + from ansible.errors import AnsibleFilterError from ansible_collections.ansible.utils.plugins.module_utils.common.to_paths import ( to_paths, ) -from ansible_collections.ansible.utils.plugins.lookup.to_paths import ( - DOCUMENTATION, -) from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( AnsibleArgSpecValidator, ) def _to_paths(*args, **kwargs): - """Flatten a complex object into a dictionary of paths and values. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_paths_lookup.rst)""" + """Flatten a complex object into a dictionary of paths and values.""" keys = ["var", "prepend", "wantlist"] data = dict(zip(keys, args)) data.update(kwargs) diff --git a/plugins/lookup/get_path.py b/plugins/lookup/get_path.py index 1749312..c281a0c 100644 --- a/plugins/lookup/get_path.py +++ b/plugins/lookup/get_path.py @@ -19,7 +19,7 @@ DOCUMENTATION = """ short_description: Retrieve the value in a variable using a path description: - Use a C(path) to retreive a nested value from a C(var) - - C(get_path) is also available as a C(filter_plugin) for convenience + - C(get_path) is also available as a C(filter plugin) for convenience options: var: description: The variable from which the value should be extraced @@ -55,29 +55,26 @@ EXAMPLES = r""" - name: Retrieve a value deep inside a using a path ansible.builtin.set_fact: - as_lookup: "{{ lookup('ansible.utils.get_path', a, path) }}" - as_filter: "{{ a|ansible.utils.get_path(path) }}" + value: "{{ lookup('ansible.utils.get_path', a, path) }}" vars: path: b.c.d[0] -# TASK [ansible.builtin.set_fact] ************************************* -# ok: [nxos101] => changed=false +# TASK [Retrieve a value deep inside a using a path] ****************** +# ok: [localhost] => changed=false # ansible_facts: -# as_filter: '0' -# as_lookup: '0' +# value: '0' #### Working with hostvars - name: Retrieve a value deep inside all of the host's vars ansible.builtin.set_fact: - as_lookup: "{{ lookup('ansible.utils.get_path', look_in, look_for) }}" - as_filter: "{{ look_in|ansible.utils.get_path(look_for) }}" + value: "{{ lookup('ansible.utils.get_path', look_in, look_for) }}" vars: look_in: "{{ hostvars[inventory_hostname] }}" look_for: a.b.c.d[0] -# TASK [Retrieve a value deep inside all of the host's vars] ********** +# TASK [Retrieve a value deep inside all of the host's vars] ******** # ok: [nxos101] => changed=false # ansible_facts: # as_filter: '0' @@ -88,7 +85,7 @@ EXAMPLES = r""" - name: Get the paths for the object ansible.builtin.set_fact: - paths: "{{ a|ansible.utils.to_paths(prepend='a') }}" + paths: "{{ lookup('ansible.utils.to_paths', a, prepend='a') }}" - name: Retrieve the value of each path from vars ansible.builtin.debug: @@ -98,9 +95,9 @@ EXAMPLES = r""" label: "{{ item }}" vars: path: "{{ item }}" - value: "{{ vars|ansible.utils.get_path(item) }}" + value: "{{ lookup('ansible.utils.get_path', hostvars[inventory_hostname], item) }}" -# TASK [Get the paths for the object] ********************************* +# TASK [Get the paths for the object] ******************************* # ok: [nxos101] => changed=false # ansible_facts: # paths: @@ -109,7 +106,7 @@ EXAMPLES = r""" # a.b.c.e[0]: true # a.b.c.e[1]: false -# TASK [Retrieve the value of each path from vars] ******************** +# TASK [Retrieve the value of each path from vars] ****************** # ok: [nxos101] => (item=a.b.c.d[0]) => # msg: The value of path a.b.c.d[0] in vars is 0 # ok: [nxos101] => (item=a.b.c.d[1]) => @@ -120,7 +117,7 @@ EXAMPLES = r""" # msg: The value of path a.b.c.e[1] in vars is False -#### Working with complex structures +#### Working with complex structures and transforming results - name: Retrieve the current interface config cisco.nxos.nxos_interfaces: @@ -129,19 +126,23 @@ EXAMPLES = r""" - name: Get the description of several interfaces ansible.builtin.debug: - msg: "{{ rekeyed|ansible.utils.get_path(item) }}" + msg: "{{ lookup('ansible.utils.get_path', rekeyed, item) }}" vars: rekeyed: by_name: "{{ interfaces.gathered|ansible.builtin.rekey_on_member('name') }}" loop: - by_name['Ethernet1/1'].description - - by_name['Ethernet1/2'].description + - by_name['Ethernet1/2'].description|upper + - by_name['Ethernet1/3'].description|default('') -# TASK [Get the description of several interfaces] ******************** -# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => -# msg: Configured by Ansible -# ok: [nxos101] => (item=by_name['Ethernet1/2'].description) => -# msg: Configured by Ansible Network + +# TASK [Get the description of several interfaces] ****************** +# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false +# msg: Configured by ansible +# ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false +# msg: CONFIGURED BY ANSIBLE +# ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false +# msg: '' """ diff --git a/plugins/lookup/index_of.py b/plugins/lookup/index_of.py index 7e5b242..00889cf 100644 --- a/plugins/lookup/index_of.py +++ b/plugins/lookup/index_of.py @@ -18,9 +18,9 @@ DOCUMENTATION = """ version_added: "1.0" short_description: Find the indicies of items in a list matching some criteria description: - - This lookup returns the indicies of items matching some criteria in a list + - This plugin returns the indicies of items matching some criteria in a list - When working with a list of dictionaries, the key to evaluate can be specified - - C(index_of) is also available as a C(filter_plugin) for convenience + - C(index_of) is also available as a C(filter plugin) for convenience options: data: description: A list of items to enumerate and test against @@ -61,7 +61,7 @@ DOCUMENTATION = """ EXAMPLES = r""" -#### Simple examples using a list of values +#### Simple examples - set_fact: data: @@ -69,66 +69,41 @@ EXAMPLES = r""" - 2 - 3 -- name: Find the index of 2, lookup or filter +- name: Find the index of 2 set_fact: - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 2) }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 2) }}" + indices: "{{ lookup('ansible.utils.index_of', data, 'eq', 2) }}" -# TASK [Find the index of 2, lookup or filter] ******************************* -# ok: [sw01] => changed=false +# TASK [Find the index of 2] ************************************************* +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: '1' -# as_lookup: '1' +# indices: '1' -- name: Any test can be negated using not or ! +- name: Find the index of 2, ensure list is returned set_fact: - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'not in', [1,2]) }}" - as_filter: "{{ data|ansible.utils.index_of('!in', [1,2]) }}" + indices: "{{ lookup('ansible.utils.index_of', data, 'eq', 2, wantlist=True) }}" -# TASK [Any test can be negated using not or !] ****************************** -# ok: [localhost] => changed=false +# TASK [Find the index of 2, ensure list is returned] ************************ +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: '2' -# as_lookup: '2' - -- name: Find the index of 2, lookup or filter, ensure list is returned - set_fact: - as_query: "{{ query('ansible.utils.index_of', data, 'eq', 2) }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 2, wantlist=True) }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" - -# TASK [Find the index of 2, lookup or filter, ensure list is returned] ****** -# ok: [sw01] => changed=false -# ansible_facts: -# as_filter: -# - 1 -# as_lookup: -# - 1 -# as_query: +# indices: # - 1 - name: Find the index of 3 using the long format set_fact: - as_query: "{{ query('ansible.utils.index_of', data=data, test='eq', value=value) }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data=data, test='eq',value =value, wantlist=True) }}" - as_filter: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" + indices: "{{ lookup('ansible.utils.index_of', data=data, test='eq', value=value, wantlist=True) }}" vars: value: 3 # TASK [Find the index of 3 using the long format] *************************** -# ok: [sw01] => changed=false +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: -# - 2 -# as_lookup: -# - 2 -# as_query: +# indices: # - 2 - name: Find numbers greater than 1, using loop debug: msg: "{{ data[item] }} is {{ test }} than {{ value }}" - loop: "{{ data|ansible.utils.index_of(test, value) }}" + loop: "{{ lookup('ansible.utils.index_of', data, test, value) }}" vars: test: '>' value: 1 @@ -150,13 +125,12 @@ EXAMPLES = r""" value: 1 # TASK [Find numbers greater than 1, using with] ***************************** -# ok: [sw01] => (item=1) => +# ok: [nxos101] => (item=1) => # msg: 2 is > than 1 -# ok: [sw01] => (item=2) => +# ok: [nxos101] => (item=2) => # msg: 3 is > than 1 - #### Working with lists of dictionaries - set_fact: @@ -172,52 +146,43 @@ EXAMPLES = r""" - name: Find the index of all firewalls using the type key set_fact: - as_query: "{{ query('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" + firewalls: "{{ lookup('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" # TASK [Find the index of all firewalls using the type key] ****************** -# ok: [sw01] => changed=false +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: -# - 2 -# - 3 -# as_lookup: -# - 2 -# - 3 -# as_query: +# firewalls: # - 2 # - 3 -- name: Find the index of all firewalls, use in a loop, as a filter +- name: Find the index of all firewalls, use in a loop debug: msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." - loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" + loop: "{{ lookup('ansible.utils.index_of', data, 'eq', device_type, 'type') }}" vars: device_type: firewall -# TASK [Find the index of all firewalls, use in a loop] ********************** -# ok: [sw01] => (item=2) => -# msg: The type of firewall at index 2 has name fw01.example.corp -# ok: [sw01] => (item=3) => -# msg: The type of firewall at index 3 has name fw02.example.corp +# TASK [Find the index of all firewalls, use in a loop, as a filter] ********* +# ok: [nxos101] => (item=2) => +# msg: The type of firewall at index 2 has name fw01.example.corp. +# ok: [nxos101] => (item=3) => +# msg: The type of firewall at index 3 has name fw02.example.corp. -- name: Find the index of all devices with a .corp name, as a lookup +- name: Find the index of all devices with a .corp name debug: msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" - loop: "{{ lookup('ansible.utils.index_of', data, 'regex', regex, 'name') }}" + loop: "{{ lookup('ansible.utils.index_of', data, 'regex', expression, 'name') }}" vars: - regex: '\.corp$' # ends with .corp + expression: '\.corp$' # ends with .corp -# TASK [Find the index of all devices with a .corp name, as a lookup] ********** -# ok: [sw01] => (item=2) => +# TASK [Find the index of all devices with a .corp name] ********************* +# ok: [nxos101] => (item=2) => # msg: The device named fw01.example.corp is a firewall -# ok: [sw01] => (item=3) => +# ok: [nxos101] => (item=3) => # msg: The device named fw02.example.corp is a firewall - -#### Working with data from resource modules +#### Working with complex structures from resource modules - name: Retrieve the current L3 interface configuration cisco.nxos.nxos_l3_interfaces: @@ -235,17 +200,17 @@ EXAMPLES = r""" # - address: 192.168.101.14/24 # name: mgmt0 -- name: Find the index of the interface and address with a 192.168.101.xx ip address +- name: Find the indices interfaces with a 192.168.101.xx ip address set_fact: found: "{{ found + entry }}" with_indexed_items: "{{ current_l3.gathered }}" vars: found: [] ip: '192.168.101.' - address: "{{ item.1.ipv4|d([])|ansible.utils.index_of('search', ip, 'address', wantlist=True) }}" + address: "{{ lookup('ansible.utils.index_of', item.1.ipv4|d([]), 'search', ip, 'address', wantlist=True) }}" entry: - interface_idx: "{{ item.0 }}" - address_idxs: "{{ address }}" + address_idxs: "{{ address }}" when: address # TASK [debug] *************************************************************** @@ -263,85 +228,82 @@ EXAMPLES = r""" interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" address: "{{ interface.ipv4[item.1].address }}" -# TASK [debug] *************************************************************** -# ok: [sw01] => (item=[{'interface_idx': '128', 'address_idx': [0]}, 0]) => +# TASK [Show all interfaces and their address] ******************************* +# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => # msg: mgmt0 has ip 192.168.101.14/24 - -#### Working with complex structures +#### Working with deeply nested data - set_fact: data: interfaces: interface: - - config: - description: configured by Ansible - 1 - enabled: True - loopback-mode: False - mtu: 1024 + - config: + description: configured by Ansible - 1 + enabled: True + loopback-mode: False + mtu: 1024 + name: loopback0000 + type: eth name: loopback0000 - type: eth - name: loopback0000 - subinterfaces: - subinterface: - - config: - description: subinterface configured by Ansible - 1 - enabled: True - index: 5 - index: 5 - - config: - description: subinterface configured by Ansible - 2 - enabled: False - index: 2 - index: 2 - - config: - description: configured by Ansible - 2 - enabled: False - loopback-mode: False - mtu: 2048 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 1 + enabled: True + index: 5 + index: 5 + - config: + description: subinterface configured by Ansible - 2 + enabled: False + index: 2 + index: 2 + - config: + description: configured by Ansible - 2 + enabled: False + loopback-mode: False + mtu: 2048 + name: loopback1111 + type: virt name: loopback1111 - type: virt - name: loopback1111 - subinterfaces: - subinterface: - - config: - description: subinterface configured by Ansible - 3 - enabled: True - index: 10 - index: 10 - - config: - description: subinterface configured by Ansible - 4 - enabled: False - index: 3 - index: 3 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 3 + enabled: True + index: 10 + index: 10 + - config: + description: subinterface configured by Ansible - 4 + enabled: False + index: 3 + index: 3 - name: Find the description of loopback111, subinterface index 10 debug: msg: |- {{ data.interfaces.interface[int_idx|int] - .subinterfaces.subinterface[subint_idx|int] - .config.description }} + .subinterfaces.subinterface[subint_idx|int] + .config.description }} vars: # the values to search for int_name: loopback1111 sub_index: 10 # retrieve the index in each nested list int_idx: | - {{ data.interfaces.interface| - ansible.utils.index_of('eq', int_name, 'name') }} + {{ lookup('ansible.utils.index_of', + data.interfaces.interface, + 'eq', int_name, 'name') }} subint_idx: | - {{ data.interfaces.interface[int_idx|int] - .subinterfaces.subinterface| - ansible.utils.index_of('eq', sub_index, 'index') }} + {{ lookup('ansible.utils.index_of', + data.interfaces.interface[int_idx|int].subinterfaces.subinterface, + 'eq', sub_index, 'index') }} # TASK [Find the description of loopback111, subinterface index 10] ************ # ok: [sw01] => # msg: subinterface configured by Ansible - 3 - - - """ RETURN = """ diff --git a/plugins/lookup/to_paths.py b/plugins/lookup/to_paths.py index b2ffc09..4c8e1c1 100644 --- a/plugins/lookup/to_paths.py +++ b/plugins/lookup/to_paths.py @@ -57,19 +57,12 @@ EXAMPLES = r""" - False - ansible.builtin.set_fact: - as_lookup: "{{ lookup('ansible.utils.to_paths', a) }}" - as_filter: "{{ a|ansible.utils.to_paths }}" + paths: "{{ lookup('ansible.utils.to_paths', a) }}" -# TASK [set_fact] ***************************************************** -# task path: /home/brad/github/dotbracket/site.yaml:17 -# ok: [localhost] => changed=false +# TASK [ansible.builtin.set_fact] ******************************************** +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: -# b.c.d[0]: 0 -# b.c.d[1]: 1 -# b.c.e[0]: true -# b.c.e[1]: false -# as_lookup: +# paths: # b.c.d[0]: 0 # b.c.d[1]: 1 # b.c.e[0]: true @@ -77,18 +70,12 @@ EXAMPLES = r""" - name: Use prepend to add the initial variable name ansible.builtin.set_fact: - as_lookup: "{{ lookup('ansible.utils.to_paths', a, prepend=('a')) }}" - as_filter: "{{ a|ansible.utils.to_paths(prepend='a') }}" + paths: "{{ lookup('ansible.utils.to_paths', a, prepend='a') }}" -# TASK [Use prepend to add the initial variable name] ***************** +# TASK [Use prepend to add the initial variable name] ************************** # ok: [nxos101] => changed=false # ansible_facts: -# as_filter: -# a.b.c.d[0]: 0 -# a.b.c.d[1]: 1 -# a.b.c.e[0]: true -# a.b.c.e[1]: false -# as_lookup: +# paths: # a.b.c.d[0]: 0 # a.b.c.d[1]: 1 # a.b.c.e[0]: true @@ -110,12 +97,12 @@ EXAMPLES = r""" - name: Flatten the complex object set_fact: - flattened: "{{ result.json|ansible.utils.to_paths }}" + paths: "{{ lookup('ansible.utils.to_paths', result.json) }}" -# TASK [Flatten the complex object] ******************** +# TASK [Flatten the complex object] ****************************************** # ok: [nxos101] => changed=false # ansible_facts: -# flattened: +# paths: # interfaces.interface[0].config.enabled: 'true' # interfaces.interface[0].config.mtu: '1500' # interfaces.interface[0].config.name: eth1/71 @@ -124,7 +111,8 @@ EXAMPLES = r""" # interfaces.interface[0].ethernet.state.counters['in-crc-errors']: '0' # interfaces.interface[0].ethernet.state.counters['in-fragment-frames']: '0' # interfaces.interface[0].ethernet.state.counters['in-jabber-frames']: '0' - +# interfaces.interface[0].ethernet.state.counters['in-mac-control-frames']: '0' +# <...> """ diff --git a/tests/integration/targets/get_path/tasks/include/examples_filter.yaml b/tests/integration/targets/get_path/tasks/include/examples_filter.yaml new file mode 100644 index 0000000..03c8ad4 --- /dev/null +++ b/tests/integration/targets/get_path/tasks/include/examples_filter.yaml @@ -0,0 +1,101 @@ +- ansible.builtin.set_fact: + a: + b: + c: + d: + - 0 + - 1 + e: + - True + - False + +- name: Retrieve a value deep inside a using a path + ansible.builtin.set_fact: + value: "{{ a|ansible.utils.get_path(path) }}" + vars: + path: b.c.d[0] + +# TASK [Retrieve a value deep inside a using a path] ****************** +# ok: [localhost] => changed=false +# ansible_facts: +# value: '0' + + +#### Working with hostvars + +- name: Retrieve a value deep inside all of the host's vars + ansible.builtin.set_fact: + value: "{{ look_in|ansible.utils.get_path(look_for) }}" + vars: + look_in: "{{ hostvars[inventory_hostname] }}" + look_for: a.b.c.d[0] + +# TASK [Retrieve a value deep inside all of the host's vars] ******** +# ok: [nxos101] => changed=false +# ansible_facts: +# as_filter: '0' +# as_lookup: '0' + + +#### Used alongside ansible.utils.to_paths + +- name: Get the paths for the object + ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths(prepend='a') }}" + +- name: Retrieve the value of each path from vars + ansible.builtin.debug: + msg: "The value of path {{ path }} in vars is {{ value }}" + loop: "{{ paths.keys()|list }}" + loop_control: + label: "{{ item }}" + vars: + path: "{{ item }}" + value: "{{ vars|ansible.utils.get_path(item) }}" + +# TASK [Get the paths for the object] ******************************* +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# a.b.c.d[0]: 0 +# a.b.c.d[1]: 1 +# a.b.c.e[0]: true +# a.b.c.e[1]: false + +# TASK [Retrieve the value of each path from vars] ****************** +# ok: [nxos101] => (item=a.b.c.d[0]) => +# msg: The value of path a.b.c.d[0] in vars is 0 +# ok: [nxos101] => (item=a.b.c.d[1]) => +# msg: The value of path a.b.c.d[1] in vars is 1 +# ok: [nxos101] => (item=a.b.c.e[0]) => +# msg: The value of path a.b.c.e[0] in vars is True +# ok: [nxos101] => (item=a.b.c.e[1]) => +# msg: The value of path a.b.c.e[1] in vars is False + + +#### Working with complex structures and transforming results + +# - name: Retrieve the current interface config +# cisco.nxos.nxos_interfaces: +# state: gathered +# register: interfaces + +# - name: Get the description of several interfaces +# ansible.builtin.debug: +# msg: "{{ rekeyed|ansible.utils.get_path(item) }}" +# vars: +# rekeyed: +# by_name: "{{ interfaces.gathered|ansible.builtin.rekey_on_member('name') }}" +# loop: +# - by_name['Ethernet1/1'].description +# - by_name['Ethernet1/2'].description|upper +# - by_name['Ethernet1/3'].description|default('') + + +# TASK [Get the description of several interfaces] ****************** +# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false +# msg: Configured by ansible +# ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false +# msg: CONFIGURED BY ANSIBLE +# ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false +# msg: '' diff --git a/tests/integration/targets/get_path/tasks/include/examples_lookup.yaml b/tests/integration/targets/get_path/tasks/include/examples_lookup.yaml new file mode 100644 index 0000000..ce4d262 --- /dev/null +++ b/tests/integration/targets/get_path/tasks/include/examples_lookup.yaml @@ -0,0 +1,101 @@ +- ansible.builtin.set_fact: + a: + b: + c: + d: + - 0 + - 1 + e: + - True + - False + +- name: Retrieve a value deep inside a using a path + ansible.builtin.set_fact: + value: "{{ lookup('ansible.utils.get_path', a, path) }}" + vars: + path: b.c.d[0] + +# TASK [Retrieve a value deep inside a using a path] ****************** +# ok: [localhost] => changed=false +# ansible_facts: +# value: '0' + + +#### Working with hostvars + +- name: Retrieve a value deep inside all of the host's vars + ansible.builtin.set_fact: + value: "{{ lookup('ansible.utils.get_path', look_in, look_for) }}" + vars: + look_in: "{{ hostvars[inventory_hostname] }}" + look_for: a.b.c.d[0] + +# TASK [Retrieve a value deep inside all of the host's vars] ******** +# ok: [nxos101] => changed=false +# ansible_facts: +# as_filter: '0' +# as_lookup: '0' + + +#### Used alongside ansible.utils.to_paths + +- name: Get the paths for the object + ansible.builtin.set_fact: + paths: "{{ lookup('ansible.utils.to_paths', a, prepend='a') }}" + +- name: Retrieve the value of each path from vars + ansible.builtin.debug: + msg: "The value of path {{ path }} in vars is {{ value }}" + loop: "{{ paths.keys()|list }}" + loop_control: + label: "{{ item }}" + vars: + path: "{{ item }}" + value: "{{ lookup('ansible.utils.get_path', hostvars[inventory_hostname], item) }}" + +# TASK [Get the paths for the object] ******************************* +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# a.b.c.d[0]: 0 +# a.b.c.d[1]: 1 +# a.b.c.e[0]: true +# a.b.c.e[1]: false + +# TASK [Retrieve the value of each path from vars] ****************** +# ok: [nxos101] => (item=a.b.c.d[0]) => +# msg: The value of path a.b.c.d[0] in vars is 0 +# ok: [nxos101] => (item=a.b.c.d[1]) => +# msg: The value of path a.b.c.d[1] in vars is 1 +# ok: [nxos101] => (item=a.b.c.e[0]) => +# msg: The value of path a.b.c.e[0] in vars is True +# ok: [nxos101] => (item=a.b.c.e[1]) => +# msg: The value of path a.b.c.e[1] in vars is False + + +#### Working with complex structures and transforming results + +# - name: Retrieve the current interface config +# cisco.nxos.nxos_interfaces: +# state: gathered +# register: interfaces + +# - name: Get the description of several interfaces +# ansible.builtin.debug: +# msg: "{{ lookup('ansible.utils.get_path', rekeyed, item) }}" +# vars: +# rekeyed: +# by_name: "{{ interfaces.gathered|ansible.builtin.rekey_on_member('name') }}" +# loop: +# - by_name['Ethernet1/1'].description +# - by_name['Ethernet1/2'].description|upper +# - by_name['Ethernet1/3'].description|default('') + + +# TASK [Get the description of several interfaces] ****************** +# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false +# msg: Configured by ansible +# ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false +# msg: CONFIGURED BY ANSIBLE +# ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false +# msg: '' diff --git a/tests/integration/targets/index_of/tasks/include/examples.yaml b/tests/integration/targets/index_of/tasks/include/examples_filter.yaml similarity index 64% rename from tests/integration/targets/index_of/tasks/include/examples.yaml rename to tests/integration/targets/index_of/tasks/include/examples_filter.yaml index 48e630e..776b409 100644 --- a/tests/integration/targets/index_of/tasks/include/examples.yaml +++ b/tests/integration/targets/index_of/tasks/include/examples_filter.yaml @@ -1,7 +1,4 @@ -# These are the examples for the lookup plugin -# here for future use if needed -# Note: the cisco example has been commented out -# to eliminate a cross collection dependancy +#### Simple examples - set_fact: data: @@ -9,49 +6,35 @@ - 2 - 3 -- name: Find the index of 2, lookup or filter +- name: Find the index of 2 set_fact: - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 2) }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 2) }}" + indices: "{{ data|ansible.utils.index_of('eq', 2) }}" -# TASK [Find the index of 2, lookup or filter] ******************************* -# ok: [sw01] => changed=false +# TASK [Find the index of 2] ************************************************* +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: '1' -# as_lookup: '1' +# indices: '1' -- name: Find the index of 2, lookup or filter, ensure list is returned +- name: Find the index of 2, ensure list is returned set_fact: - as_query: "{{ query('ansible.utils.index_of', data, 'eq', 2) }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 2, wantlist=True) }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" + indices: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}" -# TASK [Find the index of 2, lookup or filter, ensure list is returned] ****** -# ok: [sw01] => changed=false +# TASK [Find the index of 2, ensure list is returned] ************************ +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: -# - 1 -# as_lookup: -# - 1 -# as_query: +# indices: # - 1 - name: Find the index of 3 using the long format set_fact: - as_query: "{{ query('ansible.utils.index_of', data=data, test='eq', value=value) }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data=data, test='eq',value =value, wantlist=True) }}" - as_filter: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" + indices: "{{ data|ansible.utils.index_of(test='eq', value=value, wantlist=True) }}" vars: value: 3 # TASK [Find the index of 3 using the long format] *************************** -# ok: [sw01] => changed=false +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: -# - 2 -# as_lookup: -# - 2 -# as_query: +# indices: # - 2 - name: Find numbers greater than 1, using loop @@ -68,16 +51,8 @@ # ok: [sw01] => (item=2) => # msg: 3 is > than 1 -- name: Find numbers greater than 1, using with - debug: - msg: "{{ data[item] }} is {{ params.test }} than {{ params.value }}" - with_ansible.utils.index_of: "{{ params }}" - vars: - params: - data: "{{ data }}" - test: '>' - value: 1 +#### Working with lists of dictionaries - set_fact: data: @@ -92,50 +67,44 @@ - name: Find the index of all firewalls using the type key set_fact: - as_query: "{{ query('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" - as_lookup: "{{ lookup('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" - as_filter: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" + firewalls: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}" # TASK [Find the index of all firewalls using the type key] ****************** -# ok: [sw01] => changed=false +# ok: [nxos101] => changed=false # ansible_facts: -# as_filter: -# - 2 -# - 3 -# as_lookup: -# - 2 -# - 3 -# as_query: +# firewalls: # - 2 # - 3 -- name: Find the index of all firewalls, use in a loop, as a filter +- name: Find the index of all firewalls, use in a loop debug: msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." loop: "{{ data|ansible.utils.index_of('eq', device_type, 'type') }}" vars: device_type: firewall -# TASK [Find the index of all firewalls, use in a loop] ********************** -# ok: [sw01] => (item=2) => -# msg: The type of firewall at index 2 has name fw01. -# ok: [sw01] => (item=3) => -# msg: The type of firewall at index 3 has name fw02. +# TASK [Find the index of all firewalls, use in a loop, as a filter] ********* +# ok: [nxos101] => (item=2) => +# msg: The type of firewall at index 2 has name fw01.example.corp. +# ok: [nxos101] => (item=3) => +# msg: The type of firewall at index 3 has name fw02.example.corp. - -- name: Find the index of all devices with a .corp name, as a lookup +- name: Find the index of all devices with a .corp name debug: msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" - loop: "{{ lookup('ansible.utils.index_of', data, 'regex', regex, 'name') }}" + loop: "{{ data|ansible.utils.index_of('regex', expression, 'name') }}" vars: - regex: '\.corp$' # ends with .corp + expression: '\.corp$' # ends with .corp -# TASK [Find the index of all devices with a .corp name, as a lookup] ********** -# ok: [sw01] => (item=2) => +# TASK [Find the index of all devices with a .corp name] ********************* +# ok: [nxos101] => (item=2) => # msg: The device named fw01.example.corp is a firewall -# ok: [sw01] => (item=3) => +# ok: [nxos101] => (item=3) => # msg: The device named fw02.example.corp is a firewall + +#### Working with complex structures from resource modules + # - name: Retrieve the current L3 interface configuration # cisco.nxos.nxos_l3_interfaces: # state: gathered @@ -152,7 +121,7 @@ # - address: 192.168.101.14/24 # name: mgmt0 -# - name: Find the index of the interface and address with a 192.168.101.xx ip address +# - name: Find the indices interfaces with a 192.168.101.xx ip address # set_fact: # found: "{{ found + entry }}" # with_indexed_items: "{{ current_l3.gathered }}" @@ -180,10 +149,13 @@ # interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" # address: "{{ interface.ipv4[item.1].address }}" -# TASK [debug] *************************************************************** -# ok: [sw01] => (item=[{'interface_idx': '128', 'address_idx': [0]}, 0]) => +# TASK [Show all interfaces and their address] ******************************* +# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => # msg: mgmt0 has ip 192.168.101.14/24 + +#### Working with deeply nested data + - set_fact: data: interfaces: diff --git a/tests/integration/targets/index_of/tasks/include/examples_lookup.yaml b/tests/integration/targets/index_of/tasks/include/examples_lookup.yaml new file mode 100644 index 0000000..e2a52e2 --- /dev/null +++ b/tests/integration/targets/index_of/tasks/include/examples_lookup.yaml @@ -0,0 +1,243 @@ +#### Simple examples + +- set_fact: + data: + - 1 + - 2 + - 3 + +- name: Find the index of 2 + set_fact: + indices: "{{ lookup('ansible.utils.index_of', data, 'eq', 2) }}" + +# TASK [Find the index of 2] ************************************************* +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: '1' + +- name: Find the index of 2, ensure list is returned + set_fact: + indices: "{{ lookup('ansible.utils.index_of', data, 'eq', 2, wantlist=True) }}" + +# TASK [Find the index of 2, ensure list is returned] ************************ +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: +# - 1 + +- name: Find the index of 3 using the long format + set_fact: + indices: "{{ lookup('ansible.utils.index_of', data=data, test='eq', value=value, wantlist=True) }}" + vars: + value: 3 + +# TASK [Find the index of 3 using the long format] *************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# indices: +# - 2 + +- name: Find numbers greater than 1, using loop + debug: + msg: "{{ data[item] }} is {{ test }} than {{ value }}" + loop: "{{ lookup('ansible.utils.index_of', data, test, value) }}" + vars: + test: '>' + value: 1 + +# TASK [Find numbers great than 1, using loop] ******************************* +# ok: [sw01] => (item=1) => +# msg: 2 is > than 1 +# ok: [sw01] => (item=2) => +# msg: 3 is > than 1 + +- name: Find numbers greater than 1, using with + debug: + msg: "{{ data[item] }} is {{ params.test }} than {{ params.value }}" + with_ansible.utils.index_of: "{{ params }}" + vars: + params: + data: "{{ data }}" + test: '>' + value: 1 + +# TASK [Find numbers greater than 1, using with] ***************************** +# ok: [nxos101] => (item=1) => +# msg: 2 is > than 1 +# ok: [nxos101] => (item=2) => +# msg: 3 is > than 1 + + +#### Working with lists of dictionaries + +- set_fact: + data: + - name: sw01.example.lan + type: switch + - name: rtr01.example.lan + type: router + - name: fw01.example.corp + type: firewall + - name: fw02.example.corp + type: firewall + +- name: Find the index of all firewalls using the type key + set_fact: + firewalls: "{{ lookup('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}" + +# TASK [Find the index of all firewalls using the type key] ****************** +# ok: [nxos101] => changed=false +# ansible_facts: +# firewalls: +# - 2 +# - 3 + +- name: Find the index of all firewalls, use in a loop + debug: + msg: "The type of {{ device_type }} at index {{ item }} has name {{ data[item].name }}." + loop: "{{ lookup('ansible.utils.index_of', data, 'eq', device_type, 'type') }}" + vars: + device_type: firewall + +# TASK [Find the index of all firewalls, use in a loop, as a filter] ********* +# ok: [nxos101] => (item=2) => +# msg: The type of firewall at index 2 has name fw01.example.corp. +# ok: [nxos101] => (item=3) => +# msg: The type of firewall at index 3 has name fw02.example.corp. + +- name: Find the index of all devices with a .corp name + debug: + msg: "The device named {{ data[item].name }} is a {{ data[item].type }}" + loop: "{{ lookup('ansible.utils.index_of', data, 'regex', expression, 'name') }}" + vars: + expression: '\.corp$' # ends with .corp + +# TASK [Find the index of all devices with a .corp name] ********************* +# ok: [nxos101] => (item=2) => +# msg: The device named fw01.example.corp is a firewall +# ok: [nxos101] => (item=3) => +# msg: The device named fw02.example.corp is a firewall + + +#### Working with complex structures from resource modules + +# - name: Retrieve the current L3 interface configuration +# cisco.nxos.nxos_l3_interfaces: +# state: gathered +# register: current_l3 + +# TASK [Retrieve the current L3 interface configuration] ********************* +# ok: [sw01] => changed=false +# gathered: +# - name: Ethernet1/1 +# - name: Ethernet1/2 +# <...> +# - name: Ethernet1/128 +# - ipv4: +# - address: 192.168.101.14/24 +# name: mgmt0 + +# - name: Find the indices interfaces with a 192.168.101.xx ip address +# set_fact: +# found: "{{ found + entry }}" +# with_indexed_items: "{{ current_l3.gathered }}" +# vars: +# found: [] +# ip: '192.168.101.' +# address: "{{ lookup('ansible.utils.index_of', item.1.ipv4|d([]), 'search', ip, 'address', wantlist=True) }}" +# entry: +# - interface_idx: "{{ item.0 }}" +# address_idxs: "{{ address }}" +# when: address + +# TASK [debug] *************************************************************** +# ok: [sw01] => +# found: +# - address_idxs: +# - 0 +# interface_idx: '128' + +# - name: Show all interfaces and their address +# debug: +# msg: "{{ interface.name }} has ip {{ address }}" +# loop: "{{ found|subelements('address_idxs') }}" +# vars: +# interface: "{{ current_l3.gathered[item.0.interface_idx|int] }}" +# address: "{{ interface.ipv4[item.1].address }}" + +# TASK [Show all interfaces and their address] ******************************* +# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) => +# msg: mgmt0 has ip 192.168.101.14/24 + + +#### Working with deeply nested data + +- set_fact: + data: + interfaces: + interface: + - config: + description: configured by Ansible - 1 + enabled: True + loopback-mode: False + mtu: 1024 + name: loopback0000 + type: eth + name: loopback0000 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 1 + enabled: True + index: 5 + index: 5 + - config: + description: subinterface configured by Ansible - 2 + enabled: False + index: 2 + index: 2 + - config: + description: configured by Ansible - 2 + enabled: False + loopback-mode: False + mtu: 2048 + name: loopback1111 + type: virt + name: loopback1111 + subinterfaces: + subinterface: + - config: + description: subinterface configured by Ansible - 3 + enabled: True + index: 10 + index: 10 + - config: + description: subinterface configured by Ansible - 4 + enabled: False + index: 3 + index: 3 + + +- name: Find the description of loopback111, subinterface index 10 + debug: + msg: |- + {{ data.interfaces.interface[int_idx|int] + .subinterfaces.subinterface[subint_idx|int] + .config.description }} + vars: + # the values to search for + int_name: loopback1111 + sub_index: 10 + # retrieve the index in each nested list + int_idx: | + {{ lookup('ansible.utils.index_of', + data.interfaces.interface, + 'eq', int_name, 'name') }} + subint_idx: | + {{ lookup('ansible.utils.index_of', + data.interfaces.interface[int_idx|int].subinterfaces.subinterface, + 'eq', sub_index, 'index') }} + +# TASK [Find the description of loopback111, subinterface index 10] ************ +# ok: [sw01] => +# msg: subinterface configured by Ansible - 3 diff --git a/tests/integration/targets/to_paths/tasks/include/examples_filter.yaml b/tests/integration/targets/to_paths/tasks/include/examples_filter.yaml new file mode 100644 index 0000000..9e45019 --- /dev/null +++ b/tests/integration/targets/to_paths/tasks/include/examples_filter.yaml @@ -0,0 +1,70 @@ +#### Simple examples + +- ansible.builtin.set_fact: + a: + b: + c: + d: + - 0 + - 1 + e: + - True + - False + +- ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths }}" + +# TASK [ansible.builtin.set_fact] ******************************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# b.c.d[0]: 0 +# b.c.d[1]: 1 +# b.c.e[0]: true +# b.c.e[1]: false + +- name: Use prepend to add the initial variable name + ansible.builtin.set_fact: + paths: "{{ a|ansible.utils.to_paths(prepend='a') }}" + +# TASK [Use prepend to add the initial variable name] ************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# a.b.c.d[0]: 0 +# a.b.c.d[1]: 1 +# a.b.c.e[0]: true +# a.b.c.e[1]: false + + +#### Using a complex object + +# - name: Make an API call +# uri: +# url: "https://nxos101/restconf/data/openconfig-interfaces:interfaces" +# headers: +# accept: "application/yang.data+json" +# url_password: password +# url_username: admin +# validate_certs: False +# register: result +# delegate_to: localhost + +# - name: Flatten the complex object +# set_fact: +# paths: "{{ result.json|ansible.utils.to_paths }}" + +# TASK [Flatten the complex object] ****************************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# interfaces.interface[0].config.enabled: 'true' +# interfaces.interface[0].config.mtu: '1500' +# interfaces.interface[0].config.name: eth1/71 +# interfaces.interface[0].config.type: ethernetCsmacd +# interfaces.interface[0].ethernet.config['auto-negotiate']: 'true' +# interfaces.interface[0].ethernet.state.counters['in-crc-errors']: '0' +# interfaces.interface[0].ethernet.state.counters['in-fragment-frames']: '0' +# interfaces.interface[0].ethernet.state.counters['in-jabber-frames']: '0' +# interfaces.interface[0].ethernet.state.counters['in-mac-control-frames']: '0' +# <...> diff --git a/tests/integration/targets/to_paths/tasks/include/examples_lookup.yaml b/tests/integration/targets/to_paths/tasks/include/examples_lookup.yaml new file mode 100644 index 0000000..0701977 --- /dev/null +++ b/tests/integration/targets/to_paths/tasks/include/examples_lookup.yaml @@ -0,0 +1,70 @@ +#### Simple examples + +- ansible.builtin.set_fact: + a: + b: + c: + d: + - 0 + - 1 + e: + - True + - False + +- ansible.builtin.set_fact: + paths: "{{ lookup('ansible.utils.to_paths', a) }}" + +# TASK [ansible.builtin.set_fact] ******************************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# b.c.d[0]: 0 +# b.c.d[1]: 1 +# b.c.e[0]: true +# b.c.e[1]: false + +- name: Use prepend to add the initial variable name + ansible.builtin.set_fact: + paths: "{{ lookup('ansible.utils.to_paths', a, prepend='a') }}" + +# TASK [Use prepend to add the initial variable name] ************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# a.b.c.d[0]: 0 +# a.b.c.d[1]: 1 +# a.b.c.e[0]: true +# a.b.c.e[1]: false + + +#### Using a complex object + +# - name: Make an API call +# uri: +# url: "https://nxos101/restconf/data/openconfig-interfaces:interfaces" +# headers: +# accept: "application/yang.data+json" +# url_password: password +# url_username: admin +# validate_certs: False +# register: result +# delegate_to: localhost + +# - name: Flatten the complex object +# set_fact: +# paths: "{{ lookup('ansible.utils.to_paths', result.json) }}" + +# TASK [Flatten the complex object] ****************************************** +# ok: [nxos101] => changed=false +# ansible_facts: +# paths: +# interfaces.interface[0].config.enabled: 'true' +# interfaces.interface[0].config.mtu: '1500' +# interfaces.interface[0].config.name: eth1/71 +# interfaces.interface[0].config.type: ethernetCsmacd +# interfaces.interface[0].ethernet.config['auto-negotiate']: 'true' +# interfaces.interface[0].ethernet.state.counters['in-crc-errors']: '0' +# interfaces.interface[0].ethernet.state.counters['in-fragment-frames']: '0' +# interfaces.interface[0].ethernet.state.counters['in-jabber-frames']: '0' +# interfaces.interface[0].ethernet.state.counters['in-mac-control-frames']: '0' +# <...> diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 61c150e..f6a0f43 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -1,4 +1 @@ plugins/module_utils/common/index_of.py pylint:ansible-bad-module-import # file's use is limited to filter and lookups on control node -plugins/filter/index_of.py pep8:E501 # ignore line length for filter doc string w/ url -plugins/filter/get_path.py pep8:E501 # ignore line length for filter doc string w/ url -plugins/filter/to_paths.py pep8:E501 # ignore line length for filter doc string w/ url diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 61c150e..f6a0f43 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -1,4 +1 @@ plugins/module_utils/common/index_of.py pylint:ansible-bad-module-import # file's use is limited to filter and lookups on control node -plugins/filter/index_of.py pep8:E501 # ignore line length for filter doc string w/ url -plugins/filter/get_path.py pep8:E501 # ignore line length for filter doc string w/ url -plugins/filter/to_paths.py pep8:E501 # ignore line length for filter doc string w/ url diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index 61c150e..f6a0f43 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,4 +1 @@ plugins/module_utils/common/index_of.py pylint:ansible-bad-module-import # file's use is limited to filter and lookups on control node -plugins/filter/index_of.py pep8:E501 # ignore line length for filter doc string w/ url -plugins/filter/get_path.py pep8:E501 # ignore line length for filter doc string w/ url -plugins/filter/to_paths.py pep8:E501 # ignore line length for filter doc string w/ url