Skip to content

Commit

Permalink
Merge pull request #106 from nautobot/next-2.0
Browse files Browse the repository at this point in the history
Updates for Nautobot 2.0
  • Loading branch information
scetron authored Sep 29, 2023
2 parents 94c1bad + 7889409 commit 7198dc2
Show file tree
Hide file tree
Showing 43 changed files with 1,691 additions and 1,346 deletions.
22 changes: 6 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ jobs:
strategy:
fail-fast: true
matrix:
python-version: ["3.8"]
nautobot-version: ["1.4.10"]
python-version: ["3.10"]
nautobot-version: ["2.0.0"]
env:
INVOKE_NAUTOBOT_DEVICE_ONBOARDING_PYTHON_VER: "${{ matrix.python-version }}"
INVOKE_NAUTOBOT_DEVICE_ONBOARDING_NAUTOBOT_VER: "${{ matrix.nautobot-version }}"
Expand Down Expand Up @@ -119,19 +119,9 @@ jobs:
strategy:
fail-fast: true
matrix:
python-version: ["3.8", "3.9", "3.10"]
python-version: ["3.10"]
db-backend: ["postgresql"]
nautobot-version: ["stable"]
include:
- python-version: "3.10"
db-backend: "postgresql"
nautobot-version: "1.4.10"
- python-version: "3.8"
db-backend: "mysql"
nautobot-version: "1.5.5"
- python-version: "3.10"
db-backend: "mysql"
nautobot-version: "latest"
nautobot-version: ["2.0.0"]

runs-on: "ubuntu-20.04"
env:
Expand Down Expand Up @@ -178,7 +168,7 @@ jobs:
- name: "Set up Python"
uses: "actions/setup-python@v2"
with:
python-version: "3.9"
python-version: "3.10"
- name: "Install Python Packages"
run: "pip install poetry"
- name: "Set env"
Expand Down Expand Up @@ -207,7 +197,7 @@ jobs:
- name: "Set up Python"
uses: "actions/setup-python@v2"
with:
python-version: "3.9"
python-version: "3.10"
- name: "Install Python Packages"
run: "pip install poetry"
- name: "Set env"
Expand Down
2 changes: 1 addition & 1 deletion development/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# !!! USE CAUTION WHEN MODIFYING LINES BELOW

# Accepts a desired Nautobot version as build argument, default to 1.4.10
ARG NAUTOBOT_VER="1.4.10"
ARG NAUTOBOT_VER="2.0.0"

# Accepts a desired Python version as build argument, default to 3.8
ARG PYTHON_VER="3.8"
Expand Down
5 changes: 3 additions & 2 deletions development/docker-compose.base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ services:
condition: "service_started"
db:
condition: "service_healthy"
<<: *nautobot-build
<<: *nautobot-base
<<:
- *nautobot-build
- *nautobot-base
worker:
entrypoint:
- "sh"
Expand Down
5 changes: 3 additions & 2 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
griffe=0.30.1
mkdocs==1.3.1
mkdocs-material==8.4.2
mkdocstrings==0.19
mkdocstrings-python==0.7.1
mkdocstrings==0.22.0
mkdocstrings-python==1.1.2
mkdocs-version-annotations==1.0.0
4 changes: 2 additions & 2 deletions nautobot_device_onboarding/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class OnboardingConfig(PluginConfig):
name = "nautobot_device_onboarding"
verbose_name = "Device Onboarding"
version = __version__
min_version = "1.4.0"
max_version = "1.99"
min_version = "2.0.0-rc.4"
max_version = "2.9"
author = "Network to Code, LLC"
author_email = "opensource@networktocode.com"
description = "Nautobot App that simplifies device onboarding (and re-onboarding) by collecting and populating common device 'facts' into Nautobot."
Expand Down
2 changes: 1 addition & 1 deletion nautobot_device_onboarding/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class OnboardingTaskAdmin(admin.ModelAdmin):
"pk",
"created_device",
"ip_address",
"site",
"location",
"role",
"device_type",
"platform",
Expand Down
89 changes: 26 additions & 63 deletions nautobot_device_onboarding/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,18 @@
"""Model serializers for the nautobot_device_onboarding REST API."""
# pylint: disable=duplicate-code

