katalon-cli 0.3.5__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/PKG-INFO +24 -2
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/README.md +23 -1
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/pyproject.toml +1 -1
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/pyproject.toml.orig +1 -1
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/compose_gen.py +5 -1
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/docker.py +18 -0
- katalon_cli-0.4.0/src/katalon_cli/core/images.py +72 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/release.py +26 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/state.py +6 -1
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/main.py +50 -6
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/templates/compose.yaml.j2 +7 -7
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/__init__.py +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/__init__.py +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/backup.py +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/checks.py +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/paths.py +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/permissions.py +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/reindex.py +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/core/selfupdate.py +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/py.typed +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/templates/Caddyfile.j2 +0 -0
- {katalon_cli-0.3.5 → katalon_cli-0.4.0}/src/katalon_cli/templates/nginx.conf.j2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: katalon-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Installer & Updater CLI für Katalon Production-Instanzen
|
|
5
5
|
Author: Karl Krägelin
|
|
6
6
|
Author-email: Karl Krägelin <karlkraegelin@gmail.com>
|
|
@@ -66,6 +66,27 @@ katalon start
|
|
|
66
66
|
katalon status
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
## Images der Fremdservices
|
|
70
|
+
|
|
71
|
+
Welche Images Postgres, Redis, Elasticsearch, Cantaloupe, nginx und Caddy nutzen, legt das
|
|
72
|
+
Katalon-Release fest (`images` in `katalon-release.json`), nicht die CLI. `install` und `update`
|
|
73
|
+
schreiben sie in `compose.yaml`, `update` pullt sie mit. Releases ohne `images` (bis Katalon
|
|
74
|
+
1.46.0) bekommen die Images, die bis katalon-cli 0.3.5 fest im Template standen.
|
|
75
|
+
|
|
76
|
+
Vor einem Update vergleicht `katalon update` die wirksamen Images (inklusive
|
|
77
|
+
`compose.override.yaml`) vor und nach dem Update, zeigt jeden Wechsel an und bricht ab, bevor
|
|
78
|
+
etwas geändert wird, wenn
|
|
79
|
+
|
|
80
|
+
- Postgres oder Elasticsearch die Major-Version wechseln würden (Postgres braucht dafür
|
|
81
|
+
Dump/Restore, Elasticsearch ein neues Datenverzeichnis und einen Reindex), oder
|
|
82
|
+
- ein Postgres- oder Elasticsearch-Image die Anforderung des Releases (`requires`, z. B.
|
|
83
|
+
`>=8.13,<9`) nicht erfüllt, etwa ein eigenes Image aus `compose.override.yaml`. `requires`
|
|
84
|
+
gilt erst ab Releases mit `images`; ältere Releases nannten dort Werte, die nie geprüft wurden.
|
|
85
|
+
|
|
86
|
+
`installation.json` merkt sich die Images je Version. `rollback` nimmt die der vorigen Version,
|
|
87
|
+
behält aber ein neueres Elasticsearch derselben Major-Version, weil Elasticsearch ein
|
|
88
|
+
Datenverzeichnis nicht mit einer älteren Version öffnet.
|
|
89
|
+
|
|
69
90
|
## API-Adresse
|
|
70
91
|
|
|
71
92
|
Die REST-API liegt direkt unter der Base-URL, ohne `/api`-Präfix: `<KATALON_BASE_URL>/v1/...`
|
|
@@ -159,13 +180,14 @@ uv run pytest
|
|
|
159
180
|
nach (`--yes` bricht dann ab), tragen neue optionale Variablen auskommentiert in `.env` ein und
|
|
160
181
|
warnen vor veralteten Variablen, die noch in der `.env` stehen.
|
|
161
182
|
- `core/compose_gen.py` + `templates/compose.yaml.j2` + `templates/nginx.conf.j2` — rendert `compose.yaml`
|
|
162
|
-
+ `nginx.conf` und bei `tls_mode=caddy` das `Caddyfile` aus Version + TLS-Modus;
|
|
183
|
+
+ `nginx.conf` und bei `tls_mode=caddy` das `Caddyfile` aus Version, Images + TLS-Modus;
|
|
163
184
|
erzeugt bei `tls_mode=standalone` ein selbstsigniertes
|
|
164
185
|
Zertifikat unter `<dir>/certs/` (eigenes Zertifikat dort ablegen, um es zu ersetzen).
|
|
165
186
|
`install` fragt zusätzlich `MEDIA_ROOT` ab und legt `<dir>/compose.override.yaml.example` an
|
|
166
187
|
(instanzspezifische Overrides — umbenennen zu `compose.override.yaml`, wird automatisch eingebunden
|
|
167
188
|
und bei Updates nicht angefasst). Optionale Env-Vars (Admin-Login, SMTP, Geonames, …) landet
|
|
168
189
|
auskommentiert in `.env`.
|
|
190
|
+
- `core/images.py` — prüft die Images der Fremdservices vor einem Update (siehe „Images der Fremdservices“).
|
|
169
191
|
- `core/backup.py` — `pg_dump` vor jedem Update; Rollback restauriert Dump statt `alembic downgrade`.
|
|
170
192
|
- `core/docker.py` — dünner `docker compose`-Subprocess-Wrapper.
|
|
171
193
|
- `main.py` — Typer-Commands, interaktive Teile über `rich.prompt`/`rich.progress`.
|
|
@@ -52,6 +52,27 @@ katalon start
|
|
|
52
52
|
katalon status
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
## Images der Fremdservices
|
|
56
|
+
|
|
57
|
+
Welche Images Postgres, Redis, Elasticsearch, Cantaloupe, nginx und Caddy nutzen, legt das
|
|
58
|
+
Katalon-Release fest (`images` in `katalon-release.json`), nicht die CLI. `install` und `update`
|
|
59
|
+
schreiben sie in `compose.yaml`, `update` pullt sie mit. Releases ohne `images` (bis Katalon
|
|
60
|
+
1.46.0) bekommen die Images, die bis katalon-cli 0.3.5 fest im Template standen.
|
|
61
|
+
|
|
62
|
+
Vor einem Update vergleicht `katalon update` die wirksamen Images (inklusive
|
|
63
|
+
`compose.override.yaml`) vor und nach dem Update, zeigt jeden Wechsel an und bricht ab, bevor
|
|
64
|
+
etwas geändert wird, wenn
|
|
65
|
+
|
|
66
|
+
- Postgres oder Elasticsearch die Major-Version wechseln würden (Postgres braucht dafür
|
|
67
|
+
Dump/Restore, Elasticsearch ein neues Datenverzeichnis und einen Reindex), oder
|
|
68
|
+
- ein Postgres- oder Elasticsearch-Image die Anforderung des Releases (`requires`, z. B.
|
|
69
|
+
`>=8.13,<9`) nicht erfüllt, etwa ein eigenes Image aus `compose.override.yaml`. `requires`
|
|
70
|
+
gilt erst ab Releases mit `images`; ältere Releases nannten dort Werte, die nie geprüft wurden.
|
|
71
|
+
|
|
72
|
+
`installation.json` merkt sich die Images je Version. `rollback` nimmt die der vorigen Version,
|
|
73
|
+
behält aber ein neueres Elasticsearch derselben Major-Version, weil Elasticsearch ein
|
|
74
|
+
Datenverzeichnis nicht mit einer älteren Version öffnet.
|
|
75
|
+
|
|
55
76
|
## API-Adresse
|
|
56
77
|
|
|
57
78
|
Die REST-API liegt direkt unter der Base-URL, ohne `/api`-Präfix: `<KATALON_BASE_URL>/v1/...`
|
|
@@ -145,13 +166,14 @@ uv run pytest
|
|
|
145
166
|
nach (`--yes` bricht dann ab), tragen neue optionale Variablen auskommentiert in `.env` ein und
|
|
146
167
|
warnen vor veralteten Variablen, die noch in der `.env` stehen.
|
|
147
168
|
- `core/compose_gen.py` + `templates/compose.yaml.j2` + `templates/nginx.conf.j2` — rendert `compose.yaml`
|
|
148
|
-
+ `nginx.conf` und bei `tls_mode=caddy` das `Caddyfile` aus Version + TLS-Modus;
|
|
169
|
+
+ `nginx.conf` und bei `tls_mode=caddy` das `Caddyfile` aus Version, Images + TLS-Modus;
|
|
149
170
|
erzeugt bei `tls_mode=standalone` ein selbstsigniertes
|
|
150
171
|
Zertifikat unter `<dir>/certs/` (eigenes Zertifikat dort ablegen, um es zu ersetzen).
|
|
151
172
|
`install` fragt zusätzlich `MEDIA_ROOT` ab und legt `<dir>/compose.override.yaml.example` an
|
|
152
173
|
(instanzspezifische Overrides — umbenennen zu `compose.override.yaml`, wird automatisch eingebunden
|
|
153
174
|
und bei Updates nicht angefasst). Optionale Env-Vars (Admin-Login, SMTP, Geonames, …) landet
|
|
154
175
|
auskommentiert in `.env`.
|
|
176
|
+
- `core/images.py` — prüft die Images der Fremdservices vor einem Update (siehe „Images der Fremdservices“).
|
|
155
177
|
- `core/backup.py` — `pg_dump` vor jedem Update; Rollback restauriert Dump statt `alembic downgrade`.
|
|
156
178
|
- `core/docker.py` — dünner `docker compose`-Subprocess-Wrapper.
|
|
157
179
|
- `main.py` — Typer-Commands, interaktive Teile über `rich.prompt`/`rich.progress`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Rendert compose.yaml + nginx.conf aus Template + Release
|
|
1
|
+
"""Rendert compose.yaml + nginx.conf aus Template + Release (Version, Images) + TLS-Modus."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -10,6 +10,8 @@ from urllib.parse import urlparse
|
|
|
10
10
|
|
|
11
11
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
12
12
|
|
|
13
|
+
from .release import ReleaseImages
|
|
14
|
+
|
|
13
15
|
_env = Environment(
|
|
14
16
|
loader=PackageLoader("katalon_cli", "templates"),
|
|
15
17
|
autoescape=select_autoescape(disabled_extensions=(".yaml", ".j2", ".conf")),
|
|
@@ -23,6 +25,7 @@ def render_compose(
|
|
|
23
25
|
version: str,
|
|
24
26
|
base_url: str,
|
|
25
27
|
tls_mode: str,
|
|
28
|
+
images: ReleaseImages | None = None,
|
|
26
29
|
registry: str = "ghcr.io/katalon-collections/katalon",
|
|
27
30
|
) -> str:
|
|
28
31
|
if tls_mode == "caddy":
|
|
@@ -34,6 +37,7 @@ def render_compose(
|
|
|
34
37
|
base_url=base_url,
|
|
35
38
|
tls_mode=tls_mode,
|
|
36
39
|
registry=registry,
|
|
40
|
+
images=images or ReleaseImages(),
|
|
37
41
|
port=parsed_port,
|
|
38
42
|
)
|
|
39
43
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import shutil
|
|
6
7
|
import subprocess
|
|
7
8
|
import time
|
|
@@ -45,6 +46,23 @@ def compose(
|
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
def config_images(instance_dir: Path, compose_file: Path | None = None) -> dict[str, str]:
|
|
50
|
+
"""Images je Service, wie Compose sie startet (inkl. compose.override.yaml)."""
|
|
51
|
+
files = ["-f", str(compose_file or instance_dir / "compose.yaml")]
|
|
52
|
+
override = instance_dir / "compose.override.yaml"
|
|
53
|
+
if override.exists():
|
|
54
|
+
files += ["-f", str(override)]
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
[*compose_command(), "--project-directory", str(instance_dir), *files, "config", "--format", "json"],
|
|
57
|
+
cwd=instance_dir,
|
|
58
|
+
check=True,
|
|
59
|
+
text=True,
|
|
60
|
+
capture_output=True,
|
|
61
|
+
)
|
|
62
|
+
services = json.loads(result.stdout).get("services", {})
|
|
63
|
+
return {name: svc["image"] for name, svc in services.items() if svc.get("image")}
|
|
64
|
+
|
|
65
|
+
|
|
48
66
|
def reload_nginx(instance_dir: Path) -> None:
|
|
49
67
|
"""`up -d` erstellt nginx nicht neu, wenn nur die gemountete nginx.conf sich ändert."""
|
|
50
68
|
compose(instance_dir, "exec", "-T", "nginx", "nginx", "-t")
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Prüft die Fremdservice-Images eines Updates: Major-Wechsel und `requires` des Releases."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import operator
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from .release import ReleaseImages, ReleaseRequirements
|
|
9
|
+
|
|
10
|
+
# Compose-Service → Rolle in ReleaseImages/ReleaseRequirements, für die ein Major-Wechsel
|
|
11
|
+
# nicht per Image-Tausch geht: Postgres braucht Dump/Restore, Elasticsearch öffnet ein
|
|
12
|
+
# Datenverzeichnis einer anderen Major-Version nicht (Rückweg gar nicht).
|
|
13
|
+
DATA_SERVICES = {"db": "postgres", "elasticsearch": "elasticsearch"}
|
|
14
|
+
|
|
15
|
+
_OPS = {">=": operator.ge, "<=": operator.le, "==": operator.eq, ">": operator.gt, "<": operator.lt}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def image_version(image: str) -> tuple[int, ...] | None:
|
|
19
|
+
"""Version aus dem Tag (`postgis/postgis:16-3.4` → (16,), `…:8.13.4` → (8, 13, 4))."""
|
|
20
|
+
name = image.rsplit("/", 1)[-1]
|
|
21
|
+
if ":" not in name:
|
|
22
|
+
return None
|
|
23
|
+
match = re.match(r"\d+(?:\.\d+)*", name.split(":", 1)[1])
|
|
24
|
+
return tuple(int(part) for part in match.group(0).split(".")) if match else None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def satisfies(version: tuple[int, ...], spec: str) -> bool:
|
|
28
|
+
"""Minimaler Specifier-Vergleich für `requires` (z. B. ">=8.13,<9")."""
|
|
29
|
+
for clause in spec.split(","):
|
|
30
|
+
match = re.fullmatch(r"\s*(>=|<=|==|>|<)\s*(\d+(?:\.\d+)*)\s*", clause)
|
|
31
|
+
if not match:
|
|
32
|
+
raise ValueError(f"Ungültige Versionsanforderung: {spec}")
|
|
33
|
+
bound = tuple(int(part) for part in match.group(2).split("."))
|
|
34
|
+
if not _OPS[match.group(1)](version[: len(bound)], bound):
|
|
35
|
+
return False
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def update_problems(
|
|
40
|
+
current: dict[str, str], target: dict[str, str], requires: ReleaseRequirements
|
|
41
|
+
) -> tuple[list[str], list[str]]:
|
|
42
|
+
"""(Fehler, Hinweise) für den Wechsel der wirksamen Images `current` → `target`."""
|
|
43
|
+
errors: list[str] = []
|
|
44
|
+
notes: list[str] = []
|
|
45
|
+
for service, role in DATA_SERVICES.items():
|
|
46
|
+
old, new = current.get(service), target.get(service)
|
|
47
|
+
if not new:
|
|
48
|
+
continue
|
|
49
|
+
new_version = image_version(new)
|
|
50
|
+
spec = getattr(requires, role)
|
|
51
|
+
if spec and new_version and not satisfies(new_version, spec):
|
|
52
|
+
errors.append(f"{service}: {new} erfüllt die Anforderung des Releases nicht ({role} {spec}).")
|
|
53
|
+
elif spec and not new_version:
|
|
54
|
+
notes.append(f"{service}: Version von {new} nicht erkennbar, Anforderung {spec} ungeprüft.")
|
|
55
|
+
old_version = image_version(old) if old else None
|
|
56
|
+
if old_version and new_version and old_version[0] != new_version[0]:
|
|
57
|
+
errors.append(
|
|
58
|
+
f"{service}: Major-Wechsel {old} → {new} geht nicht per Image-Tausch "
|
|
59
|
+
"(Postgres: Dump/Restore, Elasticsearch: neues Datenverzeichnis und Reindex)."
|
|
60
|
+
)
|
|
61
|
+
return errors, notes
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def rollback_images(current: ReleaseImages, previous: ReleaseImages) -> ReleaseImages:
|
|
65
|
+
"""Images für den Rollback: die des vorigen Releases, außer einem neueren Elasticsearch.
|
|
66
|
+
|
|
67
|
+
Elasticsearch öffnet ein Datenverzeichnis nicht mehr mit einer älteren Version; die
|
|
68
|
+
Katalon-Version davor läuft mit dem neueren Minor-Release derselben Major-Version."""
|
|
69
|
+
cur, prev = image_version(current.elasticsearch), image_version(previous.elasticsearch)
|
|
70
|
+
if cur and prev and cur[0] == prev[0] and cur > prev:
|
|
71
|
+
return previous.model_copy(update={"elasticsearch": current.elasticsearch})
|
|
72
|
+
return previous
|
|
@@ -13,6 +13,20 @@ class ReleaseRequirements(BaseModel):
|
|
|
13
13
|
elasticsearch: str | None = None
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
class ReleaseImages(BaseModel):
|
|
17
|
+
"""Images der Fremdservices, die das Katalon-Release festlegt (`images` in katalon-release.json).
|
|
18
|
+
|
|
19
|
+
Die Defaults sind der Stand der Releases vor diesem Feld (bis Katalon 1.46.0) und
|
|
20
|
+
bleiben deshalb unverändert; neue Versionen kommen über das Release, nicht hierher."""
|
|
21
|
+
|
|
22
|
+
postgres: str = "postgis/postgis:16-3.4"
|
|
23
|
+
redis: str = "redis:7-alpine"
|
|
24
|
+
elasticsearch: str = "docker.elastic.co/elasticsearch/elasticsearch:8.13.4"
|
|
25
|
+
cantaloupe: str = "islandora/cantaloupe:7.0.22"
|
|
26
|
+
nginx: str = "nginx:alpine"
|
|
27
|
+
caddy: str = "caddy:2-alpine"
|
|
28
|
+
|
|
29
|
+
|
|
16
30
|
class ReleaseEnvVar(BaseModel):
|
|
17
31
|
"""Neu hinzugekommene Umgebungsvariable eines Releases (katalon-release.json)."""
|
|
18
32
|
|
|
@@ -38,10 +52,22 @@ class ReleaseMetadata(BaseModel):
|
|
|
38
52
|
reindex_required: bool = False
|
|
39
53
|
breaking: bool
|
|
40
54
|
compose_revision: int
|
|
55
|
+
# Fehlt bei Releases bis Katalon 1.46.0; dort galten die Images des Templates.
|
|
56
|
+
images: ReleaseImages | None = None
|
|
41
57
|
requires: ReleaseRequirements = ReleaseRequirements()
|
|
42
58
|
env_vars: list[ReleaseEnvVar] = Field(default_factory=list)
|
|
43
59
|
deprecated_env_vars: list[DeprecatedEnvVar] = Field(default_factory=list)
|
|
44
60
|
|
|
61
|
+
@property
|
|
62
|
+
def service_images(self) -> ReleaseImages:
|
|
63
|
+
return self.images or ReleaseImages()
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def checked_requires(self) -> ReleaseRequirements:
|
|
67
|
+
"""`requires` gilt erst ab Releases mit `images`; davor prüfte es niemand, und der
|
|
68
|
+
Wert war nicht verlässlich (1.46.0 nennt Elasticsearch >=8.15, läuft aber mit 8.13.4)."""
|
|
69
|
+
return self.requires if self.images else ReleaseRequirements()
|
|
70
|
+
|
|
45
71
|
|
|
46
72
|
def _fetch_release_asset(tag: str | None) -> dict:
|
|
47
73
|
"""Lädt katalon-release.json vom GitHub-Release (tag=None → latest)."""
|
|
@@ -18,6 +18,8 @@ class HistoryEntry(BaseModel):
|
|
|
18
18
|
compose_revision: int
|
|
19
19
|
action: str # "install" | "update" | "rollback"
|
|
20
20
|
at: datetime
|
|
21
|
+
# Images der Fremdservices aus dem Release (Rolle → Image); fehlt bei Einträgen vor katalon-cli 0.4.0.
|
|
22
|
+
images: dict[str, str] = Field(default_factory=dict)
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class InstallationState(BaseModel):
|
|
@@ -41,13 +43,16 @@ class InstallationState(BaseModel):
|
|
|
41
43
|
path = instance_dir / STATE_FILENAME
|
|
42
44
|
path.write_text(self.model_dump_json(indent=2))
|
|
43
45
|
|
|
44
|
-
def record(
|
|
46
|
+
def record(
|
|
47
|
+
self, version: str, compose_revision: int, action: str, images: dict[str, str] | None = None
|
|
48
|
+
) -> None:
|
|
45
49
|
self.history.append(
|
|
46
50
|
HistoryEntry(
|
|
47
51
|
version=version,
|
|
48
52
|
compose_revision=compose_revision,
|
|
49
53
|
action=action,
|
|
50
54
|
at=datetime.now(timezone.utc),
|
|
55
|
+
images=images or {},
|
|
51
56
|
)
|
|
52
57
|
)
|
|
53
58
|
self.version = version
|
|
@@ -29,8 +29,9 @@ from rich.table import Table
|
|
|
29
29
|
|
|
30
30
|
from .core import backup as backup_mod
|
|
31
31
|
from .core import checks, docker, permissions, release, selfupdate
|
|
32
|
+
from .core import images as images_mod
|
|
32
33
|
from .core import reindex as reindex_mod
|
|
33
|
-
from .core.compose_gen import caddy_host, write_compose
|
|
34
|
+
from .core.compose_gen import caddy_host, render_compose, write_compose
|
|
34
35
|
from .core.paths import default_instance_dir, save_instance_dir
|
|
35
36
|
from .core.state import InstallationState, instance_dir_or_raise
|
|
36
37
|
|
|
@@ -369,7 +370,7 @@ def install(
|
|
|
369
370
|
progress.add_task("Schreibe compose.yaml + .env …", total=None)
|
|
370
371
|
dir.mkdir(parents=True, exist_ok=True)
|
|
371
372
|
Path(media_root).mkdir(parents=True, exist_ok=True)
|
|
372
|
-
write_compose(dir, version=meta.version, base_url=base_url, tls_mode=tls_mode)
|
|
373
|
+
write_compose(dir, version=meta.version, base_url=base_url, tls_mode=tls_mode, images=meta.service_images)
|
|
373
374
|
|
|
374
375
|
_ensure_env_vars(dir, base_url, media_root=media_root, release_env_vars=meta.env_vars, provided=provided)
|
|
375
376
|
_write_override_example(dir)
|
|
@@ -381,7 +382,7 @@ def install(
|
|
|
381
382
|
tls_mode=tls_mode,
|
|
382
383
|
installed_at=datetime.now(timezone.utc),
|
|
383
384
|
)
|
|
384
|
-
state.record(meta.version, meta.compose_revision, "install")
|
|
385
|
+
state.record(meta.version, meta.compose_revision, "install", images=meta.service_images.model_dump())
|
|
385
386
|
state.save(dir)
|
|
386
387
|
|
|
387
388
|
save_instance_dir(dir)
|
|
@@ -456,6 +457,40 @@ def _media_root(dir: Path) -> str:
|
|
|
456
457
|
return str(path if path.is_absolute() else dir / path)
|
|
457
458
|
|
|
458
459
|
|
|
460
|
+
def _check_update_images(dir: Path, state: InstallationState, meta: release.ReleaseMetadata) -> None:
|
|
461
|
+
"""Zeigt, welche Fremdservice-Images das Update wechselt, und bricht ab, wenn eins nicht passt.
|
|
462
|
+
|
|
463
|
+
Verglichen werden die wirksamen Images (inkl. compose.override.yaml) vor und nach dem
|
|
464
|
+
Update, damit auch ein eigenes Image aus dem Override gegen `requires` geprüft wird."""
|
|
465
|
+
preview = dir / "compose.yaml.next"
|
|
466
|
+
try:
|
|
467
|
+
preview.write_text(
|
|
468
|
+
render_compose(
|
|
469
|
+
version=meta.version, base_url=state.base_url, tls_mode=state.tls_mode, images=meta.service_images
|
|
470
|
+
)
|
|
471
|
+
)
|
|
472
|
+
current = docker.config_images(dir)
|
|
473
|
+
target = docker.config_images(dir, preview)
|
|
474
|
+
except (docker.DockerError, subprocess.CalledProcessError, ValueError) as exc:
|
|
475
|
+
console.print(f"[yellow]⚠ Images der Fremdservices nicht prüfbar: {exc}[/]")
|
|
476
|
+
return
|
|
477
|
+
finally:
|
|
478
|
+
preview.unlink(missing_ok=True)
|
|
479
|
+
|
|
480
|
+
for service in sorted(target):
|
|
481
|
+
old, new = current.get(service), target[service]
|
|
482
|
+
if old and old != new and "katalon-collections/katalon-" not in new:
|
|
483
|
+
console.print(f"[yellow]⚠ {service}: {old} → {new}[/]")
|
|
484
|
+
errors, notes = images_mod.update_problems(current, target, meta.checked_requires)
|
|
485
|
+
for note in notes:
|
|
486
|
+
console.print(f"[yellow]⚠ {note}[/]")
|
|
487
|
+
if errors:
|
|
488
|
+
for error in errors:
|
|
489
|
+
console.print(f"[red]✖ {error}[/]")
|
|
490
|
+
console.print("[red]Update abgebrochen, nichts geändert.[/]")
|
|
491
|
+
raise typer.Exit(1)
|
|
492
|
+
|
|
493
|
+
|
|
459
494
|
def _require_installer_version(minimum: str) -> None:
|
|
460
495
|
"""Bricht ab, wenn das Release eine neuere CLI braucht (z. B. neues Compose-Template).
|
|
461
496
|
|
|
@@ -730,6 +765,8 @@ def update(
|
|
|
730
765
|
f"(Revision {state.compose_revision} → {meta.compose_revision})[/]"
|
|
731
766
|
)
|
|
732
767
|
|
|
768
|
+
_check_update_images(dir, state, meta)
|
|
769
|
+
|
|
733
770
|
env_existing = _parse_env_file((dir / ".env").read_text() if (dir / ".env").exists() else "")
|
|
734
771
|
_print_new_env_vars(env_existing, env_vars)
|
|
735
772
|
_print_deprecated_env_vars(env_existing, deprecated_env_vars)
|
|
@@ -749,7 +786,9 @@ def update(
|
|
|
749
786
|
),
|
|
750
787
|
(
|
|
751
788
|
"compose.yaml rendern",
|
|
752
|
-
lambda: write_compose(
|
|
789
|
+
lambda: write_compose(
|
|
790
|
+
dir, version=meta.version, base_url=state.base_url, tls_mode=state.tls_mode, images=meta.service_images
|
|
791
|
+
),
|
|
753
792
|
),
|
|
754
793
|
("Images pullen", lambda: docker.compose(dir, "pull")),
|
|
755
794
|
("Medienrechte prüfen", lambda: _ensure_media_permissions(dir)),
|
|
@@ -782,7 +821,7 @@ def update(
|
|
|
782
821
|
console.print("[red]✖ Healthcheck fehlgeschlagen — `katalon rollback` erwägen.[/]")
|
|
783
822
|
raise typer.Exit(1)
|
|
784
823
|
|
|
785
|
-
state.record(meta.version, meta.compose_revision, "update")
|
|
824
|
+
state.record(meta.version, meta.compose_revision, "update", images=meta.service_images.model_dump())
|
|
786
825
|
state.save(dir)
|
|
787
826
|
console.print(f"[green]✔[/] Update auf {meta.version} abgeschlossen.")
|
|
788
827
|
|
|
@@ -840,7 +879,12 @@ def rollback(
|
|
|
840
879
|
)
|
|
841
880
|
|
|
842
881
|
if previous:
|
|
843
|
-
|
|
882
|
+
images = images_mod.rollback_images(
|
|
883
|
+
release.ReleaseImages(**state.history[-1].images), release.ReleaseImages(**previous.images)
|
|
884
|
+
)
|
|
885
|
+
write_compose(
|
|
886
|
+
dir, version=previous.version, base_url=state.base_url, tls_mode=state.tls_mode, images=images
|
|
887
|
+
)
|
|
844
888
|
docker.compose(dir, "pull")
|
|
845
889
|
|
|
846
890
|
_ensure_media_permissions(dir)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
services:
|
|
2
2
|
db:
|
|
3
|
-
image:
|
|
3
|
+
image: {{ images.postgres }}
|
|
4
4
|
platform: linux/amd64
|
|
5
5
|
restart: unless-stopped
|
|
6
6
|
environment:
|
|
@@ -16,7 +16,7 @@ services:
|
|
|
16
16
|
retries: 10
|
|
17
17
|
|
|
18
18
|
redis:
|
|
19
|
-
image: redis
|
|
19
|
+
image: {{ images.redis }}
|
|
20
20
|
restart: unless-stopped
|
|
21
21
|
healthcheck:
|
|
22
22
|
test: ["CMD", "redis-cli", "ping"]
|
|
@@ -25,7 +25,7 @@ services:
|
|
|
25
25
|
retries: 10
|
|
26
26
|
|
|
27
27
|
elasticsearch:
|
|
28
|
-
image:
|
|
28
|
+
image: {{ images.elasticsearch }}
|
|
29
29
|
restart: unless-stopped
|
|
30
30
|
environment:
|
|
31
31
|
discovery.type: single-node
|
|
@@ -42,7 +42,7 @@ services:
|
|
|
42
42
|
# Legt den Derivat-Cache an und übergibt ihn dem Cantaloupe-User des Images.
|
|
43
43
|
# Ändert nur Einträge mit falschem Eigentümer (idempotent, schnell bei vollem Cache).
|
|
44
44
|
cantaloupe-cache-init:
|
|
45
|
-
image:
|
|
45
|
+
image: {{ images.cantaloupe }}
|
|
46
46
|
user: "0:0"
|
|
47
47
|
entrypoint: ["sh", "-c", "mkdir -p /data/cache && find /data ! -user cantaloupe -exec chown cantaloupe:cantaloupe {} +"]
|
|
48
48
|
volumes:
|
|
@@ -50,7 +50,7 @@ services:
|
|
|
50
50
|
restart: "no"
|
|
51
51
|
|
|
52
52
|
cantaloupe:
|
|
53
|
-
image:
|
|
53
|
+
image: {{ images.cantaloupe }}
|
|
54
54
|
restart: unless-stopped
|
|
55
55
|
volumes:
|
|
56
56
|
- ${MEDIA_ROOT:-./media}:/var/lib/cantaloupe/images:ro
|
|
@@ -185,7 +185,7 @@ services:
|
|
|
185
185
|
api: { condition: service_healthy }
|
|
186
186
|
|
|
187
187
|
nginx:
|
|
188
|
-
image: nginx
|
|
188
|
+
image: {{ images.nginx }}
|
|
189
189
|
restart: unless-stopped
|
|
190
190
|
volumes:
|
|
191
191
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
@@ -210,7 +210,7 @@ services:
|
|
|
210
210
|
|
|
211
211
|
{% if tls_mode == "caddy" %}
|
|
212
212
|
caddy:
|
|
213
|
-
image: caddy
|
|
213
|
+
image: {{ images.caddy }}
|
|
214
214
|
restart: unless-stopped
|
|
215
215
|
ports:
|
|
216
216
|
- "80:80"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|