1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

9.1 KiB

Custom Scripts

Custom scripting was introduced to provide a way for users to execute custom logic from within the NetBox UI. Custom scripts enable the user to directly and conveniently manipulate NetBox data in a prescribed fashion. They can be used to accomplish myriad tasks, such as:

  • Automatically populate new devices and cables in preparation for a new site deployment
  • Create a range of new reserved prefixes or IP addresses
  • Fetch data from an external source and import it to NetBox

Custom scripts are Python code and exist outside of the official NetBox code base, so they can be updated and changed without interfering with the core NetBox installation. And because they're written from scratch, a custom script can be used to accomplish just about anything.

Writing Custom Scripts

All custom scripts must inherit from the extras.scripts.Script base class. This class provides the functionality necessary to generate forms and log activity.

from extras.scripts import Script

class MyScript(Script):
    ..

Scripts comprise two core components: variables and a run() method. Variables allow your script to accept user input via the NetBox UI. The run() method is where your script's execution logic lives. (Note that your script can have as many methods as needed: this is merely the point of invocation for NetBox.)

class MyScript(Script):
    var1 = StringVar(...)
    var2 = IntegerVar(...)
    var3 = ObjectVar(...)

    def run(self, data, commit):
        ...

The run() method should accept two arguments:

  • data - A dictionary containing all of the variable data passed via the web form.
  • commit - A boolean indicating whether database changes will be committed.

!!! note The commit argument was introduced in NetBox v2.7.8. Backward compatibility is maintained for scripts which accept only the data argument, however moving forward scripts should accept both arguments.

Defining variables is optional: You may create a script with only a run() method if no user input is needed.

Returning output from your script is optional. Any raw output generated by the script will be displayed under the "output" tab in the UI.

Module Attributes

name

You can define name within a script module (the Python file which contains one or more scripts) to set the module name. If name is not defined, the filename will be used.

Script Attributes

Script attributes are defined under a class named Meta within the script. These are optional, but encouraged.

name

This is the human-friendly names of your script. If omitted, the class name will be used.

description

A human-friendly description of what your script does.

field_order

A list of field names indicating the order in which the form fields should appear. This is optional, however on Python 3.5 and earlier the fields will appear in random order. (Declarative ordering is preserved on Python 3.6 and above.) For example:

field_order = ['var1', 'var2', 'var3']

commit_default

The checkbox to commit database changes when executing a script is checked by default. Set commit_default to False under the script's Meta class to leave this option unchecked by default.

commit_default = False

Accessing Request Data

Details of the current HTTP request (the one being made to execute the script) are available as the instance attribute self.request. This can be used to infer, for example, the user executing the script and the client IP address:

username = self.request.user.username
ip_address = self.request.META.get('HTTP_X_FORWARDED_FOR') or self.request.META.get('REMOTE_ADDR')
self.log_info("Running as user {} (IP: {})...".format(username, ip_address))

For a complete list of available request parameters, please see the Django documentation.

Reading Data from Files

The Script class provides two convenience methods for reading data from files:

  • load_yaml
  • load_json

These two methods will load data in YAML or JSON format, respectively, from files within the local path (i.e. SCRIPTS_ROOT).

Logging

The Script object provides a set of convenient functions for recording messages at different severity levels:

  • log_debug
  • log_success
  • log_info
  • log_warning
  • log_failure

Log messages are returned to the user upon execution of the script. Markdown rendering is supported for log messages.

Variable Reference

StringVar

Stores a string of characters (i.e. a line of text). Options include:

  • min_length - Minimum number of characters
  • max_length - Maximum number of characters
  • regex - A regular expression against which the provided value must match

Note: min_length and max_length can be set to the same number to effect a fixed-length field.

TextVar

Arbitrary text of any length. Renders as multi-line text input field.

IntegerVar

Stored a numeric integer. Options include:

  • min_value - Minimum value
  • max_value - Maximum value

BooleanVar

A true/false flag. This field has no options beyond the defaults.

ChoiceVar

A set of choices from which the user can select one.

  • choices - A list of (value, label) tuples representing the available choices. For example:
CHOICES = (
    ('n', 'North'),
    ('s', 'South'),
    ('e', 'East'),
    ('w', 'West')
)

direction = ChoiceVar(choices=CHOICES)

ObjectVar

A NetBox object. The list of available objects is defined by the queryset parameter. Each instance of this variable is limited to a single object type.

FileVar

An uploaded file. Note that uploaded files are present in memory only for the duration of the script's execution: They will not be save for future use.

IPAddressVar

An IPv4 or IPv6 address, without a mask. Returns a netaddr.IPAddress object.

IPAddressWithMaskVar

An IPv4 or IPv6 address with a mask. Returns a netaddr.IPNetwork object which includes the mask.

IPNetworkVar

An IPv4 or IPv6 network with a mask. Returns a netaddr.IPNetwork object. Two attributes are available to validate the provided mask:

  • min_prefix_length - Minimum length of the mask (default: none)
  • max_prefix_length - Maximum length of the mask (default: none)

Default Options

All variables support the following default options:

  • default - The field's default value
  • description - A brief description of the field
  • label - The name of the form field
  • required - Indicates whether the field is mandatory (default: true)
  • widget - The class of form widget to use (see the Django documentation)

Example

Below is an example script that creates new objects for a planned site. The user is prompted for three variables:

  • The name of the new site
  • The device model (a filtered list of defined device types)
  • The number of access switches to create

These variables are presented as a web form to be completed by the user. Once submitted, the script's run() method is called to create the appropriate objects.

from django.utils.text import slugify

from dcim.choices import DeviceStatusChoices, SiteStatusChoices
from dcim.models import Device, DeviceRole, DeviceType, Site
from extras.scripts import *


class NewBranchScript(Script):

    class Meta:
        name = "New Branch"
        description = "Provision a new branch site"
        field_order = ['site_name', 'switch_count', 'switch_model']

    site_name = StringVar(
        description="Name of the new site"
    )
    switch_count = IntegerVar(
        description="Number of access switches to create"
    )
    switch_model = ObjectVar(
        description="Access switch model",
        queryset = DeviceType.objects.filter(
            manufacturer__name='Cisco',
            model__in=['Catalyst 3560X-48T', 'Catalyst 3750X-48T']
        )
    )

    def run(self, data, commit):

        # Create the new site
        site = Site(
            name=data['site_name'],
            slug=slugify(data['site_name']),
            status=SiteStatusChoices.STATUS_PLANNED
        )
        site.save()
        self.log_success("Created new site: {}".format(site))

        # Create access switches
        switch_role = DeviceRole.objects.get(name='Access Switch')
        for i in range(1, data['switch_count'] + 1):
            switch = Device(
                device_type=data['switch_model'],
                name='{}-switch{}'.format(site.slug, i),
                site=site,
                status=DeviceStatusChoices.STATUS_PLANNED,
                device_role=switch_role
            )
            switch.save()
            self.log_success("Created new switch: {}".format(switch))

        # Generate a CSV table of new devices
        output = [
            'name,make,model'
        ]
        for switch in Device.objects.filter(site=site):
            attrs = [
                switch.name,
                switch.device_type.manufacturer.name,
                switch.device_type.model
            ]
            output.append(','.join(attrs))

        return '\n'.join(output)