Nautobot, Beyond Network Source of Truth

As the title suggests, this post will use Nautobot for something other than a Network Source of Truth. This post will use Nautobot to solve one of life’s most difficult questions, perhaps one of the most frequently asked questions. This question is known to ruin the evening, but with Nautobot’s help, this no longer has to be the case: The question Nautobot will answer for us is, “What’s for dinner?”

This post will walk through using one new feature introduced in Nautobot version 1.1 and one existing feature, in order to have each Site in Nautobot provide a recipe recommendation. The two features are independent, but often work hand in hand to provide dynamic data to a specific entry in Nautobot. The primary feature that will provide the Site with a recipe is Custom Links. The Custom Link will make use of a Custom Jinja2 Filter in order to generate a recommendation from a third-party API.

High-Level Architecture

The API used by the Custom Jinja2 Filter will be SpoonacularSpoonacular requires an account and token to use the API, but it allows for several API requests per day for free. The API also has several filtering capabilities, the two that will be used here are: cuisine and ingredients. A Nautobot Plugin needs to be created in order to give Nautobot access to the Custom Jinja2 Filter, which will also provide access to the Spoonacular API token using PLUGINS_CONFIG in nautbot_config.py.

The Custom Link will be added to the dcim | site Content Type, and will use the Site object to pass the Jinja2 Filter a cuisine and an ingredient. The cuisine will be obtained from the Site’s parent Region’s slug field, and the Site’s slug field will be used to provide an ingredient. This means that:

  • Each Region that is associated with a Site must have a valid Spoonacular cuisine for its slug value
  • Each Site must be associated to a Region
  • Each Site must have an ingredient for its slug value

A list of acceptable cuisines for the API can be found at Spoonacular Cuisines.

Project Structure

This Plugin will only provide a Jinja2 Filter, so the file structure is very simple:

  • nautobot_plugin_recipe_filter directory for the Plugin App
  • nautobot_plugin_recipe_filter/__init__.py file with the PluginConfig definition
  • nautobot_plugin_recipe_filter/recipe_filters.py for providing the filter to get recipe recommendations
  • pyproject.tomlREADME.md, and LICENSE for installing the Plugin in Nautobot.
$ tree nautobot-plugin-recipe-filter
.
├── nautobot_plugin_recipe_filter
│     ├── __init__.py
│     └── recipe_filters.py
├── LICENSE
├── pyproject.toml
└── README.md

nautobot_plugin_recipe_filter/init.py

"""Plugin declaration for nautobot_plugin_recipe_filter."""

__version__ = "0.1.0"

from nautobot.extras.plugins import PluginConfig


class NautobotPluginRecipeFilterConfig(PluginConfig):
    """Plugin configuration for the nautobot_plugin_recipe_filter plugin."""

    name = "nautobot_plugin_recipe_filter"
    verbose_name = "Nautobot Plugin Recipe Filter"
    version = __version__
    author = "Network to Code, LLC"
    description = "Nautobot Plugin Jinja2 Filter for Recipe Recommendations"
    required_settings = ["spoonacular_token"]
    min_version = "1.1.0"
    max_version = "1.9999"
    default_settings = {}
    caching_config = {}
    jinja_filters = "recipe_filters.py"


config = NautobotPluginRecipeFilterConfig

pyproject.toml

[tool.poetry]
name = "nautobot-plugin-recipe-filter"
version = "0.1.0"
description = "Nautobot Plugin Jinja2 Filter for Recipe Recommendations"
authors = ["Network to Code, LLC <info@networktocode.com>"]
license = "Apache-2.0"
readme = "README.md"
homepage = "https://github.com/networktocode/nautobot-plugin-recipe-filter"
repository = "https://github.com/networktocode/nautobot-plugin-recipe-filter"
keywords = ["nautobot", "nautobot-plugin"]
include = ["LICENSE", "README.md"]
packages = [
    { include = "nautobot_plugin_recipe_filter" },
]

[tool.poetry.dependencies]
python = "^3.6"
nautobot = "^v1.1.0"
requests = "*"

Creating the Custom Jinja2 Filter

