templates: persist appliance metadata on install

Appliance fields that describe the appliance (vendor information, default
credentials, installation instructions...) were dropped when installing a
template. Keep them in a new appliance_metadata JSON column on the
templates table, filled by the appliance-to-template conversion for both
registry v1-6 and v8 (version level values override the appliance level
ones). The nested schema allows extra fields so future registry fields
persist without a migration.
This commit is contained in:
YueGuobin committed 2026-08-21 21:41:42 +08:00
1 parent 1c68a52856
commit 300c53e6fb
7 files changed
+216

No files matched your search

@@ -23,6 +23,27 @@ from .controller_error import ControllerError
log = logging.getLogger(__name__)
# appliance fields that describe the appliance (vendor information, default
# credentials...) and are kept on the template as metadata instead of being
# dropped at installation time
_APPLIANCE_METADATA_FIELDS = (
"description",
"vendor_name",
"vendor_url",
"vendor_logo_url",
"documentation_url",
"product_name",
"product_url",
"status",
"availability",
"maintainer",
"maintainer_email",
"installation_instructions",
"default_username",
"default_password",
)
class ApplianceToTemplate:
"""
Appliance installation.
@@ -61,6 +82,10 @@ class ApplianceToTemplate:
if appliance_config.get("netmiko_device_type"):
new_template["netmiko_device_type"] = appliance_config["netmiko_device_type"]
appliance_metadata = self._build_appliance_metadata(appliance_config, version)
if appliance_metadata:
new_template["appliance_metadata"] = appliance_metadata
if new_template.get("symbol") is None:
if appliance_config["category"] == "guest":
if "docker" in appliance_config:
@@ -192,6 +217,10 @@ class ApplianceToTemplate:
if appliance_config.get("netmiko_device_type"):
new_template["netmiko_device_type"] = appliance_config["netmiko_device_type"]
appliance_metadata = self._build_appliance_metadata(appliance_config, version)
if appliance_metadata:
new_template["appliance_metadata"] = appliance_metadata
if not new_template.get("symbol"):
# apply a default symbol based on the effective category and template type
if category_before_remap == "guest":
@@ -221,6 +250,26 @@ class ApplianceToTemplate:
return new_template
def _build_appliance_metadata(self, appliance_config, version):
"""
Builds the appliance metadata kept on the template: the fields that
describe the appliance, with version level values (e.g. credentials
specific to the installed version) overriding the appliance level ones.
"""
version = version or {}
metadata = {}
for field in _APPLIANCE_METADATA_FIELDS:
value = version.get(field)
if value is None:
value = appliance_config.get(field)
if value is not None:
metadata[field] = value
appliance_id = appliance_config.get("appliance_id")
if appliance_id:
metadata["appliance_id"] = str(appliance_id)
return metadata or None
def get_template_type(self, appliance_config, version):
"""
Returns the template type of the settings set used to install the given
+1
View File
@@ -36,6 +36,7 @@ class Template(BaseTable):
builtin = Column(Boolean, default=False)
usage = Column(String)
netmiko_device_type = Column(String)
appliance_metadata = Column(JSON)
template_type = Column(String)
tags = Column(JSON)
compute_id = Column(String)
@@ -0,0 +1,26 @@
"""add appliance_metadata to templates table
Revision ID: c7e4a9f1d2b6
Revises: b3c7e2a91d4f
Create Date: 2026-08-16 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'c7e4a9f1d2b6'
down_revision = 'b3c7e2a91d4f'
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column('templates', sa.Column('appliance_metadata', sa.JSON()))
def downgrade() -> None:
op.drop_column('templates', 'appliance_metadata')
@@ -34,6 +34,34 @@ class Category(str, Enum):
firewall = "firewall"
class ApplianceMetadata(BaseModel):
"""
Metadata kept on a template installed from an appliance: vendor
information, default credentials and other fields that describe
the appliance but are not node properties.
"""
model_config = ConfigDict(extra="allow")
appliance_id: Optional[str] = Field(
None, description="ID of the appliance the template was installed from"
)
description: Optional[str] = None
vendor_name: Optional[str] = None
vendor_url: Optional[str] = None
vendor_logo_url: Optional[str] = None
documentation_url: Optional[str] = None
product_name: Optional[str] = None
product_url: Optional[str] = None
status: Optional[str] = None
availability: Optional[str] = None
maintainer: Optional[str] = None
maintainer_email: Optional[str] = None
installation_instructions: Optional[str] = None
default_username: Optional[str] = None
default_password: Optional[str] = None
class TemplateBase(BaseModel):
"""
Common template properties.
@@ -57,6 +85,10 @@ class TemplateBase(BaseModel):
default_factory=list,
description="User-defined metadata tags (e.g. 'vendor:cisco' or 'model:7200')"
)
appliance_metadata: Optional[ApplianceMetadata] = Field(
None,
description="Metadata inherited from the appliance the template was installed from"
)
class TemplateCreate(TemplateBase):