Admin-Plattform für die Tenants von Onyscribe: Editor mit Live-Vorschau (echter Core in Sandbox), Versionen, KI-Agent (Cortecs), OIDC-Abgleich über die Login-Developer-API, Deploy-Engine (Mittwald, gesperrt ohne DEPLOY_LIVE, Freigabe pro Tenant), Core-Vertrag (Schema/Manifest), Core-Releases mit Kompatibilitätsprüfung, Canary-Rollout und Rollback, Zustandsabgleich. Runbook: docs/RUNBOOK_PARALLELTEST.md Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
55 lines
2.3 KiB
Python
55 lines
2.3 KiB
Python
"""Seed-Export/-Import: Startdaten (importierte Tenants) als Datei im Repo.
|
||
|
||
So startet eine frische Instanz (z. B. Coolify) mit den aktuellen Tenants, ohne Zugriff auf das alte Git-Repo.
|
||
Secrets-WERTE sind nie enthalten – nur die Schlüsselnamen.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import base64
|
||
import gzip
|
||
import json
|
||
from pathlib import Path
|
||
|
||
from sqlalchemy import select
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.models import Blob, Secret, Setting, Tenant, Version
|
||
|
||
COLS = ["id", "name", "status", "source", "subdomain", "custom_domain", "domain_status", "cpu", "memory",
|
||
"current_version", "deployed_version", "deployed_image", "preview_sample", "oidc", "notes"]
|
||
|
||
|
||
def export(db: Session, path: Path) -> None:
|
||
tenants = db.scalars(select(Tenant).order_by(Tenant.id)).all()
|
||
shas = set()
|
||
out = {"tenants": [], "versions": [], "secrets": [], "settings": {}}
|
||
for t in tenants:
|
||
out["tenants"].append({c: getattr(t, c) for c in COLS})
|
||
for v in db.scalars(select(Version).where(Version.tenant_id == t.id).order_by(Version.number)):
|
||
out["versions"].append({"tenant_id": t.id, "number": v.number, "message": v.message, "author": v.author,
|
||
"config": v.config, "files": v.files})
|
||
shas |= set(v.files.values())
|
||
for s in db.scalars(select(Secret)):
|
||
out["secrets"].append({"tenant_id": s.tenant_id, "key": s.key})
|
||
out["settings"] = {s.key: s.value for s in db.scalars(select(Setting))}
|
||
out["blobs"] = {sha: {"b64": base64.b64encode(db.get(Blob, sha).content).decode(), "text": db.get(Blob, sha).is_text} for sha in shas}
|
||
path.parent.mkdir(parents=True, exist_ok=True)
|
||
path.write_bytes(gzip.compress(json.dumps(out).encode(), 9))
|
||
|
||
|
||
def load(db: Session, path: Path) -> int:
|
||
data = json.loads(gzip.decompress(path.read_bytes()))
|
||
for sha, b in data["blobs"].items():
|
||
raw = base64.b64decode(b["b64"])
|
||
db.add(Blob(sha=sha, content=raw, size=len(raw), is_text=b["text"]))
|
||
for t in data["tenants"]:
|
||
db.add(Tenant(**t))
|
||
db.flush()
|
||
for v in data["versions"]:
|
||
db.add(Version(**v))
|
||
for s in data["secrets"]:
|
||
db.add(Secret(tenant_id=s["tenant_id"], key=s["key"], value_enc=None, source="sops-import"))
|
||
for k, v in data["settings"].items():
|
||
db.add(Setting(key=k, value=v))
|
||
return len(data["tenants"])
|