docker: inject config files into containers via extra_configs

Add an `extra_configs` field (list of {target, content}) to the docker
node/template/appliance schemas. For each entry GNS3 writes `content` to a
file in the node working directory and bind-mounts it read-only at `target`
inside the container.

This lets a NOS appliance seed its startup config without rebuilding the
image: XRd points XR_FIRST_BOOT_CONFIG at an injected /firstboot.cfg, FRR at
/etc/frr/frr.conf, etc. The bind is a single-file mount applied at create
time, so it works for both the generic init.sh path and vendor nodes that
skip init.sh (console_type=docker_exec). Entries are only injected when
present, so ordinary nodes are unaffected.

The content can't go through `environment` (it is line-delimited, one var per
line), hence a dedicated field -- the same plumbing shape as extra_volumes.
This commit is contained in:
YueGuobin committed 2026-08-13 23:33:34 +08:00
1 parent 2bee34031c
commit 08e37a4509
7 files changed
+98 -3

No files matched your search

+33
View File
@@ -89,6 +89,7 @@ class DockerVM(BaseNode):
console_http_path="/",
extra_hosts=None,
extra_volumes=[],
extra_configs=None,
memory=0,
cpus=0,
):
@@ -118,6 +119,7 @@ class DockerVM(BaseNode):
self._console_websocket = None
self._extra_hosts = extra_hosts
self._extra_volumes = extra_volumes or []
self._extra_configs = extra_configs or []
self._memory = memory
self._cpus = cpus
self._permissions_fixed = True
@@ -164,6 +166,7 @@ class DockerVM(BaseNode):
"node_directory": self.working_path,
"extra_hosts": self.extra_hosts,
"extra_volumes": self.extra_volumes,
"extra_configs": self.extra_configs,
"memory": self.memory,
"cpus": self.cpus,
}
@@ -295,6 +298,14 @@ class DockerVM(BaseNode):
def extra_volumes(self, extra_volumes):
self._extra_volumes = extra_volumes
@property
def extra_configs(self):
return self._extra_configs
@extra_configs.setter
def extra_configs(self, extra_configs):
self._extra_configs = extra_configs or []
@property
def memory(self):
return self._memory
@@ -391,6 +402,28 @@ class DockerVM(BaseNode):
"Target": "/gns3volumes{}".format(volume)
})
# Inject extra config files: write each to the node working directory and
# bind-mount it read-only at its target path. Single-file binds are applied
# at create time, so this works for the generic init.sh path AND for vendor
# nodes that skip init.sh (the NOS reads its startup config from the mount).
for cfg in self._extra_configs:
target = cfg["target"] if isinstance(cfg, dict) else cfg.target
content = cfg["content"] if isinstance(cfg, dict) else cfg.content
if not target.startswith("/") or ".." in target.split("/"):
raise DockerError(
f"Extra config target '{target}' must be an absolute path and not contain '..'."
)
host_path = os.path.join(self.working_dir, "configs", target.lstrip("/"))
os.makedirs(os.path.dirname(host_path), exist_ok=True)
with open(host_path, "w") as f:
f.write(content)
binds.append({
"Type": "bind",
"Source": host_path,
"Target": target,
"ReadOnly": True,
})
return binds
def _create_network_config(self):