from rest_framework import serializers

from nautobot.dcim.models import Site, DeviceRole, Platform
from nautobot.core.api.serializers import NotesSerializerMixin, ValidatedModelSerializer
from nautobot.dcim.models import Location

from nautobot_device_onboarding.models import OnboardingTask
from nautobot_device_onboarding.utils.credentials import Credentials
from nautobot_device_onboarding.worker import enqueue_onboarding_task


class OnboardingTaskSerializer(serializers.ModelSerializer):
class OnboardingTaskSerializer(NotesSerializerMixin, ValidatedModelSerializer):
"""Serializer for the OnboardingTask model."""

site = serializers.SlugRelatedField(
many=False,
read_only=False,
queryset=Site.objects.all(),
slug_field="slug",
required=True,
help_text="Nautobot site 'slug' value",
)

ip_address = serializers.CharField(
required=True,
help_text="IP Address to reach device",
)

username = serializers.CharField(
required=False,
write_only=True,
Expand All @@ -45,50 +31,18 @@ class OnboardingTaskSerializer(serializers.ModelSerializer):
help_text="Device secret password",
)

port = serializers.IntegerField(required=False, help_text="Device PORT to check for online")

timeout = serializers.IntegerField(required=False, help_text="Timeout (sec) for device connect")

role = serializers.SlugRelatedField(
many=False,
read_only=False,
queryset=DeviceRole.objects.all(),
slug_field="slug",
required=False,
help_text="Nautobot device role 'slug' value",
)

device_type = serializers.CharField(
required=False,
help_text="Nautobot device type 'slug' value",
)

platform = serializers.SlugRelatedField(
many=False,
read_only=False,
queryset=Platform.objects.all(),
slug_field="slug",
required=False,
help_text="Nautobot Platform 'slug' value",
)

