Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initiating Nautobot jobs #224

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bc17159
+ Add working concept of initiating Nautobot jobs
Jul 31, 2023
6c42a8b
+ Run black code formatter
Jul 31, 2023
8784462
+ pydocstyle CI fix
Jul 31, 2023
e9a4e05
+ Correct user testing typo
Jul 31, 2023
5133183
+ Pylint CI fixes
Jul 31, 2023
1878676
+ Update markdown
Aug 1, 2023
4126f47
Adds some tests
jvanderaa Aug 2, 2023
8318612
Black.
jvanderaa Aug 2, 2023
c4f9605
Changelog
jvanderaa Aug 2, 2023
0932274
Updates nautobot tests.
jvanderaa Aug 3, 2023
3103a45
Updates tests.
jvanderaa Aug 3, 2023
7647f40
Adds additional town crier.
jvanderaa Aug 3, 2023
6de3746
Updates unittest.
jvanderaa Aug 3, 2023
ce5e0e2
Updated logo.
jjeff07 Aug 15, 2023
e088089
Create 239.fixed
Sep 12, 2023
e02d341
Sort list of ICMP types for diagrams so easier to select in the dropd…
jjeff07 Sep 12, 2023
f9c61b3
Create 253.fixed
Sep 12, 2023
6fed194
Merge pull request #240 from justinjeffery-ipf/develop
smk4664 Sep 20, 2023
87f8b6c
Merge pull request #254 from justinjeffery-ipf/sort_IPF_icmp_list
smk4664 Sep 22, 2023
0d7060a
Remove Grafana urls and navigation if disabled.
smk4664 Sep 22, 2023
e500705
Merge pull request #262 from smk4664/grafana-fix-navigation
smk4664 Sep 22, 2023
7a58f86
Merge pull request #227 from jvanderaa/add_some_tests
smk4664 Sep 22, 2023
e54beb3
Release 2.0.3
smk4664 Sep 22, 2023
a77e55c
Merge pull request #263 from smk4664/release-2.0.3
smk4664 Sep 23, 2023
4bcb58c
Update init_job for branch `next-2.0` dispatch.user feature for user …
Sep 25, 2023
c64b769
run black formatter
Sep 25, 2023
9071558
Merge branch 'nautobot:develop' into nikko-run-job-concept
MeganerdDev Sep 25, 2023
cc257fe
Add filter_jobs subcommand, and correct job_filters logic
Sep 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion changes/223.fixed

This file was deleted.

3 changes: 3 additions & 0 deletions changes/224.added
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add init_job Nautobot subcommand, which starts a Nautobot job by job name.
Add get_jobs Nautobot subcommand, which gets all Nautobot jobs.
Add filter_jobs Nautobot subcommand, which gets filtered set of Nautobot jobs.
13 changes: 13 additions & 0 deletions docs/admin/release_notes/version_2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
# v2.0 Release Notes

<!-- towncrier release notes start -->
## [v2.0.3 (2023-09-22)](https://github.com/nautobot/nautobot-plugin-chatops/releases/tag/v2.0.3)

### Added

