Transforming Inventory Data

Imagine your data looks like:

[1]:
%cat transforming_inventory_data/inventory/hosts.yaml
---
rtr00:
    data:
        user: automation_user
rtr01:
    data:
        user: automation_user

And we want to do two things:

  1. Map user to username
  2. Prompt the user for the password and add it

You can easily do that using a transform_function. For instance:

Modifying hosts’ data

You can modify inventory data (regardless of the plugin you are using) on the fly easily by passing a transform_function like this:

[2]:
from nornir import InitNornir
import pprint

def adapt_host_data(host):
    # This function receives a Host object for manipulation
    host.username = host["user"]

nr = InitNornir(
    inventory={
            "plugin": "nornir.plugins.inventory.simple.SimpleInventory",
            "options": {
                "host_file": "transforming_inventory_data/inventory/hosts.yaml",
                "group_file": "transforming_inventory_data/inventory/groups.yaml",
            },
            "transform_function": adapt_host_data,
    },
)
for name, host in nr.inventory.hosts.items():
    print(f"{name}.username: {host.username}")
rtr00.username: automation_user
rtr01.username: automation_user

Specifying the transform function in the configuration

You can also specify the transform function in the configuration. In order for that to work the function must be importable. For instance:

[3]:
%cat transforming_inventory_data/helpers.py
def adapt_host_data(host):
    # This function receives a Host object for manipulation
    host.username = host["user"]

Now let’s verify we can, indeed, import the function:

[4]:
from transforming_inventory_data.helpers import adapt_host_data
adapt_host_data
[4]:
<function transforming_inventory_data.helpers.adapt_host_data(host)>

Now the only thing left to do is put the import path as the transform_function configuration option:

[5]:
%cat transforming_inventory_data/config.yaml
---
inventory:
    plugin: nornir.plugins.inventory.simple.SimpleInventory
    options:
        host_file: "transforming_inventory_data/inventory/hosts.yaml"
        group_file: "transforming_inventory_data/inventory/groups.yaml"
    transform_function: "transforming_inventory_data.helpers.adapt_host_data"

And then we won’t need to specify it when calling InitNornir:

[6]:
from nornir import InitNornir
import pprint

nr = InitNornir(
    config_file="transforming_inventory_data/config.yaml",
)
for name, host in nr.inventory.hosts.items():
    print(f"{name}.username: {host.username}")
rtr00.username: automation_user
rtr01.username: automation_user

Setting a default password

You might be in a situation where you may want to prompt the user for some information, for instance, a password. You can leverage the defaults for something like this (although you could just put in under the hosts themselves or the groups). Let’s see an example:

[7]:
from nornir import InitNornir

# let's pretend we used raw_input or something like that
# password = raw_input("Please, enter password: ")
password = "a_secret_password"

nr = InitNornir(
    config_file="transforming_inventory_data/config.yaml",
)
print("Before setting password: ", nr.inventory.hosts["rtr00"].password)
nr.inventory.defaults.password = password
print("After setting password: ", nr.inventory.hosts["rtr00"].password)
print("After setting password: ", nr.inventory.defaults.password)
Before setting password:  None
After setting password:  a_secret_password
After setting password:  a_secret_password

For more information on how inheritance works check the tutorial section inheritance model

Passing options to the transform function

You can also pass options to the transform function, like in this example:

[8]:
from nornir import InitNornir


def adapt_host_data(host, password, something_else):
    host.data["password"] = password


options = {
    "password": "a_secret_password",
    "something_else": "unused"
}

nr = InitNornir(
    inventory={
        "plugin": "nornir.plugins.inventory.simple.SimpleInventory",
        "options": {
            "host_file": "transforming_inventory_data/inventory/hosts.yaml",
            "group_file": "transforming_inventory_data/inventory/groups.yaml",
        },
        "transform_function": adapt_host_data,
        "transform_function_options": options,
    }
)
for name, host in nr.inventory.hosts.items():
    print(f"{name}.password: {host.password}")
rtr00.password: None
rtr01.password: None

Using ConnectionOptions

Additionally, you might come across the need to include certain ConnectionOptions to be able to connect to your devices.
Documentation specific to nornir can be found here and for napalm can be found here.

The following example shows how to correctly do so in the helpers.py:

[9]:
from nornir.core.inventory import ConnectionOptions

def adapt_host_data(host):

    # This function receives a Host object for manipulation
    host.username = host["user"]
    host.password = host["password"]
    host.connection_options["napalm"] = ConnectionOptions(
        extras={
            "optional_args": {
                "secret":host.password
            }
        }
    )

You can also specify them as a default for your entire inventory in this manner:

[10]:
from nornir.core.inventory import ConnectionOptions

nr.inventory.defaults.connection_options = ConnectionOptions(extras={"optional_args":{"secret":"my_secret"}})