Compare commits

..

21 Commits

Author SHA1 Message Date
IluaAir fc0a232808 add script order 2025-10-06 17:59:59 +03:00
IluaAir 774412d5dd fix 2025-10-06 17:30:01 +03:00
IluaAir 909a7f10b9 fix tenant group 2025-10-06 17:28:31 +03:00
IluaAir f7edac730a test tenants group 2025-10-06 17:26:04 +03:00
IluaAir 8f014953af fix tenant group 2025-10-06 17:24:49 +03:00
IluaAir 7e8b74f3b8 add device test template 2025-10-06 17:21:10 +03:00
IluaAir dd851d388c 3 fix 2025-10-06 15:12:27 +03:00
IluaAir 88fad0f9fa doble fix tanant check 2025-10-06 15:04:50 +03:00
IluaAir 1e0c3b808b fix tenant check 2025-10-06 15:03:15 +03:00
IluaAir 12cbfef017 refactor 2025-10-06 13:32:42 +03:00
gitark b416d9d530 Update add_tenant_with_prefix.py 2025-10-01 17:03:14 +03:00
gitark 109e644dad Update add_tenant_with_prefix.py 2025-10-01 16:17:24 +03:00
IluaAir 1d825546cb role fix 2025-09-30 18:41:22 +03:00
IluaAir d75d98d1a2 another fix assigment 2025-09-30 18:36:35 +03:00
IluaAir cc72c4619d fix assigment 2025-09-30 18:34:41 +03:00
IluaAir edfa955edf fix contacts role 2025-09-30 18:29:42 +03:00
IluaAir 6388522486 fix name 2025-09-30 17:51:32 +03:00
IluaAir e03c28e7d9 fix contacts 2025-09-30 17:21:56 +03:00
IluaAir 569a88a248 add contact deps tenant 2025-09-30 17:04:15 +03:00
IluaAir e6df4820a6 update contacts 2025-09-30 16:24:34 +03:00
IluaAir 070a1722d2 init 2025-09-29 12:48:09 +03:00
3 changed files with 326 additions and 76 deletions
+2 -1
View File
@@ -1 +1,2 @@
/.idea
/.venv/
/.idea/
+324
View File
@@ -0,0 +1,324 @@
import re
from tenancy.models import Tenant, TenantGroup, Contact, ContactRole, ContactAssignment
from ipam.models import Prefix, VRF, Role
from dcim.models import DeviceType, DeviceRole, Device
from extras.scripts import Script, StringVar, IPNetworkVar, ChoiceVar, ObjectVar
from django.core.exceptions import ValidationError
from extras.models import CustomField
from utilities.exceptions import AbortScript
def slugify(text: str):
CYRILLIC_TO_LATIN = {
"а": "a",
"б": "b",
"в": "v",
"г": "g",
"д": "d",
"е": "e",
"ё": "e",
"ж": "zh",
"з": "z",
"и": "i",
"й": "i",
"к": "k",
"л": "l",
"м": "m",
"н": "n",
"о": "o",
"п": "p",
"р": "r",
"с": "s",
"т": "t",
"у": "u",
"ф": "f",
"х": "h",
"ц": "c",
"ч": "ch",
"ш": "sh",
"щ": "shch",
"ъ": "",
"ы": "y",
"ь": "",
"э": "e",
"ю": "yu",
"я": "ya",
"А": "A",
"Б": "B",
"В": "V",
"Г": "G",
"Д": "D",
"Е": "E",
"Ё": "E",
"Ж": "ZH",
"З": "Z",
"И": "I",
"Й": "I",
"К": "K",
"Л": "L",
"М": "M",
"Н": "N",
"О": "O",
"П": "P",
"Р": "R",
"С": "S",
"Т": "T",
"У": "U",
"Ф": "F",
"Х": "H",
"Ц": "C",
"Ч": "CH",
"Ш": "SH",
"Щ": "SHCH",
"Ъ": "",
"Ы": "Y",
"Ь": "",
"Э": "E",
"Ю": "YU",
"Я": "YA",
}
text = text.replace("ООО", "").lstrip()
text = "".join(CYRILLIC_TO_LATIN.get(c, c) for c in text)
text = text.lower()
text = re.sub(r"\s+", "_", text)
text = re.sub(r"[^a-z0-9_-]", "", text)
text = re.sub(r"[-_]{2,}", "_", text).strip("_-")
return text
class CreateTenant(Script):
class Meta:
name = "Создание Оператора"
description = "Создание оператора и связанного префикса"
scheduling_enabled = False
fieldsets = (
(
"Оператор",
(
"tenant_name",
"tenant_group",
"tenant_connection_type",
"tenant_description",
),
),
(
"Префикс",
("prefix_cidr", "prefix_vrf", "prefix_role", "prefix_description"),
),
("Документы", ("tenant_contract", "tenant_prefix_task", "tenant_tr")),
("Контакты", ("contacts_fio", "contacts_phone", "contacts_email")),
)
connection_types = CustomField.objects.get(name="connection_type_tenant")
tenant_name = StringVar(label="Имя оператора")
tenant_group = ObjectVar(
label="Группа оператора", model=TenantGroup, required=False
)
tenant_connection_type = ChoiceVar(
label="Тип подключения", choices=connection_types.choices, required=True
)
tenant_description = StringVar(label="Описание оператора", required=False)
tenant_contract = StringVar(
label="Договор",
description="Номер договора на разработку комплекса",
required=False,
)
tenant_tr = StringVar(
label="Проект/ТР", description="Пример: DOCXXXXX", required=False
)
tenant_prefix_task = StringVar(
label="Задача",
description="URL или номер задачи из redmine, jira, CRM",
required=False,
)
prefix_cidr = IPNetworkVar(
label="Префикс для оператора (CIDR)",
description="Указывается с маской подсети /xx",
)
prefix_vrf = ObjectVar(label="VRF проекта", model=VRF, required=True)
prefix_role = ObjectVar(
label="Роль",
description="Роль для малых операторов IPMI-SSH-MGMT",
model=Role,
required=True,
)
prefix_description = StringVar(label="Описание префикса", required=False)
contacts_fio = StringVar(
label="ФИО",
description="Указывается в формате Иванов Иван Иванович, остальные значения игнорируются при отсутсвии ФИО",
required=False,
)
contacts_phone = StringVar(label="Номер тел.", required=False)
contacts_email = StringVar(label="E-mail", required=False)
contacts_role = ObjectVar(label="Роль контакта", model=ContactRole, required=False)
def check_tenant_exists(self, data):
if Tenant.objects.filter(name=data["tenant_name"]).exists():
raise ValidationError(
f"Оператор с именем '{data['tenant_name']}' уже существует"
)
def check_prefix_exists(self, data):
if Prefix.objects.filter(prefix=data["prefix_cidr"]).exists():
self.log_warning(
f"Префикс {data['prefix_cidr']} уже существует, оператор будет создан без префикса"
)
return False
return True
def run(self, data, commit):
try:
self.check_tenant_exists(data)
validate_prefix = self.check_prefix_exists(data)
tenant_slug = slugify(data["tenant_name"])
tenant_data = {
"name": data["tenant_name"],
"slug": tenant_slug,
"group": data["tenant_group"],
}
if data.get("tenant_description"):
tenant_data["description"] = data["tenant_description"]
tenant = Tenant(**tenant_data)
tenant.custom_field_data["dogovor"] = data["tenant_contract"]
tenant.custom_field_data["connection_type_tenant"] = data[
"tenant_connection_type"
]
tenant.custom_field_data["link_on_docs"] = data["tenant_tr"]
tenant.full_clean()
if commit:
tenant.save()
self.log_success(
f"Создан новый оператор: {tenant.name} - {tenant.slug}"
)
if validate_prefix:
prefix = Prefix(
prefix=data["prefix_cidr"],
tenant=tenant,
description=data.get("prefix_description") or "",
vrf=data["prefix_vrf"],
role=data["prefix_role"],
)
prefix.custom_field_data["Project"] = data["tenant_tr"]
prefix.custom_field_data["zadacha"] = data["tenant_prefix_task"]
prefix.full_clean()
prefix.save()
self.log_success(
f"Создан префикс {prefix.prefix} для оператора: {tenant.name}"
)
if data.get("contacts_fio"):
contact = Contact(
name=data["contacts_fio"],
phone=data["contacts_phone"],
email=data["contacts_email"],
)
try:
contact.full_clean()
contact.save()
self.log_success(f"Создан контакт: {contact.name}")
except Exception as e:
self.log_failure(f"Ошибка при создании контакта: {e}")
try:
role = data.get("contacts_role") or ContactRole.objects.first()
assignment = ContactAssignment(
contact=contact,
role=role,
object=tenant,
)
assignment.full_clean()
assignment.save()
self.log_success(
f"Связан контакт {contact.name} с оператором: {tenant.name}"
)
except Exception as e:
self.log_failure(
f"Ошибка при связывании контакта с оператором: {e}"
)
else:
self.log_info(
f"Тестовый режим: Будет создан оператор: {tenant.name} (slug: {tenant.slug})"
)
self.log_info(
f"Тестовый режим: Будет создан префикс: {data['prefix_cidr']}"
)
try:
tenant.full_clean()
prefix = Prefix(
prefix=data["prefix_cidr"],
tenant=tenant,
description=data.get("prefix_description") or "",
)
prefix.full_clean()
contact = Contact(
name=data["contacts_fio"],
phone=data["contacts_phone"],
email=data["contacts_email"],
)
contact.full_clean()
role = data.get("contacts_role") or ContactRole.objects.first()
assignment = ContactAssignment(
contact=contact,
role=role,
object=tenant,
)
assignment.full_clean()
self.log_success("Все данные валидны")
except ValidationError as e:
self.log_failure(f"Ошибка валидации: {e}")
except ValidationError as e:
raise AbortScript(f"Ошибка валидации: {e}")
except Exception as e:
self.log_failure(f"Ошибка при выполнении скрипта: {e}")
if commit:
self.log_failure("Изменения отменены из-за ошибки")
raise
class CreateDevice(Script):
class Meta:
name = "Создание устройства"
description = "Создание устройства и привязка к оператору"
scheduling_enabled = False
fieldsets = (
(
"Устройство",
(
"device_name",
"device_type",
"device_role",
"device_description",
"tenant_group",
"device_tenant",
),
),
)
device_name = StringVar(label="Имя устройства")
device_role = ObjectVar(label="Роль устройства", model=DeviceRole, required=True)
device_type = ObjectVar(label="Тип устройства", model=DeviceType, required=True)
device_description = StringVar(label="Описание устройства", required=False)
tenant_group = ObjectVar(
label="Группа оператора", model=TenantGroup, required=False
)
device_tenant = ObjectVar(
label="Оператор",
model=Tenant,
required=True,
query_params={
"group_id": "$tenant_group",
},
)
def run(self, data, commit):
pass
script_order = (CreateTenant, CreateDevice)
-75
View File
@@ -1,75 +0,0 @@
import re
from dcim.models.devices import Device
from extras.models.tags import Tag
from extras.scripts import ObjectVar, Script
from dcim.models import VirtualChassis, Interface
from netbox.choices import ColorChoices
from utilities.exceptions import AbortScript
TAGS = "sync:vchas_interface"
class Vchassis_checker(Script):
IFACE_RE = re.compile(
r"^(?P<prefix>[A-Za-z]+)\s?(?:(?P<slot>\d+)/)?(?P<rest>\d+/\d+)$"
)
class Meta(Script.Meta):
commit_default = False
scheduling_enabled = False
name = "Переопределение интерфейсов"
description = "Переопределение интерфейсов у стекированных коммутаторов"
virtual_chassis = ObjectVar(
model=VirtualChassis,
description="Виртуальное шасси без тегов",
query_params={"tag__n": [TAGS.replace(":", "_")]},
)
def run(self, data, commit):
if Tag.objects.filter(name=TAGS).exists():
tag = Tag.objects.get(name=TAGS)
else:
self.log_warning(f"Отсутствует tag {TAGS}")
tag_data = {
"color": ColorChoices.COLOR_LIGHT_BLUE,
"name": TAGS,
"slug": TAGS.replace(":", "_"),
"description": "тег для автоматического назначения на синхронизированные интерфейсы vchassis",
}
tag = Tag(**tag_data)
if commit:
tag.full_clean()
tag.save()
self.log_success(
f"Создан тег: {TAGS}",
obj=tag,
)
vc = data["virtual_chassis"]
devices: list[Device] = list(vc.members.all().order_by("vc_position"))
if not vc._state.adding and vc.master and vc.master not in devices:
raise AbortScript(f"В стеке {vc.name} отсутствует мастер")
device_ids = [d.pk for d in devices]
interfaces_qs = Interface.objects.filter(
device_id__in=device_ids
).select_related("device")
to_update = []
for iface in interfaces_qs:
m = self.IFACE_RE.match(iface.name)
if m:
prefix = m.group("prefix")
slot = m.group("slot")
rest = m.group("rest")
iface.name = f"{prefix}{iface.device.vc_position}/{rest}"
to_update.append(iface)
if commit:
Interface.objects.bulk_update(to_update, fields=["name"], batch_size=100)
vc.tags.add(tag)
for iface in to_update:
iface.tags.add(tag)
self.log_success(f"Обновлено интерфейсов: {len(to_update)}")
else:
self.log_success(f"Будет обновлено интерфейсов: {len(to_update)}")