mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-09-29 15:20:13 +03:00
Merge pull request #2659 from GNS3/appliance-v8
Add support for appliance format version 8
This commit is contained in:
commit
1d10bda22e
@ -21,7 +21,7 @@ API routes for appliances.
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends, status
|
||||||
from typing import Optional, List
|
from typing import Optional, List, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
@ -94,7 +94,7 @@ def get_appliance(appliance_id: UUID) -> schemas.Appliance:
|
|||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
dependencies=[Depends(has_privilege("Appliance.Allocate"))]
|
dependencies=[Depends(has_privilege("Appliance.Allocate"))]
|
||||||
)
|
)
|
||||||
def add_appliance_version(appliance_id: UUID, appliance_version: schemas.ApplianceVersion) -> dict:
|
def add_appliance_version(appliance_id: UUID, appliance_version: Union[schemas.ApplianceVersion, schemas.ApplianceVersionV8]) -> dict:
|
||||||
"""
|
"""
|
||||||
Add a version to an appliance.
|
Add a version to an appliance.
|
||||||
|
|
||||||
|
|||||||
@ -44,6 +44,10 @@ class Appliance:
|
|||||||
def status(self):
|
def status(self):
|
||||||
return self._data["status"]
|
return self._data["status"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def registry_version(self):
|
||||||
|
return self._data.get("registry_version")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def symbol(self):
|
def symbol(self):
|
||||||
return self._data.get("symbol")
|
return self._data.get("symbol")
|
||||||
|
|||||||
@ -37,6 +37,7 @@ from .appliance_to_template import ApplianceToTemplate
|
|||||||
from ..utils.images import InvalidImageError, write_image, read_image_info
|
from ..utils.images import InvalidImageError, write_image, read_image_info
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
|
from gns3server.schemas.controller.appliances import ApplianceModel
|
||||||
from gns3server.utils.images import default_images_directory
|
from gns3server.utils.images import default_images_directory
|
||||||
from gns3server.db.repositories.images import ImagesRepository
|
from gns3server.db.repositories.images import ImagesRepository
|
||||||
from gns3server.db.repositories.templates import TemplatesRepository
|
from gns3server.db.repositories.templates import TemplatesRepository
|
||||||
@ -248,7 +249,8 @@ class ApplianceManager:
|
|||||||
appliances_info = self._find_appliances_from_image_checksum(image_checksum)
|
appliances_info = self._find_appliances_from_image_checksum(image_checksum)
|
||||||
for appliance, image_version in appliances_info:
|
for appliance, image_version in appliances_info:
|
||||||
try:
|
try:
|
||||||
schemas.Appliance.model_validate(appliance.asdict())
|
# Validate with discriminated union - automatically routes to correct version
|
||||||
|
ApplianceModel.model_validate(appliance.asdict())
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
log.warning(f"Could not validate appliance '{appliance.id}': {e}")
|
log.warning(f"Could not validate appliance '{appliance.id}': {e}")
|
||||||
if appliance.versions:
|
if appliance.versions:
|
||||||
@ -279,7 +281,8 @@ class ApplianceManager:
|
|||||||
raise ControllerNotFoundError(message=f"Could not find appliance '{appliance_id}'")
|
raise ControllerNotFoundError(message=f"Could not find appliance '{appliance_id}'")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
schemas.Appliance.model_validate(appliance.asdict())
|
# Validate with discriminated union - automatically routes to correct version
|
||||||
|
ApplianceModel.model_validate(appliance.asdict())
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise ControllerError(message=f"Could not validate appliance '{appliance_id}': {e}")
|
raise ControllerError(message=f"Could not validate appliance '{appliance_id}': {e}")
|
||||||
|
|
||||||
@ -334,7 +337,9 @@ class ApplianceManager:
|
|||||||
appliance = Appliance(path, json.load(f), builtin=builtin)
|
appliance = Appliance(path, json.load(f), builtin=builtin)
|
||||||
json_data = appliance.asdict() # Check if loaded without error
|
json_data = appliance.asdict() # Check if loaded without error
|
||||||
if appliance.status != "broken":
|
if appliance.status != "broken":
|
||||||
schemas.Appliance.model_validate(json_data)
|
# Validate using discriminated union - automatically routes to correct version
|
||||||
|
log.debug(f"Validating appliance '{appliance.id}' with registry version {appliance.registry_version}")
|
||||||
|
ApplianceModel.model_validate(json_data)
|
||||||
self._appliances[appliance.id] = appliance
|
self._appliances[appliance.id] = appliance
|
||||||
if not appliance.symbol or appliance.symbol.startswith(":/symbols/"):
|
if not appliance.symbol or appliance.symbol.startswith(":/symbols/"):
|
||||||
# apply a default symbol if the appliance has none or a default symbol
|
# apply a default symbol if the appliance has none or a default symbol
|
||||||
|
|||||||
@ -24,7 +24,7 @@ from .controller.links import LinkCreate, LinkUpdate, Link, UDPPortInfo, Etherne
|
|||||||
from .controller.computes import ComputeCreate, ComputeUpdate, ComputeVirtualBoxVM, ComputeVMwareVM, ComputeDockerImage, AutoIdlePC, Compute
|
from .controller.computes import ComputeCreate, ComputeUpdate, ComputeVirtualBoxVM, ComputeVMwareVM, ComputeDockerImage, AutoIdlePC, Compute
|
||||||
from .controller.templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template
|
from .controller.templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template
|
||||||
from .controller.images import Image, ImageType
|
from .controller.images import Image, ImageType
|
||||||
from .controller.appliances import ApplianceVersion, Appliance
|
from .controller.appliances import ApplianceVersion, ApplianceVersionV8, Appliance
|
||||||
from .controller.drawings import Drawing
|
from .controller.drawings import Drawing
|
||||||
from .controller.gns3vm import GNS3VM
|
from .controller.gns3vm import GNS3VM
|
||||||
from .controller.nodes import NodeCreate, NodeUpdate, NodeDuplicate, NodeCapture, Node
|
from .controller.nodes import NodeCreate, NodeUpdate, NodeDuplicate, NodeCapture, Node
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2021 GNS3 Technologies Inc.
|
# Copyright (C) 2026 GNS3 Technologies Inc.
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -14,15 +14,20 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# Generated from JSON schema using https://github.com/koxudaxi/datamodel-code-generator
|
# Unified Pydantic model supporting both appliance registry versions using discriminated unions
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional, Union
|
from typing import Annotated, List, Literal, Optional, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from pydantic import AnyUrl, BaseModel, EmailStr, Field, confloat, conint, constr
|
from pydantic import AnyUrl, BaseModel, Discriminator, EmailStr, Field, Tag
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Shared Enums
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
class Category(str, Enum):
|
class Category(str, Enum):
|
||||||
|
"""Appliance category enum"""
|
||||||
|
|
||||||
router = 'router'
|
router = 'router'
|
||||||
multilayer_switch = 'multilayer_switch'
|
multilayer_switch = 'multilayer_switch'
|
||||||
@ -31,17 +36,8 @@ class Category(str, Enum):
|
|||||||
guest = 'guest'
|
guest = 'guest'
|
||||||
|
|
||||||
|
|
||||||
class RegistryVersion(int, Enum):
|
|
||||||
|
|
||||||
version1 = 1
|
|
||||||
version2 = 2
|
|
||||||
version3 = 3
|
|
||||||
version4 = 4
|
|
||||||
version5 = 5
|
|
||||||
version6 = 6
|
|
||||||
|
|
||||||
|
|
||||||
class Status(str, Enum):
|
class Status(str, Enum):
|
||||||
|
"""Appliance status enum"""
|
||||||
|
|
||||||
stable = 'stable'
|
stable = 'stable'
|
||||||
experimental = 'experimental'
|
experimental = 'experimental'
|
||||||
@ -49,6 +45,7 @@ class Status(str, Enum):
|
|||||||
|
|
||||||
|
|
||||||
class Availability(str, Enum):
|
class Availability(str, Enum):
|
||||||
|
"""Image availability enum"""
|
||||||
|
|
||||||
free = 'free'
|
free = 'free'
|
||||||
with_registration = 'with-registration'
|
with_registration = 'with-registration'
|
||||||
@ -56,52 +53,32 @@ class Availability(str, Enum):
|
|||||||
service_contract = 'service-contract'
|
service_contract = 'service-contract'
|
||||||
|
|
||||||
|
|
||||||
class ConsoleType(str, Enum):
|
class Compression(str, Enum):
|
||||||
|
"""Compression type enum"""
|
||||||
|
|
||||||
telnet = 'telnet'
|
bzip2 = 'bzip2'
|
||||||
vnc = 'vnc'
|
gzip = 'gzip'
|
||||||
http = 'http'
|
lzma = 'lzma'
|
||||||
https = 'https'
|
xz = 'xz'
|
||||||
none = 'none'
|
rar = 'rar'
|
||||||
|
zip = 'zip'
|
||||||
|
field_7z = '7z'
|
||||||
|
|
||||||
|
|
||||||
class Docker(BaseModel):
|
class DynamipsPlatform(str, Enum):
|
||||||
|
"""Dynamips platform enum"""
|
||||||
|
|
||||||
adapters: int = Field(..., title='Number of ethernet adapters')
|
c1700 = 'c1700'
|
||||||
image: str = Field(..., title='Docker image in the Docker Hub')
|
c2600 = 'c2600'
|
||||||
start_command: Optional[str] = Field(
|
c2691 = 'c2691'
|
||||||
None,
|
c3725 = 'c3725'
|
||||||
title='Command executed when the container start. Empty will use the default',
|
c3745 = 'c3745'
|
||||||
)
|
c3600 = 'c3600'
|
||||||
environment: Optional[str] = Field(None, title='One KEY=VAR environment by line')
|
c7200 = 'c7200'
|
||||||
console_type: Optional[ConsoleType] = Field(
|
|
||||||
None, title='Type of console connection for the administration of the appliance'
|
|
||||||
)
|
|
||||||
console_http_port: Optional[int] = Field(
|
|
||||||
None, description='Internal port in the container of the HTTP server'
|
|
||||||
)
|
|
||||||
console_http_path: Optional[str] = Field(
|
|
||||||
None, description='Path of the web interface'
|
|
||||||
)
|
|
||||||
extra_hosts: Optional[str] = Field(
|
|
||||||
None, description='Hosts which will be written to /etc/hosts into container'
|
|
||||||
)
|
|
||||||
extra_volumes: Optional[List[str]] = Field(
|
|
||||||
None,
|
|
||||||
description='Additional directories to make persistent that are not included in the images VOLUME directive',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Iou(BaseModel):
|
class DynamipsChassis(str, Enum):
|
||||||
|
"""Dynamips chassis enum"""
|
||||||
ethernet_adapters: int = Field(..., title='Number of ethernet adapters')
|
|
||||||
serial_adapters: int = Field(..., title='Number of serial adapters')
|
|
||||||
nvram: int = Field(..., title='Host NVRAM')
|
|
||||||
ram: int = Field(..., title='Host RAM')
|
|
||||||
startup_config: str = Field(..., title='Config loaded at startup')
|
|
||||||
|
|
||||||
|
|
||||||
class Chassis(str, Enum):
|
|
||||||
|
|
||||||
chassis_1720 = '1720'
|
chassis_1720 = '1720'
|
||||||
chassis_1721 = '1721'
|
chassis_1721 = '1721'
|
||||||
@ -120,26 +97,56 @@ class Chassis(str, Enum):
|
|||||||
chassis_3620 = '3620'
|
chassis_3620 = '3620'
|
||||||
chassis_3640 = '3640'
|
chassis_3640 = '3640'
|
||||||
chassis_3660 = '3660'
|
chassis_3660 = '3660'
|
||||||
|
_ = ''
|
||||||
|
|
||||||
|
|
||||||
class Platform(str, Enum):
|
class DynamipsSlot(str, Enum):
|
||||||
|
"""Dynamips slot enum"""
|
||||||
|
|
||||||
c1700 = 'c1700'
|
C7200_IO_2FE = 'C7200-IO-2FE'
|
||||||
c2600 = 'c2600'
|
C7200_IO_FE = 'C7200-IO-FE'
|
||||||
c2691 = 'c2691'
|
C7200_IO_GE_E = 'C7200-IO-GE-E'
|
||||||
c3725 = 'c3725'
|
NM_16ESW = 'NM-16ESW'
|
||||||
c3745 = 'c3745'
|
NM_1E = 'NM-1E'
|
||||||
c3600 = 'c3600'
|
NM_1FE_TX = 'NM-1FE-TX'
|
||||||
c7200 = 'c7200'
|
NM_4E = 'NM-4E'
|
||||||
|
NM_4T = 'NM-4T'
|
||||||
|
PA_2FE_TX = 'PA-2FE-TX'
|
||||||
|
PA_4E = 'PA-4E'
|
||||||
|
PA_4T_ = 'PA-4T+'
|
||||||
|
PA_8E = 'PA-8E'
|
||||||
|
PA_8T = 'PA-8T'
|
||||||
|
PA_A1 = 'PA-A1'
|
||||||
|
PA_FE_TX = 'PA-FE-TX'
|
||||||
|
PA_GE = 'PA-GE'
|
||||||
|
PA_POS_OC3 = 'PA-POS-OC3'
|
||||||
|
C2600_MB_2FE = 'C2600-MB-2FE'
|
||||||
|
C2600_MB_1E = 'C2600-MB-1E'
|
||||||
|
C1700_MB_1FE = 'C1700-MB-1FE'
|
||||||
|
C2600_MB_2E = 'C2600-MB-2E'
|
||||||
|
C2600_MB_1FE = 'C2600-MB-1FE'
|
||||||
|
C1700_MB_WIC1 = 'C1700-MB-WIC1'
|
||||||
|
GT96100_FE = 'GT96100-FE'
|
||||||
|
Leopard_2FE = 'Leopard-2FE'
|
||||||
|
_ = ''
|
||||||
|
|
||||||
|
class DynamipsWic(str, Enum):
|
||||||
|
"""Dynamips WIC enum"""
|
||||||
|
|
||||||
class Midplane(str, Enum):
|
WIC_1ENET = 'WIC-1ENET'
|
||||||
|
WIC_1T = 'WIC-1T'
|
||||||
|
WIC_2T = 'WIC-2T'
|
||||||
|
_ = ''
|
||||||
|
|
||||||
|
class DynamipsMidplane(str, Enum):
|
||||||
|
"""Dynamips midplane enum"""
|
||||||
|
|
||||||
std = 'std'
|
std = 'std'
|
||||||
vxr = 'vxr'
|
vxr = 'vxr'
|
||||||
|
|
||||||
|
|
||||||
class Npe(str, Enum):
|
class DynamipsNpe(str, Enum):
|
||||||
|
"""Dynamips NPE enum"""
|
||||||
|
|
||||||
npe_100 = 'npe-100'
|
npe_100 = 'npe-100'
|
||||||
npe_150 = 'npe-150'
|
npe_150 = 'npe-150'
|
||||||
@ -151,12 +158,20 @@ class Npe(str, Enum):
|
|||||||
npe_g2 = 'npe-g2'
|
npe_g2 = 'npe-g2'
|
||||||
|
|
||||||
|
|
||||||
class AdapterType(str, Enum):
|
class QemuConsoleType(str, Enum):
|
||||||
|
"""Qemu console type enum"""
|
||||||
|
|
||||||
|
telnet = 'telnet'
|
||||||
|
vnc = 'vnc'
|
||||||
|
spice = 'spice'
|
||||||
|
spice_agent = 'spice+agent'
|
||||||
|
none = 'none'
|
||||||
|
|
||||||
|
|
||||||
|
class QemuAdapterType(str, Enum):
|
||||||
|
"""Qemu adapter type enum"""
|
||||||
|
|
||||||
e1000 = 'e1000'
|
e1000 = 'e1000'
|
||||||
e1000_82544gc = 'e1000-82544gc'
|
|
||||||
e1000_82545em = 'e1000-82545em'
|
|
||||||
e1000e = 'e1000e'
|
|
||||||
i82550 = 'i82550'
|
i82550 = 'i82550'
|
||||||
i82551 = 'i82551'
|
i82551 = 'i82551'
|
||||||
i82557a = 'i82557a'
|
i82557a = 'i82557a'
|
||||||
@ -173,28 +188,14 @@ class AdapterType(str, Enum):
|
|||||||
igb = 'igb'
|
igb = 'igb'
|
||||||
ne2k_pci = 'ne2k_pci'
|
ne2k_pci = 'ne2k_pci'
|
||||||
pcnet = 'pcnet'
|
pcnet = 'pcnet'
|
||||||
rocker = 'rocker'
|
|
||||||
rtl8139 = 'rtl8139'
|
rtl8139 = 'rtl8139'
|
||||||
virtio = 'virtio'
|
virtio = 'virtio'
|
||||||
virtio_net_pci = 'virtio-net-pci'
|
virtio_net_pci = 'virtio-net-pci'
|
||||||
vmxnet3 = 'vmxnet3'
|
vmxnet3 = 'vmxnet3'
|
||||||
|
|
||||||
|
|
||||||
class DiskInterface(str, Enum):
|
class QemuPlatform(str, Enum):
|
||||||
|
"""Qemu platform enum"""
|
||||||
ide = 'ide'
|
|
||||||
sata = 'sata'
|
|
||||||
nvme = 'nvme'
|
|
||||||
scsi = 'scsi'
|
|
||||||
sd = 'sd'
|
|
||||||
mtd = 'mtd'
|
|
||||||
floppy = 'floppy'
|
|
||||||
pflash = 'pflash'
|
|
||||||
virtio = 'virtio'
|
|
||||||
none = 'none'
|
|
||||||
|
|
||||||
|
|
||||||
class Arch(str, Enum):
|
|
||||||
|
|
||||||
aarch64 = 'aarch64'
|
aarch64 = 'aarch64'
|
||||||
alpha = 'alpha'
|
alpha = 'alpha'
|
||||||
@ -226,16 +227,23 @@ class Arch(str, Enum):
|
|||||||
xtensaeb = 'xtensaeb'
|
xtensaeb = 'xtensaeb'
|
||||||
|
|
||||||
|
|
||||||
class ConsoleType1(str, Enum):
|
class QemuDiskInterface(str, Enum):
|
||||||
|
"""Disk interface enum"""
|
||||||
|
|
||||||
telnet = 'telnet'
|
ide = 'ide'
|
||||||
vnc = 'vnc'
|
sata = 'sata'
|
||||||
spice = 'spice'
|
nvme = 'nvme'
|
||||||
spice_agent = 'spice+agent'
|
scsi = 'scsi'
|
||||||
|
sd = 'sd'
|
||||||
|
mtd = 'mtd'
|
||||||
|
floppy = 'floppy'
|
||||||
|
pflash = 'pflash'
|
||||||
|
virtio = 'virtio'
|
||||||
none = 'none'
|
none = 'none'
|
||||||
|
|
||||||
|
|
||||||
class BootPriority(str, Enum):
|
class QemuBootPriority(str, Enum):
|
||||||
|
"""Boot priority enum"""
|
||||||
|
|
||||||
c = 'c'
|
c = 'c'
|
||||||
d = 'd'
|
d = 'd'
|
||||||
@ -248,14 +256,16 @@ class BootPriority(str, Enum):
|
|||||||
nd = 'nd'
|
nd = 'nd'
|
||||||
|
|
||||||
|
|
||||||
class Kvm(str, Enum):
|
class QemuOnClose(str, Enum):
|
||||||
|
"""Qemu on_close action enum"""
|
||||||
|
|
||||||
require = 'require'
|
power_off = 'power_off'
|
||||||
allow = 'allow'
|
shutdown_signal = 'shutdown_signal'
|
||||||
disable = 'disable'
|
save_vm_state = 'save_vm_state'
|
||||||
|
|
||||||
|
|
||||||
class ProcessPriority(str, Enum):
|
class QemuProcessPriority(str, Enum):
|
||||||
|
"""Qemu process priority enum"""
|
||||||
|
|
||||||
realtime = 'realtime'
|
realtime = 'realtime'
|
||||||
very_high = 'very high'
|
very_high = 'very high'
|
||||||
@ -263,80 +273,117 @@ class ProcessPriority(str, Enum):
|
|||||||
normal = 'normal'
|
normal = 'normal'
|
||||||
low = 'low'
|
low = 'low'
|
||||||
very_low = 'very low'
|
very_low = 'very low'
|
||||||
null = 'null'
|
|
||||||
|
|
||||||
|
class DockerConsoleType(str, Enum):
|
||||||
|
"""Docker console type enum"""
|
||||||
|
|
||||||
|
telnet = 'telnet'
|
||||||
|
vnc = 'vnc'
|
||||||
|
http = 'http'
|
||||||
|
https = 'https'
|
||||||
|
none = 'none'
|
||||||
|
|
||||||
|
|
||||||
|
class ChecksumType(str, Enum):
|
||||||
|
"""Checksum type enum"""
|
||||||
|
|
||||||
|
md5 = 'md5'
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateType(str, Enum):
|
||||||
|
"""Template type enum"""
|
||||||
|
|
||||||
|
docker = 'docker'
|
||||||
|
iou = 'iou'
|
||||||
|
dynamips = 'dynamips'
|
||||||
|
qemu = 'qemu'
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Version 1-6 Specific Enums
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class Kvm(str, Enum):
|
||||||
|
"""KVM requirements enum"""
|
||||||
|
|
||||||
|
require = 'require'
|
||||||
|
allow = 'allow'
|
||||||
|
disable = 'disable'
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Version 1-6 Models
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class Docker(BaseModel):
|
||||||
|
"""Docker configuration for v1-6"""
|
||||||
|
|
||||||
|
adapters: int = Field(..., title='Number of Ethernet adapters')
|
||||||
|
image: str = Field(..., title='Docker image in the Docker Hub')
|
||||||
|
start_command: Optional[str] = Field(None, title='Command executed when the container start. Empty will use the default')
|
||||||
|
environment: Optional[str] = Field(None, title='One KEY=VAR environment by line')
|
||||||
|
console_type: Optional[DockerConsoleType] = Field(None, title='Type of console connection for the administration of the appliance')
|
||||||
|
console_http_port: Optional[int] = Field(None, description='Internal port in the container of the HTTP server')
|
||||||
|
console_http_path: Optional[str] = Field(None, description='Path of the web interface')
|
||||||
|
extra_hosts: Optional[str] = Field(None, description='Hosts which will be written to /etc/hosts into container')
|
||||||
|
extra_volumes: Optional[List[str]] = Field(None, description='Additional directories to make persistent that are not included in the images VOLUME directive')
|
||||||
|
|
||||||
|
|
||||||
|
class Iou(BaseModel):
|
||||||
|
"""IOU configuration for v1-6"""
|
||||||
|
|
||||||
|
ethernet_adapters: int = Field(..., title='Number of Ethernet adapters')
|
||||||
|
serial_adapters: int = Field(..., title='Number of serial adapters')
|
||||||
|
nvram: int = Field(..., title='Host NVRAM')
|
||||||
|
ram: int = Field(..., title='Host RAM')
|
||||||
|
startup_config: str = Field(..., title='Config loaded at startup')
|
||||||
|
|
||||||
|
|
||||||
|
class Dynamips(BaseModel):
|
||||||
|
"""Dynamips configuration for v1-6"""
|
||||||
|
|
||||||
|
chassis: Optional[DynamipsChassis] = Field(None, title='Chassis type')
|
||||||
|
platform: DynamipsPlatform = Field(..., title='Platform type')
|
||||||
|
ram: Annotated[int, Field(ge=1)] = Field(..., title='Amount of ram')
|
||||||
|
nvram: Annotated[int, Field(ge=1)] = Field(..., title='Amount of nvram')
|
||||||
|
startup_config: Optional[str] = Field(None, title='Config loaded at startup')
|
||||||
|
wic0: Optional[DynamipsWic] = None
|
||||||
|
wic1: Optional[DynamipsWic] = None
|
||||||
|
wic2: Optional[DynamipsWic] = None
|
||||||
|
slot0: Optional[DynamipsSlot] = None
|
||||||
|
slot1: Optional[DynamipsSlot] = None
|
||||||
|
slot2: Optional[DynamipsSlot] = None
|
||||||
|
slot3: Optional[DynamipsSlot] = None
|
||||||
|
slot4: Optional[DynamipsSlot] = None
|
||||||
|
slot5: Optional[DynamipsSlot] = None
|
||||||
|
slot6: Optional[DynamipsSlot] = None
|
||||||
|
midplane: Optional[DynamipsMidplane] = None
|
||||||
|
npe: Optional[DynamipsNpe] = None
|
||||||
|
|
||||||
|
|
||||||
class Qemu(BaseModel):
|
class Qemu(BaseModel):
|
||||||
|
"""QEMU configuration for v1-6"""
|
||||||
|
|
||||||
adapter_type: AdapterType = Field(..., title='Type of network adapter')
|
adapter_type: QemuAdapterType = Field(..., title='Type of network adapter')
|
||||||
adapters: int = Field(..., title='Number of adapters')
|
adapters: int = Field(..., title='Number of adapters')
|
||||||
ram: int = Field(..., title='Ram allocated to the appliance (MB)')
|
ram: int = Field(..., title='RAM allocated to the appliance (MB)')
|
||||||
cpus: Optional[int] = Field(None, title='Number of Virtual CPU')
|
cpus: Optional[int] = Field(None, title='Number of Virtual CPU')
|
||||||
hda_disk_interface: Optional[DiskInterface] = Field(
|
hda_disk_interface: Optional[QemuDiskInterface] = Field(None, title='Disk interface for the installed hda_disk_image')
|
||||||
None, title='Disk interface for the installed hda_disk_image'
|
hdb_disk_interface: Optional[QemuDiskInterface] = Field(None, title='Disk interface for the installed hdb_disk_image')
|
||||||
)
|
hdc_disk_interface: Optional[QemuDiskInterface] = Field(None, title='Disk interface for the installed hdc_disk_image')
|
||||||
hdb_disk_interface: Optional[DiskInterface] = Field(
|
hdd_disk_interface: Optional[QemuDiskInterface] = Field(None, title='Disk interface for the installed hdd_disk_image')
|
||||||
None, title='Disk interface for the installed hdb_disk_image'
|
arch: QemuPlatform = Field(..., title='Architecture emulated')
|
||||||
)
|
console_type: QemuConsoleType = Field(..., title='Type of console connection for the administration of the appliance')
|
||||||
hdc_disk_interface: Optional[DiskInterface] = Field(
|
boot_priority: Optional[QemuBootPriority] = Field(None, title='Disk boot priority')
|
||||||
None, title='Disk interface for the installed hdc_disk_image'
|
kernel_command_line: Optional[str] = Field(None, title='Command line parameters sent to the kernel')
|
||||||
)
|
|
||||||
hdd_disk_interface: Optional[DiskInterface] = Field(
|
|
||||||
None, title='Disk interface for the installed hdd_disk_image'
|
|
||||||
)
|
|
||||||
arch: Arch = Field(..., title='Architecture emulated')
|
|
||||||
console_type: ConsoleType1 = Field(
|
|
||||||
..., title='Type of console connection for the administration of the appliance'
|
|
||||||
)
|
|
||||||
boot_priority: Optional[BootPriority] = Field(
|
|
||||||
None,
|
|
||||||
title='Disk boot priority. Refer to -boot option in qemu manual for more details.',
|
|
||||||
)
|
|
||||||
kernel_command_line: Optional[str] = Field(
|
|
||||||
None, title='Command line parameters send to the kernel'
|
|
||||||
)
|
|
||||||
kvm: Kvm = Field(..., title='KVM requirements')
|
kvm: Kvm = Field(..., title='KVM requirements')
|
||||||
options: Optional[str] = Field(
|
options: Optional[str] = Field(None, title='Optional additional qemu command line options')
|
||||||
None, title='Optional additional qemu command line options'
|
cpu_throttling: Optional[Annotated[float, Field(ge=0.0, le=100.0)]] = Field(None, title='Throttle the CPU')
|
||||||
)
|
on_close: Optional[QemuOnClose] = Field(None, title='Action to execute on the VM is closed')
|
||||||
cpu_throttling: Optional[confloat(ge=0.0, le=100.0)] = Field(
|
process_priority: Optional[QemuProcessPriority] = Field(None, title='Process priority for QEMU')
|
||||||
None, title='Throttle the CPU'
|
|
||||||
)
|
|
||||||
process_priority: Optional[ProcessPriority] = Field(
|
|
||||||
None, title='Process priority for QEMU'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Compression(str, Enum):
|
|
||||||
|
|
||||||
bzip2 = 'bzip2'
|
|
||||||
gzip = 'gzip'
|
|
||||||
lzma = 'lzma'
|
|
||||||
xz = 'xz'
|
|
||||||
rar = 'rar'
|
|
||||||
zip = 'zip'
|
|
||||||
field_7z = '7z'
|
|
||||||
|
|
||||||
|
|
||||||
class ApplianceImage(BaseModel):
|
|
||||||
|
|
||||||
filename: str = Field(..., title='Filename')
|
|
||||||
version: str = Field(..., title='Version of the file')
|
|
||||||
md5sum: str = Field(..., title='md5sum of the file', pattern='^[a-f0-9]{32}$')
|
|
||||||
filesize: int = Field(..., title='File size in bytes')
|
|
||||||
download_url: Optional[Union[AnyUrl, constr(max_length=0)]] = Field(
|
|
||||||
None, title='Download url where you can download the appliance from a browser'
|
|
||||||
)
|
|
||||||
direct_download_url: Optional[Union[AnyUrl, constr(max_length=0)]] = Field(
|
|
||||||
None,
|
|
||||||
title='Optional. Non authenticated url to the image file where you can download the image.',
|
|
||||||
)
|
|
||||||
compression: Optional[Compression] = Field(
|
|
||||||
None, title='Optional, compression type of direct download url image.'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplianceVersionImages(BaseModel):
|
class ApplianceVersionImages(BaseModel):
|
||||||
|
"""Appliance version images configuration for v1-6"""
|
||||||
|
|
||||||
kernel_image: Optional[str] = Field(None, title='Kernel image')
|
kernel_image: Optional[str] = Field(None, title='Kernel image')
|
||||||
initrd: Optional[str] = Field(None, title='Initrd disk image')
|
initrd: Optional[str] = Field(None, title='Initrd disk image')
|
||||||
@ -350,101 +397,141 @@ class ApplianceVersionImages(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ApplianceVersion(BaseModel):
|
class ApplianceVersion(BaseModel):
|
||||||
|
"""Appliance version definition for v1-6"""
|
||||||
|
|
||||||
name: str = Field(..., title='Name of the version')
|
name: str = Field(..., title='Name of the version')
|
||||||
idlepc: Optional[str] = Field(None, pattern='^0x[0-9a-f]{8}')
|
idlepc: Optional[str] = Field(None, pattern='^0x[0-9a-f]{8}')
|
||||||
images: Optional[ApplianceVersionImages] = Field(None, title='Images used for this version')
|
images: Optional[ApplianceVersionImages] = Field(None, title='Images used for this version')
|
||||||
|
|
||||||
|
|
||||||
class DynamipsSlot(str, Enum):
|
class ApplianceImage(BaseModel):
|
||||||
|
"""Appliance image definition - compatible with both versions"""
|
||||||
|
|
||||||
C7200_IO_2FE = 'C7200-IO-2FE'
|
filename: str = Field(..., title='Filename')
|
||||||
C7200_IO_FE = 'C7200-IO-FE'
|
version: str = Field(..., title='Version of the file')
|
||||||
C7200_IO_GE_E = 'C7200-IO-GE-E'
|
md5sum: Optional[str] = Field(None, title='md5sum of the file', pattern='^[a-f0-9]{32}$')
|
||||||
NM_16ESW = 'NM-16ESW'
|
filesize: int = Field(..., title='File size in bytes')
|
||||||
NM_1E = 'NM-1E'
|
download_url: Optional[Union[AnyUrl, Annotated[str, Field(max_length=0)]]] = Field(
|
||||||
NM_1FE_TX = 'NM-1FE-TX'
|
None,
|
||||||
NM_4E = 'NM-4E'
|
title='Download url where you can download the appliance from a browser'
|
||||||
NM_4T = 'NM-4T'
|
)
|
||||||
PA_2FE_TX = 'PA-2FE-TX'
|
direct_download_url: Optional[Union[AnyUrl, Annotated[str, Field(max_length=0)]]] = Field(
|
||||||
PA_4E = 'PA-4E'
|
None,
|
||||||
PA_4T_ = 'PA-4T+'
|
title='Optional. Non authenticated url to the image file where you can download the image.',
|
||||||
PA_8E = 'PA-8E'
|
)
|
||||||
PA_8T = 'PA-8T'
|
compression: Optional[Compression] = Field(
|
||||||
PA_A1 = 'PA-A1'
|
None, title='Optional, compression type of direct download url image.'
|
||||||
PA_FE_TX = 'PA-FE-TX'
|
)
|
||||||
PA_GE = 'PA-GE'
|
checksum: Optional[str] = Field(None, title='checksum of the image file')
|
||||||
PA_POS_OC3 = 'PA-POS-OC3'
|
checksum_type: Optional[ChecksumType] = Field(None, title='checksum type of the image file')
|
||||||
C2600_MB_2FE = 'C2600-MB-2FE'
|
compression_target: Optional[str] = Field(
|
||||||
C2600_MB_1E = 'C2600-MB-1E'
|
None, title='Optional, file name of the image file inside the compressed file.'
|
||||||
C1700_MB_1FE = 'C1700-MB-1FE'
|
)
|
||||||
C2600_MB_2E = 'C2600-MB-2E'
|
|
||||||
C2600_MB_1FE = 'C2600-MB-1FE'
|
|
||||||
C1700_MB_WIC1 = 'C1700-MB-WIC1'
|
|
||||||
GT96100_FE = 'GT96100-FE'
|
|
||||||
Leopard_2FE = 'Leopard-2FE'
|
|
||||||
_ = ''
|
|
||||||
|
|
||||||
|
|
||||||
class DynamipsWic(str, Enum):
|
# ============================================================================
|
||||||
|
# Version 8 Models
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
WIC_1ENET = 'WIC-1ENET'
|
class CustomAdapterItem(BaseModel):
|
||||||
WIC_1T = 'WIC-1T'
|
"""Custom adapter configuration (v8)"""
|
||||||
WIC_2T = 'WIC-2T'
|
|
||||||
|
adapter_number: int = Field(..., title='Adapter number')
|
||||||
|
port_name: Optional[str] = Field(None, title='Custom port name')
|
||||||
|
adapter_type: Optional[QemuAdapterType] = Field(None, title='Custom adapter type')
|
||||||
|
mac_address: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
title='Custom MAC address',
|
||||||
|
pattern=r'^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Dynamips(BaseModel):
|
class DockerPropertiesV8(BaseModel):
|
||||||
|
"""Docker template properties (v8)"""
|
||||||
|
|
||||||
chassis: Optional[Chassis] = Field(None, title='Chassis type')
|
name: Optional[str] = Field(None, title='Name of the template')
|
||||||
platform: Platform = Field(..., title='Platform type')
|
category: Optional[Category] = Field(None, title='Category of the template')
|
||||||
ram: conint(ge=1) = Field(..., title='Amount of ram')
|
default_name_format: Optional[str] = Field(None, title='Default name format')
|
||||||
nvram: conint(ge=1) = Field(..., title='Amount of nvram')
|
usage: Optional[str] = Field(None, title='How to use the template')
|
||||||
|
symbol: Optional[str] = Field(None, title='Symbol of the template')
|
||||||
|
image: str = Field(..., title='Docker image')
|
||||||
|
adapters: Optional[int] = Field(None, title='Number of ethernet adapters')
|
||||||
|
start_command: Optional[str] = Field(
|
||||||
|
None, title='Command executed when the container start. Empty will use the default'
|
||||||
|
)
|
||||||
|
environment: Optional[str] = Field(None, title='One KEY=VAR environment by line')
|
||||||
|
console_type: Optional[DockerConsoleType] = Field(
|
||||||
|
None, title='Type of console'
|
||||||
|
)
|
||||||
|
console_http_port: Optional[int] = Field(
|
||||||
|
None, title='Internal port in the container of the HTTP server'
|
||||||
|
)
|
||||||
|
console_http_path: Optional[str] = Field(None, title='Path of the web interface')
|
||||||
|
console_resolution: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
title='Console resolution for VNC, for example 1024x768',
|
||||||
|
pattern=r'^[0-9]+x[0-9]+$',
|
||||||
|
)
|
||||||
|
extra_hosts: Optional[str] = Field(None, title='Docker extra hosts (added to /etc/hosts)')
|
||||||
|
extra_volumes: Optional[List[str]] = Field(
|
||||||
|
None, title='Additional directories to make persistent'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IouPropertiesV8(BaseModel):
|
||||||
|
"""IOU template properties (v8)"""
|
||||||
|
|
||||||
|
name: Optional[str] = Field(None, title='Name of the template')
|
||||||
|
category: Optional[Category] = Field(None, title='Category of the template')
|
||||||
|
default_name_format: Optional[str] = Field(None, title='Default name format')
|
||||||
|
usage: Optional[str] = Field(None, title='How to use the template')
|
||||||
|
symbol: Optional[str] = Field(None, title='Symbol of the template')
|
||||||
|
ethernet_adapters: Optional[int] = Field(None, title='Number of ethernet adapters')
|
||||||
|
serial_adapters: Optional[int] = Field(None, title='Number of serial adapters')
|
||||||
|
ram: Optional[int] = Field(None, title='Host RAM')
|
||||||
|
nvram: Optional[int] = Field(None, title='Host NVRAM')
|
||||||
startup_config: Optional[str] = Field(None, title='Config loaded at startup')
|
startup_config: Optional[str] = Field(None, title='Config loaded at startup')
|
||||||
wic0: Optional[DynamipsWic] = None
|
|
||||||
wic1: Optional[DynamipsWic] = None
|
|
||||||
wic2: Optional[DynamipsWic] = None
|
|
||||||
slot0: Optional[DynamipsSlot] = None
|
|
||||||
slot1: Optional[DynamipsSlot] = None
|
|
||||||
slot2: Optional[DynamipsSlot] = None
|
|
||||||
slot3: Optional[DynamipsSlot] = None
|
|
||||||
slot4: Optional[DynamipsSlot] = None
|
|
||||||
slot5: Optional[DynamipsSlot] = None
|
|
||||||
slot6: Optional[DynamipsSlot] = None
|
|
||||||
midplane: Optional[Midplane] = None
|
|
||||||
npe: Optional[Npe] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Appliance(BaseModel):
|
class DynamipsPropertiesV8(BaseModel):
|
||||||
|
"""Dynamips template properties (v8)"""
|
||||||
|
|
||||||
appliance_id: UUID = Field(..., title='Appliance ID')
|
name: Optional[str] = Field(None, title='Name of the template')
|
||||||
name: str = Field(..., title='Appliance name')
|
category: Optional[Category] = Field(None, title='Category of the template')
|
||||||
builtin: Optional[bool] = Field(None, title='Whether the appliance is builtin or not')
|
default_name_format: Optional[str] = Field(None, title='Default name format')
|
||||||
category: Category = Field(..., title='Category of the appliance')
|
usage: Optional[str] = Field(None, title='How to use the template')
|
||||||
description: str = Field(
|
symbol: Optional[str] = Field(None, title='Symbol of the template')
|
||||||
..., title='Description of the appliance. Could be a marketing description'
|
chassis: Optional[DynamipsChassis] = Field(None, title='Chassis type')
|
||||||
)
|
platform: Optional[DynamipsPlatform] = Field(None, title='Platform type')
|
||||||
vendor_name: str = Field(..., title='Name of the vendor')
|
ram: Optional[Annotated[int, Field(ge=1)]] = Field(None, title='Amount of ram')
|
||||||
vendor_url: Optional[Union[AnyUrl, constr(max_length=0)]] = Field(None, title='Website of the vendor')
|
nvram: Optional[Annotated[int, Field(ge=1)]] = Field(None, title='Amount of nvram')
|
||||||
documentation_url: Optional[Union[AnyUrl, constr(max_length=0)]] = Field(
|
idlepc: Optional[str] = Field(None, pattern=r'^0x[0-9a-f]{8}')
|
||||||
None,
|
startup_config: Optional[str] = Field(None, title='Config loaded at startup')
|
||||||
title='An optional documentation for using the appliance on vendor website',
|
wic0: Optional[str] = Field(None)
|
||||||
)
|
wic1: Optional[str] = Field(None)
|
||||||
product_name: str = Field(..., title='Product name')
|
wic2: Optional[str] = Field(None)
|
||||||
product_url: Optional[Union[AnyUrl, constr(max_length=0)]] = Field(
|
slot0: Optional[str] = Field(None)
|
||||||
None, title='An optional product url on vendor website'
|
slot1: Optional[str] = Field(None)
|
||||||
)
|
slot2: Optional[str] = Field(None)
|
||||||
registry_version: RegistryVersion = Field(
|
slot3: Optional[str] = Field(None)
|
||||||
..., title='Version of the registry compatible with this appliance'
|
slot4: Optional[str] = Field(None)
|
||||||
)
|
slot5: Optional[str] = Field(None)
|
||||||
status: Status = Field(..., title='Document if the appliance is working or not')
|
slot6: Optional[str] = Field(None)
|
||||||
availability: Optional[Availability] = Field(
|
midplane: Optional[DynamipsMidplane] = Field(None)
|
||||||
None,
|
npe: Optional[DynamipsNpe] = Field(None)
|
||||||
title='About image availability: can be downloaded directly; download requires a free registration; paid but a trial version (time or feature limited) is available; not available publicly',
|
|
||||||
)
|
|
||||||
maintainer: str = Field(..., title='Maintainer name')
|
class QemuPropertiesV8(BaseModel):
|
||||||
maintainer_email: Optional[Union[EmailStr, constr(max_length=0)]] = Field(None, title='Maintainer email')
|
"""Qemu template properties (v8)"""
|
||||||
usage: Optional[str] = Field(None, title='How to use the appliance')
|
|
||||||
symbol: Optional[str] = Field(None, title='An optional symbol for the appliance')
|
name: Optional[str] = Field(None, title='Name of the template')
|
||||||
|
category: Optional[Category] = Field(None, title='Category of the template')
|
||||||
|
default_name_format: Optional[str] = Field(None, title='Default name format')
|
||||||
|
usage: Optional[str] = Field(None, title='How to use the template')
|
||||||
|
symbol: Optional[str] = Field(None, title='Symbol of the template')
|
||||||
|
adapter_type: Optional[QemuAdapterType] = Field(None, title='Type of network adapter')
|
||||||
|
adapters: Optional[int] = Field(None, title='Number of adapters')
|
||||||
|
custom_adapters: Optional[List[CustomAdapterItem]] = Field(None, title='Custom adapters')
|
||||||
first_port_name: Optional[str] = Field(
|
first_port_name: Optional[str] = Field(
|
||||||
None, title='Optional name of the first networking port example: eth0'
|
None, title='Optional name of the first networking port example: eth0'
|
||||||
)
|
)
|
||||||
@ -455,12 +542,177 @@ class Appliance(BaseModel):
|
|||||||
None,
|
None,
|
||||||
title='Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2',
|
title='Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2',
|
||||||
)
|
)
|
||||||
linked_clone: Optional[bool] = Field(
|
linked_clone: Optional[bool] = Field(None, title="False if you don't want to use a single image for all nodes")
|
||||||
None, title="False if you don't want to use a single image for all nodes"
|
ram: Optional[int] = Field(None, title='Ram allocated to the appliance (MB)')
|
||||||
|
cpus: Optional[int] = Field(None, title='Number of Virtual CPU')
|
||||||
|
hda_disk_interface: Optional[QemuDiskInterface] = Field(None, title='Disk interface for the installed hda_disk_image')
|
||||||
|
hdb_disk_interface: Optional[QemuDiskInterface] = Field(None, title='Disk interface for the installed hdb_disk_image')
|
||||||
|
hdc_disk_interface: Optional[QemuDiskInterface] = Field(None, title='Disk interface for the installed hdc_disk_image')
|
||||||
|
hdd_disk_interface: Optional[QemuDiskInterface] = Field(None, title='Disk interface for the installed hdd_disk_image')
|
||||||
|
platform: Optional[QemuPlatform] = Field(None, title='Platform to emulate')
|
||||||
|
console_type: Optional[QemuConsoleType] = Field(
|
||||||
|
None, title='Type of console connection for the administration of the appliance'
|
||||||
)
|
)
|
||||||
|
boot_priority: Optional[QemuBootPriority] = Field(
|
||||||
|
None,
|
||||||
|
title='Optional define the disk boot priory. Refer to -boot option in qemu manual for more details.',
|
||||||
|
)
|
||||||
|
kernel_command_line: Optional[str] = Field(None, title='Command line parameters send to the kernel')
|
||||||
|
options: Optional[str] = Field(None, title='Optional additional qemu command line options')
|
||||||
|
cpu_throttling: Optional[Annotated[float, Field(ge=0.0, le=100.0)]] = Field(None, title='Throttle the CPU')
|
||||||
|
tpm: Optional[bool] = Field(None, title='Enable the Trusted Platform Module (TPM)')
|
||||||
|
uefi: Optional[bool] = Field(None, title='Enable the UEFI boot mode')
|
||||||
|
on_close: Optional[QemuOnClose] = Field(None, title='Action to execute on the VM is closed')
|
||||||
|
process_priority: Optional[QemuProcessPriority] = Field(None, title='Process priority for QEMU')
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateSetting(BaseModel):
|
||||||
|
"""Emulator settings configuration (v8)"""
|
||||||
|
|
||||||
|
name: Optional[str] = Field(None, title='Name of the settings set')
|
||||||
|
default: Optional[bool] = Field(None, title='Whether these are the default settings')
|
||||||
|
inherit_default_properties: Optional[bool] = Field(True, title='Whether the default properties should be used')
|
||||||
|
template_type: TemplateType = Field(..., title='Type of emulator properties')
|
||||||
|
template_properties: Union[QemuPropertiesV8, DynamipsPropertiesV8, IouPropertiesV8, DockerPropertiesV8] = Field(
|
||||||
|
...,
|
||||||
|
title='Properties for the template'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplianceVersionV8(BaseModel):
|
||||||
|
"""Appliance version definition (v8)"""
|
||||||
|
|
||||||
|
name: str = Field(..., title='Name of the version')
|
||||||
|
settings: Optional[str] = Field(None, title='Template settings to use to run the version')
|
||||||
|
category: Optional[Category] = Field(None, title='Category of the version')
|
||||||
|
installation_instructions: Optional[str] = Field(None, title='Optional installation instructions for the version')
|
||||||
|
usage: Optional[str] = Field(None, title='Optional instructions about using the version')
|
||||||
|
default_username: Optional[str] = Field(None, title='Default username for the version')
|
||||||
|
default_password: Optional[str] = Field(None, title='Default password for the version')
|
||||||
|
symbol: Optional[str] = Field(None, title='An optional symbol for the version')
|
||||||
|
images: Optional[ApplianceVersionImages] = Field(None, title='Images used for this version')
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Child Models with Discriminated Union
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class ApplianceV1_6(BaseModel):
|
||||||
|
"""GNS3 Appliance model for registry versions 1-6"""
|
||||||
|
|
||||||
|
registry_version: Literal[1, 2, 3, 4, 5, 6] = Field(..., title='Version of the registry compatible with this appliance')
|
||||||
|
appliance_id: UUID = Field(..., title='Appliance ID')
|
||||||
|
name: str = Field(..., title='Appliance name')
|
||||||
|
builtin: Optional[bool] = Field(None, title='Whether the appliance is builtin or not')
|
||||||
|
category: Category = Field(..., title='Category of the appliance')
|
||||||
|
description: str = Field(..., title='Description of the appliance. Could be a marketing description')
|
||||||
|
vendor_name: str = Field(..., title='Name of the vendor')
|
||||||
|
vendor_url: Optional[Union[AnyUrl, Annotated[str, Field(max_length=0)]]] = Field(None, title='Website of the vendor')
|
||||||
|
documentation_url: Optional[Union[AnyUrl, Annotated[str, Field(max_length=0)]]] = Field(
|
||||||
|
None,
|
||||||
|
title='An optional documentation for using the appliance on vendor website'
|
||||||
|
)
|
||||||
|
product_name: str = Field(..., title='Product name')
|
||||||
|
product_url: Optional[Union[AnyUrl, Annotated[str, Field(max_length=0)]]] = Field(None, title='An optional product url on vendor website')
|
||||||
|
status: Status = Field(..., title='Document if the appliance is working or not')
|
||||||
|
availability: Optional[Availability] = Field(
|
||||||
|
None,
|
||||||
|
title='About image availability: can be downloaded directly; download requires a free registration; paid but a trial version (time or feature limited) is available; not available publicly',
|
||||||
|
)
|
||||||
|
maintainer: str = Field(..., title='Maintainer name')
|
||||||
|
maintainer_email: Optional[Union[EmailStr, Annotated[str, Field(max_length=0)]]] = Field(None, title='Maintainer email')
|
||||||
|
usage: Optional[str] = Field(None, title='How to use the appliance')
|
||||||
|
symbol: Optional[str] = Field(None, title='An optional symbol for the appliance')
|
||||||
|
first_port_name: Optional[str] = Field(None, title='Optional name of the first networking port example: eth0')
|
||||||
|
port_name_format: Optional[str] = Field(None, title='Optional formating of the networking port example: eth{0}')
|
||||||
|
port_segment_size: Optional[int] = Field(
|
||||||
|
None,
|
||||||
|
title='Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2',
|
||||||
|
)
|
||||||
|
linked_clone: Optional[bool] = Field(None, title="False if you don't want to use a single image for all nodes")
|
||||||
docker: Optional[Docker] = Field(None, title='Docker specific options')
|
docker: Optional[Docker] = Field(None, title='Docker specific options')
|
||||||
iou: Optional[Iou] = Field(None, title='IOU specific options')
|
iou: Optional[Iou] = Field(None, title='IOU specific options')
|
||||||
dynamips: Optional[Dynamips] = Field(None, title='Dynamips specific options')
|
dynamips: Optional[Dynamips] = Field(None, title='Dynamips specific options')
|
||||||
qemu: Optional[Qemu] = Field(None, title='Qemu specific options')
|
qemu: Optional[Qemu] = Field(None, title='Qemu specific options')
|
||||||
images: Optional[List[ApplianceImage]] = Field(None, title='Images for this appliance')
|
images: Optional[List[ApplianceImage]] = Field(None, title='Images for this appliance')
|
||||||
versions: Optional[List[ApplianceVersion]] = Field(None, title='Versions of the appliance')
|
versions: Optional[List[ApplianceVersion]] = Field(None, title='Versions of the appliance')
|
||||||
|
|
||||||
|
|
||||||
|
class ApplianceV8(BaseModel):
|
||||||
|
"""GNS3 Appliance model for registry version 8"""
|
||||||
|
|
||||||
|
registry_version: Literal[8] = Field(
|
||||||
|
...,
|
||||||
|
title='Version of the registry compatible with this appliance (version >=8 introduced breaking changes)',
|
||||||
|
)
|
||||||
|
appliance_id: str = Field(
|
||||||
|
...,
|
||||||
|
title='Appliance ID',
|
||||||
|
pattern=r'^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$',
|
||||||
|
)
|
||||||
|
name: str = Field(..., title='Appliance name')
|
||||||
|
builtin: Optional[bool] = Field(None, title='Whether the appliance is builtin or not')
|
||||||
|
category: Category = Field(..., title='Category of the appliance')
|
||||||
|
description: str = Field(..., title='Description of the appliance. Could be a marketing description')
|
||||||
|
vendor_name: str = Field(..., title='Name of the vendor')
|
||||||
|
vendor_url: AnyUrl = Field(..., title='Website of the vendor')
|
||||||
|
vendor_logo_url: Optional[AnyUrl] = Field(None, title='Link to the vendor logo (used by the GNS3 marketplace)')
|
||||||
|
documentation_url: Optional[AnyUrl] = Field(None, title='An optional documentation for using the appliance on vendor website')
|
||||||
|
product_name: str = Field(..., title='Product name')
|
||||||
|
product_url: Optional[AnyUrl] = Field(None, title='An optional product url on vendor website')
|
||||||
|
status: Status = Field(..., title='Document if the appliance is working or not')
|
||||||
|
availability: Optional[Availability] = Field(
|
||||||
|
None,
|
||||||
|
title='About image availability: can be downloaded directly; download requires a free registration; paid but a trial version (time or feature limited) is available; not available publicly',
|
||||||
|
)
|
||||||
|
maintainer: str = Field(..., title='Maintainer name')
|
||||||
|
maintainer_email: EmailStr = Field(..., title='Maintainer email')
|
||||||
|
installation_instructions: Optional[str] = Field(None, title='Optional installation instructions')
|
||||||
|
usage: Optional[str] = Field(None, title='How to use the appliance')
|
||||||
|
default_username: Optional[str] = Field(None, title='Default username for the appliance')
|
||||||
|
default_password: Optional[str] = Field(None, title='Default password for the appliance')
|
||||||
|
symbol: Optional[str] = Field(None, title='An optional symbol for the appliance')
|
||||||
|
settings: List[TemplateSetting] = Field(..., title='Settings for running the appliance')
|
||||||
|
images: Optional[List[ApplianceImage]] = Field(None, title='Images for this appliance')
|
||||||
|
versions: Optional[List[ApplianceVersionV8]] = Field(None, title='Versions of the appliance')
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Discriminated Union
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Define the discriminated union type
|
||||||
|
ApplianceUnion = Annotated[
|
||||||
|
Union[
|
||||||
|
Annotated[ApplianceV1_6, Tag('v1_6')],
|
||||||
|
Annotated[ApplianceV8, Tag('v8')],
|
||||||
|
],
|
||||||
|
Discriminator('registry_version'),
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
Discriminated union type supporting both registry versions 1-6 and 8.
|
||||||
|
Uses registry_version field to automatically route to correct model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# For type hints in function signatures
|
||||||
|
Appliance = ApplianceUnion
|
||||||
|
|
||||||
|
# Create a validator wrapper for convenience
|
||||||
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
|
_appliance_validator = TypeAdapter(ApplianceUnion)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplianceModel:
|
||||||
|
"""
|
||||||
|
Wrapper class to provide model_validate() method for the Appliance union type.
|
||||||
|
This allows seamless usage of schemas.Appliance.model_validate() in existing code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def model_validate(data: dict) -> Union[ApplianceV1_6, ApplianceV8]:
|
||||||
|
"""
|
||||||
|
Validate appliance data and return appropriate model instance.
|
||||||
|
Automatically routes to ApplianceV1_6 or ApplianceV8 based on registry_version.
|
||||||
|
"""
|
||||||
|
return _appliance_validator.validate_python(data)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user