mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
Merge branch '3.0' into project-monitoring
This commit is contained in:
commit
2ce7662c71
10
CHANGELOG
10
CHANGELOG
@ -1,5 +1,15 @@
|
||||
# Change Log
|
||||
|
||||
## 2.2.57 23/03/2026
|
||||
|
||||
* feat(telnet_server): improve error handling and connection management
|
||||
* Deactivate 'use default IOU values' by default and update RAM/NVRAM values
|
||||
* Make sure the node shows as stopped when the wrap console cannot be stopped
|
||||
* tests(controller,port): short_name finishes successfully when port name is None
|
||||
* fix(controller,port): handle None port name to prevent TypeError in short_name method
|
||||
* fix: busybox static link detection on Alpine/musl
|
||||
* Take populated disks into consideration when calculating PCI device ID
|
||||
|
||||
## 2.2.56.1 28/01/2026
|
||||
|
||||
* Fix telnet keepalive options on macOS
|
||||
|
||||
@ -171,11 +171,13 @@ router.include_router(
|
||||
router.include_router(
|
||||
_llm_router,
|
||||
prefix="/access",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["LLM Model Configurations"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
_chat_router,
|
||||
prefix="/projects/{project_id}/chat",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["Chat"]
|
||||
)
|
||||
|
||||
@ -21,7 +21,7 @@ API routes for appliances.
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from typing import Optional, List
|
||||
from typing import Optional, List, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
@ -94,7 +94,7 @@ def get_appliance(appliance_id: UUID) -> schemas.Appliance:
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
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.
|
||||
|
||||
|
||||
@ -47,14 +47,20 @@ async def get_user_from_token(
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
username = auth_service.get_username_from_token(token)
|
||||
user = await user_repo.get_user_by_username(username)
|
||||
token_data = auth_service.get_token_data(token)
|
||||
user = await user_repo.get_user_by_username(token_data.username)
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
if token_data.token_version != user.token_version:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Token has been revoked for '{token_data.username}'",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@ -87,13 +93,18 @@ async def get_current_active_user_from_websocket(
|
||||
await websocket.accept()
|
||||
|
||||
try:
|
||||
username = auth_service.get_username_from_token(token)
|
||||
user = await user_repo.get_user_by_username(username)
|
||||
token_data = auth_service.get_token_data(token)
|
||||
user = await user_repo.get_user_by_username(token_data.username)
|
||||
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Could not validate credentials for '{username}'"
|
||||
detail=f"Could not validate credentials for '{token_data.username}'"
|
||||
)
|
||||
if token_data.token_version != user.token_version:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Token has been revoked for '{token_data.username}'"
|
||||
)
|
||||
|
||||
# Super admin is always authorized
|
||||
@ -103,7 +114,7 @@ async def get_current_active_user_from_websocket(
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"'{username}' is not an active user"
|
||||
detail=f"'{token_data.username}' is not an active user"
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
@ -65,7 +65,10 @@ async def login(
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer")
|
||||
token = schemas.Token(
|
||||
access_token=auth_service.create_access_token(user.username, token_version=user.token_version),
|
||||
token_type="bearer"
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
@ -87,10 +90,25 @@ async def authenticate(
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer")
|
||||
token = schemas.Token(
|
||||
access_token=auth_service.create_access_token(user.username, token_version=user.token_version),
|
||||
token_type="bearer"
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def logout(
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Logout the current user by revoking all existing tokens.
|
||||
"""
|
||||
|
||||
await users_repo.logout_user(current_user.user_id)
|
||||
|
||||
|
||||
@router.get("/me", response_model=schemas.User)
|
||||
async def get_logged_in_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User:
|
||||
"""
|
||||
|
||||
21
gns3server/appliances/alpinet.gns3a
Normal file
21
gns3server/appliances/alpinet.gns3a
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"appliance_id": "a2209414-88a6-403b-a2e1-477a057ec954",
|
||||
"name": "AlpiNet",
|
||||
"category": "guest",
|
||||
"description": "AlpiNet is a lightweight Alpine-based networking toolbox for GNS3.\n\nCOMPREHENSIVE TOOLKIT:\n• Network: ip, ifconfig, ping, traceroute, mtr, arping, nmap, tcpdump\n• Performance: iperf, iperf3\n• HTTP/Web: curl, wget\n• TCP/UDP: netcat, socat, telnet\n• Advanced: ethtool, bridge-utils, vlan\n• Firewall: iptables, ip6tables, nftables\n• File Transfer: FTP (lftp), TFTP, SSH (scp/sftp), rsync\n• DNS: host, nslookup, dig\n• System: nano, tmux, screen, htop, bash\n• Text Tools: grep, sed, awk, jq, less\n• Compression: tar, gzip\n• Utilities: tree, file, openssl\n\nType 'alpinet-tools' in the container for the complete tool list.\n\nOptimized for network testing, troubleshooting, and education.",
|
||||
"vendor_name": "AlpiNet Project",
|
||||
"vendor_url": "https://www.alpinelinux.org/",
|
||||
"product_name": "AlpiNet",
|
||||
"registry_version": 4,
|
||||
"status": "stable",
|
||||
"maintainer": "nazDridoy",
|
||||
"maintainer_email": "nazdridoy399@gmail.com",
|
||||
"usage": "The /root directory is persistent across restarts. Type 'alpinet-tools' to see all available utilities.",
|
||||
"symbol": "AlpiNet.svg",
|
||||
"docker": {
|
||||
"adapters": 1,
|
||||
"image": "gns3/alpinet:latest",
|
||||
"console_type": "telnet",
|
||||
"environment": "TERM=xterm"
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,13 @@
|
||||
"options": "-cpu host"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "vEOS64-lab-4.35.3F.qcow2",
|
||||
"version": "4.35.3F",
|
||||
"md5sum": "00d11e33dc4288f509441a4ab6319ca0",
|
||||
"filesize": 638910464,
|
||||
"download_url": "https://www.arista.com/en/support/software-download"
|
||||
},
|
||||
{
|
||||
"filename": "vEOS64-lab-4.33.2F.qcow2",
|
||||
"version": "4.33.2F",
|
||||
@ -58,6 +65,13 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "4.35.3F",
|
||||
"images": {
|
||||
"hda_disk_image": "Aboot-veos-serial-8.0.2.iso",
|
||||
"hdb_disk_image": "vEOS64-lab-4.35.3F.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "4.33.2F",
|
||||
"images": {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"appliance_id": "4aa78f74-c769-43e0-9ef1-5dd2cf1909c6",
|
||||
"name": "Asterfusion AsterNOS-VPP",
|
||||
"category": "router",
|
||||
"description": "AsterNOS-VPP leverages SONiC’s robust routing features and management capability with VPP’s high-performance data forwarding. It supports L3 routing, NAT, PPPoE, and VPN services. Minimum requirements: 4 vCPUs and 4GB RAM.",
|
||||
"description": "AsterNOS-VPP leverages SONiC’s robust routing features and management capability with VPP’s high-performance data forwarding. It supports L3 routing, NAT, PPPoE, and VPN services. Minimum requirements: 4 vCPUs and 8GB RAM.",
|
||||
"vendor_name": "Asterfusion",
|
||||
"vendor_url": "https://cloudswit.ch/product/sonic-enterprise-distribution/#vAsterNOS",
|
||||
"documentation_url": "https://docs.asternos.com/routing",
|
||||
@ -11,23 +11,32 @@
|
||||
"status": "stable",
|
||||
"maintainer": "Asterfusion Product Team",
|
||||
"maintainer_email": "bd@cloudswit.ch",
|
||||
"availability": "with-registration",
|
||||
"images": [
|
||||
{
|
||||
"filename": "AsterNOS-VPP_V6.1-R0101P02_x86.img.gz",
|
||||
"version": "V6.1",
|
||||
"filename": "AsterNOS-VPP_V6.1-R0101P02_x86.img",
|
||||
"version": "6.1",
|
||||
"md5sum": "55834c3e8849ab226144d79ef76acf81",
|
||||
"filesize": 1318201015,
|
||||
"download_url": "https://asternos.dev/api/file/b2c9fd83-a9d3-46f3-9676-6b49dd481d6c",
|
||||
"comments": "Registration is required to download the image."
|
||||
"compression": "gzip"
|
||||
}
|
||||
],
|
||||
"qemu": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 4,
|
||||
"ram": 4096,
|
||||
"ram": 8192,
|
||||
"cpus": 4,
|
||||
"arch": "x86_64",
|
||||
"console_type": "telnet",
|
||||
"kvm": "require"
|
||||
}
|
||||
},
|
||||
"versions": [
|
||||
{
|
||||
"name": "6.1",
|
||||
"images": {
|
||||
"hda_disk_image": "AsterNOS-VPP_V6.1-R0101P02_x86.img"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -15,11 +15,17 @@
|
||||
"iou": {
|
||||
"ethernet_adapters": 4,
|
||||
"serial_adapters": 0,
|
||||
"nvram": 512,
|
||||
"ram": 512,
|
||||
"nvram": 256,
|
||||
"ram": 1024,
|
||||
"startup_config": "iou_l2_base_startup-config.txt"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "x86_64_crb_linux_l2-adventerprisek9-ms.17.16.1a.iol",
|
||||
"version": "17.16.1a",
|
||||
"md5sum": "d55879318e51c5401fb9314d45c67728",
|
||||
"filesize": 244265240
|
||||
},
|
||||
{
|
||||
"filename": "x86_64_crb_linux_l2-adventerprisek9-ms.iol",
|
||||
"version": "17.15.1",
|
||||
@ -33,25 +39,25 @@
|
||||
"filesize": 240355720
|
||||
},
|
||||
{
|
||||
"filename": "i86bi-linux-l2-ipbasek9-15.1g.bin",
|
||||
"version": "15.1g",
|
||||
"md5sum": "0b8b9e14ca99b68c654e44c4296857ba",
|
||||
"filesize": 62137336
|
||||
"filename": "i86bi_linux_l2-adventerprisek9-ms.SSA.high_iron_20190423.bin",
|
||||
"version": "15.2(20190423)",
|
||||
"md5sum": "ffe884f8e762b762d7886c51d8ee015c",
|
||||
"filesize": 126249700
|
||||
},
|
||||
{
|
||||
"filename": "i86bi-linux-l2-adventerprisek9-15.1a.bin",
|
||||
"version": "15.1a",
|
||||
"md5sum": "9549a20a7391fb849da32caa77a0d254",
|
||||
"filesize": 72726092
|
||||
},
|
||||
{
|
||||
"filename": "i86bi-linux-l2-adventerprisek9-15.2d.bin",
|
||||
"version": "15.2d",
|
||||
"md5sum": "f16db44433beb3e8c828db5ddad1de8a",
|
||||
"filesize": 105036380
|
||||
"filename": "i86bi_LinuxL2-AdvEnterpriseK9-M_152_May_2018.bin",
|
||||
"version": "15.2(20180510)",
|
||||
"md5sum": "d704b68c5f4c4f92e56b754b49b92012",
|
||||
"filesize": 126226692
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "17.16.1a",
|
||||
"images": {
|
||||
"image": "x86_64_crb_linux_l2-adventerprisek9-ms.17.16.1a.iol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "17.15.1",
|
||||
"images": {
|
||||
@ -65,21 +71,15 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "15.1g",
|
||||
"name": "15.2(20190423)",
|
||||
"images": {
|
||||
"image": "i86bi-linux-l2-ipbasek9-15.1g.bin"
|
||||
"image": "i86bi_linux_l2-adventerprisek9-ms.SSA.high_iron_20190423.bin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "15.1a",
|
||||
"name": "15.2(20180510)",
|
||||
"images": {
|
||||
"image": "i86bi-linux-l2-adventerprisek9-15.1a.bin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "15.2d",
|
||||
"images": {
|
||||
"image": "i86bi-linux-l2-adventerprisek9-15.2d.bin"
|
||||
"image": "i86bi_LinuxL2-AdvEnterpriseK9-M_152_May_2018.bin"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -15,11 +15,17 @@
|
||||
"iou": {
|
||||
"ethernet_adapters": 2,
|
||||
"serial_adapters": 2,
|
||||
"nvram": 512,
|
||||
"ram": 512,
|
||||
"nvram": 256,
|
||||
"ram": 1024,
|
||||
"startup_config": "iou_l3_base_startup-config.txt"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "x86_64_crb_linux-adventerprisek9-ms.17.16.1a.iol",
|
||||
"version": "17.16.1a",
|
||||
"md5sum": "39bdf959c3b99a3c81d66451f1cf0404",
|
||||
"filesize": 293062920
|
||||
},
|
||||
{
|
||||
"filename": "x86_64_crb_linux-adventerprisek9-ms.iol",
|
||||
"version": "17.15.1",
|
||||
@ -37,21 +43,15 @@
|
||||
"version": "15.7(3)M2",
|
||||
"md5sum": "d6874260c3daeeb96d10fc844ae0b93b",
|
||||
"filesize": 184759244
|
||||
},
|
||||
{
|
||||
"filename": "i86bi-linux-l3-adventerprisek9-ms.155-2.T.bin",
|
||||
"version": "155-2T",
|
||||
"md5sum": "45e99761a95cbd3ee3924ecf0f3d89e5",
|
||||
"filesize": 172982492
|
||||
},
|
||||
{
|
||||
"filename": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin",
|
||||
"version": "15.4.1T",
|
||||
"md5sum": "2eabae17778316c49cbc80e8e81262f9",
|
||||
"filesize": 152677848
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "17.16.1a",
|
||||
"images": {
|
||||
"image": "x86_64_crb_linux-adventerprisek9-ms.17.16.1a.iol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "17.15.1",
|
||||
"images": {
|
||||
@ -69,18 +69,6 @@
|
||||
"images": {
|
||||
"image": "i86bi_LinuxL3-AdvEnterpriseK9-M2_157_3_May_2018.bin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "155-2T",
|
||||
"images": {
|
||||
"image": "i86bi-linux-l3-adventerprisek9-ms.155-2.T.bin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "15.4.1T",
|
||||
"images": {
|
||||
"image": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"vendor_url": "https://www.extremenetworks.com",
|
||||
"documentation_url": "https://www.extremenetworks.com/support/documentation",
|
||||
"product_name": "EXOS VM",
|
||||
"registry_version": 4,
|
||||
"registry_version": 5,
|
||||
"status": "stable",
|
||||
"maintainer": "Extreme Networks",
|
||||
"maintainer_email": "GitHubscripting@extremenetworks.com",
|
||||
@ -18,18 +18,25 @@
|
||||
"qemu": {
|
||||
"adapter_type": "rtl8139",
|
||||
"adapters": 13,
|
||||
"ram": 512,
|
||||
"ram": 1024,
|
||||
"hda_disk_interface": "ide",
|
||||
"arch": "x86_64",
|
||||
"console_type": "telnet",
|
||||
"boot_priority": "dc",
|
||||
"kvm": "allow",
|
||||
"options": "-cpu core2duo"
|
||||
"options": "-nographic -cpu host -enable-kvm"
|
||||
},
|
||||
|
||||
"images": [
|
||||
|
||||
{
|
||||
{
|
||||
"filename": "EXOS-VM_33.6.1.14.qcow2",
|
||||
"version": "33.6.1.14",
|
||||
"md5sum": "01ea4bea3f321f91910565bb223a2540",
|
||||
"filesize": 278647296,
|
||||
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_EXOS/EXOS-VM_33.6.1.14.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "EXOS-VM_32.7.2.19.qcow2",
|
||||
"version": "32.7.2.19",
|
||||
"md5sum": "eba580a2e18d2a9cc972c9ece8917ea8",
|
||||
@ -48,6 +55,12 @@
|
||||
|
||||
"versions": [
|
||||
|
||||
{
|
||||
"name": "33.6.1.14",
|
||||
"images": {
|
||||
"hda_disk_image": "EXOS-VM_33.6.1.14.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32.7.2.19",
|
||||
"images": {
|
||||
|
||||
60
gns3server/appliances/home-assistant.gns3a
Normal file
60
gns3server/appliances/home-assistant.gns3a
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"appliance_id": "4ac0d5bc-714d-4a5a-8af5-8fb452046acd",
|
||||
"name": "Home Assistant",
|
||||
"category": "guest",
|
||||
"description": "Open source home automation that puts local control and privacy first. Powered by a worldwide community of tinkerers and DIY enthusiasts.",
|
||||
"vendor_name": "Open Home Foundation",
|
||||
"vendor_url": "https://www.openhomefoundation.org/",
|
||||
"vendor_logo_url": "https://www.openhomefoundation.org/assets/images/logo/open-home-foundation.svg",
|
||||
"documentation_url": "https://www.home-assistant.io/installation/alternative",
|
||||
"product_name": "haos",
|
||||
"product_url": "https://www.home-assistant.io/",
|
||||
"registry_version": 8,
|
||||
"status": "stable",
|
||||
"availability": "free",
|
||||
"maintainer": "Neyder Achahuanco Apaza",
|
||||
"maintainer_email": "gns3@neyder.net",
|
||||
"usage": "You will be able to reach Home Assistant on homeassistant.local:8123, you might need to access Home Assistant at homeassistant:8123 or http://X.X.X.X:8123 (replace X.X.X.X with your virtual machine\u2019s IP address).\nYou can access trough console with root user without password.",
|
||||
"symbol": "home-assistant-logomark-color-on-light.svg",
|
||||
"default_username": "cisco",
|
||||
"default_password": "admin",
|
||||
"settings": [
|
||||
{
|
||||
"name": "Default template settings",
|
||||
"default": true,
|
||||
"template_type": "qemu",
|
||||
"template_properties":
|
||||
{
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"platform": "x86_64",
|
||||
"adapters": 1,
|
||||
"ram": 2048,
|
||||
"cpus": 2,
|
||||
"hda_disk_interface": "virtio",
|
||||
"console_type": "telnet",
|
||||
"boot_priority": "cd",
|
||||
"on_close": "shutdown_signal",
|
||||
"uefi": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"filename": "haos_ova-17.1.qcow2",
|
||||
"version": "17.1",
|
||||
"md5sum": "e1b950430045859a3ac035cb420df158",
|
||||
"filesize": 1024393216,
|
||||
"download_url": "https://www.home-assistant.io/installation/alternative",
|
||||
"direct_download_url": "https://github.com/home-assistant/operating-system/releases/download/17.1/haos_ova-17.1.qcow2.xz",
|
||||
"compression": "xz"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"images": {
|
||||
"hda_disk_image": "haos_ova-17.1.qcow2"
|
||||
},
|
||||
"name": "17.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -111,9 +111,37 @@
|
||||
"md5sum": "e2eb1a4fff9d56815f37c2e82a6279fb",
|
||||
"version": "25.11.0",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v25.11.0/infix-x86_64-disk-25.11.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "infix-x86_64-v26.01.0.qcow2",
|
||||
"filesize": 307363840,
|
||||
"md5sum": "bd3b9c56f98ba264c36e4d6f69931546",
|
||||
"version": "26.01.0",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v26.01.0/infix-x86_64-v26.01.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "infix-x86_64-v26.02.1.qcow2",
|
||||
"filesize": 316407808,
|
||||
"md5sum": "0c4da508cfcb702c066f47227644b65f",
|
||||
"version": "26.02.1",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v26.02.1/infix-x86_64-v26.02.1.qcow2"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "26.02.1",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "infix-x86_64-v26.02.1.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "26.01.0",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "infix-x86_64-v26.01.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "25.11.0",
|
||||
"images": {
|
||||
|
||||
@ -14,11 +14,11 @@
|
||||
"maintainer_email": "github@sugarpapa.mozmail.com",
|
||||
"usage": "GNS3 SHOULD be a baremetal installation. Using the GNS3 VM MIGHT result in unwanted issues. Default user is root. No password is needed.",
|
||||
"symbol": "juniper-vmx.svg",
|
||||
"first_port_name": "ge-0/0/0",
|
||||
"first_port_name": "fxp0",
|
||||
"port_name_format": "ge-0/0/{port0}",
|
||||
"qemu": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 17,
|
||||
"adapters": 10,
|
||||
"ram": 5120,
|
||||
"cpus": 4,
|
||||
"hda_disk_interface": "virtio",
|
||||
@ -72,4 +72,4 @@
|
||||
"name": "23.2R1.15"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,15 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "openwrt-25.12.0-x86-64-generic-ext4-combined.img",
|
||||
"version": "25.12.0",
|
||||
"md5sum": "8c0e01307fc47c1f862a5d0c9a51c2c5",
|
||||
"filesize": 126353408,
|
||||
"download_url": "https://downloads.openwrt.org/releases/25.12.0/targets/x86/64/",
|
||||
"direct_download_url": "https://downloads.openwrt.org/releases/25.12.0/targets/x86/64/openwrt-25.12.0-x86-64-generic-ext4-combined.img.gz",
|
||||
"compression": "gzip"
|
||||
},
|
||||
{
|
||||
"filename": "openwrt-24.10.2-x86-64-generic-ext4-combined.img",
|
||||
"version": "24.10.2",
|
||||
@ -232,6 +241,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "25.12.0",
|
||||
"images": {
|
||||
"hda_disk_image": "openwrt-25.12.0-x86-64-generic-ext4-combined.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "24.10.2",
|
||||
"images": {
|
||||
|
||||
@ -25,6 +25,15 @@
|
||||
"kvm": "require"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "OPNsense-26.1-nano-amd64.img",
|
||||
"version": "26.1",
|
||||
"md5sum": "3b774e8fb651a5d00aa6fa3e0d1b1ff0",
|
||||
"filesize": 3221225472,
|
||||
"download_url": "https://opnsense.c0urier.net/releases/26.1/",
|
||||
"direct_download_url": "https://opnsense.c0urier.net/releases/26.1/OPNsense-26.1-nano-amd64.img.bz2",
|
||||
"compression": "bzip2"
|
||||
},
|
||||
{
|
||||
"filename": "OPNsense-25.7-nano-amd64.img",
|
||||
"version": "25.7",
|
||||
@ -90,6 +99,24 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "26.1",
|
||||
"images": {
|
||||
"hda_disk_image": "OPNsense-26.1-nano-amd64.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "25.7",
|
||||
"images": {
|
||||
"hda_disk_image": "OPNsense-25.7-nano-amd64.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "25.1",
|
||||
"images": {
|
||||
"hda_disk_image": "OPNsense-25.1-nano-amd64.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "24.7",
|
||||
"images": {
|
||||
|
||||
@ -13,11 +13,11 @@
|
||||
"availability": "service-contract",
|
||||
"maintainer": "Da-Geek",
|
||||
"maintainer_email": "dageek@dageeks-geeks.gg",
|
||||
"usage": "You should download Red Hat Enterprise Linux KVM Guest Image from https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.5/x86_64/product-software attach/customize rhel-cloud-init.iso and start.\nusername: cloud-user\npassword: redhat",
|
||||
"usage": "You should download Red Hat Enterprise Linux KVM Guest Image from https://access.redhat.com/downloads/content/rhel attach/customize rhel-cloud-init.iso and start.\nusername: cloud-user\npassword: redhat",
|
||||
"qemu": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 1,
|
||||
"ram": 1024,
|
||||
"ram": 1536,
|
||||
"hda_disk_interface": "virtio",
|
||||
"arch": "x86_64",
|
||||
"console_type": "telnet",
|
||||
@ -27,11 +27,32 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "rhel-9.5-x86_64-kvm.qcow2",
|
||||
"version": "9.5",
|
||||
"md5sum": "8174396d5cb47727c59dd04dd9a05418",
|
||||
"filesize": 974389248,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.5/x86_64/product-software"
|
||||
"filename": "rhel-10.1-x86_64-kvm.qcow2",
|
||||
"version": "10.1",
|
||||
"md5sum": "b2f35b1ac0600de31cadf30a622a2fef",
|
||||
"filesize": 1010827264,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---10/10.1/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-10.0-x86_64-kvm.qcow2",
|
||||
"version": "10.0",
|
||||
"md5sum": "ea5918fa116ec53f9ef1ce4be69ae2be",
|
||||
"filesize": 855506944,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---10/10.0/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-9.7-x86_64-kvm.qcow2",
|
||||
"version": "9.7",
|
||||
"md5sum": "abc2f42cc5daeea6b03d0d2d478a0d1c",
|
||||
"filesize": 1229979648,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.7/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-9.6-x86_64-kvm.qcow2",
|
||||
"version": "9.6",
|
||||
"md5sum": "056648d716275053ebe77b1d4f68c862",
|
||||
"filesize": 1060438016,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.6/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-9.4-x86_64-kvm.qcow2",
|
||||
@ -40,34 +61,6 @@
|
||||
"filesize": 957218816,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.4/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-9.3-x86_64-kvm.qcow2",
|
||||
"version": "9.3",
|
||||
"md5sum": "409d8d15f5177db2617b0e3e02139b5c",
|
||||
"filesize": 858193920,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.3/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-9.2-x86_64-kvm.qcow2",
|
||||
"version": "9.2",
|
||||
"md5sum": "f33845298b387dbcfbf162c6b4e3f8c8",
|
||||
"filesize": 819265536,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/9.2/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-baseos-9.1-x86_64-kvm.qcow2",
|
||||
"version": "9.1",
|
||||
"md5sum": "622de743da83bcec1ad2959ecaedb8f4",
|
||||
"filesize": 753401856,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/9.1/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-baseos-9.0-x86_64-kvm.qcow2",
|
||||
"version": "9.0",
|
||||
"md5sum": "4a41497d354fe99a4abf55f1ed73edcb",
|
||||
"filesize": 696582144,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/9.0/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-8.10-x86_64-kvm.qcow2",
|
||||
"version": "8.10",
|
||||
@ -75,13 +68,6 @@
|
||||
"filesize": 1065091072,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.10/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-8.9-x86_64-kvm.qcow2",
|
||||
"version": "8.9",
|
||||
"md5sum": "23295fe508678cbdebfbdbd41ef6e6e2",
|
||||
"filesize": 971833344,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.9/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-8.8-x86_64-kvm.qcow2",
|
||||
"version": "8.8",
|
||||
@ -89,13 +75,6 @@
|
||||
"filesize": 926810112,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.8/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-8.7-x86_64-kvm.qcow2",
|
||||
"version": "8.7",
|
||||
"md5sum": "ab71a2c4cc276441bf999f531e064507",
|
||||
"filesize": 858128384,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.7/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-8.6-x86_64-kvm.qcow2",
|
||||
"version": "8.6",
|
||||
@ -104,31 +83,10 @@
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.6/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-8.5-x86_64-kvm.qcow2",
|
||||
"version": "8.5",
|
||||
"md5sum": "1efb78dbb2033ba4ac6589a06c95c2d4",
|
||||
"filesize": 779419648,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.5/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-8.4-x86_64-kvm.qcow2",
|
||||
"version": "8.4",
|
||||
"md5sum": "db4c3a72857b784dc6e96120351f2894",
|
||||
"filesize": 727449600,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.4/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-8.3-x86_64-kvm.qcow2",
|
||||
"version": "8.3",
|
||||
"md5sum": "dd554c059e0910379fff88f677f4a4b3",
|
||||
"filesize": 1316683776,
|
||||
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---8/8.3/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
"filename": "rhel-server-7.9-x86_64-kvm.qcow2",
|
||||
"filename": "rhel-server-7.9-update-12-x86_64-kvm.qcow2",
|
||||
"version": "7.9",
|
||||
"md5sum": "8d6669b3e2bb8df15b9b4280936cf950",
|
||||
"filesize": 827777024,
|
||||
"md5sum": "f77fc7e3cf31a210a8244e486466ce34",
|
||||
"filesize": 838036992,
|
||||
"download_url": "https://access.redhat.com/downloads/content/69/ver=/rhel---7/7.9/x86_64/product-software"
|
||||
},
|
||||
{
|
||||
@ -140,17 +98,39 @@
|
||||
},
|
||||
{
|
||||
"filename": "rhel-cloud-init.iso",
|
||||
"version": "1.0",
|
||||
"md5sum": "421745b0d13615ecd48696f98d8b6352",
|
||||
"version": "1.1",
|
||||
"md5sum": "f1f908d36e67f843dd94db1c2e8c8373",
|
||||
"filesize": 374784,
|
||||
"download_url": "https://gitlab.com/neyder/rhel-cloud-init/raw/master/rhel-cloud-init.iso"
|
||||
"download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/rhel",
|
||||
"direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/rhel/rhel-cloud-init.iso"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "9.5",
|
||||
"name": "10.1",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-9.5-x86_64-kvm.qcow2",
|
||||
"hda_disk_image": "rhel-10.1-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "10.0",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-10.0-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9.7",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-9.7-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9.6",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-9.6-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
@ -161,34 +141,6 @@
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9.3",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-9.3-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9.2",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-9.2-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9.1",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-baseos-9.1-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9.0",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-baseos-9.0-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.10",
|
||||
"images": {
|
||||
@ -196,13 +148,6 @@
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.9",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-8.9-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.8",
|
||||
"images": {
|
||||
@ -210,13 +155,6 @@
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.7",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-8.7-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.6",
|
||||
"images": {
|
||||
@ -224,31 +162,10 @@
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.5",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-8.5-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.4",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-8.4-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.3",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-8.3-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.9",
|
||||
"images": {
|
||||
"hda_disk_image": "rhel-server-7.9-x86_64-kvm.qcow2",
|
||||
"hda_disk_image": "rhel-server-7.9-update-12-x86_64-kvm.qcow2",
|
||||
"cdrom_image": "rhel-cloud-init.iso"
|
||||
}
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"appliance_id": "ed6b9f98-7de2-4d61-a3ed-ad4c3e323ace",
|
||||
"name": "Tiny Core Linux",
|
||||
"category": "guest",
|
||||
"description": "Core Linux is a smaller variant of Tiny Core without a graphical desktop.\n\nIt provides a complete Linux system using only a few MiB.",
|
||||
"description": "TinyCore is a Linux system with a graphical desktop using only a few MiB.",
|
||||
"vendor_name": "Team Tiny Core",
|
||||
"vendor_url": "http://distro.ibiblio.org/tinycorelinux",
|
||||
"documentation_url": "http://wiki.tinycorelinux.net/",
|
||||
@ -25,6 +25,14 @@
|
||||
"options": "-vga std -usbdevice tablet"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "linux-tinycore-17.0.qcow2",
|
||||
"version": "17.0",
|
||||
"md5sum": "6434d4da25dd0277cc90b000bb4ac8f1",
|
||||
"filesize": 35061760,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-tinycore-17.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "linux-tinycore-11.1.qcow2",
|
||||
"version": "11.1",
|
||||
@ -51,6 +59,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "17.0",
|
||||
"images": {
|
||||
"hda_disk_image": "linux-tinycore-17.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "11.1",
|
||||
"images": {
|
||||
|
||||
@ -9,27 +9,54 @@
|
||||
"documentation_url": "https://docs.vyos.io/",
|
||||
"product_name": "VyOS Universal Router",
|
||||
"product_url": "https://vyos.io/vyos-universal-router",
|
||||
"registry_version": 4,
|
||||
"registry_version": 8,
|
||||
"status": "stable",
|
||||
"availability": "service-contract",
|
||||
"maintainer": "VyOS Inc.",
|
||||
"maintainer_email": "support@vyos.io",
|
||||
"usage": "\nDefault credentials:\nUser: vyos\nPassword: vyos",
|
||||
"installation_instructions": "To install the appliance, you must:\n\n1. Have an active VyOS subscription (for details, visit https://vyos.io/).\n2. Download the QEMU/KVM image for your target version from the support portal: https://support.vyos.io/.\n3. Import the downloaded image for installation in the next step.\n\nAlternatively, you can:\n- Build your own image, or\n- Use any compatible qcow2 images (including rolling or stream releases) by selecting \"Create a new version\" and enabling the \"Allow custom files\" feature.",
|
||||
"usage": "No extra installation steps are required - simply boot the router.\nFor more details, please refer to the documentation: https://docs.vyos.io/",
|
||||
"default_username": "vyos",
|
||||
"default_password": "vyos",
|
||||
"symbol": "vyos.svg",
|
||||
"port_name_format": "eth{0}",
|
||||
"qemu": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 10,
|
||||
"ram": 2048,
|
||||
"cpus": 4,
|
||||
"hda_disk_interface": "virtio",
|
||||
"arch": "x86_64",
|
||||
"console_type": "telnet",
|
||||
"boot_priority": "c",
|
||||
"kvm": "require",
|
||||
"on_close": "shutdown_signal"
|
||||
},
|
||||
"settings": [
|
||||
{
|
||||
"name": "default x86_64",
|
||||
"default": true,
|
||||
"template_type": "qemu",
|
||||
"template_properties": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 10,
|
||||
"port_name_format": "eth{0}",
|
||||
"ram": 2048,
|
||||
"cpus": 4,
|
||||
"hda_disk_interface": "virtio",
|
||||
"platform": "x86_64",
|
||||
"console_type": "telnet",
|
||||
"boot_priority": "c",
|
||||
"options": "-cpu host",
|
||||
"uefi": false,
|
||||
"on_close": "shutdown_signal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1.5 x86_64",
|
||||
"inherit_default_properties": true,
|
||||
"template_type": "qemu",
|
||||
"template_properties": {
|
||||
"ram": 8192,
|
||||
"cpus": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"filename": "vyos-1.5.0-kvm-amd64.qcow2",
|
||||
"version": "1.5.0",
|
||||
"md5sum": "f84587bd6c45b139563a1d1d2fe0b2e8",
|
||||
"filesize": 618528768,
|
||||
"download_url": "https://support.vyos.io/"
|
||||
},
|
||||
{
|
||||
"filename": "vyos-1.4.4-kvm-amd64.qcow2",
|
||||
"version": "1.4.4",
|
||||
@ -132,11 +159,18 @@
|
||||
"filename": "vyos-1.2.5-amd64.qcow2",
|
||||
"version": "1.2.5",
|
||||
"md5sum": "110c22309ec480600446fd2fb4f27a0d",
|
||||
"filesize": 411500544 ,
|
||||
"filesize": 411500544,
|
||||
"download_url": "https://support.vyos.io/"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "1.5.0",
|
||||
"settings": "1.5 x86_64",
|
||||
"images": {
|
||||
"hda_disk_image": "vyos-1.5.0-kvm-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1.4.4",
|
||||
"images": {
|
||||
|
||||
@ -30,19 +30,11 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "WinDev2308Eval-disk1.vmdk",
|
||||
"version": "2308",
|
||||
"md5sum": "6a9b4ed6d7481f7bbf8a054c797b1eee",
|
||||
"filesize": 24945341952,
|
||||
"download_url": "https://download.microsoft.com/download/7/1/3/7135f2ab-8528-49fc-9252-8d5d94c697ef/WinDev2308Eval.VMWare.zip",
|
||||
"compression": "zip"
|
||||
},
|
||||
{
|
||||
"filename": "WinDev2212Eval-disk1.vmdk",
|
||||
"version": "2212",
|
||||
"md5sum": "c79f393a067b92e01a513a118d455ac8",
|
||||
"filesize": 24620493824,
|
||||
"download_url": "https://aka.ms/windev_VM_vmware",
|
||||
"filename": "WinDev2407Eval-disk1.vmdk",
|
||||
"version": "2407",
|
||||
"md5sum": "66e7233c7922ef128c5b7c957d20bcbe",
|
||||
"filesize": 25984826368,
|
||||
"download_url": "https://download.microsoft.com/download/e/e/c/eec4775f-f2e8-4476-98b2-ca51502a6429/WinDev2407Eval.VMWare.zip",
|
||||
"compression": "zip"
|
||||
},
|
||||
{
|
||||
@ -57,17 +49,10 @@
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "2308",
|
||||
"name": "2407",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "WinDev2308Eval-disk1.vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2212",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "WinDev2212Eval-disk1.vmdk"
|
||||
"hda_disk_image": "WinDev2407Eval-disk1.vmdk"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -44,6 +44,10 @@ class Appliance:
|
||||
def status(self):
|
||||
return self._data["status"]
|
||||
|
||||
@property
|
||||
def registry_version(self):
|
||||
return self._data.get("registry_version")
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
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 gns3server import schemas
|
||||
from gns3server.schemas.controller.appliances import ApplianceModel
|
||||
from gns3server.utils.images import default_images_directory
|
||||
from gns3server.db.repositories.images import ImagesRepository
|
||||
from gns3server.db.repositories.templates import TemplatesRepository
|
||||
@ -248,7 +249,8 @@ class ApplianceManager:
|
||||
appliances_info = self._find_appliances_from_image_checksum(image_checksum)
|
||||
for appliance, image_version in appliances_info:
|
||||
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:
|
||||
log.warning(f"Could not validate appliance '{appliance.id}': {e}")
|
||||
if appliance.versions:
|
||||
@ -279,7 +281,8 @@ class ApplianceManager:
|
||||
raise ControllerNotFoundError(message=f"Could not find appliance '{appliance_id}'")
|
||||
|
||||
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:
|
||||
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)
|
||||
json_data = appliance.asdict() # Check if loaded without error
|
||||
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
|
||||
if not appliance.symbol or appliance.symbol.startswith(":/symbols/"):
|
||||
# apply a default symbol if the appliance has none or a default symbol
|
||||
|
||||
@ -83,7 +83,7 @@ class Port:
|
||||
# If port name format has changed we use the port name as the short name (1.X behavior)
|
||||
if self._short_name:
|
||||
return self._short_name
|
||||
elif "/" in self._name:
|
||||
elif self._name and '/' in self._name:
|
||||
return self._name.replace(self.long_name_type(), self.short_name_type())
|
||||
elif self._name.startswith(f"{self.long_name_type()}{self._interface_number}"):
|
||||
return self.short_name_type() + f"{self._interface_number}"
|
||||
|
||||
40
gns3server/custom_symbols/AlpiNet.svg
Normal file
40
gns3server/custom_symbols/AlpiNet.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
width="1024"
|
||||
height="1024"
|
||||
viewBox="0 0 1024 1024"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><g
|
||||
id="layer1"><circle
|
||||
style="fill:#1a1a1a;stroke:#0bdcd1;stroke-width:1.51529"
|
||||
id="path7"
|
||||
cx="511.54706"
|
||||
cy="511.54706"
|
||||
r="509.74625" /></g><g
|
||||
id="g1"><g
|
||||
id="g2"
|
||||
transform="matrix(0.84744126,0,0,0.84744126,80.284286,51.918792)"><path
|
||||
id="path38"
|
||||
style="fill:#feffff;fill-opacity:1;stroke-width:1.96125"
|
||||
d="m 550.48438,383.83594 c -1.42517,-1.97626 -34.04408,49.48574 -35.94532,52.8164 4.89656,6.76742 67.90321,99.44362 73.35938,108.60157 0.63819,0.0157 10.56542,-9.61649 22.0625,-21.40625 11.49708,-11.78973 21.54624,-22.59197 20.90429,-23.58008 C 616.78191,478.5901 564.8254,403.72241 550.48438,383.83594 Z m -49.10743,73.1914 C 493.61984,468.80306 385.5652,625.20965 385.19922,625.7793 l 75.51562,-1.06446 79.44528,-115.18592 c -11.81985,-16.44323 -27.61076,-38.3512 -38.78317,-52.50158 z m 193.43946,172.58985 h 12.34179 12.34571 c -3.17219,-3.92548 -7.3417,-9.48657 -10.33768,-13.16127 -4.89572,4.54527 -9.61638,8.79114 -14.34982,13.16127 z" /><path
|
||||
style="fill:#0be1db;fill-opacity:1;stroke-width:1.96125"
|
||||
d="m 806.2793,581.50586 c -3.01268,-0.10613 -6.23682,0.44998 -9.5586,1.83789 -19.90636,8.31739 -13.00606,38.42969 8.80664,38.42969 5.68137,0 8.85021,-1.42245 13.46485,-6.03711 13.6829,-13.68274 3.55593,-33.65733 -12.71289,-34.23047 z"
|
||||
id="path6" /><path
|
||||
style="fill:#0be1db;fill-opacity:1;stroke-width:1.96125"
|
||||
d="m 737.26367,535.58789 c -0.3996,-0.0138 -0.79463,-0.0112 -1.1875,0.008 -4.04078,0.19573 -7.74137,2.13022 -11.46875,5.85742 -5.1324,5.13247 -5.83502,7.03923 -4.97265,13.46876 l 1.0039,7.48828 -92.45508,92.44336 -92.45507,92.43945 h -8.22071 c -12.13802,0 -19.23774,6.68557 -18.94531,17.83984 0.43422,16.55665 18.46138,23.54071 30.27344,11.72852 4.34105,-4.34105 5.73437,-7.55681 5.73437,-13.2461 0,-7.29657 2.65655,-10.17249 92.18555,-99.68359 79.80293,-79.78713 92.87359,-92.08577 97.26367,-91.51953 10.65687,1.37451 22.36524,-8.10861 22.36524,-18.11523 0,-5.72019 -4.75364,-13.26581 -10.09375,-16.02735 -3.25973,-1.68564 -6.23011,-2.58511 -9.02735,-2.68164 z"
|
||||
id="path5" /><path
|
||||
style="fill:#0be1db;fill-opacity:1;stroke-width:1.96125"
|
||||
d="m 455.92376,722.85975 c -0.84707,-0.72828 47.84156,-50.42929 108.40436,-111.55311 108.58163,-109.58717 110.22043,-111.13283 117.8711,-111.13281 10.29522,0 15.09709,-2.84515 18.48242,-10.94727 2.42863,-5.81275 2.42863,-7.59739 0,-13.41015 -3.29727,-7.89159 -8.17895,-10.94337 -17.49805,-10.94336 -10.26482,0 -15.68307,5.51226 -16.93945,17.24023 l -1.05664,9.8711 -109.83008,110.49414 -109.83008,110.49218 -9.61132,0.98047 c -11.9215,1.21642 -17.75977,7.75795 -17.75977,19.9043 0,14.08063 15.05196,21.87028 28.19336,14.58984 0,0 4.79393,-2.74146 7.39858,-9.58463 2.41703,-5.84882 2.17557,-16.00093 2.17557,-16.00093 z"
|
||||
id="path4" /><path
|
||||
style="fill:#0be1db;fill-opacity:1;stroke-width:1.96125"
|
||||
d="m 473.79688,753.47852 -4.02344,11.125 c -8.10176,22.4276 -28.88459,62.69349 -43.53321,84.34179 -8.27082,12.22324 -15.03515,22.59353 -15.03515,23.04492 0,1.33096 15.06325,0.89351 39.84961,-1.16015 45.22245,-3.74688 88.30968,-16.33338 120.97265,-35.33399 28.6667,-16.67594 57.23525,-43.40695 80.25,-75.08398 10.12949,-13.94196 13.32428,-17.04576 17.54883,-17.06055 8.68032,-0.0304 68.55714,-9.3178 101.50977,-15.74414 56.57872,-11.03382 98.42003,-23.18263 123.70508,-35.91601 23.22575,-11.6963 52.4281,-37.10662 60.62695,-52.75782 l 2.82617,-5.39257 h -13.76758 -13.76562 l -5.68555,7.84375 c -20.38767,28.14421 -64.05598,46.64235 -152.21875,64.47656 -38.76599,7.84185 -105.32401,17.89648 -118.47266,17.89648 -1.06202,0 -7.44413,8.04878 -14.18359,17.88867 -41.25483,60.23358 -96.4226,94.58811 -169.97461,105.84766 -8.8286,1.35151 -16.61104,1.90075 -17.29101,1.2207 -0.68016,-0.68006 1.86184,-5.90514 5.64843,-11.61328 9.19319,-13.85824 24.65964,-48.53637 33.5,-75.11133 l 7.18164,-21.58398 82.30664,-82.29297 82.3125,-82.29297 7.84961,0.56836 c 10.81534,0.78089 17.76953,-6.0197 17.76953,-17.37695 0,-10.88387 -6.63291,-17.65039 -17.29882,-17.65039 -11.55984,0 -18.00391,5.93557 -18.00391,16.58594 v 8.45898 l -82.89062,83.87305 -82.88868,83.875 -8.49279,7.67062 c -4.37775,4.78585 -6.33142,11.6536 -6.33142,11.6536 z"
|
||||
id="path3" /><path
|
||||
style="fill:#0be1db;fill-opacity:1;stroke-width:1.96125"
|
||||
d="m 373.99023,211.99414 c -0.0344,-7e-4 -0.0688,-3.3e-4 -0.10351,0.002 -0.98102,0.0644 -2.10455,1.31778 -2.89258,3.80079 -2.03892,6.42412 -24.74098,28.13382 -36.38672,34.79687 -5.95281,3.40588 -18.29163,8.49765 -27.42187,11.31836 -37.67005,11.63774 -42.75152,13.50188 -57.41407,21.06445 -8.42419,4.34504 -20.3145,12.63104 -26.42578,18.41016 -6.11146,5.77909 -11.36802,10.50781 -11.67968,10.50781 -0.31164,0 -5.46152,-3.99126 -11.44532,-8.86523 C 182.04946,288.22871 166.18703,281.58024 125.59961,271.74219 79.004275,260.4479 65.632446,254.23318 45.181641,234.37109 l -15.001953,-14.56836 1.0625,27.90235 c 1.317375,34.7172 7.141981,58.58456 21.324218,87.33398 17.104514,34.6735 47.117582,61.57002 89.041014,79.79492 8.29279,3.60496 15.72195,7.29187 16.50586,8.19336 0.78412,0.90149 3.43183,7.81633 5.88477,15.36719 24.32174,74.8691 56.30712,138.84104 91.1914,182.39649 20.53651,25.64116 48.30532,51.28183 75.09766,69.34179 13.24808,8.93024 57.31457,33.61613 60.01758,33.6211 0.9112,0.002 7.92469,-6.83547 15.58593,-15.19532 7.66107,-8.35988 32.40695,-33.88213 54.99219,-56.71289 39.282,-39.70856 41.34032,-41.46323 47.39649,-40.48047 8.38632,1.36091 14.42306,-2.14179 17.85742,-10.36132 5.4619,-13.0723 -2.6771,-24.87696 -17.15234,-24.87696 -11.10326,0 -17.71761,7.16137 -17.05079,18.46289 0.45266,7.67056 -8.6e-4,8.20117 -48.45703,56.68555 -26.90315,26.91938 -49.87638,48.94727 -51.05273,48.94727 -1.17656,0 -9.12807,-3.66317 -17.66797,-8.13672 -54.09518,-28.33734 -97.88787,-70.01058 -131.10547,-124.76172 -23.25659,-38.33261 -52.27565,-105.397 -63,-145.59961 -1.87045,-7.0115 -4.37689,-13.79295 -5.57031,-15.06641 -1.19324,-1.27344 -9.2279,-5.09536 -17.85742,-8.49609 -29.62635,-11.67514 -42.151,-19.45548 -59.857426,-37.18359 C 87.096674,350.6975 78.254387,339.68207 74.416016,332.38867 67.510822,319.26824 57.280692,289.84931 55.296875,277.4082 l -1.275391,-8.00586 13.353516,6.38868 c 7.345497,3.5138 24.830532,9.11907 38.85352,12.45703 40.51504,9.64398 48.25971,11.96052 61.46289,18.38672 15.11246,7.3555 26.19259,16.87899 36.59961,31.45312 4.31475,6.0424 8.48834,11.00408 9.27539,11.02734 0.78705,0.0233 3.13298,-3.2959 5.21289,-7.37304 5.58959,-10.95646 17.9453,-25.16502 29.30078,-33.69727 10.54313,-7.92204 22.19687,-12.7658 59.17773,-24.58984 12.40494,-3.96629 29.0662,-10.68945 37.0293,-14.94141 l 14.48047,-7.73047 -1.38281,14.03125 c -3.03308,30.78166 -14.64384,60.5119 -31.81641,81.46875 -8.04664,9.81974 -32.54098,28.924 -47.58008,37.10938 -5.47623,2.98046 -9.95508,5.95643 -9.95508,6.61328 0,3.73903 11.30914,16.75745 21.06836,24.25391 22.71643,17.44914 45.10102,25.24878 73.07227,25.45507 31.74568,0.23412 55.43489,-9.37797 79.52148,-32.25781 17.14805,-16.28895 30.22025,-35.07905 39.09571,-56.19531 l 6.32421,-15.04492 40.00196,-1.27149 c 44.98514,-1.42911 74.13488,-4.34758 154.42578,-15.44922 107.63099,-14.88186 159.73557,-16.99101 195.11523,-7.90234 37.26023,9.57167 53.08811,26.43688 68.68164,73.18945 16.26158,48.75581 27.24674,156.12769 18.1543,177.44141 -6.65103,15.5911 -21.53587,24.969 -64.14648,40.4082 -25.02213,9.06634 -78.95856,23.42148 -118.44727,31.52539 l -21.57227,4.42774 -4.29882,8.6582 c -4.9808,10.0349 -5.51649,13.56055 -2.05664,13.56055 5.19615,0 84.14889,-18.07526 111.77929,-25.58985 37.44153,-10.18286 59.61697,-18.03292 78.375,-27.74414 19.88145,-10.29281 31.43782,-20.73308 38.20117,-34.50586 4.9839,-10.14961 5.56841,-13.55107 6.1875,-36.06836 1.28661,-46.7948 -9.45292,-120.18526 -24.22851,-165.58398 -11.90503,-36.57905 -28.77477,-57.78313 -56.86523,-71.46094 -23.49703,-11.44114 -40.24828,-14.28288 -84.33399,-14.31445 -40.76785,-0.0292 -63.71716,1.89835 -124.53906,10.47266 -99.38018,14.01006 -132.83218,17.44542 -184.66602,18.96484 l -42.47265,1.24609 -1.12305,5.61914 c -1.93693,9.68429 -12.97928,34.36585 -20.66211,46.18555 -13.97375,21.49833 -34.72095,38.31796 -56.51562,45.81641 -22.73271,7.82124 -57.98854,3.99323 -79.54688,-8.64063 -14.42562,-8.45411 -14.55332,-9.51253 -2.21289,-18.01172 14.80611,-10.19733 40.11694,-37.65518 47.52539,-51.55468 15.98875,-29.99796 23.13484,-67.35268 19.96094,-104.34571 -0.97161,-11.32625 -1.80255,-22.57882 -1.84961,-25.00586 -0.0543,-2.81579 -0.93059,-4.26743 -1.9961,-4.28906 z"
|
||||
id="path37" /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 8.7 KiB |
4
gns3server/custom_symbols/home-assistant-logomark-color-on-light.svg
Executable file
4
gns3server/custom_symbols/home-assistant-logomark-color-on-light.svg
Executable file
@ -0,0 +1,4 @@
|
||||
<svg width="240" height="240" viewBox="0 0 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M240 224.762C240 233.012 233.25 239.762 225 239.762H15C6.75 239.762 0 233.012 0 224.762V134.762C0 126.512 4.77 114.993 10.61 109.153L109.39 10.3725C115.22 4.5425 124.77 4.5425 130.6 10.3725L229.39 109.162C235.22 114.992 240 126.522 240 134.772V224.772V224.762Z" fill="#F2F4F9"/>
|
||||
<path d="M229.39 109.153L130.61 10.3725C124.78 4.5425 115.23 4.5425 109.4 10.3725L10.61 109.153C4.78 114.983 0 126.512 0 134.762V224.762C0 233.012 6.75 239.762 15 239.762H107.27L66.64 199.132C64.55 199.852 62.32 200.262 60 200.262C48.7 200.262 39.5 191.062 39.5 179.762C39.5 168.462 48.7 159.262 60 159.262C71.3 159.262 80.5 168.462 80.5 179.762C80.5 182.092 80.09 184.322 79.37 186.412L111 218.042V102.162C104.2 98.8225 99.5 91.8425 99.5 83.7725C99.5 72.4725 108.7 63.2725 120 63.2725C131.3 63.2725 140.5 72.4725 140.5 83.7725C140.5 91.8425 135.8 98.8225 129 102.162V183.432L160.46 151.972C159.84 150.012 159.5 147.932 159.5 145.772C159.5 134.472 168.7 125.272 180 125.272C191.3 125.272 200.5 134.472 200.5 145.772C200.5 157.072 191.3 166.272 180 166.272C177.5 166.272 175.12 165.802 172.91 164.982L129 208.892V239.772H225C233.25 239.772 240 233.022 240 224.772V134.772C240 126.522 235.23 115.002 229.39 109.162V109.153Z" fill="#18BCF2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from sqlalchemy import Table, Boolean, Column, String, DateTime, ForeignKey, event
|
||||
from sqlalchemy import Table, Boolean, Column, Integer, String, DateTime, ForeignKey, event
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseTable, generate_uuid, GUID
|
||||
@ -45,6 +45,7 @@ class User(BaseTable):
|
||||
full_name = Column(String)
|
||||
hashed_password = Column(String)
|
||||
last_login = Column(DateTime)
|
||||
token_version = Column(Integer, default=0, nullable=False, server_default="0")
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_superadmin = Column(Boolean, default=False)
|
||||
groups = relationship("UserGroup", secondary=user_group_map, back_populates="users")
|
||||
|
||||
@ -113,6 +113,18 @@ class UsersRepository(BaseRepository):
|
||||
await self._db_session.refresh(user_db) # force refresh of updated_at value
|
||||
return user_db
|
||||
|
||||
async def logout_user(self, user_id: UUID) -> None:
|
||||
"""
|
||||
Increment token_version to invalidate all existing tokens for the user.
|
||||
"""
|
||||
|
||||
query = update(models.User).\
|
||||
where(models.User.user_id == user_id).\
|
||||
values(token_version=models.User.token_version + 1)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
|
||||
async def delete_user(self, user_id: UUID) -> bool:
|
||||
"""
|
||||
Delete a user.
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
"""add token version to users table
|
||||
|
||||
Revision ID: aff810fc119a
|
||||
Revises: ec4b7b198555
|
||||
Create Date: 2026-04-06 19:49:12.155446
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'aff810fc119a'
|
||||
down_revision = 'ec4b7b198555'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
|
||||
op.add_column('users', sa.Column('token_version', sa.Integer(), nullable=False, server_default='0'))
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
op.drop_column('users', 'token_version')
|
||||
@ -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.templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template
|
||||
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.gns3vm import GNS3VM
|
||||
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
|
||||
# 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
|
||||
# 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 typing import List, Optional, Union
|
||||
from typing import Annotated, List, Literal, Optional, Union
|
||||
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):
|
||||
"""Appliance category enum"""
|
||||
|
||||
router = 'router'
|
||||
multilayer_switch = 'multilayer_switch'
|
||||
@ -31,17 +36,8 @@ class Category(str, Enum):
|
||||
guest = 'guest'
|
||||
|
||||
|
||||
class RegistryVersion(int, Enum):
|
||||
|
||||
version1 = 1
|
||||
version2 = 2
|
||||
version3 = 3
|
||||
version4 = 4
|
||||
version5 = 5
|
||||
version6 = 6
|
||||
|
||||
|
||||
class Status(str, Enum):
|
||||
"""Appliance status enum"""
|
||||
|
||||
stable = 'stable'
|
||||
experimental = 'experimental'
|
||||
@ -49,6 +45,7 @@ class Status(str, Enum):
|
||||
|
||||
|
||||
class Availability(str, Enum):
|
||||
"""Image availability enum"""
|
||||
|
||||
free = 'free'
|
||||
with_registration = 'with-registration'
|
||||
@ -56,52 +53,32 @@ class Availability(str, Enum):
|
||||
service_contract = 'service-contract'
|
||||
|
||||
|
||||
class ConsoleType(str, Enum):
|
||||
class Compression(str, Enum):
|
||||
"""Compression type enum"""
|
||||
|
||||
telnet = 'telnet'
|
||||
vnc = 'vnc'
|
||||
http = 'http'
|
||||
https = 'https'
|
||||
none = 'none'
|
||||
bzip2 = 'bzip2'
|
||||
gzip = 'gzip'
|
||||
lzma = 'lzma'
|
||||
xz = 'xz'
|
||||
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')
|
||||
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[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',
|
||||
)
|
||||
c1700 = 'c1700'
|
||||
c2600 = 'c2600'
|
||||
c2691 = 'c2691'
|
||||
c3725 = 'c3725'
|
||||
c3745 = 'c3745'
|
||||
c3600 = 'c3600'
|
||||
c7200 = 'c7200'
|
||||
|
||||
|
||||
class Iou(BaseModel):
|
||||
|
||||
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):
|
||||
class DynamipsChassis(str, Enum):
|
||||
"""Dynamips chassis enum"""
|
||||
|
||||
chassis_1720 = '1720'
|
||||
chassis_1721 = '1721'
|
||||
@ -120,26 +97,56 @@ class Chassis(str, Enum):
|
||||
chassis_3620 = '3620'
|
||||
chassis_3640 = '3640'
|
||||
chassis_3660 = '3660'
|
||||
_ = ''
|
||||
|
||||
|
||||
class Platform(str, Enum):
|
||||
class DynamipsSlot(str, Enum):
|
||||
"""Dynamips slot enum"""
|
||||
|
||||
c1700 = 'c1700'
|
||||
c2600 = 'c2600'
|
||||
c2691 = 'c2691'
|
||||
c3725 = 'c3725'
|
||||
c3745 = 'c3745'
|
||||
c3600 = 'c3600'
|
||||
c7200 = 'c7200'
|
||||
C7200_IO_2FE = 'C7200-IO-2FE'
|
||||
C7200_IO_FE = 'C7200-IO-FE'
|
||||
C7200_IO_GE_E = 'C7200-IO-GE-E'
|
||||
NM_16ESW = 'NM-16ESW'
|
||||
NM_1E = 'NM-1E'
|
||||
NM_1FE_TX = 'NM-1FE-TX'
|
||||
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'
|
||||
vxr = 'vxr'
|
||||
|
||||
|
||||
class Npe(str, Enum):
|
||||
class DynamipsNpe(str, Enum):
|
||||
"""Dynamips NPE enum"""
|
||||
|
||||
npe_100 = 'npe-100'
|
||||
npe_150 = 'npe-150'
|
||||
@ -151,12 +158,20 @@ class Npe(str, Enum):
|
||||
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_82544gc = 'e1000-82544gc'
|
||||
e1000_82545em = 'e1000-82545em'
|
||||
e1000e = 'e1000e'
|
||||
i82550 = 'i82550'
|
||||
i82551 = 'i82551'
|
||||
i82557a = 'i82557a'
|
||||
@ -173,28 +188,14 @@ class AdapterType(str, Enum):
|
||||
igb = 'igb'
|
||||
ne2k_pci = 'ne2k_pci'
|
||||
pcnet = 'pcnet'
|
||||
rocker = 'rocker'
|
||||
rtl8139 = 'rtl8139'
|
||||
virtio = 'virtio'
|
||||
virtio_net_pci = 'virtio-net-pci'
|
||||
vmxnet3 = 'vmxnet3'
|
||||
|
||||
|
||||
class DiskInterface(str, 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):
|
||||
class QemuPlatform(str, Enum):
|
||||
"""Qemu platform enum"""
|
||||
|
||||
aarch64 = 'aarch64'
|
||||
alpha = 'alpha'
|
||||
@ -226,16 +227,23 @@ class Arch(str, Enum):
|
||||
xtensaeb = 'xtensaeb'
|
||||
|
||||
|
||||
class ConsoleType1(str, Enum):
|
||||
class QemuDiskInterface(str, Enum):
|
||||
"""Disk interface enum"""
|
||||
|
||||
telnet = 'telnet'
|
||||
vnc = 'vnc'
|
||||
spice = 'spice'
|
||||
spice_agent = 'spice+agent'
|
||||
ide = 'ide'
|
||||
sata = 'sata'
|
||||
nvme = 'nvme'
|
||||
scsi = 'scsi'
|
||||
sd = 'sd'
|
||||
mtd = 'mtd'
|
||||
floppy = 'floppy'
|
||||
pflash = 'pflash'
|
||||
virtio = 'virtio'
|
||||
none = 'none'
|
||||
|
||||
|
||||
class BootPriority(str, Enum):
|
||||
class QemuBootPriority(str, Enum):
|
||||
"""Boot priority enum"""
|
||||
|
||||
c = 'c'
|
||||
d = 'd'
|
||||
@ -248,14 +256,16 @@ class BootPriority(str, Enum):
|
||||
nd = 'nd'
|
||||
|
||||
|
||||
class Kvm(str, Enum):
|
||||
class QemuOnClose(str, Enum):
|
||||
"""Qemu on_close action enum"""
|
||||
|
||||
require = 'require'
|
||||
allow = 'allow'
|
||||
disable = 'disable'
|
||||
power_off = 'power_off'
|
||||
shutdown_signal = 'shutdown_signal'
|
||||
save_vm_state = 'save_vm_state'
|
||||
|
||||
|
||||
class ProcessPriority(str, Enum):
|
||||
class QemuProcessPriority(str, Enum):
|
||||
"""Qemu process priority enum"""
|
||||
|
||||
realtime = 'realtime'
|
||||
very_high = 'very high'
|
||||
@ -263,80 +273,117 @@ class ProcessPriority(str, Enum):
|
||||
normal = 'normal'
|
||||
low = '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):
|
||||
"""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')
|
||||
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')
|
||||
hda_disk_interface: Optional[DiskInterface] = Field(
|
||||
None, title='Disk interface for the installed hda_disk_image'
|
||||
)
|
||||
hdb_disk_interface: Optional[DiskInterface] = Field(
|
||||
None, title='Disk interface for the installed hdb_disk_image'
|
||||
)
|
||||
hdc_disk_interface: Optional[DiskInterface] = Field(
|
||||
None, title='Disk interface for the installed hdc_disk_image'
|
||||
)
|
||||
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'
|
||||
)
|
||||
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')
|
||||
arch: QemuPlatform = Field(..., title='Architecture emulated')
|
||||
console_type: QemuConsoleType = Field(..., title='Type of console connection for the administration of the appliance')
|
||||
boot_priority: Optional[QemuBootPriority] = Field(None, title='Disk boot priority')
|
||||
kernel_command_line: Optional[str] = Field(None, title='Command line parameters sent to the kernel')
|
||||
kvm: Kvm = Field(..., title='KVM requirements')
|
||||
options: Optional[str] = Field(
|
||||
None, title='Optional additional qemu command line options'
|
||||
)
|
||||
cpu_throttling: Optional[confloat(ge=0.0, le=100.0)] = Field(
|
||||
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.'
|
||||
)
|
||||
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')
|
||||
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 ApplianceVersionImages(BaseModel):
|
||||
"""Appliance version images configuration for v1-6"""
|
||||
|
||||
kernel_image: Optional[str] = Field(None, title='Kernel image')
|
||||
initrd: Optional[str] = Field(None, title='Initrd disk image')
|
||||
@ -350,101 +397,141 @@ class ApplianceVersionImages(BaseModel):
|
||||
|
||||
|
||||
class ApplianceVersion(BaseModel):
|
||||
"""Appliance version definition for v1-6"""
|
||||
|
||||
name: str = Field(..., title='Name of the version')
|
||||
idlepc: Optional[str] = Field(None, pattern='^0x[0-9a-f]{8}')
|
||||
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'
|
||||
C7200_IO_FE = 'C7200-IO-FE'
|
||||
C7200_IO_GE_E = 'C7200-IO-GE-E'
|
||||
NM_16ESW = 'NM-16ESW'
|
||||
NM_1E = 'NM-1E'
|
||||
NM_1FE_TX = 'NM-1FE-TX'
|
||||
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'
|
||||
_ = ''
|
||||
filename: str = Field(..., title='Filename')
|
||||
version: str = Field(..., title='Version of the file')
|
||||
md5sum: Optional[str] = Field(None, title='md5sum of the file', pattern='^[a-f0-9]{32}$')
|
||||
filesize: int = Field(..., title='File size in bytes')
|
||||
download_url: Optional[Union[AnyUrl, Annotated[str, Field(max_length=0)]]] = Field(
|
||||
None,
|
||||
title='Download url where you can download the appliance from a browser'
|
||||
)
|
||||
direct_download_url: Optional[Union[AnyUrl, Annotated[str, Field(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.'
|
||||
)
|
||||
checksum: Optional[str] = Field(None, title='checksum of the image file')
|
||||
checksum_type: Optional[ChecksumType] = Field(None, title='checksum type of the image file')
|
||||
compression_target: Optional[str] = Field(
|
||||
None, title='Optional, file name of the image file inside the compressed file.'
|
||||
)
|
||||
|
||||
|
||||
class DynamipsWic(str, Enum):
|
||||
# ============================================================================
|
||||
# Version 8 Models
|
||||
# ============================================================================
|
||||
|
||||
WIC_1ENET = 'WIC-1ENET'
|
||||
WIC_1T = 'WIC-1T'
|
||||
WIC_2T = 'WIC-2T'
|
||||
class CustomAdapterItem(BaseModel):
|
||||
"""Custom adapter configuration (v8)"""
|
||||
|
||||
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')
|
||||
platform: Platform = Field(..., title='Platform type')
|
||||
ram: conint(ge=1) = Field(..., title='Amount of ram')
|
||||
nvram: conint(ge=1) = Field(..., title='Amount of nvram')
|
||||
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')
|
||||
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')
|
||||
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: 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, constr(max_length=0)]] = Field(None, title='Website of the vendor')
|
||||
documentation_url: Optional[Union[AnyUrl, constr(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, constr(max_length=0)]] = Field(
|
||||
None, title='An optional product url on vendor website'
|
||||
)
|
||||
registry_version: RegistryVersion = Field(
|
||||
..., title='Version of the registry compatible with this appliance'
|
||||
)
|
||||
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, constr(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')
|
||||
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')
|
||||
chassis: Optional[DynamipsChassis] = Field(None, title='Chassis type')
|
||||
platform: Optional[DynamipsPlatform] = Field(None, title='Platform type')
|
||||
ram: Optional[Annotated[int, Field(ge=1)]] = Field(None, title='Amount of ram')
|
||||
nvram: Optional[Annotated[int, Field(ge=1)]] = Field(None, title='Amount of nvram')
|
||||
idlepc: Optional[str] = Field(None, pattern=r'^0x[0-9a-f]{8}')
|
||||
startup_config: Optional[str] = Field(None, title='Config loaded at startup')
|
||||
wic0: Optional[str] = Field(None)
|
||||
wic1: Optional[str] = Field(None)
|
||||
wic2: Optional[str] = Field(None)
|
||||
slot0: Optional[str] = Field(None)
|
||||
slot1: Optional[str] = Field(None)
|
||||
slot2: Optional[str] = Field(None)
|
||||
slot3: Optional[str] = Field(None)
|
||||
slot4: Optional[str] = Field(None)
|
||||
slot5: Optional[str] = Field(None)
|
||||
slot6: Optional[str] = Field(None)
|
||||
midplane: Optional[DynamipsMidplane] = Field(None)
|
||||
npe: Optional[DynamipsNpe] = Field(None)
|
||||
|
||||
|
||||
class QemuPropertiesV8(BaseModel):
|
||||
"""Qemu 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')
|
||||
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(
|
||||
None, title='Optional name of the first networking port example: eth0'
|
||||
)
|
||||
@ -455,12 +542,177 @@ class Appliance(BaseModel):
|
||||
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"
|
||||
linked_clone: Optional[bool] = Field(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')
|
||||
iou: Optional[Iou] = Field(None, title='IOU specific options')
|
||||
dynamips: Optional[Dynamips] = Field(None, title='Dynamips specific options')
|
||||
qemu: Optional[Qemu] = Field(None, title='Qemu specific options')
|
||||
images: Optional[List[ApplianceImage]] = Field(None, title='Images for this 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)
|
||||
|
||||
@ -27,3 +27,4 @@ class Token(BaseModel):
|
||||
class TokenData(BaseModel):
|
||||
|
||||
username: Optional[str] = None
|
||||
token_version: int = 0
|
||||
|
||||
@ -45,12 +45,12 @@ class AuthService:
|
||||
|
||||
return bcrypt.checkpw(password=password.encode('utf-8'), hashed_password=hashed_password.encode('utf-8'))
|
||||
|
||||
def create_access_token(self, username, secret_key: str = None, expires_in: int = 0) -> str:
|
||||
def create_access_token(self, username, token_version: int = 0, secret_key: str = None, expires_in: int = 0) -> str:
|
||||
|
||||
if not expires_in:
|
||||
expires_in = Config.instance().settings.Controller.jwt_access_token_expire_minutes
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=expires_in)
|
||||
to_encode = {"sub": username, "exp": expire}
|
||||
to_encode = {"sub": username, "exp": expire, "ver": token_version}
|
||||
if secret_key is None:
|
||||
secret_key = Config.instance().settings.Controller.jwt_secret_key
|
||||
if secret_key is None:
|
||||
@ -61,7 +61,7 @@ class AuthService:
|
||||
encoded_jwt = jwt.encode({"alg": algorithm}, to_encode, key)
|
||||
return encoded_jwt
|
||||
|
||||
def get_username_from_token(self, token: str, secret_key: str = None) -> Optional[str]:
|
||||
def get_token_data(self, token: str, secret_key: str = None) -> TokenData:
|
||||
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@ -80,7 +80,11 @@ class AuthService:
|
||||
username: str = payload.claims.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
token_data = TokenData(username=username)
|
||||
token_version: int = payload.claims.get("ver", 0)
|
||||
token_data = TokenData(username=username, token_version=token_version)
|
||||
except (JoseError, ValidationError, ValueError):
|
||||
raise credentials_exception
|
||||
return token_data.username
|
||||
return token_data
|
||||
|
||||
def get_username_from_token(self, token: str, secret_key: str = None) -> Optional[str]:
|
||||
return self.get_token_data(token, secret_key).username
|
||||
|
||||
@ -247,6 +247,7 @@ class TestUserLogin:
|
||||
key = OctKey.import_key(jwt_secret)
|
||||
payload = jwt.decode(token, key, algorithms=["HS256"])
|
||||
assert "sub" in payload.claims
|
||||
assert "ver" in payload.claims
|
||||
username = payload.claims.get("sub")
|
||||
assert username == test_user.username
|
||||
|
||||
@ -373,6 +374,117 @@ class TestUserMe:
|
||||
assert response.status_code == status_code
|
||||
|
||||
|
||||
class TestLogout:
|
||||
|
||||
async def test_logout_returns_no_content(
|
||||
self,
|
||||
app: FastAPI,
|
||||
unauthorized_client: AsyncClient,
|
||||
test_user: User,
|
||||
) -> None:
|
||||
|
||||
# login to get a fresh token that includes token_version
|
||||
credentials = {"username": test_user.username, "password": "user1_password"}
|
||||
response = await unauthorized_client.post(app.url_path_for("authenticate"), json=credentials)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
token = response.json()["access_token"]
|
||||
|
||||
response = await unauthorized_client.post(
|
||||
app.url_path_for("logout"),
|
||||
headers={"Authorization": f"Bearer {token}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
async def test_token_is_rejected_after_logout(
|
||||
self,
|
||||
app: FastAPI,
|
||||
unauthorized_client: AsyncClient,
|
||||
test_user: User,
|
||||
db_session: AsyncSession,
|
||||
) -> None:
|
||||
|
||||
# login and get a token
|
||||
credentials = {"username": test_user.username, "password": "user1_password"}
|
||||
response = await unauthorized_client.post(app.url_path_for("authenticate"), json=credentials)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
token = response.json()["access_token"]
|
||||
|
||||
# logout — increments token_version
|
||||
await unauthorized_client.post(
|
||||
app.url_path_for("logout"),
|
||||
headers={"Authorization": f"Bearer {token}"}
|
||||
)
|
||||
|
||||
# old token must now be rejected
|
||||
response = await unauthorized_client.get(
|
||||
app.url_path_for("get_logged_in_user"),
|
||||
headers={"Authorization": f"Bearer {token}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
assert response.json()["message"] == f"Token has been revoked for '{test_user.username}'"
|
||||
|
||||
async def test_new_token_works_after_logout_and_relogin(
|
||||
self,
|
||||
app: FastAPI,
|
||||
unauthorized_client: AsyncClient,
|
||||
test_user: User,
|
||||
) -> None:
|
||||
|
||||
credentials = {"username": test_user.username, "password": "user1_password"}
|
||||
|
||||
# login, then logout
|
||||
response = await unauthorized_client.post(app.url_path_for("authenticate"), json=credentials)
|
||||
old_token = response.json()["access_token"]
|
||||
await unauthorized_client.post(
|
||||
app.url_path_for("logout"),
|
||||
headers={"Authorization": f"Bearer {old_token}"}
|
||||
)
|
||||
|
||||
# login again to get a fresh token
|
||||
response = await unauthorized_client.post(app.url_path_for("authenticate"), json=credentials)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
new_token = response.json()["access_token"]
|
||||
|
||||
# new token must work
|
||||
response = await unauthorized_client.get(
|
||||
app.url_path_for("get_logged_in_user"),
|
||||
headers={"Authorization": f"Bearer {new_token}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["username"] == test_user.username
|
||||
|
||||
async def test_stale_version_token_is_rejected(
|
||||
self,
|
||||
app: FastAPI,
|
||||
unauthorized_client: AsyncClient,
|
||||
test_user: User,
|
||||
db_session: AsyncSession,
|
||||
) -> None:
|
||||
|
||||
# craft a token with ver=0 while the user's token_version is already higher
|
||||
user_repo = UsersRepository(db_session)
|
||||
user_in_db = await user_repo.get_user_by_username(test_user.username)
|
||||
|
||||
# force token_version ahead so any ver=0 token is stale
|
||||
await user_repo.logout_user(user_in_db.user_id)
|
||||
|
||||
stale_token = auth_service.create_access_token(test_user.username, token_version=0)
|
||||
response = await unauthorized_client.get(
|
||||
app.url_path_for("get_logged_in_user"),
|
||||
headers={"Authorization": f"Bearer {stale_token}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
async def test_logout_without_token_returns_unauthorized(
|
||||
self,
|
||||
app: FastAPI,
|
||||
unauthorized_client: AsyncClient,
|
||||
) -> None:
|
||||
|
||||
response = await unauthorized_client.post(app.url_path_for("logout"))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
class TestSuperAdmin:
|
||||
|
||||
async def test_super_admin_exists(
|
||||
|
||||
@ -133,6 +133,13 @@ def test_list_ports_port_name_format(node):
|
||||
assert node.asdict()["ports"][1]["name"] == "eth0/0"
|
||||
|
||||
|
||||
def test_short_name_none():
|
||||
"""
|
||||
Test short_name does not raise exception when name is None
|
||||
"""
|
||||
assert EthernetPort(None, 0, 0, 0).short_name is None
|
||||
|
||||
|
||||
def test_list_ports_adapters(node):
|
||||
"""
|
||||
List port using adapters properties
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user