Skip to content

Commit

Permalink
Merge pull request #1 from k1nky/v0.2.0-pre
Browse files Browse the repository at this point in the history
v0.2.0
  • Loading branch information
k1nky authored Aug 20, 2024
2 parents b681cb8 + a916383 commit 93884df
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 19 deletions.
63 changes: 61 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ echo netbox-scripthelper >> local_requirements.txt
Inspired by https://github.com/netbox-community/netbox/discussions/9326.
This field allows you to select available models (such as VLAN, Prefix, Address) in the NetBox Script interface.

Supported locations:

* `/api/plugins/scripthelper/ip-ranges/{{iprange}}/available-ips/` - returns list of available IP addresses from the IP range `{{iprange}}`;
* `/api/plugins/scripthelper/prefixes/{{prefix}}/available-prefixes/` - returns list of available prefixes from the parent prefix `{{prefix}}`;
* `/api/plugins/scripthelper/prefixes/{{prefix}}/available-ips/` - returns list of available IP addresses from the prefix `{{prefix}}`;
* `/api/plugins/scripthelper/vlan-groups/{{vlan_group}}/available-vlans/` - returns list of available VLANs from the VLAN group `{{vlan_group}}`.

You can set a limit on the number of result records using the `limit` query parameter. For example, `/api/plugins/scripthelper/prefixes/{{prefix}}/available-ips/?limit=10` returns no more than 10 records.

Additionally `prefixes/{{prefix}}/available-prefixes/` provides a `prefixlen` query parameter, which specifies that the returned networks have fixed size.

### Example

```
import extras.scripts as es
from ipam.models import Prefix
Expand All @@ -38,12 +51,58 @@ class ExampleDynamicChoiceField(es.Script):
prefix = es.ObjectVar(
model=Prefix,
required=True,
)
# show available IP addresses from the prefix specified in the field above
address = DynamicChoiceVar(
api_url="/api/plugins/scripthelper/prefixes/{{prefix}}/available-ips/",
label='Address',
required=True,
)
# show available VLANs from the fixed VLAN group with ID 10
vlan = DynamicChoiceVar(
api_url="/api/plugins/scripthelper/vlan-groups/10/available-vlans/",
label='VLAN',
)
# show no more than 20 child prefixes from the prefix {{prefix}} with size of 24
child_prefix = DynamicChoiceVar(
api_url="/api/plugins/scripthelper/prefixes/{{prefix}}/available-prefixes/",
label='Child prefix',
query_params={
'limit': 20,
'prefixlen': 24,
}
)
def run(self, data, commit):
# data['address'] => selected IP Address as string
# data['vlan'] => selected VID as string
# data['child_prefix'] => selected child prefix with mask as string
pass
```

## ExpandableStringVar

A small wrapper around the NetBox original `ExpandableNameField` for use in custom scripts. The field allows for numeric range expansion, such as `Gi0/[1-3]`.


### Example

```
import extras.scripts as es
from netbox_scripthelper.fields import ExpandableStringVar
class ExampleExpandableStringVar(es.Script):
vm_name = ExpandableStringVar(
label='Name',
description="Alphanumeric ranges are supported for bulk creation. (example: my-new-vm[1-3].exmaple.com)."
)
def run(self, data, commit):
# data['vm_name'] => ['my-new-vm1.exmaple.com', 'my-new-vm2.exmaple.com', 'my-new-vm3.exmaple.com']
pass
```
4 changes: 2 additions & 2 deletions netbox_scripthelper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ class ScriptHelperConfig(PluginConfig):
name = 'netbox_scripthelper'
verbose_name = 'NetBox ScriptHelper'
description = 'Collections of utilities for Netbox custom scripts.'
version = '0.1'
version = '0.2.0'
author = 'Andrey Shalashov'
author_email = 'avshalashov@yandex.ru'
base_url = 'scripthelper'
required_settings = []
default_settings = {}
django_apps = []
min_version = '3.3.0'
max_version = '3.7.99'
max_version = '4.0.99'


config = ScriptHelperConfig
11 changes: 5 additions & 6 deletions netbox_scripthelper/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
app_name = 'scripthelper'

