Simplifying Network Automation Workflows with Infrahub, Nornir, and Jinja2

Overview

In this blog post, we will explore how InfraHub integrates with Jinja2 and Nornir to simplify network automation workflows. To demonstrate, we’ll add two Arista devices to InfraHub, treating them as basic access switches. We’ll then input the necessary details for these devices to generate configurations. We’ll focus on creating VLAN and some interface configurations to keep it simple.

For each device, we’ll assign a primary IP (used for SSH), configure a few interfaces with descriptions, and specify an untagged VLAN for each interface. Additionally, we’ll define these VLANs globally in InfraHub (not tied to any specific device). A Jinja2 template will then use this information to generate configurations for each device. Finally, we’ll use the nornir-infrahub plugin as the inventory source and Napalm to push the generated configurations to each device.

Prerequisites

This blog post assumes you are somewhat familiar with Git and Docker. If you’re new to InfraHub, don’t worry, you should still be able to follow along. Make sure Git and Docker are installed on your local machine before getting started.

You’ll also need the following tools as we proceed.

  • Infrahub – Of course, you need an Infrahub instance to follow along. 
  • Infrahubctl – This is the CLI tool used to interact with InfraHub.
  • InfraHub Python SDK – This is required for programmatically creating and managing data in InfraHub.

Let’s install them using pip. To keep your environment clean, create a Python virtual environment to isolate the packages.

python -m venv venv
source venv/bin/activate

pip install 'infrahub-sdk[ctl]'

Lastly, you’ll need to set an environment variable for the API key/token. Generate a token in InfraHub as shown in the screenshot and export it as an environment variable.

AD 4nXf 6nXfT3qMSR1jtYs27 h6o64lQdleaBCHQw NjILvIx1O7DAaqcEzJZvn7Jk1 SBhyH7IeHU6kfrWDrnwQZnmA7kQj97cB1l4UNWoa7uz8iyHE0QV6uldRsi1byuS Y61J9iZ?key=nujqMd3jy0HFJ2J0wbnHeZO
export INFRAHUB_API_TOKEN="1803a5a3-8cf7-ec6b-35cb-c51a83c2a410"

This blog post is based on InfrahHub v1.1.0 and uses the following schemas from the Schema Library. 

  • schema-library/base/
  • schema-library/extensions/vlan/
  • schema-library/extensions/location_minimal/

To import the schemas into your Infrahub instance, first clone the schema-library GitHub repo.

git clone https://github.com/opsmill/schema-library.git

Next, import the schemas using infrahubctl CLI tool.

infrahubctl schema load /schema-library/base/
infrahubctl schema load /schema-library/extensions/vlan/
infrahubctl schema load /schema-library/extensions/location_minimal/

The Components Needed

To get started with the example, let’s create some data in InfraHub. To keep it simple, we’ll focus on creating IP addresses, VLANs, devices, and interfaces. You can add this data in several ways, including the web GUI, GraphQL queries, or the Python SDK.

We’ve included a Python script (to be added) to help you quickly add all the necessary data into InfraHub, but feel free to use whichever method works best for you. Let’s start by creating a location and three VLANs in Infrahub, each with an ID and a name using the web GUI. 

First, create a location by navigating to the Location > Site and create a Site called ‘HQ’

AD 4nXfgJrSPin81tfB3 sO0Qhi1o63Kwpb3BqAyQ7ci6 WU nYZuVfk1A6WcNhICbPFy1iR gBGo uzS2Db4jnnxciDP1w5fKblCb5v5GRWTKaxfbYtBRl c2GQTXhyPmOKi4Ope3O9zQ?key=nujqMd3jy0HFJ2J0wbnHeZO

Next, navigate to Layer 2 Domain and create a domain called ‘campus’. 

AD 4nXepSP1LwBWg3lspM7KRDkmvyIWJTKXoFdvYcGrzFPgodJpFcNyU8vTG4fqSLXRqTwPCmzoQ5bi6sveffSYB3AXO007zk H5JyBLcH S xTeyZp1tft0niFjyxi4PcOwVaz7Nu4w g?key=nujqMd3jy0HFJ2J0wbnHeZO

Next, create the following three VLANs by navigating to Layer 2 Domain > VLAN in Infrahub. Here, you can input the VLAN ID, name, domain (select the domain you created earlier), and status.

