Skip to content

Commit

Permalink
Merge branch 'feature/aci-app-profile'
Browse files Browse the repository at this point in the history
Add ACIAppProfile model for documenting ACI application profiles.
  • Loading branch information
pheus committed May 10, 2024
2 parents 7004a61 + 93d32d0 commit cfbe871
Show file tree
Hide file tree
Showing 21 changed files with 768 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Documentation: https://pheus.github.io/netbox-aci-plugin/
## Features

- Tenants
- Application Profiles

## Compatibility

Expand Down
23 changes: 23 additions & 0 deletions docs/features/tenants.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ An ACI fabric manages one or more *tenants* based on the tenant portion of the h
```mermaid
flowchart TD
TN([Tenant])
AP(Application Profile)
subgraph graphTN [Tenant]
TN
end
subgraph graphAP [Application Profile]
TN -->|1:n| AP
end
```

## Tenant
Expand All @@ -27,3 +31,22 @@ The *ACITenant* model has the following fields:
- **NetBox Tenant**: an assignment to the NetBox tenant model
- **Comments**: a text field for additional notes
- **Tags**: a list of NetBox tags

## Application Profile

An *application profile* contains *endpoint groups* (EPGs) and may be modeled after applications, stages or domains.

The *ACIAppProfile* model has the following fields:

*Required fields*:

- **Name**: represent the application profile name in the ACI
- **ACI Tenant**: a reference to the ACITenant model.

*Optional fields*:

- **Alias**: an alias in the ACI for the application profile
- **Description**: a description of the application profile
- **NetBox Tenant**: a reference to the NetBox tenant model
- **Comments**: a text field for additional notes
- **Tags**: a list of NetBox tags
39 changes: 39 additions & 0 deletions netbox_aci_plugin/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from rest_framework import serializers
from tenancy.api.serializers import TenantSerializer

from ..models.tenant_app_profiles import ACIAppProfile
from ..models.tenants import ACITenant


Expand Down Expand Up @@ -42,3 +43,41 @@ class Meta:
"description",
"nb_tenant",
)


class ACIAppProfileSerializer(NetBoxModelSerializer):
"""Serializer for ACI Application Profile model."""

url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:netbox_aci_plugin-api:aciappprofile-detail"
)
aci_tenant = ACITenantSerializer(nested=True, required=True)
nb_tenant = TenantSerializer(nested=True, required=False, allow_null=True)

class Meta:
model = ACIAppProfile
fields: tuple = (
"id",
"url",
"display",
"name",
"alias",
"description",
"aci_tenant",
"nb_tenant",
"comments",
"tags",
"custom_fields",
"created",
"last_updated",
)
brief_fields: tuple = (
"id",
"url",
"display",
"name",
"alias",
"description",
"aci_tenant",
"nb_tenant",
)
3 changes: 2 additions & 1 deletion netbox_aci_plugin/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

app_name = "netbox_aci_plugin"
router = NetBoxRouter()
router.register("tenant", views.ACITenantListViewSet)
router.register("tenants", views.ACITenantListViewSet)
router.register("app-profiles", views.ACIAppProfileListViewSet)

urlpatterns = router.urls
16 changes: 14 additions & 2 deletions netbox_aci_plugin/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,26 @@

from netbox.api.viewsets import NetBoxModelViewSet

from ..filtersets.tenant_app_profiles import ACIAppProfileFilterSet
from ..filtersets.tenants import ACITenantFilterSet
from ..models.tenant_app_profiles import ACIAppProfile
from ..models.tenants import ACITenant
from .serializers import ACITenantSerializer
from .serializers import ACIAppProfileSerializer, ACITenantSerializer


class ACITenantListViewSet(NetBoxModelViewSet):
"""API view for listing ACI Tenant instances."""

queryset = ACITenant.objects.prefetch_related("tags")
queryset = ACITenant.objects.prefetch_related("nb_tenant", "tags")
serializer_class = ACITenantSerializer
filterset_class = ACITenantFilterSet