Creating a Custom Jinja2 Filter that can be consumed within Nautobot follows the standard Jinja2 process, and is auto-registered to the Nautobot Environment when a Plugin is installed and enabled on the Nautobot instance. Each Plugin can define the name of the file where Jinja2 Filters should be loaded from using the PluginConfig; the default location is jinja_filters.py. The above PluginConfig specified recipe_filters.py, so the filters made available by this plugin will go in that file.

In order to register the Jinja2 Filter with Nautobot, the Filter function is decorated with @library.filter, where library is imported from the django_jinja library. The django_jinja library is included with Nautobot in order to support the Custom Jinja2 Filters feature. This Filter function will use the requests library to make API calls to the Spoonacular API. The Jinja2 Filter will return the URL to the recipe recommended by Spoonacular. In order to interact with the Spoonacular API, the token will be retrieved using the spoonacular_token setting in the PLUGINS_CONFIG; this setting is marked as required in the above PluginConfig.

recipe_filters.py

"""Custom filters for nautobot_plugin_recipe_filter."""
import requests

from django.conf import settings

from django_jinja import library


SPOONACULAR_TOKEN = settings.PLUGINS_CONFIG["nautobot_plugin_recipe_filter"]["spoonacular_token"]
SPOONACULAR_URL = "https://api.spoonacular.com/recipes"
SPOONACULAR_RECOMMENDATION_URL = f"{SPOONACULAR_URL}/complexSearch"  # URL to get recommendations
SPOONACULAR_RECIPE_URL = f"{SPOONACULAR_URL}//information"  # URL to get details of recommendation


@library.filter
def get_recipe_url(cuisine:str, ingredient:str) -> str:
    """
    Get Recipe recommendation based on cuisine and single ingredient.
    
    Args:
        cuisine (str): The cuisine derived from Nautobot's Region.slug value.
        ingredient (str): The ingredient to include in the recipe based on Nautobot's Site.slug value.
    
    Returns:
        str: The URL of the recommended recipe.
    """
    recommendation_params = {
        "apiKey": SPOONACULAR_TOKEN,
        "number": 1,  # Limit number of responses to a single recipe
        "cuisine": cuisine,
        "includeIngredients": ingredient,
    }
    # Get recipe recommendation
    recommendation_response = requests.get(url=SPOONACULAR_RECOMMENDATION_URL, params=recommendation_params)
    recommendation_response.raise_for_status()
    recipe_id = recommendation_response.json()["results"][0]["id"]
    recipe_params = {"apiKey": SPOONACULAR_TOKEN}
    # Get recipe detailed information
    recipe_response = requests.get(url=SPOONACULAR_RECIPE_URL.format(id=recipe_id), params=recipe_params)
    recipe_response.raise_for_status()
    recipe_json = recipe_response.json()
    # Get URL to recipe on Spoonacular's website
    recipe_url = recipe_json["spoonacularSourceUrl"]

    return recipe_url

The Plugin is ready to be installed into the Nautobot environment. See the Plugin Installation Guide for details. The Database Migration and Collect Static Files steps can be skipped, since the Plugin does not use a database table and does not provide static files.

Example Config Settings

PLUGINS = ["nautobot_plugin_recipe_filter"]
PLUGINS_CONFIG = {
    "nautobot_plugin_recipe_filter": {
        "spoonacular_token": "abc123"
    }
}

The Jinja2 Filters provided by Plugins are available in Nautobot wherever Jinja2 text fields are used. This post will showcase using the above Jinja2 Filter to create a Custom Link; however, the Filter could also be used by the new Computed Fields feature added in v1.1.

The reason for using Custom Links in this instance is that the goal is to provide a link to another site with the recipe and cooking instructions. Computed Fields are designed to be text fields, and have HTML escaped; this means that an HTML hyperlink would be rendered as plain-text and not be clickable.

