Creating Custom Chat Commands Using Nautobot ChatOps
Josh VanDeraa
April 1, 2021
HomeBlogCreating Custom Chat Commands Using Nautobot ChatOps
ChatOps and chat bots are becoming an ever more popular method to interact with network systems. Whether the command is to get operational status, to full-on operational configuration commands, interactions via chat is here. Nautobot not only is a Source of Truth, but is also a Network Automation Platform to build apps on top of! One app already released is the ChatOps plugin that you can use to accelerate the implementation of your individual chatbot needs! You can explore a public demo of the ChatOps plugin in the Network to Code (NTC) Slack channel #nautobot-chatops. The ChatOps plugin today has support for integration with Microsoft Teams, Cisco Webex, Slack, and Mattermost. To install and configure the plugin with your own particular chat environment, take a look at the docs.
Extending the ChatOps plugin to support your own commands is quite straightforward. This post provides a walk-through guide to getting up and started with your own custom chat commands. Underneath the entire plugin is Python, which allows your chat commands to interact with nearly any system you want. Want to get information from a third-party API and present it back? Want to kick off an Ansible playbook execution in AWX/Tower? Want to interact with a network device over SSH? All of these are absolutely possible!
Exploring ChatOps Connection to Nautobot
By default and out of the box, the Nautobot ChatOps plugin will provide the capability to query the Nautobot environment directly. This is what is enabled on the NTC Slack channel. It is tied to the demo instance of Nautobot. Take a look at our NFD talk introduction to ChatOps and demonstration for more examples of Nautobot ChatOps features available with installation.
Exploring Slack-Specific Commands
Slack Command Help
Within Slack specifically, to get started you probably want to see all of the commands that are available for the chatbot. In this case the bot name is nautobot, so when you send the command /nautobot to the channel you get the response with the following:
Some points to note as you start thinking about adding your own commands:
Ordering of the commands: This is controlled by the order of the functions as they get defined in the plugin definition file.
Help text: This is gathered from the first line of the docstring that accompanies the function.
Slack Command Example – Get Devices
First, looking at the help output for get-devices it shows /nautobot get-devices [filter-type] [filter-value]. If one were to issue the command with the filter-type and filter-value defined, then that is all that is needed to kick off the command. The second option is to just issue the command /nautobot get-devices and the bot will prompt back for those particular items. As the prompt gets answered, the next option continues on. So in this example you can get the data with either the command where you follow the prompts, or just issue the complete command /nautobot get-devices site nyc.
The result is a text entry that is:
Name Status Tenant Site Rack Role Type IP Address==================================================================================================nyc-bb-01.infra.ntc.com Active New York City Backbone vMX nyc-leaf-01.infra.ntc.com Active New York City leaf vEOS nyc-leaf-02.infra.ntc.com Active New York City leaf vEOS nyc-rtr-01.infra.ntc.com Active New York City Router vMX nyc-rtr-02.infra.ntc.com Active New York City Router vMX nyc-spine-01.infra.ntc.com Active New York City spine vEOS nyc-spine-02.infra.ntc.com Active New York City spine vEOS
Command Output Observations
Some things from the code here:
Site nyc is a slug, as in the imagery it shows the full name New York City but in the output shortcut that gets added it shows nyc. This matches the slug since spaces are not allowed in the section of the commands. Each space is a separator for the command to process.
Shortcut text is specified and written in the code had as part of the response for the bot. The bot itself does not add this or any other text.
Creating Your Own Chat Command
This walk-through will be to write a new chat command (as a Nautobot ChatOps plugin) that will be named netchat within Slack. The focus here is on getting started writing your own code rather than the chat platform itself. What gets outlined here should work for any other chat platform with minimal modifications. The document for chat setup referenced earlier is your guide to walk through getting the chat platform ready.
Once you have written the code, the same code is designed to work with Slack, Microsoft Teams, Cisco Webex, and Mattermost. Nautobot ChatOps can interact with multiple platforms from the same API endpoint. Allowing for interactions from any of the platforms defined in the Nautobot configuration.
There is not a limitation to only these four chat platforms. This can be expanded with additional dispatchers to be added into the ChatOps plugin. Recently this was extended to support Mattermost as an example, which was not supported at the beginning of 2021.
In this walk-through, we will be adding a command to get the device inventory from a Meraki Organization by interacting with the Meraki Dashboard API.
Demo Setup
Demo Setup Assumptions
Nautobot is locally installed via the local installation methods
The Nautobot ChatOps Plugin is required for adding custom chat commands
Demo Setup – Nautobot Configuration
The first step is to set up the Nautobot configuration. Within (/opt/nautobot/nautobot_config.py) a dictionary is added to PLUGINS_CONFIG. It shows as follows:
nautobot_chatops is the required first key to indicate this is the plugin configuration for the ChatOps plugin.
The value of nautobot_chatops is another dictionary that has the keys:
enable_slack: Boolean field for having Slack enabled. There are enable_* values for each of the chat platforms there are dispatchers for. There can be multiple platforms enabled at one time.
slack_api_token: Token for the bot defined within the chatbot config on api.slack.com
slack_signing_secret: Signing secret as defined within the chatbot config on api.slack.com
There are other keys per platform, depending on the chat platform
Demo Setup – Nautobot Environment Setup
The Slack configuration is configured in the environment, not in the configuration file. The environment is read by the configuration file and thus loaded into Nautobot. In order to get the updated environment, the Nautobot service files are updated to reference the environment file /opt/nautobot.env.
Add EnvironmentFile=/opt/nautobot/.env to both /etc/systemd/system/nautobot.service and /etc/systemd/system/nautobot-worker.service files.
After modifying the systemd files, execute daemon-reload and restart the services. Once the service is restarted it is recommended to check the status of the bot by issuing a Slack command /nautobot to get the help context.
Inside the environment file is the Meraki API key to put the API key into the environment as required by the Meraki SDK.
Using Existing Methods for Portability
The first recommendation is to look at the design docs that show how the plugin is designed. It is recommended, wherever possible, to use the Python methods provided, especially in the interaction back to the chat platform. For example, one could set up the mention of @username easily in the response to tag a user. However, there is a Python method within the dispatcher to get the user mention. This way, should a platform need to be migrated say from Slack to Microsoft Teams, updates are not needed to the code, just a change of the settings. This allows for the chat bot to interact with multiple chat platforms at the same time. Take a look at the code inside of GitHub for your specific platform to see what methods are available!
Creating the Custom Chat Commands
After setting up the settings mentioned above to load the configuration into the environment, I logged into the user account for nautobot with sudo -iu nautobot. Following the instructions for Nautobot v1.0.0b2 or later, this has the user root in /opt/nautobot. Here create a new directory plugins/netchat (/opt/nautobot/plugins/netchat/).
The directory structure that will be built out is:
First step in this is to build out the directory structure and adding the __init__.py file. This creates a new /opt/nautobot/plugins directory, as well as the directory structure for the package netchat.
cd /opt/nautobotmkdir -p plugins/netchat/netchattouch /opt/nautobot/plugins/netchat/netchat/__init__.py
This ChatOps plugin will use Python Poetry to handle the packaging and configuration. To install Poetry, take a look at the installation steps outlined by Poetry. This should follow their installation methods, and do not install with Python PIP. They were specifically chosen to help with the entry point configuration which is required to add onto the chatops plugin. The configuration pieces specific to the netchat plugin for the /opt/nautobot/plugins/netchat/pyproject.toml:
[tool.poetry]name ="netchat"version ="0.1.1"description =""authors = ["Network to Code <opensource@networktocode.com>"][tool.poetry.plugins."nautobot.workers"]"netchat"="netchat.worker:netchat"
The first section of tool.poetry outlines the Python package itself, in this instance it is called netchat. The second section tool.poetry.plugins."nautobot.workers" defines the “entry point” to register the code as an extension of the ChatOps plugin.
The left-hand side of "netchat" = corresponds to the slash command that is being installed, and the right-hand side of the line = "netchat.worker:netchat is how to get to the function. In this case, netchat.worker refers to the file netchat/worker.py, and the netchat to the right of the : is the function name inside of this file.
In the netchat plugin, the code for the chat bot will be housed within /opt/nautobot/plugins/netchat/worker.py. This is from the reference in the pyproject.toml file in the configuration "netchat" = "netchat.worker:netchat". This could be any file name, something that meaningful to the plugin setup. The code for worker.py is:
"""Demo netchat addition to Nautobot."""import loggingfrom django_rq import jobfrom nautobot_chatops.workers import subcommand_of, handle_subcommandsfrom nautobot_chatops.choices import CommandStatusChoicesimport merakilogger = logging.getLogger("rq.worker")@job("default")def netchat(subcommand,**kwargs):"""Interact with netchat.""" return handle_subcommands("netchat", subcommand,**kwargs)def get_meraki_orgs():"""Query the Meraki Dashboard API for a list of defined organizations.""" dashboard = meraki.DashboardAPI(suppress_logging=True) return dashboard.organizations.getOrganizations()def meraki_devices(org_name):"""Query the Meraki Dashboard API for a list of devices in the given organization.""" # Get the org ID from the Get Meraki Orgs org_list =get_meraki_orgs() dashboard = meraki.DashboardAPI(suppress_logging=True) for org inorg_list:if org["name"] ==org_name: device_list = dashboard.organizations.getOrganizationDevices(organizationId=org["id"])return device_listreturn []@subcommand_of("netchat")def get_meraki_devices(dispatcher, org_name=None):"""Gathers devices from Meraki API endpoint.""" logger.info(f"ORG NAME: {org_name}")if not org_name: # The user didn't specify an organization, so prompt them to pick one org_list =get_meraki_orgs() # Build the list of sites, each asapairof (user-visiblestring,internalvalue) entries choices = [(x["name"], x["name"]) for x in org_list] dispatcher.prompt_from_menu(f"netchat get-meraki-devices","Select Organization", choices) # Returning False indicates that the command needed to prompt the user for more information return False # If gathering information from another system may take some time, it's useful to send the user dispatcher.send_markdown( f"Stand by {dispatcher.user_mention()}, I'm getting the devices at the Organization {org_name}!" ) devices =meraki_devices(org_name) # Render the list of devices to Markdown for display to the user's chat client blocks = [ dispatcher.markdown_block(f"{dispatcher.user_mention()} here are the devices at {org_name}"), dispatcher.markdown_block("\n".join([x["name"] for x in devices])), ] dispatcher.send_blocks(blocks)return CommandStatusChoices.STATUS_SUCCEEDED
The important parts are, first, the function registration of the chat command:
@job("default")def netchat(subcommand,**kwargs):"""Interact with netchat."""returnhandle_subcommands("netchat", subcommand,**kwargs)
This registers the netchat slash command to the ChatOps plugin. It corresponds with the entry point registration. Note the docstring of the function is what will be displayed to the chat user when listing available commands.
The chat command itself is then handled by the next defined function get_meraki_devices. This function registers itself to be a subcommand of netchat The first argument must always be dispatcher, which is an object used for all interactions with the chat platform. The remaining arguments are potential user inputs to the chat command; here we have just one, the org_name argument. This is the part of the chat command `/netchat get-meraki-devices `. When a user first sends the command `/netchat get-meraki-devices`, the org_name is `None`, which allows for the function to gather a list of choices and present it back.
@subcommand_of("netchat")def get_meraki_devices(dispatcher, org_name=None):"""Gathers devices from Meraki API endpoint."""if not org_name:<gatherorganizations> # When org_name is passed in<domorewithcode> ... # More Code
Through the prompts, the chat applications will not sort the data. In the example, everything is alphabetized because the code called for it to be alphabetized. Slack itself does not alphabetize the options. The text boxes are something that you can type in to filter for yourself.
Installation via Poetry
The final step after writing the code is that the package must be installed into the Nautobot environment. In this case since the package has not been published to PyPI, we will use Poetry to install it directly from source into the venv. This does require the virtual environment to be activated. As the Nautobot user:
cd ~source bin/activate
Once activated, move to the plugin directory, use Poetry to add requirements (in this case typing-extensions and meraki), then install the local package to the virtual environment.
Once the netchat package is installed, exit out of the Nautobot user. Then restart the Nautobot application and Nautobot worker process. If there are access grants that are restricting access to the chatbot, be sure to make the necessary updates in Nautobot to allow the new slash command.
sudo systemctl restart nautobot nautobot-worker
With the workers restarted, the slash command should be available on the chat application!
Conclusion
The ChatOps plugin is an excellent starting point for your own custom chat application. As demonstrated by its built-in commands, you can write chat commands that interact directly with Nautobot. But the ChatOps plugin can do far more than that. With Python, the capabilities to interact with other third-party services are endless!
Does this all sound amazing? Want to know more about how Network to Code can help you do this, reach out to our sales team. If you want to help make this a reality for our clients, check out our careers page.
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies. In case of sale of your personal information, you may opt out by using the link Do not sell my personal information. Privacy | Cookies
This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
__hssc
30 minutes
HubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.
__hssrc
session
This cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.
cookielawinfo-checkbox-advertisement
1 year
Set by the GDPR Cookie Consent plugin, this cookie records the user consent for the cookies in the "Advertisement" category.
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
CookieLawInfoConsent
1 year
CookieYes sets this cookie to record the default button state of the corresponding category and the status of CCPA. It works only in coordination with the primary cookie.
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Cookie
Duration
Description
__cf_bm
30 minutes
Cloudflare set the cookie to support Cloudflare Bot Management.
li_gc
5 months 27 days
Linkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.
lidc
1 day
LinkedIn sets the lidc cookie to facilitate data center selection.
UserMatchHistory
1 month
LinkedIn sets this cookie for LinkedIn Ads ID syncing.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Cookie
Duration
Description
__hstc
5 months 27 days
Hubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).
_ga
1 year 1 month 4 days
Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.
_gat_gtag_UA_*
1 minute
Google Analytics sets this cookie to store a unique user ID.
_gid
1 day
Google Analytics sets this cookie to store information on how visitors use a website while also creating an analytics report of the website's performance. Some of the collected data includes the number of visitors, their source, and the pages they visit anonymously.
AnalyticsSyncHistory
1 month
Linkedin set this cookie to store information about the time a sync took place with the lms_analytics cookie.
CONSENT
2 years
YouTube sets this cookie via embedded YouTube videos and registers anonymous statistical data.
hubspotutk
5 months 27 days
HubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.
ln_or
1 day
Linkedin sets this cookie to registers statistical data on users' behaviour on the website for internal analytics.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Cookie
Duration
Description
bcookie
1 year
LinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.
bscookie
1 year
LinkedIn sets this cookie to store performed actions on the website.
li_sugr
3 months
LinkedIn sets this cookie to collect user behaviour data to optimise the website and make advertisements on the website more relevant.
VISITOR_INFO1_LIVE
5 months 27 days
YouTube sets this cookie to measure bandwidth, determining whether the user gets the new or old player interface.
YSC
session
Youtube sets this cookie to track the views of embedded videos on Youtube pages.
yt-remote-connected-devices
never
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
yt-remote-device-id
never
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
yt.innertube::nextId
never
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.
yt.innertube::requests
never
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.