Пользовательские скрипты
Пользовательское скриптование было введено для предоставления пользователям возможности выполнять пользовательскую логику из интерфейса InfraVision. Пользовательские скрипты позволяют пользователю напрямую и удобно манипулировать данными InfraVision предписанным образом. Они могут использоваться для выполнения множества задач, таких как:
- Автоматическое заполнение новых устройств и кабелей при подготовке к развёртыванию новой площадки
- Создание диапазона новых зарезервированных префиксов или IP-адресов
- Получение данных из внешнего источника и импорт их в InfraVision
- Обновление объектов с недействительными или неполными данными
Они также могут использоваться как механизм для проверки целостности данных в InfraVision. Авторы скриптов могут определять тесты для проверки объектов на соответствие определённым правилам и условиям. Например, вы можете написать скрипт для проверки того, что:
- Все коммутаторы верхнего уровня стойки имеют консольное подключение
- Каждый маршрутизатор имеет loopback-интерфейс с назначенным IP-адресом
- Каждое описание интерфейса соответствует стандартному формату
- Каждая площадка имеет определённый минимальный набор VLAN
- Все IP-адреса имеют родительский префикс
Пользовательские скрипты — это Python-код, который существует за пределами кодовой базы InfraVision, поэтому их можно обновлять и изменять без вмешательства в основную установку InfraVision. И поскольку они полностью пользовательские, нет никаких врождённых ограничений на то, что может делать скрипт.
Опасность
Устанавливайте только доверенные скрипты. Пользовательские скрипты имеют неограниченный доступ для изменения чего-либо в базе данных и по своей природе небезопасны, и должны устанавливаться и запускаться только из доверенных источников. Вы также должны просмотреть и установить разрешения на то, кто может запускать скрипты, если скрипт может изменять какие-либо данные.
Написание пользовательских скриптов
Все пользовательские скрипты должны наследоваться от базового класса extras.scripts.Script. Этот класс предоставляет функциональность, необходимую для генерации форм и логирования активности.
from extras.scripts import Script
class MyScript(Script):
...
Скрипты состоят из двух основных компонентов: набора переменных и метода run(). Переменные позволяют вашему скрипту принимать пользовательский ввод через интерфейс InfraVision, но они необязательны: если вашему скрипту не требуется никакой пользовательский ввод, нет необходимости определять какие-либо переменные.
Метод run() — это место, где находится логика выполнения вашего скрипта. (Обратите внимание, что ваш скрипт может иметь столько методов, сколько необходимо: это просто точка вызова для InfraVision.)
class MyScript(Script):
var1 = StringVar(...)
var2 = IntegerVar(...)
var3 = ObjectVar(...)
def run(self, data, commit):
...
Метод run() должен принимать два аргумента:
data- Словарь, содержащий все данные переменных, переданные через веб-форму.commit- Логическое значение, указывающее, будут ли изменения базы данных зафиксированы.
Определение переменных скрипта необязательно: вы можете создать скрипт только с методом run(), если пользовательский ввод не нужен.
Любой вывод, сгенерированный скриптом во время его выполнения, будет отображён на вкладке "output" в интерфейсе.
По умолчанию скрипты внутри модуля упорядочены по алфавиту на странице списка скриптов. Чтобы возвращать скрипты в определённом порядке, вы можете определить переменную script_order в конце вашего модуля. Переменная script_order — это кортеж, содержащий каждый класс Script в желаемом порядке. Любые скрипты, опущенные в этом списке, будут перечислены последними.
from extras.scripts import Script
class MyCustomScript(Script):
...
class AnotherCustomScript(Script):
...
script_order = (MyCustomScript, AnotherCustomScript)
Атрибуты скрипта
Атрибуты скрипта определяются в классе с именем Meta внутри скрипта. Они необязательны, но рекомендуются.
Внимание
Они также определены и используются как свойства базового класса пользовательского скрипта, поэтому не используйте те же имена для переменных и не переопределяйте их в своём пользовательском скрипте.
name
Это удобочитаемое имя вашего скрипта. Если опущено, будет использоваться имя класса.
description
Удобочитаемое описание того, что делает ваш скрипт.
field_order
По умолчанию переменные скрипта будут упорядочены в форме так, как они определены в скрипте. field_order может быть определён как итерируемый объект имён полей для определения порядка, в котором переменные отображаются в группе "Script Data" по умолчанию. Любые поля, не включённые в этот итерируемый объект, будут перечислены последними. Если определён fieldsets, field_order будет игнорироваться. Группа fieldset для "Script Execution Parameters" будет добавлена в конец формы по умолчанию для пользователя.
fieldsets
fieldsets может быть определён как итерируемый объект групп полей и их имён полей для определения порядка, в котором переменные группируются и отображаются. Любые поля, не включённые в этот итерируемый объект, не будут отображаться в форме. Если определён fieldsets, field_order будет игнорироваться. Группа fieldset для "Script Execution Parameters" будет добавлена в конец fieldsets по умолчанию для пользователя.
Ниже приведён пример определения fieldset:
class MyScript(Script):
class Meta:
fieldsets = (
('First group', ('field1', 'field2', 'field3')),
('Second group', ('field4', 'field5')),
)
commit_default
Флажок для фиксации изменений базы данных при выполнении скрипта установлен по умолчанию. Установите commit_default в False в классе Meta скрипта, чтобы оставить эту опцию неотмеченной по умолчанию.
commit_default = False
scheduling_enabled
По умолчанию скрипт может быть запланирован для выполнения в более позднее время. Установка scheduling_enabled в False отключает эту возможность: будет возможно только немедленное выполнение. (Это также отключает возможность установки интервала повторяющегося выполнения.)
job_timeout
Установите максимально допустимое время выполнения для скрипта. Если не установлено, будет использоваться RQ_DEFAULT_TIMEOUT.
Доступ к данным запроса
Детали текущего HTTP-запроса (того, который выполняется для запуска скрипта) доступны как атрибут экземпляра self.request. Это может использоваться для определения, например, пользователя, выполняющего скрипт, и IP-адреса клиента:
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(f"Running as user {username} (IP: {ip_address})...")
Полный список доступных параметров запроса см. в документации Django.
Чтение данных из файлов
Класс Script предоставляет два удобных метода для чтения данных из файлов:
load_yamlload_json
Эти два метода загружают данные в формате YAML или JSON соответственно из файлов в локальном каталоге (т.е. SCRIPTS_ROOT).
Примечание: Эти удобные методы устарели и будут удалены в InfraVision v4.4. Они работают только при запуске скриптов в локальном каталоге, они не будут работать при использовании хранилища, отличного от ScriptFileSystemStorage.
Логирование
Объект Script предоставляет набор удобных функций для записи сообщений с разными уровнями серьёзности:
log_debug(message=None, obj=None)log_success(message=None, obj=None)log_info(message=None, obj=None)log_warning(message=None, obj=None)log_failure(message=None, obj=None)
Сообщения журнала возвращаются пользователю при выполнении скрипта. Поддерживается рендеринг Markdown для сообщений журнала. Сообщение может быть опционально связано с конкретным объектом, передав его в качестве второго аргумента методу логирования.
Тестовые методы
Скрипт может определять один или несколько тестовых методов для отчёта об определённых условиях. Все тестовые методы должны иметь имя, начинающееся с test_, и не принимать никаких аргументов, кроме self.
Эти методы обнаруживаются и запускаются автоматически при выполнении скрипта, если его метод run() не был переопределён. (При переопределении run() можно вызвать run_tests() для запуска всех тестовых методов, присутствующих в скрипте.)
Вызов любого из этих методов логирования без сообщения увеличит соответствующий счётчик, но не сгенерирует строку вывода в журнале скрипта.
Информация
Эта функциональность была перенесена из устаревших отчётов в InfraVision v4.0.
Пример
from dcim.choices import DeviceStatusChoices
from dcim.models import ConsolePort, Device, PowerPort
from extras.scripts import Script
class DeviceConnectionsReport(Script):
description = "Validate the minimum physical connections for each device"
def test_console_connection(self):
# Check that every console port for every active device has a connection defined.
active = DeviceStatusChoices.STATUS_ACTIVE
for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=active):
if not console_port.connected_endpoints:
self.log_failure(
f"No console connection defined for {console_port.name}",
console_port.device,
)
elif not console_port.connection_status:
self.log_warning(
f"Console connection for {console_port.name} marked as planned",
console_port.device,
)
else:
self.log_success("Passed", console_port.device)
def test_power_connections(self):
# Check that every active device has at least two connected power supplies.
for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE):
connected_ports = 0
for power_port in PowerPort.objects.filter(device=device):
if power_port.connected_endpoints:
connected_ports += 1
if not power_port.path.is_active:
self.log_warning(
f"Power connection for {power_port.name} marked as planned",
device,
)
if connected_ports < 2:
self.log_failure(
f"{connected_ports} connected power supplies found (2 needed)",
device,
)
else:
self.log_success("Passed", device)
Логирование изменений
Для генерации правильных данных журнала изменений при редактировании существующего объекта необходимо сделать снимок объекта перед внесением каких-либо изменений.
if obj.pk and hasattr(obj, 'snapshot'):
obj.snapshot()
obj.property = "New Value"
obj.full_clean()
obj.save()
Обработка ошибок
Иногда что-то идёт не так, и скрипт сталкивается с исключением. Если это происходит и необработанное исключение вызывается пользовательским скриптом, выполнение прерывается и сообщается полная трассировка стека.
Хотя это полезно для отладки, в некоторых ситуациях может потребоваться чисто прервать выполнение пользовательского скрипта (например, из-за недействительных входных данных) и тем самым убедиться, что никакие изменения не выполняются в базе данных. В этом случае скрипт может бросить исключение AbortScript, которое предотвратит отображение трассировки стека, но всё же прервёт выполнение скрипта и сообщит заданное сообщение об ошибке.
from utilities.exceptions import AbortScript
if some_error:
raise AbortScript("Some meaningful error message")
Справочник по переменным
Параметры по умолчанию
Все переменные пользовательских скриптов поддерживают следующие параметры по умолчанию:
default- Значение поля по умолчаниюdescription- Краткое удобочитаемое описание поляlabel- Имя поля, отображаемое в отрендеренной формеrequired- Указывает, является ли поле обязательным (все поля обязательны по умолчанию)widget- Класс виджета формы для использования (см. документацию Django)
StringVar
Хранит строку символов (т.е. текст). Параметры включают:
min_length- Минимальное количество символовmax_length- Максимальное количество символовregex- Регулярное выражение, которому должно соответствовать предоставленное значение
Обратите внимание, что min_length и max_length могут быть установлены на одно и то же число для создания поля фиксированной длины.
TextVar
Произвольный текст любой длины. Отображается как многострочное текстовое поле ввода.
IntegerVar
Хранит числовое целое значение. Параметры включают:
min_value- Минимальное значениеmax_value- Максимальное значение
DecimalVar
Хранит числовое десятичное значение. Параметры включают:
min_value- Минимальное значениеmax_value- Максимальное значениеmax_digits- Максимальное количество цифр, включая десятичные знакиdecimal_places- Количество десятичных знаков
BooleanVar
Флаг истина/ложь. Это поле не имеет параметров, кроме перечисленных выше по умолчанию.
ChoiceVar
Набор вариантов, из которых пользователь может выбрать один.
choices- Список кортежей(value, label), представляющих доступные варианты. Например:
CHOICES = (
('n', 'North'),
('s', 'South'),
('e', 'East'),
('w', 'West')
)
direction = ChoiceVar(choices=CHOICES)
В приведённом выше примере выбор варианта с меткой "North" отправит значение n.
MultiChoiceVar
Аналогично ChoiceVar, но позволяет выбирать несколько вариантов.
ObjectVar
Конкретный объект в InfraVision. Каждый ObjectVar должен указывать конкретную модель и позволяет пользователю выбрать один из доступных экземпляров. ObjectVar принимает несколько аргументов, перечисленных ниже.
model- Класс моделиquery_params- Словарь параметров запроса для использования при получении доступных опций (необязательно)context- Пользовательский словарь, сопоставляющий контекстные переменные шаблона с полями, используемый при рендеринге элементов<option>в выпадающем меню (необязательно; см. ниже)null_option- Метка, представляющая "нулевой" или пустой выбор (необязательно)selector- Логическое значение, которое, если True, включает расширенный виджет выбора объекта для помощи пользователю в идентификации нужного объекта (необязательно; по умолчанию False)
Чтобы ограничить доступные выборы в списке, можно передать дополнительные параметры запроса как словарь query_params. Например, чтобы показывать только устройства со статусом "active":
device = ObjectVar(
model=Device,
query_params={
'status': 'active'
}
)
Несколько значений можно указать, назначив список ключу словаря. Также можно ссылаться на значение других полей в форме, добавляя знак доллара ($) к имени переменной.
region = ObjectVar(
model=Region
)
site = ObjectVar(
model=Site,
query_params={
'region_id': '$region'
}
)
Контекстные переменные
Пользовательские контекстные переменные могут быть переданы для переопределения имён атрибутов по умолчанию или для отображения дополнительной информации, такой как родительский объект.
| Имя | По умолчанию | Описание |
|---|---|---|
value |
"id" |
Атрибут, содержащий значение опции |
label |
"display" |
Атрибут, используемый как удобочитаемая метка опции |
description |
"description" |
Атрибут для использования в качестве описания |
depth1 |
"_depth" |
Атрибут, указывающий глубину объекта в рекурсивной иерархии |
disabled |
-- | Атрибут, который, если true, означает, что опция должна быть отключена |
parent |
-- | Атрибут, представляющий родительский объект |
count1 |
-- | Атрибут, содержащий числовое количество связанных объектов |
MultiObjectVar
Аналогично ObjectVar, но позволяет выбирать несколько объектов.
FileVar
Загруженный файл. Обратите внимание, что загруженные файлы находятся в памяти только на время выполнения скрипта: они не будут автоматически сохранены для будущего использования. Скрипт несёт ответственность за запись содержимого файла на диск, где это необходимо.
IPAddressVar
IPv4 или IPv6 адрес без маски. Возвращает объект netaddr.IPAddress.
IPAddressWithMaskVar
IPv4 или IPv6 адрес с маской. Возвращает объект netaddr.IPNetwork, который включает маску.
IPNetworkVar
IPv4 или IPv6 сеть с маской. Возвращает объект netaddr.IPNetwork. Доступны два атрибута для проверки предоставленной маски:
min_prefix_length- Минимальная длина маскиmax_prefix_length- Максимальная длина маски
DateVar
Календарная дата. Возвращает объект datetime.date.
DateTimeVar
Полная дата и время. Возвращает объект datetime.datetime.
Запуск пользовательских скриптов
Примечание
Для запуска пользовательского скрипта пользователю должны быть назначены разрешения для объектов Extras > Script, Extras > Script Module и Core > Managed File. Им также должно быть назначено разрешение extras.run_script. Это достигается путём назначения пользователю (или группе) разрешения на объект Script и указания действия run в "Permissions", как показано ниже.