AD 4nXdaoD9T1Vr3Dd5D71sMUQiHRI9O2MTLEMMTeNieGYH x QCH35vR zaa Ln4Wk5lBXGao895qyoddOOpF1MyrR4NvBY1xX1TDckcQBoofpepjdB3J3hqgUrykXGLcOi9k6LgOEdA?key=nujqMd3jy0HFJ2J0wbnHeZO

If you prefer to create them using GraphQL queries, feel free to do so. Below is an example of the query for creating Layer 2 Domain and VLANs using GraphQL.

You can access the GraphQL sandbox by navigating to Admin > GraphQL Sandbox

AD 4nXc0iXX1Z1p425wk17ztheQgU8HajzVMbWU5Lvq3DI7Exa6sw85oYNS58B2HKfnyEI5P PCjfdE1aHSTnyKgCwFn4YLyG8yOlVNPgqpCMMNIxMjGXaHSndoMpKjHLwXC4vlEptbq?key=nujqMd3jy0HFJ2J0wbnHeZO
mutation {
  LocationSiteCreate(
	data: {name: {value: "HQ"}, shortname:{value: "hq"}}
  ) {
	ok
	object {
  	id
	}
  }
}
mutation {
  IpamL2DomainCreate(
	data: {name: {value: "campus"}}
  ) {
	ok
	object {
  	id
	}
  }
}
mutation {
  vlan10: IpamVLANCreate(
	data: {
  	vlan_id: {value: 10},
  	status: {value: "active"},
  	name: {value: "finance"},
  	l2domain: {hfid: "campus"},
  	description: {value: "VLAN for Finance Users"},
  	role: {value: "user"}
	}
  ) {
	ok
	object {
  	id
	}
  }
 
  vlan20: IpamVLANCreate(
	data: {
  	vlan_id: {value: 20},
  	status: {value: "active"},
  	name: {value: "sales"},
  	l2domain: {hfid: "campus"},
  	description: {value: "VLAN for Sales Users"},
  	role: {value: "user"}
	}
  ) {
	ok
	object {
  	id
	}
  }
 
  vlan30: IpamVLANCreate(
	data: {
  	vlan_id: {value: 30},
  	status: {value: "active"},
  	name: {value: "admin"},
  	l2domain: {hfid: "campus"},
  	description: {value: "VLAN for Admin Users"},
  	role: {value: "user"}
	}
  ) {
	ok
	object {
  	id
	}
  }
}

Next, we’ll add two devices to Infrahub, named access-01 and access-02, and assign a primary IP to each device. Like any other device in Infrahub, these can be associated with a specific location, status, device type, platform, and more. 

Before creating the devices, let’s first create the ManufacturerDevice Type, Platform and IP address. For the platform, we’ll specify eos as the Napalm driver. This will be used later in the blog post to demonstrate its significance and how it integrates with the workflow.

AD 4nXf PeJLa6SByf0v85GpJVin7oYxpH4xeWNGanqpCidiiEWRxaMsBan28L4ihJRXf5IQqAhwHFyJtBu3Jr2qlky5JpA FOQ0cNVJIlhx6pSq73Sfuez5GM2XruHN6B7voHT7K7MGA?key=nujqMd3jy0HFJ2J0wbnHeZO
AD 4nXe tIQkuXuZbeiAh9dDMoZg00IKkZVE4avbNvhlgvQqp48CGq nDIakDro5GmNnKLp62R75R3Ykv2QNv438zcPW7RzMuvy1Wb4Hg7poQzjJsiNWcLVEMumMWUqU5kgVMMop9vt0?key=nujqMd3jy0HFJ2J0wbnHeZO
AD 4nXftNkTb0dchPbB6fT9ZJRGpUDHKCqNHQVaen167n9akwmnoq5v8Yxo9dMYmodv1vutYo5GcaKOzkOU1JzeIUfJIFYlzUQHp6efj9Tbp4mc zK6Z8tQ4M oH8MDb25M F3qBTUAnfQ?key=nujqMd3jy0HFJ2J0wbnHeZO

For creating IP addresses, first, navigate to IPAM > IP Prefixes and create a prefix with the 192.168.100.0/24 subnet. Once the prefix is created, go to IPAM > IP Addresses and add two IP addresses for our devices.