- [#227](https://github.com/nautobot/nautobot-plugin-chatops/issues/227) - Added some tests for VLAN chatops.

### Fixed

- [#227](https://github.com/nautobot/nautobot-plugin-chatops/issues/227) - Fixed parameters that should be set to None if they have not been defined yet by default.
- [#239](https://github.com/nautobot/nautobot-plugin-chatops/issues/239) - Updated IP Fabric Logo.
- [#241](https://github.com/nautobot/nautobot-plugin-chatops/issues/241) - Remove Grafana Navigation and urls if disabled.
- [#253](https://github.com/nautobot/nautobot-plugin-chatops/issues/253) - Sorted the ICMP types from IP Fabric diagrams package.

## [v2.0.2 (2023-08-11)](https://github.com/nautobot/nautobot-plugin-chatops/releases/tag/v2.0.2)

### Changed
Expand Down
12 changes: 6 additions & 6 deletions nautobot_chatops/integrations/grafana/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""Django urlpatterns declaration for nautobot_chatops.integrations.grafana plugin."""

# from django.urls import path
from django.conf import settings

from nautobot.core.api import OrderedDefaultRouter
from nautobot_chatops.integrations.grafana.api.views.generic import NautobotPluginChatopsGrafanaRootView


urlpatterns = []
if settings.PLUGINS_CONFIG["nautobot_chatops"]["enable_grafana"]:
router = OrderedDefaultRouter()
router.APIRootView = NautobotPluginChatopsGrafanaRootView

router = OrderedDefaultRouter()
router.APIRootView = NautobotPluginChatopsGrafanaRootView
app_name = "nautobot_chatops.grafana-api"

app_name = "nautobot_chatops.grafana-api"

urlpatterns += router.urls
urlpatterns += router.urls
2 changes: 1 addition & 1 deletion nautobot_chatops/integrations/ipfabric/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ def pathlookup_icmp(dispatcher, src_ip, dst_ip, icmp_type): # pylint: disable=t
"""Path simulation diagram lookup between source and target IP address."""
sub_cmd = "pathlookup-icmp"
icmp_type = icmp_type.upper() if isinstance(icmp_type, str) else icmp_type
icmp_types = [(icmp_type_name.upper(), icmp_type_name) for icmp_type_name in icmp.__all__]
icmp_types = sorted([(icmp_type_name.upper(), icmp_type_name) for icmp_type_name in icmp.__all__])

# identical to dialog_list in end-to-end-path; consolidate dialog_list if maintaining both cmds
dialog_list = [
Expand Down
6 changes: 5 additions & 1 deletion nautobot_chatops/navigation.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
"""Plugin additions to the Nautobot navigation menu."""

from django.conf import settings
from nautobot.extras.plugins import PluginMenuItem, PluginMenuButton
from nautobot.utilities.choices import ButtonColorChoices

from .integrations.grafana.navigation import menu_items as grafana_menu_items
if settings.PLUGINS_CONFIG["nautobot_chatops"]["enable_grafana"]:
from .integrations.grafana.navigation import menu_items as grafana_menu_items
else:
grafana_menu_items = ()

menu_items = (
PluginMenuItem(
Expand Down
Binary file not shown.
Binary file modified nautobot_chatops/static/ipfabric/ipfabric_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions nautobot_chatops/tests/workers/test_nautobot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Tests for the /nautobot chatops commands."""
from unittest.mock import MagicMock

from django.test import TestCase
from nautobot.dcim.models import Site
from nautobot.ipam.models import VLAN
from nautobot.extras.models import Status
from nautobot_chatops.choices import CommandStatusChoices

from nautobot_chatops.dispatchers import Dispatcher
from nautobot_chatops.workers.nautobot import get_vlans


class IpamTestCase(TestCase):
"""Tests related to IPAM ChatOps commands."""

def setUp(self):
"""Per-test-case setup function."""
self.active_status = Status.objects.get(name="Active")
self.site = Site.objects.create(name="site-1", status=self.active_status)
self.vlans, _ = VLAN.objects.get_or_create(vid=1, name="vlan-1", status=self.active_status, site=self.site)

# Mock the dispatcher
self.dispatcher = MagicMock(Dispatcher)

def test_get_vlans_initial_prompt(self):
"""Test get VLANs initial command."""
self.assertFalse(get_vlans(self.dispatcher))
self.dispatcher.send_error.assert_not_called()
self.dispatcher.prompt_from_menu.assert_called_with(
"nautobot get-vlans",
"select a vlan filter",
[
("VLAN ID", "id"),
("Group", "group"),
("Name", "name"),
("Role", "role"),
("Site", "site"),
("Status", "status"),
("Tenant", "tenant"),
("All (no filter)", "all"),
],
)

def test_get_vlans_filter_type_sent_filter_name(self):
"""Test get VLANs with filter type Name selected."""
self.assertFalse(get_vlans(self.dispatcher, "name"))
self.dispatcher.send_error.assert_not_called()
self.dispatcher.prompt_from_menu.assert_called_with(
"nautobot get-vlans name", "select a vlan name", [("vlan-1", "vlan-1")], offset=0
)

def test_get_vlans_filter_type_sent_filter_all(self):
"""Test get VLANs with filter type All selected."""
self.assertEqual(get_vlans(self.dispatcher, "all"), CommandStatusChoices.STATUS_SUCCEEDED)
self.dispatcher.send_error.assert_not_called()
15 changes: 10 additions & 5 deletions nautobot_chatops/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Django urlpatterns declaration for nautobot_chatops plugin."""
import logging

from django.conf import settings
from django.urls import path

from nautobot.extras.views import ObjectChangeLogView
Expand All @@ -18,15 +19,19 @@
AccessGrantBulkDeleteView,
)

try:
from nautobot_chatops.integrations.grafana.urls import urlpatterns as grafana_urlpatterns
# pylint: disable-next=broad-except
except Exception:
if settings.PLUGINS_CONFIG["nautobot_chatops"]["enable_grafana"]:
try:
from nautobot_chatops.integrations.grafana.urls import urlpatterns as grafana_urlpatterns
# pylint: disable-next=broad-except
except Exception:
grafana_urlpatterns = []
logger = logging.getLogger(__name__)
logger.warning("Grafana ChatOps integration is not available.", exc_info=True)
else:
grafana_urlpatterns = []
logger = logging.getLogger(__name__)
logger.warning("Grafana ChatOps integration is not available.", exc_info=True)


urlpatterns = [
path("", NautobotHomeView.as_view(), name="home"),
path("access/", AccessGrantListView.as_view(), name="accessgrant_list"),
Expand Down
108 changes: 106 additions & 2 deletions nautobot_chatops/workers/nautobot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Worker functions for interacting with Nautobot."""

import uuid


from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db.models import Count
from django.contrib.contenttypes.models import ContentType
Expand All @@ -11,7 +15,10 @@
from nautobot.dcim.models import Device, Site, DeviceRole, DeviceType, Manufacturer, Rack, Region, Cable
from nautobot.ipam.models import VLAN, Prefix, VLANGroup, Role
from nautobot.tenancy.models import Tenant
from nautobot.extras.models import Status
from nautobot.extras.context_managers import web_request_context
from nautobot.extras.jobs import run_job
from nautobot.extras.models import Job, JobResult, Status
from nautobot.extras.utils import get_job_content_type

from nautobot_chatops.choices import CommandStatusChoices
from nautobot_chatops.workers import subcommand_of, handle_subcommands
Expand Down Expand Up @@ -160,7 +167,7 @@ def examine_termination_endpoints(circuit):

# pylint: disable=too-many-statements
@subcommand_of("nautobot")
def get_vlans(dispatcher, filter_type, filter_value_1):
def get_vlans(dispatcher, filter_type=None, filter_value_1=None):
"""Return a filtered list of VLANs based on filter type and/or `filter_value_1`."""
# pylint: disable=no-else-return
if not filter_type:
Expand Down Expand Up @@ -1016,6 +1023,103 @@ def get_circuit_providers(dispatcher, *args):
return CommandStatusChoices.STATUS_SUCCEEDED


@subcommand_of("nautobot")
def filter_jobs(
dispatcher, job_filters: str = ""
): # We can use a Literal["enabled", "installed", "runnable"] here instead
"""Get a filtered list of jobs from Nautobot.

Args:
job_filters (str): Filter job results by literals in a comma-separated string.
Available filters are: enabled, installed or runnable.
"""
# Check for filters in user supplied input
job_filters_list = [item.strip() for item in job_filters.split(",")] if isinstance(job_filters, str) else ""
filters = ["enabled", "installed", "runnable"]
if any([key in job_filters for key in filters]):
filter_args = {key: True for key in filters if key in job_filters_list}
jobs = Job.objects.restrict(dispatch.user, "view").filter(
**filter_args
) # enabled=True, installed=True, runnable=True
else:
jobs = Job.objects.restrict(dispatch.user, "view").all()

header = ["Name", "ID"]
rows = [
(
str(job.name),
str(job.id),
)
for job in jobs
]

dispatcher.send_large_table(header, rows)

return CommandStatusChoices.STATUS_SUCCEEDED


@subcommand_of("nautobot")
def get_jobs(dispatcher):
"""Get all jobs from Nautobot."""
jobs = Job.objects.restrict(dispatch.user, "view").all()

header = ["Name", "ID"]
rows = [
(
str(job.name),
str(job.id),
)
for job in jobs
]

dispatcher.send_large_table(header, rows)

return CommandStatusChoices.STATUS_SUCCEEDED


@subcommand_of("nautobot")
def init_job(dispatcher, job_name):
"""Initiate a job in Nautobot by job name."""
# Get instance of the user who will run the job
user = get_user_model()
try:
user_instance = user.objects.get(username=dispatch.user)
except user.DoesNotExist: # Unsure if we need to check this case?
dispatcher.send_error(f"User {dispatch.user} not found")
return (CommandStatusChoices.STATUS_FAILED, f'User "{dispatch.user}" not found')

# Get the job model instance using job name
try:
job_model = Job.objects.restrict(dispatch.user, "view").get(name=job_name)
except Job.DoesNotExist:
dispatcher.send_error(f"Job {job_name} not found")
return (CommandStatusChoices.STATUS_FAILED, f'Job "{job_name}" not found')

job_class_path = job_model.class_path

# Create an instance of job result
job_result = JobResult.objects.create(
name=job_model.class_path,
job_kwargs={"data": {}, "commit": True, "profile": False},
obj_type=get_job_content_type(),
user=user_instance,
job_model=job_model,
job_id=uuid.uuid4(),
)

# Emulate HTTP context for the request as the user
with web_request_context(user=user_instance) as request:
run_job(data={}, request=request, commit=True, job_result_pk=job_result.pk)

blocks = [
dispatcher.markdown_block(f"The requested job {job_class_path} was initiated!"),
]

dispatcher.send_blocks(blocks)

return CommandStatusChoices.STATUS_SUCCEEDED


@subcommand_of("nautobot")
def about(dispatcher, *args):
"""Provide link for more information on Nautobot Apps."""
Expand Down
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "nautobot-chatops"
version = "2.0.2"
version = "2.0.3"
description = "A plugin providing chatbot capabilities for Nautobot"
authors = ["Network to Code, LLC <[email protected]>"]
readme = "README.md"
Expand Down Expand Up @@ -44,7 +44,7 @@ ipfabric-diagrams = { version = "~6.0.2", optional = true }
isodate = { version = "^0.6.1", optional = true }
meraki = { version = "^1.7.2", optional = true }
nautobot = "^1.5.4"
nautobot-capacity-metrics = "*"
nautobot-capacity-metrics = "^2.0.0"
netmiko = { version = "^3.4.0", optional = true }
netutils = { version = "^1.1.0", optional = true }
pan-os-python = { version = "^1.3.0", optional = true }
Expand Down