urlpatterns = [
# TODO: urls:ip-ranges
# path(
# 'ip-ranges/<int:pk>/available-ips/',
# views.IPRangeAvailableIPAddressesView.as_view(),
# name='iprange-available-ips'
# ),
path(
'ip-ranges/<int:pk>/available-ips/',
views.IPRangeAvailableIPAddressesView.as_view(),
name='iprange-available-ips'
),
path(
'prefixes/<int:pk>/available-prefixes/',
views.AvailablePrefixesView.as_view(),
Expand Down
25 changes: 18 additions & 7 deletions netbox_scripthelper/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from ipam.models import VLAN, VLANGroup, Prefix, IPAddress
from ipam.models import VLAN, VLANGroup, Prefix, IPAddress, IPRange
from netbox.api.viewsets.mixins import ObjectValidationMixin

from .serializers import (AvailableVLANSerializer,
Expand All @@ -11,6 +11,13 @@
from netbox_scripthelper.utils import IPSplitter


def get_results_limit(request):
try:
return int(request.query_params.get('limit', None))
except TypeError:
return None


class AvailableIPAddressesView(ObjectValidationMixin, APIView):
queryset = IPAddress.objects.all()

Expand All @@ -19,10 +26,13 @@ def get_parent(self, request, pk):

def get(self, request, pk):
parent = self.get_parent(request, pk)
limit = get_results_limit(request)

# Calculate available IPs within the parent
ip_list = []
for index, ip in enumerate(parent.get_available_ips(), start=1):
if index == limit:
break
ip_list.append(ip)
serializer = AvailableIPSerializer(ip_list, many=True, context={
'request': request,
Expand All @@ -42,20 +52,20 @@ def get_parent(self, request, pk):
return get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)


# TODO: views: ip-ranges
# class IPRangeAvailableIPAddressesView(AvailableIPAddressesView):
class IPRangeAvailableIPAddressesView(AvailableIPAddressesView):

# def get_parent(self, request, pk):
# return get_object_or_404(IPRange.objects.restrict(request.user), pk=pk)
def get_parent(self, request, pk):
return get_object_or_404(IPRange.objects.restrict(request.user), pk=pk)


class AvailableVLANsView(ObjectValidationMixin, APIView):
queryset = VLAN.objects.all()

def get(self, request, pk):
vlangroup = get_object_or_404(VLANGroup.objects.restrict(request.user), pk=pk)
limit = get_results_limit(request)

available_vlans = vlangroup.get_available_vids()
available_vlans = vlangroup.get_available_vids()[:limit]
serializer = AvailableVLANSerializer(available_vlans, many=True, context={
'request': request,
'group': vlangroup,
Expand All @@ -74,7 +84,8 @@ def get(self, request, pk):
prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
available_prefixes = prefix.get_available_prefixes()
prefix_len = int(request.query_params.get('prefixlen', 0))
subnets = IPSplitter(available_prefixes).split(prefix_len)
limit = get_results_limit(request)
subnets = IPSplitter(available_prefixes).split(prefix_len, limit)

serializer = AvailablePrefixSerializer(subnets, many=True, context={
'request': request,
Expand Down
5 changes: 5 additions & 0 deletions netbox_scripthelper/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.conf import settings
from extras.scripts import ScriptVariable
from utilities.forms import widgets
from utilities.forms.fields import ExpandableNameField


class DynamicChoiceField(forms.ChoiceField):
Expand Down Expand Up @@ -85,3 +86,7 @@ def as_field(self):
form_field.widget.attrs['data-url'] = self.api_url

return form_field


class ExpandableStringVar(ScriptVariable):
form_field = ExpandableNameField
5 changes: 4 additions & 1 deletion netbox_scripthelper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ class IPSplitter:
def __init__(self, prefixes: IPSet):
self.prefixes = prefixes

def split(self, prefix_len: int) -> List[IPNetwork]:
def split(self, prefix_len: int, limit: int) -> List[IPNetwork]:
subnets = []
if prefix_len == 0:
return list(self.prefixes.iter_cidrs())
for free_net in self.prefixes.iter_cidrs():
if free_net.prefixlen > prefix_len:
continue
subnets.extend(list(free_net.subnet(prefix_len)))
if limit and len(subnets) > limit:
subnets = subnets[:limit]
break

return subnets
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='netbox_scripthelper',
version='0.1.1',
version='0.2.0',
description='Collections of utilities for Netbox custom scripts.',
url='https://github.com/k1nky/netbox-scripthelper-plugin',
author='Andrey Shalashov',
Expand Down

0 comments on commit 93884df

Please sign in to comment.