The Flexibility and Power of Nautobot Jobs
One of Nautobot’s core tenets is extensibility. This manifests itself in the robust capabilities enabled in Nautobot’s apps, but also in a very powerful extensibility feature called Jobs.
Jobs are a way for users to execute custom logic on demand from within the Nautobot UI: jobs can interact directly with Nautobot data to accomplish various data creation, modification, and validation tasks. Jobs can add value stand-alone, as part of an app, or as a complement to an app. Jobs are Python code and exist outside the official Nautobot code base, so they can be updated and changed without interfering with the core Nautobot installation.
A job is essentially a file with Python code.
Read-Only Jobs
By default, jobs can make changes to the Nautobot database.
Developers can optionally flag a job as read-only. This type of job is ideal for report-style jobs, where there would be no change to Nautobot’s data.
One example use case for the read-only job complements Nautobot’s Data Validation Engine app. This app allows users to create custom regular expression (regex) rules. A user can define a regex format for device names, which will ensure any new devices have a name that conforms to the standard. In this example, let’s use the following regex:
^[a-z]{3}[0-9]{2}-[a-z]{4}-[0-9]{2}$
When the user tries to create a device name that does not comply with the regex, the Data Validation Engine rule prevents creation.
Data Validation Engine rules are not retroactive, however, so any devices that existed prior to that rule being defined may not conform. This is a great use case for a read-only job to inspect the names of all devices or a subset of devices and confirm each existing device name conforms to the standard and flag those that do not.
Tip: A standard job that can write to the database can be executed in
dry-run
mode so the user can preview any proposed changes without actually making the changes.
Running a Job
To run an installed job, navigate to the Extensibility top-level navigation menu, then select Jobs. You will see a list of installed jobs (more info about installing and creating jobs, as well as a couple examples, are in following sections).
Select the job you are interested in running. We will continue the use of the hostname format use case, so the example shows selection of the Verify Hostnames
job.
Once on the page for the specific job, fill in the required info and any other optional info (this will vary from job to job). In this example, we’ll populate the Hostname regex
with the exact regex from the Data Validation Engine app:
To run the job, click on the Run Job
button. The job results appear after it completes. This example shows job results with multiple non-compliant hostnames:
Job History
To view history for a specific job, navigate back to Extensibility–>Jobs, and find the job you are interested in. To view the results for the most recent run, click on the date/time entry to the right of the description (the Last Run column).
To view the results for other past runs, click on the clock icon to the right of the date/time. This will take you to a list of the job results for that job; there will be a timestamp next to each job execution. Select the timestamp for the job execution you are interested in to view the job run’s result:
Jobs Locations
There are a couple of options for where to store jobs.
Local Storage
Jobs can be stored locally on the Nautobot server. Jobs stored in Nautobot may be manually installed as files, stored locally in the JOBS_ROOT
path (which defaults to $NAUTOBOT_ROOT/jobs/
) on the Nautobot server:
$ echo $NAUTOBOT_ROOT
/opt/nautobot
$ pwd
/opt/nautobot/jobs
$ ls -l
total 12
-rw-rw-r-- 1 nautobot nautobot 2137 Sep 20 15:58 create_pop.py
-rw-rw-r-- 1 nautobot nautobot 2249 Sep 20 15:07 device_data_validation.py
-rw-rw-r-- 1 nautobot nautobot 0 Apr 15 2021 __init__.py
drwxr-xr-x 2 nautobot nautobot 4096 Sep 21 08:29 __pycache__
$
The Nautobot worker must be restarted when a new jobs Python file is added to /jobs/
:
$ sudo systemctl restart nautobot-worker.service
Tip: In Nautobot 1.3 and later, you will also need to run the
nautobot-server post_upgrade
command after adding any new Job files to this directory.
Git Repositories
Since jobs are Python code, and since you may have many jobs, it often makes sense to maintain them outside of Nautobot in a git repository.
Nautobot’s documentation has info about configuring a repository and how to configure a Git repository for jobs specifically.
To view currently configured repositories, navigate to Extensibility–>Git Repositories. From there, look for repositories with the greenJobs icon (the icon looks like a scroll). Below is an example of a repository configured to hold Jobs code:
From this same screen you can also choose to add/configure a new repository by clicking on the blue Add
button in the upper right.
To get more info on the repository, click on it to go to the detail view page for the repository. There you can also see what info a repository is configured to provide:
In a Git repository, job files must be kept in a /jobs/
directory at the root of the repo.
Note: There must be an
__init__.py
file in the/jobs/
directory for both local and repository instances.
Plugins
If you are writing a Nautobot plugin, you can include jobs as a part of the plugin by listing them in a /jobs.py
file included in the plugin. Writing plugins is out of scope for this blog post, but we mention this for sake of completeness.
Creating Jobs
Nautobot’s documentation has extensive documentation on how to write jobs code, so we won’t rehash that here.
Examples
The Nautobot documentation has a great example of a job that will create a new site with a user-defined amount of new devices.
Additionally, here is the code for the read-only example featured in this blog:
"""
Copyright 2021 Network to Code
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import re
from nautobot.extras.jobs import Job, StringVar, MultiObjectVar
from nautobot.dcim.models import Device, DeviceRole, DeviceType, Site
def filter_devices(data, log):
"""Returns a list of devices per filter parameters
Args:
data: A dictionary from the job input
log: The log instance for logs
"""
devices = Device.objects.all()
site = data["site"]
if site:
log(f"Filter sites: {normalize(site)}")
# *__in enables passing the query set as a parameter
devices = devices.filter(site__in=site)
device_role = data["device_role"]
if device_role:
log(f"Filter device roles: {normalize(device_role)}")
devices = devices.filter(device_role__in=device_role)
device_type = data["device_type"]
if device_type:
log(f"Filter device types: {normalize(device_type)}")
devices = devices.filter(device_type__in=device_type)
return devices
class FormData:
site = MultiObjectVar(
model = Site,
required = False,
)
device_role = MultiObjectVar(
model = DeviceRole,
required = False,
)
device_type = MultiObjectVar(
model = DeviceType,
required = False,
)
class VerifyHostnames(Job):
"""Demo job that verifies device hostnames match corporate standards."""
class Meta:
"""Meta class for VerifyHostnames"""
name = "Verify Hostnames"
description = "Verify device hostnames match corporate standards"
read_only = True
site = FormData.site
device_role = FormData.device_role
device_type = FormData.device_type
hostname_regex = StringVar(
description = "Regular expression to check the hostname against",
default = ".*",
required = True
)
def run(self, data=None, commit=None):
"""Executes the job"""
regex = data["hostname_regex"]
self.log(f"Using the regular expression: {regex}")
for device in filter_devices(data, self.log_debug):
if re.search(regex, device.name):
self.log_success(obj=device, message="Hostname is compliant.")
else:
self.log_failure(obj=device, message="Hostname is not compliant.")
Conclusion
Jobs are an extensibility feature that allows custom code execution. Jobs can provide value stand-alone, as part of Nautobot apps, and adding complementary capabilities with Nautobot apps.
Thank you, and have a great day!
-Tim
Tags :
Contact Us to Learn More
Share details about yourself & someone from our team will reach out to you ASAP!