AD 4nXdt7UYMFDvZMXdBiY5uzVwSv Eg U1hDet0z8FiIiDV8aQAYMp13D PWhFrOrRIJ9x aH6xjKUOfgLgbH9xhZ6HWw1b8Ax8CNK8Mq8hVAeGKjWuf5m do4r1EWa Hn6 W7JnP7pUA?key=nujqMd3jy0HFJ2J0wbnHeZO

If you prefer to create them using GraphQL, here are the queries.

mutation {
  OrganizationManufacturerCreate(
	data: {name: {value: "Arista"}}
  ) {
	ok
	object {
  	id
	}
  }
}
mutation {
  DcimDeviceTypeCreate(
	data: {name: {value: "Arista Switch"}, manufacturer: {hfid: "Arista"}}
  ) {
	ok
	object {
  	id
	}
  }
}
mutation {
  DcimPlatformCreate(
	data: {name: {value: "eos"}, napalm_driver: {value: "eos"}}
  ) {
	ok
	object {
  	id
	}
  }
}
mutation {
  IpamPrefixCreate(
	data: {status: {value: "active"}, prefix: {value: "192.168.100.0/24"}, member_type: {value: "address"}}
  ) {
	ok
	object {
  	id
	}
  }
}
mutation {
  ip_211: IpamIPAddressCreate(
	data: {
  	description: {value: "access-01"},
    	address: {value: "192.168.100.211/32"}
	}
	) {
	ok
	object {
  	id
	}
	}
ip_212: IpamIPAddressCreate(
	data: {
  	description: {value: "access-02"},
  	address: {value: "192.168.100.212/32"}
	}
	) {
	ok
	object {
  	id
	}
	}
}

Finally, let’s create the two devices and add two interfaces for each device. Each device will be associated with the IP address we created earlier. This IP address will serve as the primary IP for the device and will be used for SSH to manage the device. When we use Nornir, this is the IP address it will rely on to connect to the device.

AD 4nXeu7o KMZnaxTgCo9PWOHMkCH6mKNDsjb7a6bPCMJd2fyzPmx LB5pMjmPu7HBR4zk18NALbMUUZQsXPF8DG0e1nNGfUm4VUlaJQIm4Gv1BgAqnRUfCTK HCRWAFgurZw FHhs5Q?key=nujqMd3jy0HFJ2J0wbnHeZO
mutation {
  access_01: DcimDeviceCreate(
    data: {
      name: {value: "access-01"},
      platform: {hfid: "eos"},
      location: {id: "18178eec-8379-21fd-311d-c51b6d37a6bf"},
      device_type: {hfid: "Arista Switch"},
      status: {value: "active"},
      primary_address: {id: "181832cf-12e5-55de-311e-c516b3a8b16c"}
    }
  ) {
    ok
    object {
      id
    }
  }
  access_02: DcimDeviceCreate(
    data: {
      name: {value: "access-02"},
      platform: {hfid: "eos"},
      location: {id: "18178eec-8379-21fd-311d-c51b6d37a6bf"},
      device_type: {hfid: "Arista Switch"},
      status: {value: "active"},
      primary_address: {id: "181832cf-411c-4dae-3111-c515154e7409"}
    }
  ) {
    ok
    object {
      id
    }
  }
}

Please note that the location and primary_address fields use the id for reference. You can retrieve the corresponding IDs from the web GUI and pass them as needed.

We will then add a couple of interfaces to each device, including details like descriptions, status, and associated VLANs.