class ACIAppProfileListViewSet(NetBoxModelViewSet):
"""API view for listing ACI Application Profile instances."""

queryset = ACIAppProfile.objects.prefetch_related(
"aci_tenant", "nb_tenant", "tags"
)
serializer_class = ACIAppProfileSerializer
filterset_class = ACIAppProfileFilterSet
58 changes: 58 additions & 0 deletions netbox_aci_plugin/filtersets/tenant_app_profiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# SPDX-FileCopyrightText: 2024 Martin Hauser
#
# SPDX-License-Identifier: GPL-3.0-or-later
import django_filters
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from netbox.filtersets import NetBoxModelFilterSet
from tenancy.models import Tenant

from ..models.tenant_app_profiles import ACIAppProfile
from ..models.tenants import ACITenant


class ACIAppProfileFilterSet(NetBoxModelFilterSet):
"""Filter set for ACI Application Profile model."""

aci_tenant = django_filters.ModelMultipleChoiceFilter(
queryset=ACITenant.objects.all(),
to_field_name="name",
label=_("ACI Tenant (name)"),
)
aci_tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=ACITenant.objects.all(),
to_field_name="id",
label=_("ACI Tenant (ID)"),
)
nb_tenant = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
to_field_name="name",
label=_("NetBox Tenant (name)"),
)
nb_tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
to_field_name="id",
label=_("NetBox Tenant (ID)"),
)

class Meta:
model = ACIAppProfile
fields: tuple = (
"id",
"name",
"alias",
"description",
"aci_tenant",
"nb_tenant",
)

def search(self, queryset, name, value):
"""Return a QuerySet filtered by the models description."""
if not value.strip():
return queryset
queryset_filter = (
Q(name__icontains=value)
| Q(alias__icontains=value)
| Q(description__icontains=value)
)
return queryset.filter(queryset_filter)
103 changes: 103 additions & 0 deletions netbox_aci_plugin/forms/tenant_app_profiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# SPDX-FileCopyrightText: 2024 Martin Hauser
#
# SPDX-License-Identifier: GPL-3.0-or-later

from django import forms
from django.utils.translation import gettext_lazy as _
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelForm
from tenancy.models import Tenant, TenantGroup
from utilities.forms.fields import (
CommentField,
DynamicModelChoiceField,
DynamicModelMultipleChoiceField,
TagFilterField,
)
from utilities.forms.rendering import FieldSet

from ..models.tenant_app_profiles import ACIAppProfile
from ..models.tenants import ACITenant


class ACIAppProfileForm(NetBoxModelForm):
"""NetBox form for ACI Application Profile model."""

aci_tenant = DynamicModelChoiceField(
queryset=ACITenant.objects.all(),
label=_("ACI Tenant"),
query_params={"nb_tenant_id": "$nb_tenant"},
)
nb_tenant_group = DynamicModelChoiceField(
queryset=TenantGroup.objects.all(),
required=False,
label=_("NetBox Tenant group"),
initial_params={"tenants": "$nb_tenant"},
)
nb_tenant = DynamicModelChoiceField(
queryset=Tenant.objects.all(),
required=False,
label=_("NetBox Tenant"),
query_params={"group_id": "$nb_tenant_group"},
)
comments = CommentField()

fieldsets: tuple = (
FieldSet(
"name",
"alias",
"aci_tenant",
"description",
"tags",
name=_("ACI Application Profile"),
),
FieldSet("nb_tenant_group", "nb_tenant", name=_("NetBox Tenancy")),
)

class Meta:
model = ACIAppProfile
fields: tuple = (
"name",
"alias",
"description",
"aci_tenant",
"nb_tenant",
"comments",
"tags",
)


class ACIAppProfileFilterForm(NetBoxModelFilterSetForm):
"""NetBox filter form for ACI Application Profile model."""