Через веб-интерфейс
Пользовательские скрипты можно запускать через веб-интерфейс, перейдя к скрипту, заполнив необходимые данные формы и нажав кнопку "run script". Можно запланировать выполнение скрипта в указанное время в будущем. Запланированный скрипт можно отменить, удалив связанный объект результата задания.
Предзаполнение переменных через параметры URL
Поля формы скрипта могут быть предзаполнены путём добавления параметров запроса к URL скрипта. Каждое имя параметра должно соответствовать имени переменной, определённой в классе скрипта. Предзаполненные значения рассматриваются как начальные значения и могут быть отредактированы перед выполнением. Несколько значений можно предоставить, повторяя один и тот же параметр. Значения запроса должны быть процентно закодированы, где это требуется (например, пробелы как %20).
Примеры:
Для строковых и целочисленных переменных, когда скрипт определяет:
from extras.scripts import Script, StringVar, IntegerVar
class MyScript(Script):
name = StringVar()
count = IntegerVar()
следующий URL предзаполнит поля name и count:
https://<netbox>/extras/scripts/<script_id>/?name=Branch42&count=3
Для переменных объектов (ObjectVar) укажите первичный ключ (PK) объекта:
https://<netbox>/extras/scripts/<script_id>/?device=1
Если ID объекта не может быть разрешён или объект не виден запрашивающему пользователю, поле остаётся незаполненным.
Поддерживаемые типы переменных:
| Класс переменной | Ожидаемый ввод | Пример строки запроса |
|---|---|---|
StringVar |
строка (процентно закодированная) | ?name=Branch42 |
TextVar |
строка (процентно закодированная) | ?notes=Initial%20value |
IntegerVar |
целое число | ?count=3 |
DecimalVar |
десятичное число | ?ratio=0.75 |
BooleanVar |
значение → True; пусто → False |
?enabled=true (True), ?enabled= (False) |
ChoiceVar |
значение варианта (не метка) | ?role=edge |
MultiChoiceVar |
значения вариантов (повторять) | ?roles=edge&roles=core |
ObjectVar(Device) |
PK (целое число) | ?device=1 |
MultiObjectVar(Device) |
PK (повторять) | ?devices=1&devices=2 |
IPAddressVar |
IP-адрес | ?ip=198.51.100.10 |
IPAddressWithMaskVar |
IP-адрес с маской | ?addr=192.0.2.1/24 |
IPNetworkVar |
IP-сеть (префикс) | ?network=2001:db8::/64 |
DateVar |
дата ГГГГ-ММ-ДД |
?date=2025-01-05 |
DateTimeVar |
ISO datetime | ?when=2025-01-05T14:30:00 |
FileVar |
— (не поддерживается) | — |
Примечание
- Имена параметров выше являются примерами; используйте фактические имена атрибутов переменных, определённые скриптом.
- Для
BooleanVarтолько пустое значение (?enabled=) снимает флажок; любое другое значение, включаяfalseили0, устанавливает его. - Загрузка файлов (
FileVar) не может быть предзаполнена через параметры URL.
Через API
Чтобы запустить скрипт через REST API, выполните POST-запрос к endpoint скрипта, указав данные формы и фиксацию. Например, чтобы запустить скрипт с именем example.MyReport, мы сделаем запрос следующего вида:
curl -X POST \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json; indent=4" \
http://netbox/api/extras/scripts/example.MyReport/ \
--data '{"data": {"foo": "somevalue", "bar": 123}, "commit": true}'
Опционально schedule_at может быть передан в данных формы со строкой datetime для планирования скрипта на указанную дату и время.
Через CLI
Скрипты можно запускать в CLI, вызывая команду управления:
python3 manage.py runscript [--commit] [--loglevel {debug,info,warning,error,critical}] [--data "<data>"] <module>.<script>
Обязательный аргумент <module>.<script> — это запускаемый скрипт, где <module> — имя файла Python в каталоге scripts без расширения .py, а <script> — имя класса скрипта в <module> для запуска.
Необязательный аргумент --data "<data>" — данные для отправки скрипту
Необязательный аргумент --loglevel — желаемый уровень логирования для вывода в консоль.
Необязательный аргумент --commit зафиксирует любые изменения в скрипте в базе данных.
Пример
Ниже приведён пример скрипта, который создаёт новые объекты для планируемой площадки. Пользователю предлагается три переменные:
- Имя новой площадки
- Модель устройства (отфильтрованный список определённых типов устройств)
- Количество создаваемых коммутаторов доступа
Эти переменные представлены как веб-форма, которую должен заполнить пользователь. После отправки вызывается метод run() скрипта для создания соответствующих объектов.
from django.utils.text import slugify
from dcim.choices import DeviceStatusChoices, SiteStatusChoices
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, 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"
)
manufacturer = ObjectVar(
model=Manufacturer,
required=False
)
switch_model = ObjectVar(
description="Access switch model",
model=DeviceType,
query_params={
'manufacturer_id': '$manufacturer'
}
)
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.full_clean()
site.save()
self.log_success(f"Created new site: {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=f'{site.slug}-switch{i}',
site=site,
status=DeviceStatusChoices.STATUS_PLANNED,
role=switch_role
)
switch.full_clean()
switch.save()
self.log_success(f"Created new switch: {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)