AD 4nXc6XO0KZ qEUo 3fWfQdgsondtK2 0UZAbVS GtxnaZnKuEYuKXPhobU7Ey 7U3Z65Nz3 SKhlQQVz7bAaYXdP7QFDxDu1XYkPThURJ02kkSo8F TKLljhMB Bf7Hkwu6YINIUV9A?key=nujqMd3jy0HFJ2J0wbnHeZO
AD 4nXdmPgcbp aWnxvP7iGawfRzvEoZBpDKUjKDVUSZN9W4SKqRLgzyWkjcSK2rKQ3BMbYthb7 77yNYnh wV43 XR09keZAbl7TGuuAYqFnyuN5s4 oW6H3 ZYjrVa63W TlDEMm8LaA?key=nujqMd3jy0HFJ2J0wbnHeZO
mutation {
  access_01_eth5: DcimInterfaceL2Create(
    data: {
      name: {value: "eth5"},
      description: {value: "device-01"},
      enabled: {value: true},
      device: {hfid: "access-01"},
      untagged_vlan: {hfid: "finance"},
      speed: {value: 1000},
      l2_mode: {value: "Access"},
      status: {value: "active"}
    }
  ) {
    ok
    object {
      id
    }
  }
  access_01_eth6: DcimInterfaceL2Create(
    data: {
      name: {value: "eth6"},
      description: {value: "device-02"},
      enabled: {value: true},
      device: {hfid: "access-01"},
      untagged_vlan: {hfid: "admin"},
      speed: {value: 1000},
      l2_mode: {value: "Access"},
      status: {value: "active"}
    }
  ) {
    ok
    object {
      id
    }
  }
  access_02_eth5: DcimInterfaceL2Create(
    data: {
      name: {value: "eth5"},
      description: {value: "device-03"},
      enabled: {value: true},
      device: {hfid: "access-02"},
      untagged_vlan: {hfid: "sales"},
      speed: {value: 1000},
      l2_mode: {value: "Access"},
      status: {value: "active"}
    }
  ) {
    ok
    object {
      id
    }
  }
  access_02_eth6: DcimInterfaceL2Create(
    data: {
      name: {value: "eth6"},
      description: {value: "device-04"},
      enabled: {value: true},
      device: {hfid: "access-02"},
      untagged_vlan: {hfid: "admin"},
      speed: {value: 1000},
      l2_mode: {value: "Access"},
      status: {value: "active"}
    }
  ) {
    ok
    object {
      id
    }
  }
}

Once we have all the data in place, the next step is to use it to generate device configurations. If you’re familiar with any form of network automation, you likely know that Jinja2 is one of the best tools for generating device configurations.

We now have all the data required to generate the configuration, such as VLANs, interfaces, descriptions, and more. The next step is to create a Jinja2 template that takes these values as inputs and generates the configuration. Additionally, we need to ensure the generated configurations are correctly associated with each device.

Infrahub provides a way to achieve this by using a Jinja2 template along with a GraphQL query to generate the configuration. The generated configuration is saved in Infrahub as an artifact, which can then be associated with the devices using an artifact definition. In the next sections, we’ll look at how to configure all of this.

Jinja2 Transformation and Artifact

So, how do we use Jinja2 with Infrahub? We use an Infrahub feature called ‘Transformation’. As the name suggests, this involves taking the data stored in Infrahub and converting it into a different format. In our case, we use a Jinja2 template to transform the data into a text file (rendered configuration).

As discussed previously, we also need a GraphQL query that fetches all the inputs required for the Jinja2 template. If you’re familiar with Jinja2, you might typically use a YAML or JSON file to store the data and then pass it to the template. In our case, this data is stored in Infrahub.

The final step involves defining an Artifact definition, which groups together a transformation with a target group, forming the artifact’s definition.

We can package all these components together (Jinja2 template, GraphQL query, and Artifact definition) alongside a .infrahub.yml file in a Git repository. This repository can then be added to Infrahub. The .infrahub.yml file enables Infrahub to identify the necessary imports and tie together the various components.

Please note that the Artifact definition, for example, can also be created via the web GUI or GraphQL query, but in this example, we use a Git repository. 

Here are the contents of each file. 

config.gql

