get_path and to_paths

pull/1/head
cidrblock 2020-10-09 10:07:26 -07:00
parent 0ffadc48c6
commit 9d3a6cad0a
11 changed files with 974 additions and 63 deletions

View File

@ -1,41 +1,76 @@
# collection_template
You can build a new repository for an Ansible Collection using this template by following [Creating a repository from a template](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template). This README.md contains recommended headings for your collection README.md, with comments describing what each section should contain. Once you have created your collection repository, delete this paragraph and the title above it from your README.md.
# Foo Collection
<!-- Add CI and code coverage badges here. Samples included below. -->
[![CI](https://github.com/ansible-collections/REPONAMEHERE/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/REPONAMEHERE/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/REPONAMEHERE)](https://codecov.io/gh/ansible-collections/REPONAMEHERE)
<!-- Describe the collection and why a user would want to use it. What does the collection do? -->
# Ansible Utilities Collection
[![CI](https://zuul-ci.org/gated.svg)](https://dashboard.zuul.ansible.com/t/ansible/builds?project=ansible-collections%2Fansible.utils) <!--[![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/ansible.utils)](https://codecov.io/gh/ansible-collections/ansible.utils)-->
## Tested with Ansible
The Ansible ``ansible.utils`` collection includes FIXME
<!-- List the versions of Ansible the collection has been tested with. Must match what is in galaxy.yml. -->
<!--start requires_ansible-->
## Ansible version compatibility
## External requirements
This collection has been tested against following Ansible versions: **>=2.9.10,<2.11**.
<!-- List any external resources the collection depends on, for example minimum versions of an OS, libraries, or utilities. Do not list other Ansible collections here. -->
### Supported connections
<!-- Optional. If your collection supports only specific connection types (such as HTTPAPI, netconf, or others), list them here. -->
Plugins and modules within a collection may be tested with only specific Ansible versions.
A collection may contain metadata that identifies these versions.
PEP440 is the schema used to describe the versions of Ansible.
<!--end requires_ansible-->
## Included content
<!-- Galaxy will eventually list the module docs within the UI, but until that is ready, you may need to either describe your plugins etc here, or point to an external docsite to cover that information. -->
<!--start collection content-->
### Filter plugins
Name | Description
--- | ---
ansible.utils.get_path|Get the value within 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.to_paths|Convert complex objects to paths. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_paths_lookup.rst)
### Lookup plugins
Name | Description
--- | ---
[ansible.utils.get_path](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
[ansible.utils.to_paths](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
<!--end collection content-->
## Installing this collection
You can install the ``ansible.utils`` collection with the Ansible Galaxy CLI:
ansible-galaxy collection install ansible.utils
You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format:
```yaml
---
collections:
- name: ansible.utils
```
## Using this collection
<!--Include some quick examples that cover the most common use cases for your collection content. -->
The most common use case for this collection is FIXME
See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
**NOTE**: For Ansible 2.9, you may not see deprecation warnings when you run your playbooks with this collection. Use this documentation to track when a module is deprecated.
### See Also:
* [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
## Contributing to this collection
<!--Describe how the community can contribute to your collection. At a minimum, include how and where users can create issues to report problems or request features for this collection. List contribution requirements, including preferred workflows and necessary testing, so you can benefit from community PRs. If you are following general Ansible contributor guidelines, you can link to - [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html). -->
We welcome community contributions to this collection. If you find problems, please open an issue or create a PR against the [ansible.utils collection repository](https://github.com/ansible-collections/ansible.utils). See [Contributing to Ansible-maintained collections](https://docs.ansible.com/ansible/devel/community/contributing_maintained_collections.html#contributing-maintained-collections) for complete details.
See the [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html) for details on contributing to Ansible.
### Code of Conduct
This collection follows the Ansible project's
[Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html).
Please read and familiarize yourself with this document.
## Release notes
See the [changelog](https://github.com/ansible-collections/REPONAMEHERE/tree/main/CHANGELOG.rst).
<!--Add a link to a changelog.md file or an external docsite to cover this information. -->
Release notes are available [here](https://github.com/ansible-collections/ansible.utils/blob/main/changelogs/CHANGELOG.rst)
## Roadmap
@ -43,20 +78,13 @@ See the [changelog](https://github.com/ansible-collections/REPONAMEHERE/tree/mai
## More information
<!-- List out where the user can find additional information, such as working group meeting times, slack/IRC channels, or documentation for the product this collection automates. At a minimum, link to: -->
- [Ansible Collection overview](https://github.com/ansible-collections/overview)
- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html)
- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html)
- [Ansible Collections Checklist](https://github.com/ansible-collections/overview/blob/master/collection_requirements.rst)
- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html)
- [The Bullhorn (the Ansible Contributor newsletter)](https://us19.campaign-archive.com/home/?u=56d874e027110e35dea0e03c1&id=d6635f5420)
- [Changes impacting Contributors](https://github.com/ansible-collections/overview/issues/45)
## Licensing
<!-- Include the appropriate license information here and a pointer to the full licensing details. If the collection contains modules migrated from the ansible/ansible repo, you must use the same license that existed in the ansible/ansible repo. See the GNU license example below. -->
GNU General Public License v3.0 or later.
See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.

View File

@ -0,0 +1,270 @@
.. _ansible.utils.get_path_lookup:
**********************
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 ``filter_plugin`` for convenience
Parameters
----------
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th>Configuration</th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>_terms</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">-</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>The values below provided in the order <code>var</code>, <code>path</code>, <code>wantlist=</code>.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>path</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>The <code>path</code> in the <code>var</code> to retrieve the value of. The <code>path</code> needs to a be a valid jinja path</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>var</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">raw</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>The variable from which the value should be extraced</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>wantlist</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
</td>
<td>
<div>If set to <code>True</code>, the return value will always be a list This can also be accomplished using <code>query</code> or <code>q</code> instead of <code>lookup</code> <a href='https://docs.ansible.com/ansible/latest/plugins/lookup.html'>https://docs.ansible.com/ansible/latest/plugins/lookup.html</a></div>
</td>
</tr>
</table>
<br/>
Examples
--------
.. code-block:: yaml+jinja
- 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:
as_lookup: "{{ lookup('ansible.utils.get_path', a, path) }}"
as_filter: "{{ a|ansible.utils.get_path(path) }}"
vars:
path: b.c.d[0]
# TASK [ansible.builtin.set_fact] *************************************
# ok: [nxos101] => changed=false
# ansible_facts:
# as_filter: '0'
# as_lookup: '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) }}"
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
- 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
# 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
Return Values
-------------
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this lookup:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>_raw</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">-</span>
</div>
</td>
<td></td>
<td>
<div>One or more zero-based indicies of the matching list items</div>
<div>See <code>wantlist</code> if a list is always required</div>
<br/>
</td>
</tr>
</table>
<br/><br/>
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.

View File

@ -0,0 +1,253 @@
.. _ansible.utils.to_paths_lookup:
**********************
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 filter plugin
Parameters
----------
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Parameter</th>
<th>Choices/<font color="blue">Defaults</font></th>
<th>Configuration</th>
<th width="100%">Comments</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>_terms</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">-</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>The values below provided in the order <code>var</code>, <code>prepend=</code>, <code>wantlist=</code>.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>prepend</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>Prepend each path entry. Useful to add the initial <code>var</code> name.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>var</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">raw</span>
/ <span style="color: red">required</span>
</div>
</td>
<td>
</td>
<td>
</td>
<td>
<div>The value of <code>var</code> will be will be used.</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>wantlist</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
</td>
<td>
<div>If set to <code>True</code>, the return value will always be a list. This can also be accomplished using <code>query</code> or <code>q</code> instead of <code>lookup</code>. <a href='https://docs.ansible.com/ansible/latest/plugins/lookup.html'>https://docs.ansible.com/ansible/latest/plugins/lookup.html</a></div>
</td>
</tr>
</table>
<br/>
Examples
--------
.. code-block:: yaml+jinja
#### Simple examples
- ansible.builtin.set_fact:
a:
b:
c:
d:
- 0
- 1
e:
- True
- False
- ansible.builtin.set_fact:
as_lookup: "{{ lookup('ansible.utils.to_paths', a) }}"
as_filter: "{{ a|ansible.utils.to_paths }}"
# TASK [set_fact] *****************************************************
# task path: /home/brad/github/dotbracket/site.yaml:17
# ok: [localhost] => 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:
# 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:
as_lookup: "{{ lookup('ansible.utils.to_paths', a, prepend=('a')) }}"
as_filter: "{{ a|ansible.utils.to_paths(prepend='a') }}"
# 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:
# 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:
flattened: "{{ result.json|ansible.utils.to_paths }}"
# TASK [Flatten the complex object] ********************
# ok: [nxos101] => changed=false
# ansible_facts:
# flattened:
# 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'
Return Values
-------------
Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this lookup:
.. raw:: html
<table border=0 cellpadding=0 class="documentation-table">
<tr>
<th colspan="1">Key</th>
<th>Returned</th>
<th width="100%">Description</th>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>_raw</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">-</span>
</div>
</td>
<td></td>
<td>
<div>A dictionary of key value pairs</div>
<div>The key is the path</div>
<div>The value is the value</div>
<br/>
</td>
</tr>
</table>
<br/><br/>
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.

View File

@ -1,23 +1,13 @@
# See https://docs.ansible.com/ansible/latest/dev_guide/collections_galaxy_meta.html
namespace: community
name: FIXME
version: 0.1.0
readme: README.md
---
authors:
- YOUR NAME (github.com/YOURGITHUB)
description: null
license_file: COPYING
tags:
# tags so people can search for collections https://galaxy.ansible.com/search
# tags are all lower-case, no spaces, no dashes.
- example1
- example2
repository: https://github.com/ansible-collections/community.REPO_NAME
#documentation: https://github.com/ansible-collection-migration/community.REPO_NAME/tree/main/docs
homepage: https://github.com/ansible-collections/community.REPO_NAME
issues: https://github.com/ansible-collections/community.REPO_NAME/issues
build_ignore:
# https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#ignoring-files-and-folders
- .gitignore
- changelogs/.plugin-cache.yaml
- Ansible Community
license_file: LICENSE
name: utils
namespace: ansible
description: Ansible Collection with utilities to ease the management, manipulation, and validation of data within a playbook
readme: README.md
repository: https://github.com/ansible-collections/ansible.utils
tags: [networking, security, cloud, utilities, data, validation]
# NOTE(pabelanger): We create an empty version key to keep ansible-galaxy
# happy. We dynamically inject version info based on git information.
version: null

View File

@ -1,2 +1,2 @@
---
requires_ansible: '>=2.9.10'
requires_ansible: '>=2.9.10,<2.11'

View File

@ -17,24 +17,30 @@ from ansible.module_utils.common._collections_compat import (
MutableMapping,
)
from ansible_collections.ansible.utils.plugins.module_utils.generate_paths import (
generate_paths,
from ansible_collections.ansible.utils.plugins.module_utils.path_utils import (
to_paths,
get_path,
)
from jinja2.filters import environmentfilter
def to_paths(obj, prepend=None):
return generate_paths(obj, prepend)
def _to_paths(*args, **kwargs):
""" Convert complex objects to paths. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_paths_lookup.rst)
"""
return to_paths(*args, **kwargs)
@environmentfilter
def get_path(environment, vars, path):
string_to_variable = "{{ %s }}" % path
return environment.from_string(string_to_variable).render(**vars)
def _get_path(*args, **kwargs):
""" Get value using path. [See examples](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.get_path_lookup.rst)
"""
kwargs["environment"] = args[0]
args = args[1:]
return get_path(*args, **kwargs)
class FilterModule(object):
""" Network filter """
""" path filters """
def filters(self):
return {"to_paths": to_paths, "get_path": get_path}
return {"to_paths": _to_paths, "get_path": _get_path}

174
plugins/lookup/get_path.py Normal file
View File

@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
# Copyright 2020 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The get_path lookup plugin
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
lookup: 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(filter_plugin) for convenience
options:
_terms:
description: The values below provided in the order C(var), C(path), C(wantlist=).
required: True
var:
description: The variable from which the value should be extraced
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
This can also be accomplished using C(query) or C(q) instead of C(lookup)
U(https://docs.ansible.com/ansible/latest/plugins/lookup.html)
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:
as_lookup: "{{ lookup('ansible.utils.get_path', a, path) }}"
as_filter: "{{ a|ansible.utils.get_path(path) }}"
vars:
path: b.c.d[0]
# TASK [ansible.builtin.set_fact] *************************************
# ok: [nxos101] => changed=false
# ansible_facts:
# as_filter: '0'
# as_lookup: '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) }}"
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
- 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
# 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
"""
RETURN = """
_raw:
description:
- One or more zero-based indicies of the matching list items
- See C(wantlist) if a list is always required
"""
from ansible.plugins.lookup import LookupBase
from ansible_collections.ansible.utils.plugins.module_utils.path_utils import (
get_path,
)
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
kwargs["environment"] = self._templar.environment
if isinstance(terms, dict):
terms.update(kwargs)
res = get_path(**terms)
else:
res = get_path(*terms, **kwargs)
if not isinstance(res, list):
return [res]
return res

157
plugins/lookup/to_paths.py Normal file
View File

@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
# Copyright 2020 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The to_paths lookup plugin
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
lookup: 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 filter plugin
options:
_terms:
description: The values below provided in the order C(var), C(prepend=), C(wantlist=).
required: True
var:
description: The value of C(var) will be will be used.
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.
This can also be accomplished using C(query) or C(q) instead of C(lookup).
U(https://docs.ansible.com/ansible/latest/plugins/lookup.html)
type: bool
notes:
"""
EXAMPLES = r"""
#### Simple examples
- ansible.builtin.set_fact:
a:
b:
c:
d:
- 0
- 1
e:
- True
- False
- ansible.builtin.set_fact:
as_lookup: "{{ lookup('ansible.utils.to_paths', a) }}"
as_filter: "{{ a|ansible.utils.to_paths }}"
# TASK [set_fact] *****************************************************
# task path: /home/brad/github/dotbracket/site.yaml:17
# ok: [localhost] => 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:
# 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:
as_lookup: "{{ lookup('ansible.utils.to_paths', a, prepend=('a')) }}"
as_filter: "{{ a|ansible.utils.to_paths(prepend='a') }}"
# 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:
# 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:
flattened: "{{ result.json|ansible.utils.to_paths }}"
# TASK [Flatten the complex object] ********************
# ok: [nxos101] => changed=false
# ansible_facts:
# flattened:
# 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'
"""
RETURN = """
_raw:
description:
- A dictionary of key value pairs
- The key is the path
- The value is the value
"""
from ansible.plugins.lookup import LookupBase
from ansible_collections.ansible.utils.plugins.module_utils.path_utils import (
to_paths,
)
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
if isinstance(terms, dict):
terms.update(kwargs)
res = to_paths(**terms)
else:
res = to_paths(*terms, **kwargs)
if not isinstance(res, list):
return [res]
return res

View File

@ -17,8 +17,40 @@ from ansible.module_utils.common._collections_compat import (
MutableMapping,
)
# Note, this file can only be used on the control node
# where ansible is installed
# limit imports to filter and lookup plugins
try:
from ansible.errors import AnsibleError
except ImportError:
pass
def get_path(var, path, environment, wantlist=False):
""" Get the value of a path within an object
:param var: The var from which the value is retrieved
:type var: should be dict or list, but jinja can sort that out
:param path: The path to get
:type path: should be a string but jinja can sort that out
:param environment: The jinja Environment
:type environment: Environment
:return: The result of the jinja evaluation
:rtype: any
"""
string_to_variable = "{{ %s }}" % path
result = environment.from_string(string_to_variable).render(**var)
if wantlist:
return [result]
return result
def to_paths(var, prepend=False, wantlist=False):
if prepend:
if not isinstance(prepend, str):
raise AnsibleError("The value of 'prepend' must be a sting.")
var = {prepend: var}
def generate_paths(nested_json, prepend):
out = {}
def flatten(data, name=""):
@ -38,8 +70,7 @@ def generate_paths(nested_json, prepend):
else:
out[name] = data
if prepend:
flatten({prepend: nested_json})
else:
flatten(nested_json)
flatten(var)
if wantlist:
return [out]
return out

View File

@ -0,0 +1 @@
plugins/module_utils/path_utils.py pylint:ansible-bad-module-import # file's use is limited to filter and lookups on control node

View File

@ -0,0 +1 @@
plugins/module_utils/path_utils.py pylint:ansible-bad-module-import # file's use is limited to filter and lookups on control node