created_device = serializers.CharField(
required=False,
read_only=True,
help_text="Created device name",
location = serializers.SlugRelatedField(
queryset=Location.objects.all(),
required=True,
slug_field="name",
)

status = serializers.CharField(required=False, read_only=True, help_text="Onboarding Status")

failed_reason = serializers.CharField(required=False, read_only=True, help_text="Failure reason")

message = serializers.CharField(required=False, read_only=True, help_text="Status message")

class Meta: # noqa: D106 "Missing docstring in public nested class"
model = OnboardingTask

fields = [
"id",
"site",
"location",
"ip_address",
"username",
"password",
Expand All @@ -102,23 +56,32 @@ class Meta: # noqa: D106 "Missing docstring in public nested class"
"status",
"failed_reason",
"message",
"object_type",
]

def create(self, validated_data):
"""Create an OnboardingTask and enqueue it for processing."""
# Fields are string-type so default to empty (instead of None)
username = validated_data.pop("username", "")
password = validated_data.pop("password", "")
secret = validated_data.pop("secret", "")
extra_kwargs = {"location": {"required": True}}

read_only_fields = ["id", "created_device", "status", "failed_reason", "message", "object_type"]

def validate(self, data):
"""Custom Validate class to remove credential fields."""
attrs = data.copy()
username = attrs.pop("username", "")
password = attrs.pop("password", "")
secret = attrs.pop("secret", "")

credentials = Credentials(
self.credentials = Credentials( # pylint: disable=attribute-defined-outside-init
username=username,
password=password,
secret=secret,
)

return super().validate(attrs)

def create(self, validated_data):
"""Create an OnboardingTask and enqueue it for processing."""
onboarding_task = OnboardingTask.objects.create(**validated_data)

enqueue_onboarding_task(onboarding_task.id, credentials)
enqueue_onboarding_task(onboarding_task.id, self.credentials)

return onboarding_task
10 changes: 6 additions & 4 deletions nautobot_device_onboarding/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""REST API URLs for device onboarding."""

from rest_framework import routers
from nautobot_device_onboarding.api.views import OnboardingTaskView
from nautobot.apps.api import OrderedDefaultRouter

router = routers.DefaultRouter()
from nautobot_device_onboarding.api import views

router.register(r"onboarding", OnboardingTaskView)
router = OrderedDefaultRouter()
router.register("onboarding", views.OnboardingTaskViewSet)


app_name = "nautobot_device_onboarding-api"
urlpatterns = router.urls
31 changes: 9 additions & 22 deletions nautobot_device_onboarding/api/views.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
"""Django REST Framework API views for device onboarding."""

# from drf_yasg.openapi import Parameter, TYPE_STRING
# from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.response import Response

from rest_framework import mixins, viewsets

# from rest_framework.decorators import action
# from rest_framework.response import Response

# from nautobot.utilities.api import IsAuthenticatedOrLoginNotRequired

# from nautobot.dcim.models import Device, Site, Platform, DeviceRole
from nautobot.apps.api import NautobotModelViewSet

from nautobot_device_onboarding.models import OnboardingTask
from nautobot_device_onboarding.filters import OnboardingTaskFilterSet

# from nautobot_device_onboarding.choices import OnboardingStatusChoices
from nautobot_device_onboarding.api.serializers import OnboardingTaskSerializer


class OnboardingTaskView( # pylint: disable=too-many-ancestors
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet,
):
"""Create, check status of, and delete onboarding tasks.
In-place updates (PUT, PATCH) of tasks are not permitted.
"""
class OnboardingTaskViewSet(NautobotModelViewSet):
"""API Viewset."""

queryset = OnboardingTask.objects.all()
filterset_class = OnboardingTaskFilterSet
serializer_class = OnboardingTaskSerializer

def update(self, request, *args, **kwargs):
"""Override the update method to disallow put/patch."""
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
2 changes: 1 addition & 1 deletion nautobot_device_onboarding/choices.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""ChoiceSet classes for device onboarding."""

from nautobot.utilities.choices import ChoiceSet
from nautobot.core.choices import ChoiceSet


class OnboardingStatusChoices(ChoiceSet):
Expand Down
39 changes: 17 additions & 22 deletions nautobot_device_onboarding/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,44 @@
import django_filters
from django.db.models import Q

from nautobot.dcim.models import Site, DeviceRole, Platform
from nautobot.utilities.filters import NameSlugSearchFilterSet
from nautobot.dcim.models import Location, Platform
from nautobot.extras.models import Role
from nautobot.core.filters import BaseFilterSet

from nautobot_device_onboarding.models import OnboardingTask


class OnboardingTaskFilterSet(NameSlugSearchFilterSet):
class OnboardingTaskFilterSet(BaseFilterSet):
"""Filter capabilities for OnboardingTask instances."""

q = django_filters.CharFilter(
method="search",
label="Search",
)

site = django_filters.ModelMultipleChoiceFilter(
field_name="site__slug",
queryset=Site.objects.all(),
to_field_name="slug",
label="Site (slug)",
)

site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(),
label="Site (ID)",
location = django_filters.ModelMultipleChoiceFilter(
field_name="name",
queryset=Location.objects.all(),
label="Location (name)",
)

platform = django_filters.ModelMultipleChoiceFilter(
field_name="platform__slug",
field_name="platform__name",
queryset=Platform.objects.all(),
to_field_name="slug",
label="Platform (slug)",
to_field_name="name",
label="Platform (name)",
)

role = django_filters.ModelMultipleChoiceFilter(
field_name="role__slug",
queryset=DeviceRole.objects.all(),
to_field_name="slug",
label="Device Role (slug)",
field_name="role__name",
queryset=Role.objects.all(),
to_field_name="name",
label="Device Role (name)",
)

class Meta: # noqa: D106 "Missing docstring in public nested class"
model = OnboardingTask
fields = ["id", "site", "site_id", "platform", "role", "status", "failed_reason"]
fields = ["id", "location", "platform", "role", "status", "failed_reason"]

def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
Expand All @@ -55,7 +50,7 @@ def search(self, queryset, name, value): # pylint: disable=unused-argument
qs_filter = (
Q(id__icontains=value)
| Q(ip_address__icontains=value)
| Q(site__name__icontains=value)
| Q(location__name__icontains=value)
| Q(platform__name__icontains=value)
| Q(created_device__name__icontains=value)
| Q(status__icontains=value)
Expand Down
Loading

0 comments on commit 7198dc2

Please sign in to comment.