mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-09-15 06:20:42 +03:00
Only relying on the image name lets a moved tag (e.g. a newer :latest) silently serve stale content from a compute that already has an image under the same name. When creating a Docker node, the controller now pins the image id (Id from the Docker daemon on the controller host) into the create payload. A compute holding a different image under the same tag reports the image as missing, which routes it through the image sync added by the previous commit and re-aligns the tag. No new template fields or database changes: the controller host daemon remains the source of truth and the pin is resolved per creation. When the image is not available on the controller host the pin is omitted and behavior is unchanged (the compute pulls from the repository).
103 lines
4.9 KiB
Python
103 lines
4.9 KiB
Python
#
|
|
# Copyright (C) 2020 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
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# 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 pydantic import BaseModel, Field, field_validator
|
|
from typing import Optional, List
|
|
from uuid import UUID
|
|
|
|
from ..common import NodeStatus, CustomAdapter, ConsoleType, AuxType, ExtraConfig
|
|
|
|
|
|
class DockerBase(BaseModel):
|
|
"""
|
|
Common Docker node properties.
|
|
"""
|
|
|
|
@field_validator("start_command", "environment", "extra_hosts", "startup_config_content", mode="before")
|
|
@classmethod
|
|
def _empty_string_to_none(cls, value):
|
|
# Web clients serialize empty form fields as "" while unset values are
|
|
# stored as None on the node: normalize before the update diff runs,
|
|
# otherwise every full PUT would see a phantom change and recreate
|
|
# the container for nothing.
|
|
return value or None
|
|
|
|
@field_validator("console_http_path", mode="before")
|
|
@classmethod
|
|
def _empty_string_to_root_path(cls, value):
|
|
# the canonical "no path" value is "/" (the creation default)
|
|
return value or "/"
|
|
|
|
name: str
|
|
image: str = Field(..., description="Docker image name")
|
|
node_id: Optional[UUID] = None
|
|
console: Optional[int] = Field(None, gt=0, le=65535, description="Console TCP port")
|
|
console_type: Optional[ConsoleType] = Field(None, description="Console type")
|
|
console_resolution: Optional[str] = Field(None, pattern="^[0-9]+x[0-9]+$", description="Console resolution for VNC")
|
|
console_http_port: Optional[int] = Field(None, description="Internal port in the container for the HTTP server")
|
|
console_http_path: Optional[str] = Field(None, description="Path of the web interface")
|
|
aux: Optional[int] = Field(None, gt=0, le=65535, description="Auxiliary TCP port")
|
|
aux_type: Optional[AuxType] = Field(None, description="Auxiliary console type")
|
|
usage: Optional[str] = Field(None, description="How to use the Docker container")
|
|
start_command: Optional[str] = Field(None, description="Docker CMD entry")
|
|
adapters: Optional[int] = Field(None, ge=0, le=99, description="Number of adapters")
|
|
mac_address: Optional[str] = Field(None, description="Base MAC address", pattern="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$")
|
|
environment: Optional[str] = Field(None, description="Docker environment variables")
|
|
extra_hosts: Optional[str] = Field(None, description="Docker extra hosts (added to /etc/hosts)")
|
|
extra_volumes: Optional[List[str]] = Field(None, description="Additional directories to make persistent")
|
|
extra_configs: Optional[List[ExtraConfig]] = Field(None, description="Configuration files injected into the container (bind-mounted read-only)")
|
|
startup_config_content: Optional[str] = Field(
|
|
None, description="Startup-config content (IOL runner images: materialized into the node's NVRAM at start)"
|
|
)
|
|
memory: Optional[int] = Field(None, ge=0, description="Maximum amount of memory the container can use in MB")
|
|
cpus: Optional[float] = Field(None, ge=0, description="Maximum amount of CPU resources the container can use")
|
|
custom_adapters: Optional[List[CustomAdapter]] = Field(None, description="Custom adapters")
|
|
|
|
|
|
class DockerCreate(DockerBase):
|
|
"""
|
|
Properties to create a Docker node.
|
|
"""
|
|
|
|
application_id: Optional[int] = Field(
|
|
None, ge=1, le=1022, description="IOL application ID for iol-runner images (allocated by the controller)"
|
|
)
|
|
image_digest: Optional[str] = Field(
|
|
None, pattern=r"^sha256:[a-f0-9]{64}$",
|
|
description="Image id the controller expects for 'image' (sha256:<hex>, resolved from the Docker "
|
|
"daemon on the controller host). When set and the compute holds a different image "
|
|
"under the same tag, the image is reported as missing so the controller re-syncs it"
|
|
)
|
|
|
|
|
|
class DockerUpdate(DockerBase):
|
|
"""
|
|
Properties to update a Docker node.
|
|
"""
|
|
|
|
name: Optional[str] = None
|
|
image: Optional[str] = Field(None, description="Docker image name")
|
|
|
|
|
|
class Docker(DockerBase):
|
|
|
|
container_id: str = Field(
|
|
..., min_length=12, max_length=64, pattern="^[a-f0-9]+$", description="Docker container ID (read only)"
|
|
)
|
|
project_id: UUID = Field(..., description="Project ID")
|
|
node_directory: str = Field(..., description="Path to the node working directory (read only)")
|
|
status: NodeStatus = Field(..., description="Container status (read only)")
|