fix(storage): preserve retired Matrix containers during migration
This commit is contained in:
@@ -121,6 +121,35 @@ def validate_config(config, root):
|
||||
return env
|
||||
|
||||
|
||||
def index_containers(config, containers):
|
||||
"""Keep previously retired Matrix containers without reactivating their services."""
|
||||
indexed, retained = {}, {}
|
||||
for container in containers:
|
||||
labels = container["Config"].get("Labels", {})
|
||||
name = labels.get("com.docker.compose.service")
|
||||
require(name and name not in indexed and labels.get("com.docker.compose.oneoff", "False").lower() != "true",
|
||||
"Unbekannte oder doppelte Projektcontainer vorhanden.")
|
||||
if name not in config["services"]:
|
||||
require(name in u.LEGACY_MATRIX_SERVICES, "Unbekannter Projektcontainer: " + name)
|
||||
state, host = container["State"], container["HostConfig"]
|
||||
require(not any(state.get(k) for k in ("Running", "Restarting", "Paused")) and
|
||||
host.get("RestartPolicy", {}).get("Name") == "no" and not host.get("AutoRemove"),
|
||||
"Alter Matrix-Container ist nicht dauerhaft stillgelegt: " + name)
|
||||
volumes = []
|
||||
for mount in container.get("Mounts", []):
|
||||
if mount["Type"] == "bind":
|
||||
volumes.append({"type": "bind", "source": mount["Source"], "target": mount["Destination"]})
|
||||
else:
|
||||
# These image-declared volumes belong to already retired services.
|
||||
# Preserve their containers/volumes; never remove or restart them.
|
||||
require(mount["Type"] == "volume" and (name, mount["Destination"]) in {
|
||||
("matrix-redis", "/data"), ("matrix-turn", "/var/lib/coturn")},
|
||||
"Abweichender Datenmount eines alten Matrix-Containers: " + name)
|
||||
retained[name] = {"volumes": volumes}
|
||||
indexed[name] = container
|
||||
return indexed, retained
|
||||
|
||||
|
||||
class Migration:
|
||||
def __init__(self, root, tools_image=None):
|
||||
os.umask(0o077)
|
||||
@@ -241,21 +270,17 @@ class Migration:
|
||||
config = resolved(json.loads(self.docker("compose", "--project-directory", str(self.root), "--env-file", str(self.root / ".env"),
|
||||
"-f", str(self.root / "docker-compose.yml"), "config", "--format", "json")))
|
||||
env = validate_config(config, self.root)
|
||||
# Reuse the updater's strict legacy Matrix path validation; the services remain intact.
|
||||
self.engine.current = config
|
||||
self.engine.retired_services = sorted(set(config["services"]) & u.LEGACY_MATRIX_SERVICES)
|
||||
self.engine.matrix_backup_paths = []
|
||||
self.engine.validate_legacy_matrix()
|
||||
project = config["name"]
|
||||
ids = self.docker("ps", "-aq", "--filter", "label=com.docker.compose.project=" + project).split()
|
||||
containers = [self.inspect(i) for i in ids]
|
||||
by_service = {}
|
||||
for c in containers:
|
||||
labels = c["Config"].get("Labels", {})
|
||||
name = labels.get("com.docker.compose.service")
|
||||
require(name in config["services"] and name not in by_service and labels.get("com.docker.compose.oneoff", "False").lower() != "true",
|
||||
"Unbekannte oder doppelte Projektcontainer vorhanden.")
|
||||
by_service[name] = c
|
||||
by_service, retained = index_containers(config, containers)
|
||||
# Validate retained bind mounts with the same strict path rules as configured
|
||||
# Matrix services. Do not put retired services back into either Compose file.
|
||||
self.engine.current = copy.deepcopy(config)
|
||||
self.engine.current["services"].update(retained)
|
||||
self.engine.retired_services = sorted(set(self.engine.current["services"]) & u.LEGACY_MATRIX_SERVICES)
|
||||
self.engine.matrix_backup_paths = []
|
||||
self.engine.validate_legacy_matrix()
|
||||
require(all(by_service.get(n, {}).get("State", {}).get("Running") for n in ("db", "minio", "backend", "frontend", "traefik")),
|
||||
"Die bestehende Instanz muss vor der Planung laufen.")
|
||||
for name in ("minio", "backend", "db"):
|
||||
@@ -286,7 +311,8 @@ class Migration:
|
||||
"toolsRetainTag": tools_tag,
|
||||
"version": env.get("FEDEO_VERSION"), "fingerprint": self.fingerprint(), "writesReleased": False,
|
||||
"backendWorkingDir": by_service["backend"]["Config"].get("WorkingDir") or "/",
|
||||
"matrixPaths": self.engine.matrix_backup_paths, "containers": {name: {"id": c["Id"], "image": c["Image"],
|
||||
"matrixPaths": self.engine.matrix_backup_paths, "retainedServices": sorted(retained),
|
||||
"containers": {name: {"id": c["Id"], "image": c["Image"],
|
||||
"running": c["State"]["Running"], "restart": c["HostConfig"]["RestartPolicy"],
|
||||
"networks": c["NetworkSettings"]["Networks"]} for name, c in by_service.items()}}
|
||||
# Planning errors must not leave a usable partial state file.
|
||||
@@ -363,6 +389,8 @@ class Migration:
|
||||
for name, c in self.state["containers"].items():
|
||||
actual = self.inspect(c["id"])
|
||||
require(actual["Image"] == c["image"], "Containeridentität verändert: " + name)
|
||||
if name in self.state.get("retainedServices", []):
|
||||
index_containers({"services": {}}, [actual])
|
||||
|
||||
def freeze(self):
|
||||
self.save("freezing")
|
||||
|
||||
Reference in New Issue
Block a user