query MyQuery($device: String!) {
  IpamVLAN {
    edges {
      node {
        vlan_id {
          value
        }
        name {
          value
        }
      }
    }
  }
  DcimDevice(name__value: $device) {
    edges {
      node {
        interfaces {
          edges {
            node {
              name {
                value
              }
              description {
                value
              }
              ... on DcimInterfaceL2 {
                l2_mode {
                  value
                }
                untagged_vlan {
                  node {
                    name {
                      value
                    }
                    vlan_id {
                      value
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Here is the sample output from the query for the device access-01.

{
  "data": {
	"IpamVLAN": {
  	"edges": [
    	{
      	"node": {
        	"vlan_id": {
          	"value": 30
        	},
        	"name": {
          	"value": "admin"
        	}
      	}
    	},
    	{
      	"node": {
        	"vlan_id": {
          	"value": 10
        	},
        	"name": {
          	"value": "finance"
        	}
      	}
    	},
    	{
      	"node": {
        	"vlan_id": {
          	"value": 20
        	},
        	"name": {
          	"value": "sales"
        	}
      	}
    	}
  	]
	},
	"DcimDevice": {
  	"edges": [
    	{
      	"node": {
        	"interfaces": {
          	"edges": [
            	{
              	"node": {
                	"name": {
                  	"value": "Eth5"
                	},
                	"description": {
                  	"value": "new-description"
                	},
                	"l2_mode": {
                  	"value": "Access"
                	},
                	"untagged_vlan": {
                  	"node": {
                    	"name": {
                      	"value": "finance"
                    	},
                    	"vlan_id": {
                      	"value": 10
                    	}
                  	}
                	}
              	}
            	},
            	{
              	"node": {
                	"name": {
                  	"value": "Eth6"
                	},
                	"description": {
                  	"value": "device-02"
                	},
                	"l2_mode": {
                  	"value": "Access"
                	},
                	"untagged_vlan": {
                  	"node": {
                    	"name": {
                      	"value": "admin"
                    	},
                    	"vlan_id": {
                      	"value": 30
                    	}
                  	}
                	}
              	}
            	}
          	]
        	}
      	}
    	}
  	]
	}
  }
}

config.j2

!
{% for vlan in data['IpamVLAN']['edges'] %}
vlan {{ vlan['node']['vlan_id']['value'] }}
 name {{ vlan['node']['name']['value'] }}
!
{% endfor %}
{% for edge in data['DcimDevice']['edges'][0]['node']['interfaces']['edges'] %}
interface {{ edge['node']['name']['value'] }}
 description {{ edge['node']['description']['value'] }}
{% if edge['node']['l2_mode']['value'] == 'Access' %}
 switchport mode access
 switchport access vlan {{ edge['node']['untagged_vlan']['node']['vlan_id']['value'] }}
{% endif %}
!
{% endfor %}

.infrahub.yml

---
jinja2_transforms:
  - name: device_config
    description: "VLAN and Interface configuration"
    query: "config_query"
    template_path: "config.j2"

queries:
  - name: config_query
    file_path: "config.gql"

artifact_definitions:
  - name: "config_file"
    artifact_name: "configuration file"
    parameters:
      device: "name__value"
    content_type: "text/plain"
    targets: "Transformation"
    transformation: "device_config"

Both jinja2_transforms and queries defined in this file are straightforward, so let’s focus on artifact_definitions. Each Artifact Definition in .infrahub.yml must include the following.

  • name – the name of the Artifact Definition
  • artifact_name – the name of the Artifact created by this Artifact Definition
  • parameters – mapping of the input parameters required to render this Artifact
  • content_type – the content-type of the created Artifact
  • targets – the Infrahub Group to target when generating the Artifact
  • transformation – the name of the Transformation to use when generating the Artifact

Here, we defined a group called ‘Transformation’ and added the two devices to this group. You can create the Group by navigating to Object Management > Groups. Once the group is created, you can add the two devices as members.

AD 4nXcuquCNAZuIzS1DQVHXOqAojAalLvMAeoTT7jl AQwD2t3wdlivrL4QwcCIeIaiXtA30TdCTrM6MBRuPcXsJirnJhls1bvJPRyVP0Fjvd0gccnCTSTmG tOtItwcsrpCbhF5U4Hjw?key=nujqMd3jy0HFJ2J0wbnHeZO

So, in the end, you’ll end up with three files in your repository – a Jinja2 template, a GraphQL query, and a .infrahub.yml file that ties everything together. Commit and push these changes to your remote repository, then add the repository to InfraHub.

├── config.gql
├── config.j2
└── .infrahub.yml

To add this repository to Infrahub, navigate to Unified Storage > Repository and provide the Git remote repository link, login credentials (for example, if you use GitLab, create an access token and use it as the password for Infrahub), and a unique name for the repository. Infrahub will then connect to your remote repository and import the components defined within it—in our case, the Jinja2 template, GraphQL query, and artifact definition.

AD 4nXdi81K rbeByehd AIBLi9NpNz0N7KUzLyk WIGAExaTck49DSaknjs0rMnJkHM2IUkD99zO5Dt9w3mAd23NOTgsN6 L4n7 L0z8Jmj4KspBWRs5xHTjxYwEBWvlKNSoXEISoRZ?key=nujqMd3jy0HFJ2J0wbnHeZO

Once you add the repository to Infrahub and everything is set up correctly, you should see the artifact under the Artifact tab. If you open the artifact, you’ll find the generated configuration, as shown below.

AD 4nXfnIuCFlxahJLHvFThr44JeajFGs1BuzO9PF1gX8OelQYqtV3L8HNbx R HzAiN8FETFVx7595hJfU2ca5Z48fJskJYRZKKximNFWi V0 IxamKDIunM3WO jYXI24K4O6NM2K6Bw?key=nujqMd3jy0HFJ2J0wbnHeZO
AD 4nXfuxOWFVw3Vr4mNNbx7UbgP78gX5ivipcBz9aDJCaUBew Ou 5YepG DQ1INRAkkWGSeGsu4EAJXcntetWlSLbvkgr3C4v7Vxh qv BMjXltMGDfBr1MqAAYjoIhYb xxIFl3FfQ?key=nujqMd3jy0HFJ2J0wbnHeZO

you can also test your transformation by using infrahubctl render. When you use infrahubctl you need to pass the name of the transformation and any required variables. Here is an example, of using access-01 as the device.

infrahubctl render device_config device=access-01

!
vlan 30
 name admin
!
vlan 40
 name cctv
!
vlan 10
 name finance
!
vlan 20
 name sales
!
interface Eth5
 description cctv_01
 switchport mode access
 switchport access vlan 40
!
interface Eth6
 description device-02
 switchport mode access
 switchport access vlan 30
!

Nornir-Infrahub Plugin

We’ve now completed about 75% of the process, with the remaining steps focusing on Nornir and how to use Nornir/Napalm to retrieve and apply these artifacts (configs). If you remember, our ultimate goal is to store all necessary information in Infrahub, generate the configurations, and push them to the devices. Infrahub will act as the inventory source for Nornir and also provide the artifacts.

First, you need to install the nornir-infrahub and nornir_napalm plugins. Use the following command to install it. As always, use a virtual environment for installing pip modules.

python3 -m venv venv
source venv/bin/activate

pip install nornir-infrahub
Pip install nornir_napalm

Once installed, the following Nornir configuration file (config.yml) initializes Nornir with the Infrahub inventory plugin, fetching the required inventory and configuration details from Infrahub.

config.yml

---
inventory:
  plugin: InfrahubInventory
  options:
    address: http://10.10.10.40:8000
    token: 1811f38d-feb8-24da-2f6c-c51a2af588c8
    host_node:
      kind: DcimDevice
    schema_mappings:
      - name: hostname
        mapping: primary_address.address
      - name: platform
        mapping: platform.napalm_driver
    group_mappings:
      - platform.name
    group_file: groups.yml
  • We define schema mappings to enable Nornir to correctly interpret the data from Infrahub. For example, we map platform.napalm_driver to the platform field, ensuring that Nornir identifies the correct driver for each device.
  • When using Napalm, the platform names must match the expected values (e.g., eos for Arista). However, if you’re using Netmiko, platform names might differ (e.g., arista_eos for Arista). These differences need to be accounted for when setting up Nornir.
  • A groups.yml file is used to define group-specific attributes, such as the username and password for the eos platform. The plugin automatically creates groups based on the group_mappings specified in the configuration. Here, we use platform.name, so Nornir creates a group for each host based on the value of platform.name. In this case, it creates a group named platform__eos, with platform__ prefixed to the platform name.
  • You can then define attributes for this group in the groups.yml file, such as credentials or other platform-specific settings. If you’re using a different schema, ensure your group mappings align with your schema structure to reflect the appropriate group names.

groups.yml

platform__eos:
  username: admin
  password: admin

With the prerequisites out of the way, we can now move on to pushing the generated configurations to the devices.

In the following Python script (main.py), get_artifact function retrieves the artifact (in this case, the rendered configuration) associated with each device from Infrahub. It uses the Infrahub API to fetch the artifact’s content, which is then stored in the Nornir’s result object.

main.py

from nornir import InitNornir
from nornir_utils.plugins.functions import print_result
from nornir_napalm.plugins.tasks import napalm_configure
from nornir_infrahub.plugins.tasks.artifact import get_artifact


def main(task):
	# Fetch artifacts from Infrahub
	artifacts = task.run(task=get_artifact, artifact="config_file")
    
	# Configure devices using Napalm with the fetched artifacts
	task.run(task=napalm_configure, configuration=artifacts[0].result, dry_run=False)


if __name__ == "__main__":
	nr = InitNornir(config_file="config.yml")
	results = nr.run(task=main)
	print_result(results)

Once the artifacts are successfully fetched, the next step is to apply these configurations to the devices using the napalm_configure task. This task takes the fetched configuration (artifacts[0].result) and pushes it to the target devices.

So, just to recap, our directory structure will look like this – main.py is our Python script, config.yml specifies that Nornir should use the InfraHub inventory and defines how it interacts with it, and groups.yml is used to provide the login credentials.

.
├── groups.yml
├── main.py
├── config.yml

Let’s Make Some Changes

To demonstrate how changes are made and pushed to devices using Nornir, let’s create a new VLAN, assign it to one of the interfaces, and update the interface description.

The process begins by creating a branch in Infrahub. This branch allows us to isolate and manage the changes without affecting the main configuration. You can create a new branch (called vlan_40) in Infrahub GUI by clicking the ‘+’ button as shown below.

AD 4nXcEfwb2ZijBj4Jp4oCCc5IW4u OkXWEVZaj0XNdtpnjnfB6Mc2ro kZRN8swAbGqizaF5XNTz6I P0Ct3DqRgBaV9btOW8j8rhPcvJZAus5sMPBW8svy09ibIWwy37vnYfeSdftvA?key=nujqMd3jy0HFJ2J0wbnHeZO

Once the branch is created, select the branch to work on and create a new VLAN. 

AD 4nXfYuBRdaOuE3IJaL3saqnaM4 q28beolRWcXReMyqp0 dP8U6yQyn5WxpFf7dwPGJFv9aMJon11D0F 47YgbZl8e4CFU54zf5pC3FCRFrU8ZEteHC 6mvGFF3aA 9Y9747kJy5MYg?key=nujqMd3jy0HFJ2J0wbnHeZO

After the VLAN is created, choose an interface (e.g., access-01, Eth5 in this example) and update its configuration to use the newly created VLAN 40. Additionally, update the interface description to reflect the changes. This ensures that both the VLAN assignment and description are consistent with the new configuration requirements.

AD 4nXcDy8wWbji2iI2nUwyObUn4CzIzZ2YbBaCwr0R5FuCcG nzWTI0enzXN0OuSPdsc XNAONcHAgjw4TaMgAeq d4CncXmD1GeFcDvDs8DPXc2Udatq4FP3QJKnF3gw8Ylhod9VSP5A?key=nujqMd3jy0HFJ2J0wbnHeZO

Once you’ve made the changes, navigate to Change Control > Branches and select the branch you just created. Under the Data tab, you can view exactly what has been modified.

AD 4nXdxTBxfjEEBiLypfG6EZTX9OYvUsVC XX05F8TlK2z2VUe j23WplqlVKpvBsqoFToK7lmgIMGbVmecy2ImcNfSQAO0184ZifGa9UJnZQQp sbSx6AahJjqK2 U0JvsDOJ2mMyu?key=nujqMd3jy0HFJ2J0wbnHeZO

You can expand each field to see the specific changes in detail. For example, you’ll notice that a new VLAN was added (highlighted in green), and the description and untagged VLAN for the interface was updated (highlighted in blue)

At this point, you have the option to merge your changes directly. However, Infrahub offers a more robust way to manage changes using a feature called Proposed Changes. Let’s explore how to use this feature.

AD 4nXchOkJnsJUeYsFdG Ic6Mi5lM9kdp8tswK4EB2ligpoeNtwFkA922blAqJLmGZg7xrExsCOIkiM5UKhJinOUUEX1WJCjVcqos a2LrIm f3HzkmcH s2fNckuhG9i zSGvcrNog?key=nujqMd3jy0HFJ2J0wbnHeZO

Infrahub Proposed Changes

Infrahub’s Proposed Changes feature takes automation a step further. Instead of merging directly from Change Control > Branches, you can create a Proposed Change. In this process, you provide a name, description, the person raising the change, and the source and destination branches.

AD 4nXe9riiN GnMZmm3cQ8WK8b15AXPtn81Ayv3XswdLv8mxD5m g6IT0J9je8ZELmXWAtzLUPxUDKtJZs8wZLjhS4elm84tZqEQZd9hubWoDH6HeTOwl3yEkcOyy3a2ZCFFW1oO7TZ4Q?key=nujqMd3jy0HFJ2J0wbnHeZO

Once the proposed change is created, navigate to Proposed Change and select the change you just raised. Here, you’ll find multiple tabs:

  • Overview: Provides a general overview of the change, as the name suggests. You can also add comments here.
  • Data: Similar to what you’ve seen before, this tab shows exactly what was modified.
  • Artifact: This is the most important tab for this post. Infrahub detects the changes, renders a new configuration, and highlights exactly what has been updated.
AD 4nXd9cyHfaBRjhtDbG6Vc9e Ct7rsDH56rByFC P vQX0pt9w45EixvdeufFe2UGc 4kg390coxL6IaZjQ3GXvR7 ON4EnrQeWDpF O3FnV83hEzjYOHhNkZuCiKULyRMwYES6K3r Q?key=nujqMd3jy0HFJ2J0wbnHeZO

AD 4nXfzQ8u0GlwW6W8h5uPHiggoqYqdpMPCkCklKu8KmdBu0tTeXLsKd05CVaacRbhtcBDP9nGiwDTbps8Bp4dVxMq8vHXpCKCZGeHU 7dxomMUVD k6o Q4ECPEv4eGG8ZQ3hO9VVA?key=nujqMd3jy0HFJ2J0wbnHeZO

In our changes, we added a new VLAN, so the artifact reflects the VLAN being created on both devices. For access-01, in addition to creating the VLAN, the artifact also shows the VLAN being assigned to interface Eth5 and the description being updated. These changes are clearly visible in the web GUI, making it easy to verify and review.

If the changes look good, the reviewer can approve the proposal. As soon as the change is approved and merged, the artifact gets re-generated automatically. 

AD 4nXdsFY97kIl8OduYcz0lq0kBikcheiBhMMgLfNiX92MA6Tq1wvU VUlgqO NipZWKHsuOYM0P9OvV 3V3lAtGsktbKaw8LxBQNcll9B7u 1zswwgbHLvD3JfB5mmb28y1NSHVWoHKA?key=nujqMd3jy0HFJ2J0wbnHeZO

Now you can use Nornir to run the job again, and the updated configuration will be pushed to the devices seamlessly. This workflow ensures changes are tracked, reviewed, and implemented efficiently. 

Here are the outputs from Nornir showing what is being changed on the devices. 

access-01

---- napalm_configure ** changed : True ---------------------------------------- INFO
+vlan 40
+   name cctv
 !
 interface Ethernet5
-   description device-01
+   description cctv_01
-   switchport access vlan 10
+   switchport access vlan 40

Access-02

---- napalm_configure ** changed : True ---------------------------------------- INFO
+vlan 40
+   name cctv

If we SSH into access-01, we can confirm that the changes have taken effect. The new VLAN 40 is present, and interface Eth5 is now using this VLAN with the updated description, as expected.

access-01#show run interfaces eth5
interface Ethernet5
   description cctv_01
   switchport access vlan 40
access-01#

access-01#show vlan
VLAN  Name                            	Status	Ports
----- -------------------------------- --------- -------------------------------
1 	default                      	active	Et1, Et2
10	finance                      	active    
20	sales                        	active    
30	admin                        	active	Et6
40	cctv                         	active	Et5

Closing Up

Just to keep this post simple, we only covered the basics, but you can absolutely manage every aspect of the configuration, such as SNMP servers, NTP, trunk ports, uplinks, port channels, and more. All you need to do is input the relevant data into InfraHub, update your Jinja2 template and GraphQL query, and let Infrahub and Nornir handle the rest.

Share the Post:

JOIN OUR MAILING LIST

Please enter your email address to stay informed about OpsMill developments. Your email address will be stored according to GDPR and will never be sold.

REQUEST A DEMO

See OpsMill in action and learn how it can help you achieve your goals. Fill out the form below to schedule a personalized demo.

By submitting this form, you agree that your personal data will be stored and processed by OpsMill in accordance with our privacy policy.