-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
299 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,19 @@ | ||
"""Top-level package for NetBox Napalm Plugin.""" | ||
|
||
__author__ = """Arthur Hanson""" | ||
__email__ = '[email protected]' | ||
__version__ = '0.1.0' | ||
__email__ = "[email protected]" | ||
__version__ = "0.1.0" | ||
|
||
|
||
from extras.plugins import PluginConfig | ||
|
||
|
||
class NapalmConfig(PluginConfig): | ||
name = 'netbox_napalm_plugin' | ||
verbose_name = 'NetBox Napalm Plugin' | ||
description = 'NetBox plugin for Napalm.' | ||
version = 'version' | ||
base_url = 'netbox_napalm_plugin' | ||
name = "netbox_napalm_plugin" | ||
verbose_name = "NetBox Napalm Plugin" | ||
description = "NetBox plugin for Napalm." | ||
version = "version" | ||
base_url = "netbox_napalm_plugin" | ||
|
||
|
||
config = NapalmConfig |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from dcim.api.serializers import NestedPlatformSerializer | ||
from netbox.api.serializers import NetBoxModelSerializer | ||
from rest_framework import serializers | ||
|
||
from netbox_napalm_plugin.models import NapalmPlatform | ||
|
||
|
||
class NapalmPlatformSerializer(NetBoxModelSerializer): | ||
url = serializers.HyperlinkedIdentityField( | ||
view_name="plugins-api:netbox_napalm_plugin:napalmplatform-detail" | ||
) | ||
platform = NestedPlatformSerializer() | ||
|
||
class Meta: | ||
model = NapalmPlatform | ||
fields = [ | ||
"id", | ||
"platform", | ||
"napalm_driver", | ||
"napalm_args", | ||
"tags", | ||
"created", | ||
"last_updated", | ||
] | ||
|
||
|
||
class DeviceNAPALMSerializer(serializers.Serializer): | ||
method = serializers.JSONField() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# api/urls.py | ||
from netbox.api.routers import NetBoxRouter | ||
|
||
from .views import NapalmPlatformViewSet | ||
|
||
router = NetBoxRouter() | ||
router.register("napalmplatform", NapalmPlatformViewSet) | ||
urlpatterns = router.urls |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
from dcim.models import Device | ||
from django.shortcuts import get_object_or_404, redirect, render | ||
from netbox.api.pagination import StripCountAnnotationsPaginator | ||
from netbox.api.viewsets import NetBoxModelViewSet | ||
from netbox.config import get_config | ||
from rest_framework.decorators import action | ||
from rest_framework.response import Response | ||
|
||
from netbox_napalm_plugin import filtersets | ||
from netbox_napalm_plugin.models import NapalmPlatform | ||
|
||
from . import serializers | ||
|
||
|
||
class NapalmPlatformViewSet(NetBoxModelViewSet): | ||
queryset = NapalmPlatform.objects.prefetch_related( | ||
"platform", | ||
"tags", | ||
) | ||
serializer_class = serializers.NapalmPlatformSerializer | ||
filterset_class = filtersets.NapalmPlatformFilterSet | ||
pagination_class = StripCountAnnotationsPaginator | ||
|
||
@action(detail=True, url_path="napalm") | ||
def napalm(self, request, pk): | ||
""" | ||
Execute a NAPALM method on a Device | ||
""" | ||
device = get_object_or_404(Device.objects.all(), pk=pk) | ||
if not device.primary_ip: | ||
raise ServiceUnavailable( | ||
"This device does not have a primary IP address configured." | ||
) | ||
if device.platform is None: | ||
raise ServiceUnavailable("No platform is configured for this device.") | ||
if ( | ||
not hasattr(device.platform, "napalm") | ||
or not device.platform.napalm.napalm_driver | ||
): | ||
raise ServiceUnavailable( | ||
f"No NAPALM driver is configured for this device's platform: {device.platform}." | ||
) | ||
|
||
# Check for primary IP address from NetBox object | ||
if device.primary_ip: | ||
host = str(device.primary_ip.address.ip) | ||
else: | ||
# Raise exception for no IP address and no Name if device.name does not exist | ||
if not device.name: | ||
raise ServiceUnavailable( | ||
"This device does not have a primary IP address or device name to lookup configured." | ||
) | ||
try: | ||
# Attempt to complete a DNS name resolution if no primary_ip is set | ||
host = socket.gethostbyname(device.name) | ||
except socket.gaierror: | ||
# Name lookup failure | ||
raise ServiceUnavailable( | ||
f"Name lookup failure, unable to resolve IP address for {device.name}. Please set Primary IP or " | ||
f"setup name resolution." | ||
) | ||
|
||
# Check that NAPALM is installed | ||
try: | ||
import napalm | ||
from napalm.base.exceptions import ModuleImportError | ||
except ModuleNotFoundError as e: | ||
if getattr(e, "name") == "napalm": | ||
raise ServiceUnavailable( | ||
"NAPALM is not installed. Please see the documentation for instructions." | ||
) | ||
raise e | ||
|
||
# Validate the configured driver | ||
try: | ||
driver = napalm.get_network_driver(device.platform.napalm_driver) | ||
except ModuleImportError: | ||
raise ServiceUnavailable( | ||
"NAPALM driver for platform {} not found: {}.".format( | ||
device.platform, device.platform.napalm_driver | ||
) | ||
) | ||
|
||
# Verify user permission | ||
if not request.user.has_perm("dcim.napalm_read_device"): | ||
return HttpResponseForbidden() | ||
|
||
napalm_methods = request.GET.getlist("method") | ||
response = {m: None for m in napalm_methods} | ||
|
||
config = get_config() | ||
username = config.NAPALM_USERNAME | ||
password = config.NAPALM_PASSWORD | ||
timeout = config.NAPALM_TIMEOUT | ||
optional_args = config.NAPALM_ARGS.copy() | ||
if device.platform.napalm_args is not None: | ||
optional_args.update(device.platform.napalm_args) | ||
|
||
# Update NAPALM parameters according to the request headers | ||
for header in request.headers: | ||
if header[:9].lower() != "x-napalm-": | ||
continue | ||
|
||
key = header[9:] | ||
if key.lower() == "username": | ||
username = request.headers[header] | ||
elif key.lower() == "password": | ||
password = request.headers[header] | ||
elif key: | ||
optional_args[key.lower()] = request.headers[header] | ||
|
||
# Connect to the device | ||
d = driver( | ||
hostname=host, | ||
username=username, | ||
password=password, | ||
timeout=timeout, | ||
optional_args=optional_args, | ||
) | ||
try: | ||
d.open() | ||
except Exception as e: | ||
raise ServiceUnavailable( | ||
"Error connecting to the device at {}: {}".format(host, e) | ||
) | ||
|
||
# Validate and execute each specified NAPALM method | ||
for method in napalm_methods: | ||
if not hasattr(driver, method): | ||
response[method] = {"error": "Unknown NAPALM method"} | ||
continue | ||
if not method.startswith("get_"): | ||
response[method] = {"error": "Only get_* NAPALM methods are supported"} | ||
continue | ||
try: | ||
response[method] = getattr(d, method)() | ||
except NotImplementedError: | ||
response[method] = { | ||
"error": "Method {} not implemented for NAPALM driver {}".format( | ||
method, driver | ||
) | ||
} | ||
except Exception as e: | ||
response[method] = {"error": "Method {} failed: {}".format(method, e)} | ||
d.close() | ||
|
||
return Response(response) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,25 @@ | ||
from netbox.filtersets import NetBoxModelFilterSet | ||
import django_filters | ||
from dcim.models import Platform | ||
from django.utils.translation import gettext as _ | ||
from netbox.filtersets import (NetBoxModelFilterSet, | ||
OrganizationalModelFilterSet) | ||
|
||
from .models import NapalmPlatform | ||
|
||
|
||
# class NapalmFilterSet(NetBoxModelFilterSet): | ||
# | ||
# class Meta: | ||
# model = Napalm | ||
# fields = ['name', ] | ||
# | ||
# def search(self, queryset, name, value): | ||
# return queryset.filter(description__icontains=value) | ||
class NapalmPlatformFilterSet(NetBoxModelFilterSet): | ||
platform_id = django_filters.ModelMultipleChoiceFilter( | ||
field_name="platform", | ||
queryset=Platform.objects.all(), | ||
label=_("Platform (ID)"), | ||
) | ||
platform = django_filters.ModelMultipleChoiceFilter( | ||
field_name="platform__slug", | ||
queryset=Platform.objects.all(), | ||
to_field_name="slug", | ||
label=_("Platform (slug)"), | ||
) | ||
|
||
class Meta: | ||
model = NapalmPlatform | ||
fields = ["id", "napalm_driver"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,17 @@ | ||
from django import forms | ||
|
||
from ipam.models import Prefix | ||
from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm | ||
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelForm | ||
from utilities.forms.fields import CommentField, DynamicModelChoiceField | ||
|
||
from .models import NapalmPlatform | ||
|
||
|
||
class NapalmPlatformForm(NetBoxModelForm): | ||
|
||
class Meta: | ||
model = NapalmPlatform | ||
fields = ('tags', ) | ||
fields = ( | ||
"platform", | ||
"napalm_driver", | ||
"napalm_args", | ||
"tags", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,19 @@ | ||
from extras.plugins import PluginMenuButton, PluginMenuItem | ||
from utilities.choices import ButtonColorChoices | ||
|
||
|
||
plugin_buttons = [ | ||
PluginMenuButton( | ||
link='plugins:netbox_napalm_plugin:napalmplatform_add', | ||
title='Add', | ||
icon_class='mdi mdi-plus-thick', | ||
color=ButtonColorChoices.GREEN | ||
link="plugins:netbox_napalm_plugin:napalmplatform_add", | ||
title="Add", | ||
icon_class="mdi mdi-plus-thick", | ||
color=ButtonColorChoices.GREEN, | ||
) | ||
] | ||
|
||
menu_items = ( | ||
PluginMenuItem( | ||
link='plugins:netbox_napalm_plugin:napalmplatform_list', | ||
link_text='Napalm', | ||
buttons=plugin_buttons | ||
link="plugins:netbox_napalm_plugin:napalmplatform_list", | ||
link_text="Napalm", | ||
buttons=plugin_buttons, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,11 @@ | ||
import django_tables2 as tables | ||
from netbox.tables import ChoiceFieldColumn, NetBoxTable | ||
|
||
from netbox.tables import NetBoxTable, ChoiceFieldColumn | ||
from .models import NapalmPlatform | ||
|
||
|
||
class NapalmPlatformTable(NetBoxTable): | ||
|
||
class Meta(NetBoxTable.Meta): | ||
model = NapalmPlatform | ||
fields = ('pk', 'platform__name', 'napalm_driver', 'napalm_args', 'actions') | ||
default_columns = ('platform__name', 'napalm_driver') | ||
fields = ("pk", "platform__name", "napalm_driver", "napalm_args", "actions") | ||
default_columns = ("platform__name", "napalm_driver") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.