Nautobot’s Custom Link feature allows hyperlinks and groups of hyperlinks to be added to the upper-right side of pages displaying a single entry (DetailViews). Creating a Custom Link has several configuration options:

  • Content Type: Sets the Model that will have its entries display the hyperlink
  • Name: The name used to identify the Custom Link within Nautobot
  • Text: A Jinja2 expression that will be rendered and used as the text displayed for the hyperlink
  • URL: The URL that will be used for the hyperlink
  • Weight: Determines the ordering of the hyperlinks displayed
  • Group Name: Allows multiple hyperlinks to be displayed under a single drop-down button
  • Button Class: Determines the color of the button used for the hyperlink
  • New Window: Determines whether clicking the hyperlink should open the URL in a new tab or the current tab

There are two steps to creating most objects in Nautobot:

  • Browsing to the form to create the object from the main Navigation Bar
  • Filling out and submitting the form

Creating a Custom Link is done in the Web UI by browsing to Extensibility > Custom Links, and clicking the + icon to bring up the form to create a new Custom Link.

browse-to-add-custom-link

The Custom Link used to provide a hyperlink to Spoonacular’s recipe recommendation specifies a Content Type of dcim | site, and a Name and Text of Recipe Recommendation. The URL uses the Jinja2 Filter defined above to obtain the URL to the recipe, and clicks will open the hyperlink in a new tab.

The Jinja2 expression for the URL makes use of the variable obj, which is the Django ORM object for the database record. Since the Content Type for this Custom Link uses the Site model, it will be a record from the Site database table. Nautobot Site entries have foreign key fields to the Region, so both the Region’s slug (cuisine) and Site’s slug (ingredient) can be passed to the Custom Jinja2 Filter.

Once all required fields are filled out on the form, clicking Create will create the Custom Link.

create-custom-link

The Custom Link created above is displayed on every Site page. If the Jinja2 expression fails to render an HTML link, then the Custom Link button will not be clickable. In order to showcase the Custom Link, a guide is provided below to create a Region and Site, which results in a valid URL being created for the Site.

Guide to Create a Region

In order for a valid link to be shown, each Site must belong to a Region that has a slug value with a valid cuisine.

Browsing to Add Region Form

Creating a Region is done by browsing to Organization > Regions, and clicking the + icon to bring up the form to create a new Region.

browse-to-add-region

Filling Out the Add Region Form

The Region used in this example is Spain, which correlates to the spanish cuisine used by the Spoonacular API.

Once all required fields have been filled out on the form, clicking Create will create the Region.

create-region

Guide to Create a Site

In addition to each Site being linked to a Region, the Site must also use an ingredient for the value of its slug field.

Browsing to Add Site Form

Creating a Site is done by browsing to Organization > Sites, and clicking the + icon to bring up the form to create a new Site.

browse-to-add-site

Filling Out the Add Site Form

The Site in this example is Madrid, and is assigned to the Spain Region created above. The slug (ingredient) value uses rice. In Nautobot, Sites require the status to be set; this Site uses the built-in Active Status.

The Site form is longer than the previous forms, but scrolling to the bottom of the form displays the familiar Create button to create the Site.

create-site

Viewing the Recipe Recommendation

Clicking on the Create button above redirects to the newly created Site Page with the Custom Link added to it.

The Custom Link for the recipe recommendation is displayed in the top-right corner of the page.

site-createda

Since the check box to open the hyperlink in a new tab was selected on the Create Custom Link form, clicking the Custom Link on the Site page opens the Spoonacular recipe page in a new tab. The recommendation returned in the example is Paella.

spoonacular-recommendation

Conclusion

Nautobot is more than a Source of Truth application. The newly added Custom Jinja2 Filters provide an easy way to perform more complex operations for fields that are rendered through Jinja2. An example of a field that supports Jinja2 expressions is the URL field in Custom Links. Custom Links can be added to Pages dynamically, and provide a convenient way of giving Users access to related pages.

The recipe recommendation example used here is perhaps not practical, but something similar could be built for linking to other Source of Truth systems. Maybe Nautobot is not the System of Record for certain data points, but is instead used to aggregate multiple data points into a single place for read-only operations. The Jinja2 Filter could look up the URL to the data point in the Application used as the System of Record. It might be handy to have a link in Nautobot that is able to take users to that page in order to manage the data there.



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Author