model = ACIAppProfile
fieldsets: tuple = (
FieldSet("q", "filter_id", "tag"),
FieldSet(
"name", "alias", "aci_tenant_id", "description", name="Attributes"
),
FieldSet("nb_tenant_group_id", "nb_tenant_id", name="NetBox Tenancy"),
)

name = forms.CharField(required=False)
alias = forms.CharField(required=False)
description = forms.CharField(required=False)
aci_tenant_id = DynamicModelMultipleChoiceField(
queryset=ACITenant.objects.all(),
required=False,
null_option="None",
query_params={"tenant_id": "$nb_tenant_id"},
label=_("ACI Tenant"),
)
nb_tenant_group_id = DynamicModelMultipleChoiceField(
queryset=TenantGroup.objects.all(),
required=False,
null_option="None",
label=_("NetBox Tenant group"),
)
nb_tenant_id = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
required=False,
null_option="None",
query_params={"group_id": "$nb_tenant_group_id"},
label=_("NetBox Tenant"),
)
tag = TagFilterField(ACITenant)
10 changes: 10 additions & 0 deletions netbox_aci_plugin/graphql/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import strawberry_django
from netbox.graphql.filter_mixins import BaseFilterMixin, autotype_decorator

from ..filtersets.tenant_app_profiles import ACIAppProfileFilterSet
from ..filtersets.tenants import ACITenantFilterSet
from ..models.tenant_app_profiles import ACIAppProfile
from ..models.tenants import ACITenant


Expand All @@ -16,3 +18,11 @@ class ACITenantFilter(BaseFilterMixin):
"""GraphQL filter definition for ACITenant model."""

pass


@strawberry_django.filter(ACIAppProfile, lookups=True)
@autotype_decorator(ACIAppProfileFilterSet)
class ACIAppProfileFilter(BaseFilterMixin):
"""GraphQL filter definition for ACIAppProfile model."""

pass
20 changes: 16 additions & 4 deletions netbox_aci_plugin/graphql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import strawberry
import strawberry_django

from ..models.tenant_app_profiles import ACIAppProfile
from ..models.tenants import ACITenant
from .types import ACITenantType
from .types import ACIAppProfileType, ACITenantType


@strawberry.type
Expand All @@ -21,6 +22,17 @@ def aci_tenant(self, id: int) -> ACITenantType:
aci_tenant_list: List[ACITenantType] = strawberry_django.field()


schema = [
ACITenantsQuery,
]
@strawberry.type
class ACIAppProfilesQuery:
"""GraphQL query definition for ACIAppProfile model."""

@strawberry.field
def aci_application_profile(self, id: int) -> ACIAppProfileType:
return ACIAppProfile.objects.get(pk=id)

aci_application_profile_list: List[ACIAppProfileType] = (
strawberry_django.field()
)


schema = [ACITenantsQuery, ACIAppProfilesQuery]
13 changes: 12 additions & 1 deletion netbox_aci_plugin/graphql/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,23 @@
from netbox.graphql.types import NetBoxObjectType
from tenancy.graphql.types import TenantType

from ..models.tenant_app_profiles import ACIAppProfile
from ..models.tenants import ACITenant
from .filters import ACITenantFilter
from .filters import ACIAppProfileFilter, ACITenantFilter


@strawberry_django.type(ACITenant, fields="__all__", filters=ACITenantFilter)
class ACITenantType(NetBoxObjectType):
"""GraphQL type definition for ACITenant model."""

nb_tenant: TenantType | None


@strawberry_django.type(
ACIAppProfile, fields="__all__", filters=ACIAppProfileFilter
)
class ACIAppProfileType(NetBoxObjectType):
"""GraphQL type definition for ACIAppProfile model."""

aci_tenant: ACITenantType
nb_tenant: TenantType | None
Loading

0 comments on commit cfbe871

Please sign in to comment.