feat(apps): toon uit welk sjabloon een app gerenderd wordt
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 5m17s
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 5m17s
Sjablonen komen uit de git-cache van een app-repo. Loopt die achter, dan trekt 'Instellingen wijzigen' een oude versie over een werkende app heen zonder dat iets dat zegt — precies wat er bij UniFi gebeurde. - git.cache_status geeft commit, datum, synchronisatiemoment en de VERSION uit de cache terug. - /api/repos en /api/stacks/<naam>/config sturen die stand mee, met een outdated-vlag als de repo-VERSION ouder is dan de draaiende versie. - Het wijzigscherm toont repo, branch en hoe lang geleden er opgehaald is, en waarschuwt bij een achterlopende cache. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9Vcwu7owJZ3nj5dCf4VFT
This commit is contained in:
parent
c658418b10
commit
b40a6a5a4c
8 changed files with 226 additions and 4 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -1,3 +1,18 @@
|
||||||
|
# v0.8.25-beta — Zichtbaar uit welk sjabloon een app komt
|
||||||
|
|
||||||
|
Sjablonen komen uit de git-cache van een app-repo, niet uit het image van
|
||||||
|
Server Up. Die twee kunnen ver uit elkaar lopen: staat de repo op een branch
|
||||||
|
die achterloopt, dan rendert 'Instellingen wijzigen' een oud sjabloon over een
|
||||||
|
werkende app heen — en daar was aan niets te zien.
|
||||||
|
|
||||||
|
- **Het wijzigscherm noemt zijn bron.** Repo, branch en hoe lang geleden er
|
||||||
|
gesynchroniseerd is, boven de velden waar je op opslaan drukt.
|
||||||
|
- **Een achterlopende repo waarschuwt.** Levert de repo zelf een `VERSION` mee
|
||||||
|
(zoals deze) en is die ouder dan de draaiende versie, dan zegt het scherm dat
|
||||||
|
met de versie erbij.
|
||||||
|
- **De repolijst toont de stand.** Commit en synchronisatiemoment per repo,
|
||||||
|
zodat je niet hoeft te gokken of een sync nog nodig is.
|
||||||
|
|
||||||
# v0.8.24-beta — Koppelen laat je database met rust
|
# v0.8.24-beta — Koppelen laat je database met rust
|
||||||
|
|
||||||
Koppelen aan het gedeelde netwerk hing élke service van een stack eraan, ook de
|
Koppelen aan het gedeelde netwerk hing élke service van een stack eraan, ook de
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.8.24-beta
|
0.8.25-beta
|
||||||
|
|
|
||||||
|
|
@ -1115,7 +1115,25 @@ def api_stack_config(name):
|
||||||
app_networks=meta.get("app_networks") or [],
|
app_networks=meta.get("app_networks") or [],
|
||||||
app_services=meta.get("app_services") or [],
|
app_services=meta.get("app_services") or [],
|
||||||
source=meta.get("source", ""), repo_id=meta.get("repo_id", ""),
|
source=meta.get("source", ""), repo_id=meta.get("repo_id", ""),
|
||||||
name=meta.get("name", name))
|
name=meta.get("name", name),
|
||||||
|
# Waar dit sjabloon vandaan komt. Opslaan rendert de app
|
||||||
|
# opnieuw uit déze cache; loopt die achter, dan haal je een
|
||||||
|
# oude versie over je werkende stack heen.
|
||||||
|
source_repo=_sjabloon_herkomst(meta.get("repo_id", "")))
|
||||||
|
|
||||||
|
|
||||||
|
def _sjabloon_herkomst(rid: str) -> dict:
|
||||||
|
"""Repo, branch en stand van de cache waar een sjabloon uit komt."""
|
||||||
|
if not rid:
|
||||||
|
return {}
|
||||||
|
repo = next((r for r in cfg.load().get("APP_REPOS", [])
|
||||||
|
if isinstance(r, dict) and r.get("id") == rid), None)
|
||||||
|
if not repo:
|
||||||
|
return {"id": rid, "missing": True}
|
||||||
|
stand = git.cache_status(rid)
|
||||||
|
stand["outdated"] = _cache_loopt_achter(stand.get("version", ""))
|
||||||
|
return {"id": rid, "name": repo.get("name") or rid,
|
||||||
|
"branch": repo.get("branch") or "main", **stand}
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/stacks/<name>/reconfigure", methods=["POST"])
|
@app.route("/api/stacks/<name>/reconfigure", methods=["POST"])
|
||||||
|
|
@ -1432,10 +1450,28 @@ def api_repos():
|
||||||
if r["id"] in gezien:
|
if r["id"] in gezien:
|
||||||
continue
|
continue
|
||||||
gezien.add(r["id"])
|
gezien.add(r["id"])
|
||||||
result.append({**r, "cloned": git.cache_dir(r["id"]).exists()})
|
# Mét de stand van de cache: welke commit, van wanneer, en loopt de
|
||||||
|
# meegeleverde VERSION achter op wat hier draait? Zonder dat is niet te
|
||||||
|
# zien dat een app een oud sjabloon rendert.
|
||||||
|
stand = git.cache_status(r["id"])
|
||||||
|
stand["outdated"] = _cache_loopt_achter(stand.get("version", ""))
|
||||||
|
result.append({**r, "cloned": git.cache_dir(r["id"]).exists(),
|
||||||
|
"cache": stand})
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
def _cache_loopt_achter(repo_versie: str) -> bool:
|
||||||
|
"""Is de VERSION in de git-cache ouder dan de draaiende Server Up?
|
||||||
|
|
||||||
|
Alleen zinvol voor repo's die zelf een VERSION meeleveren; voor de rest
|
||||||
|
zegt dit niets en blijft het antwoord False.
|
||||||
|
"""
|
||||||
|
if not repo_versie:
|
||||||
|
return False
|
||||||
|
hier, daar = updater._parse(VERSION), updater._parse(repo_versie)
|
||||||
|
return bool(hier and daar and daar < hier)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/repos", methods=["POST"])
|
@app.route("/api/repos", methods=["POST"])
|
||||||
def api_repos_add():
|
def api_repos_add():
|
||||||
d = request.json or {}
|
d = request.json or {}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,44 @@ def cache_dir(repo_id: str) -> Path:
|
||||||
return CACHE / repo_id
|
return CACHE / repo_id
|
||||||
|
|
||||||
|
|
||||||
|
def cache_status(repo_id: str) -> dict:
|
||||||
|
"""Welke versie van de sjablonen staat er in de cache van deze repo?
|
||||||
|
|
||||||
|
Sjablonen komen hiervandaan en niet uit het image van Server Up. Die twee
|
||||||
|
kunnen ver uit elkaar lopen: staat de repo op een branch die achterloopt,
|
||||||
|
dan rendert een app een oud sjabloon terwijl de code splinternieuw is — en
|
||||||
|
daar is aan niets te zien.
|
||||||
|
"""
|
||||||
|
d = cache_dir(repo_id)
|
||||||
|
if not (d / ".git").exists():
|
||||||
|
return {"cloned": False}
|
||||||
|
uit: dict = {"cloned": True}
|
||||||
|
try:
|
||||||
|
r = subprocess.run(["git", "log", "-1", "--format=%h%x1f%cI%x1f%s"],
|
||||||
|
cwd=str(d), capture_output=True, text=True, timeout=10)
|
||||||
|
if r.returncode == 0 and "\x1f" in r.stdout:
|
||||||
|
sha, datum, onderwerp = r.stdout.strip().split("\x1f", 2)
|
||||||
|
uit.update(commit=sha, commit_date=datum, subject=onderwerp[:120])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Wanneer is er voor het laatst opgehaald? FETCH_HEAD wordt bij elke fetch
|
||||||
|
# aangeraakt; bij een verse kloon bestaat hij nog niet.
|
||||||
|
for naam in ("FETCH_HEAD", "HEAD"):
|
||||||
|
p = d / ".git" / naam
|
||||||
|
if p.exists():
|
||||||
|
uit["synced_at"] = p.stat().st_mtime
|
||||||
|
break
|
||||||
|
# Repo's die zelf een VERSION meeleveren (zoals deze) verraden daarmee of de
|
||||||
|
# cache achterloopt op de draaiende versie.
|
||||||
|
versie = d / "VERSION"
|
||||||
|
if versie.is_file():
|
||||||
|
try:
|
||||||
|
uit["version"] = versie.read_text(encoding="utf-8").strip()[:40]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return uit
|
||||||
|
|
||||||
|
|
||||||
def clone_or_pull(repo: dict, log_fn=None) -> tuple[bool, str]:
|
def clone_or_pull(repo: dict, log_fn=None) -> tuple[bool, str]:
|
||||||
_ensure_git_safe()
|
_ensure_git_safe()
|
||||||
url = repo.get("url", "").strip()
|
url = repo.get("url", "").strip()
|
||||||
|
|
|
||||||
|
|
@ -1392,9 +1392,17 @@ tailwind.config = {
|
||||||
<summary class="px-5 py-4 cursor-pointer font-bold text-wh dark:text-wh-dark text-sm flex items-center gap-2.5"><span class="mdi mdi-source-branch text-ac dark:text-ac-dark text-lg"></span> Git Repositories</summary>
|
<summary class="px-5 py-4 cursor-pointer font-bold text-wh dark:text-wh-dark text-sm flex items-center gap-2.5"><span class="mdi mdi-source-branch text-ac dark:text-ac-dark text-lg"></span> Git Repositories</summary>
|
||||||
<div class="px-5 pb-5 space-y-2">
|
<div class="px-5 pb-5 space-y-2">
|
||||||
<template x-for="r in repos" :key="r.id">
|
<template x-for="r in repos" :key="r.id">
|
||||||
<div class="flex items-center gap-2 py-2 border-b border-bd dark:border-bd-dark last:border-0">
|
<div class="flex items-center gap-2 py-2 border-b border-bd dark:border-bd-dark last:border-0 flex-wrap">
|
||||||
<span class="mdi mdi-source-branch text-dm dark:text-dm-dark"></span>
|
<span class="mdi mdi-source-branch text-dm dark:text-dm-dark"></span>
|
||||||
<span class="font-mono text-xs flex-1 truncate" x-text="r.name"></span>
|
<span class="font-mono text-xs flex-1 truncate" x-text="r.name"></span>
|
||||||
|
<!-- De stand van de cache: hieruit komen de sjablonen, en die
|
||||||
|
kan ver achterlopen op de draaiende versie. -->
|
||||||
|
<span x-show="(r.cache||{}).outdated" class="badge badge-warn"
|
||||||
|
:title="t('cache_outdated_hint')" x-text="(r.cache||{}).version"></span>
|
||||||
|
<span x-show="(r.cache||{}).commit && !(r.cache||{}).outdated"
|
||||||
|
class="text-[10px] font-mono text-dm dark:text-dm-dark"
|
||||||
|
:title="(r.cache||{}).subject"
|
||||||
|
x-text="(r.cache||{}).commit + ' · ' + geleden((r.cache||{}).synced_at)"></span>
|
||||||
<!-- De branch bepaalt welke sjablonen je krijgt; die stilzwijgend
|
<!-- De branch bepaalt welke sjablonen je krijgt; die stilzwijgend
|
||||||
weglaten maakte het onmogelijk te zien waarom een app een
|
weglaten maakte het onmogelijk te zien waarom een app een
|
||||||
oude versie bleef renderen. -->
|
oude versie bleef renderen. -->
|
||||||
|
|
@ -1765,6 +1773,27 @@ tailwind.config = {
|
||||||
<span x-text="t('reconfigure_warning')"></span>
|
<span x-text="t('reconfigure_warning')"></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<!-- Waar het sjabloon vandaan komt. Opslaan rendert de app opnieuw uit
|
||||||
|
déze cache: loopt die achter, dan trek je een oude versie over een
|
||||||
|
werkende stack heen zonder dat iets dat zegt. -->
|
||||||
|
<div x-show="install.mode === 'edit' && install.source_repo && install.source_repo.id"
|
||||||
|
class="text-[11px] rounded-xl border px-3 py-2 flex items-start gap-2"
|
||||||
|
:class="(install.source_repo||{}).outdated
|
||||||
|
? 'border-wr/40 bg-wr/5 text-wr'
|
||||||
|
: 'border-bd dark:border-bd-dark text-t2 dark:text-t2-dark'">
|
||||||
|
<span class="mdi shrink-0"
|
||||||
|
:class="(install.source_repo||{}).outdated ? 'mdi-alert-outline' : 'mdi-source-branch'"></span>
|
||||||
|
<span class="min-w-0">
|
||||||
|
<span x-text="t('template_from', {
|
||||||
|
repo: (install.source_repo||{}).name || (install.source_repo||{}).id,
|
||||||
|
branch: (install.source_repo||{}).branch || 'main'})"></span>
|
||||||
|
<span x-show="(install.source_repo||{}).synced_at"
|
||||||
|
x-text="' · ' + t('synced_ago', {ago: geleden((install.source_repo||{}).synced_at)})"></span>
|
||||||
|
<span x-show="(install.source_repo||{}).outdated" class="block mt-0.5"
|
||||||
|
x-text="t('cache_outdated', {version: (install.source_repo||{}).version, current: version})"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Uitleg bij het veld waar je op het info-icoon klikte -->
|
<!-- Uitleg bij het veld waar je op het info-icoon klikte -->
|
||||||
<div x-show="fieldInfo" x-cloak
|
<div x-show="fieldInfo" x-cloak
|
||||||
class="rounded-xl border border-ac/30 bg-ac/5 px-3.5 py-3">
|
class="rounded-xl border border-ac/30 bg-ac/5 px-3.5 py-3">
|
||||||
|
|
@ -4090,6 +4119,7 @@ function app() {
|
||||||
repo_id: d.repo_id || '',
|
repo_id: d.repo_id || '',
|
||||||
app_networks: d.app_networks || (d.connected ? [this.sharedNetwork] : []),
|
app_networks: d.app_networks || (d.connected ? [this.sharedNetwork] : []),
|
||||||
app_services: d.app_services || [], service_info: [],
|
app_services: d.app_services || [], service_info: [],
|
||||||
|
source_repo: d.source_repo || {},
|
||||||
};
|
};
|
||||||
await this.loadAppNets(stack);
|
await this.loadAppNets(stack);
|
||||||
this.fieldInfo = null;
|
this.fieldInfo = null;
|
||||||
|
|
@ -4276,6 +4306,17 @@ function app() {
|
||||||
if (!ts) return '';
|
if (!ts) return '';
|
||||||
return new Date(ts * 1000).toLocaleString();
|
return new Date(ts * 1000).toLocaleString();
|
||||||
},
|
},
|
||||||
|
// "3 dagen geleden" leest bij een synchronisatie prettiger dan een datum:
|
||||||
|
// de vraag is niet wannéér het was, maar of het lang geleden is.
|
||||||
|
geleden(ts) {
|
||||||
|
if (!ts) return '';
|
||||||
|
const sec = Math.max(0, Date.now() / 1000 - ts);
|
||||||
|
const stappen = [[86400, 'd'], [3600, 'u'], [60, 'm']];
|
||||||
|
for (const [n, letter] of stappen) {
|
||||||
|
if (sec >= n) return Math.floor(sec / n) + letter;
|
||||||
|
}
|
||||||
|
return this.t('just_now');
|
||||||
|
},
|
||||||
|
|
||||||
// Bijwerken: de container vervangt zichzelf, dus na het startsein is deze
|
// Bijwerken: de container vervangt zichzelf, dus na het startsein is deze
|
||||||
// pagina zijn server kwijt. We pollen /healthz tot de nieuwe versie leeft.
|
// pagina zijn server kwijt. We pollen /healthz tot de nieuwe versie leeft.
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@
|
||||||
"no_repos": "No repository added yet — add one to see apps.",
|
"no_repos": "No repository added yet — add one to see apps.",
|
||||||
"edit_repo": "Edit repository",
|
"edit_repo": "Edit repository",
|
||||||
"repo_branch_hint": "The branch decides which version of the templates you get. Saving re-fetches the repo right away; existing apps only change once you save their settings.",
|
"repo_branch_hint": "The branch decides which version of the templates you get. Saving re-fetches the repo right away; existing apps only change once you save their settings.",
|
||||||
|
"template_from": "Template from {repo} ({branch})",
|
||||||
|
"synced_ago": "fetched {ago} ago",
|
||||||
|
"cache_outdated": "This repo is on {version} while Server Up runs {current}. Sync it or pick another branch — otherwise saving renders an older template over your app.",
|
||||||
|
"cache_outdated_hint": "The templates in this repo lag behind the running version of Server Up.",
|
||||||
|
"just_now": "just now",
|
||||||
"add_repo": "Add repository",
|
"add_repo": "Add repository",
|
||||||
"repo_name": "Name",
|
"repo_name": "Name",
|
||||||
"repo_url": "Git URL",
|
"repo_url": "Git URL",
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@
|
||||||
"no_repos": "Nog geen repository toegevoegd — voeg er een toe om apps te zien.",
|
"no_repos": "Nog geen repository toegevoegd — voeg er een toe om apps te zien.",
|
||||||
"edit_repo": "Repository bewerken",
|
"edit_repo": "Repository bewerken",
|
||||||
"repo_branch_hint": "De branch bepaalt welke versie van de sjablonen je krijgt. Na opslaan wordt de repo meteen opnieuw opgehaald; bestaande apps veranderen pas als je hun instellingen opslaat.",
|
"repo_branch_hint": "De branch bepaalt welke versie van de sjablonen je krijgt. Na opslaan wordt de repo meteen opnieuw opgehaald; bestaande apps veranderen pas als je hun instellingen opslaat.",
|
||||||
|
"template_from": "Sjabloon uit {repo} ({branch})",
|
||||||
|
"synced_ago": "{ago} geleden opgehaald",
|
||||||
|
"cache_outdated": "Deze repo staat op {version} terwijl Server Up {current} draait. Synchroniseer hem of kies een andere branch — anders rendert opslaan een ouder sjabloon over je app heen.",
|
||||||
|
"cache_outdated_hint": "De sjablonen in deze repo lopen achter op de draaiende versie van Server Up.",
|
||||||
|
"just_now": "zojuist",
|
||||||
"add_repo": "Repository toevoegen",
|
"add_repo": "Repository toevoegen",
|
||||||
"repo_name": "Naam",
|
"repo_name": "Naam",
|
||||||
"repo_url": "Git URL",
|
"repo_url": "Git URL",
|
||||||
|
|
|
||||||
|
|
@ -357,3 +357,85 @@ def test_viewer_mag_geen_repo_bewerken(client, env):
|
||||||
).get_json()["csrf_token"]
|
).get_json()["csrf_token"]
|
||||||
assert client.put("/api/repos/proef", json={"branch": "dev"},
|
assert client.put("/api/repos/proef", json={"branch": "dev"},
|
||||||
headers={"X-CSRF-Token": kijker}).status_code == 403
|
headers={"X-CSRF-Token": kijker}).status_code == 403
|
||||||
|
|
||||||
|
|
||||||
|
# ── Herkomst van een sjabloon ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_repolijst_toont_de_stand_van_de_cache(client, env, tmp_path, monkeypatch):
|
||||||
|
"""Sjablonen komen uit de git-cache, niet uit het image. Loopt die achter,
|
||||||
|
dan rendert een app een oude versie terwijl de code nieuw is — en daar was
|
||||||
|
aan niets te zien."""
|
||||||
|
import shutil as _shutil
|
||||||
|
from core import git
|
||||||
|
if not _shutil.which("git"):
|
||||||
|
pytest.skip("git niet aanwezig")
|
||||||
|
csrf = login(client)
|
||||||
|
bron = _repo_met_twee_takken(tmp_path)
|
||||||
|
(git.cache_dir("proef")).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
monkeypatch.setattr("core.valid_repo_url", lambda u: True)
|
||||||
|
client.post("/api/repos", json={"url": f"file://{bron}", "id": "proef",
|
||||||
|
"branch": "main"},
|
||||||
|
headers={"X-CSRF-Token": csrf})
|
||||||
|
ok, msg = git.clone_or_pull({"id": "proef", "url": f"file://{bron}",
|
||||||
|
"branch": "main"})
|
||||||
|
assert ok, msg
|
||||||
|
|
||||||
|
repo = next(r for r in client.get("/api/repos").get_json() if r["id"] == "proef")
|
||||||
|
assert repo["cache"]["cloned"] is True
|
||||||
|
assert repo["cache"]["commit"]
|
||||||
|
assert repo["cache"]["synced_at"] > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_achterlopende_repo_wordt_gemeld(client, env, tmp_path, monkeypatch):
|
||||||
|
"""Precies het geval dat drie releases lang onzichtbaar bleef: de repo staat
|
||||||
|
op een oudere versie dan Server Up zelf."""
|
||||||
|
import shutil as _shutil
|
||||||
|
from core import git
|
||||||
|
if not _shutil.which("git"):
|
||||||
|
pytest.skip("git niet aanwezig")
|
||||||
|
csrf = login(client)
|
||||||
|
bron = _repo_met_twee_takken(tmp_path)
|
||||||
|
(bron / "VERSION").write_text("0.0.01-beta\n")
|
||||||
|
import subprocess as sp
|
||||||
|
sp.run(["git", "add", "-A"], cwd=bron, check=True, capture_output=True)
|
||||||
|
sp.run(["git", "commit", "-qm", "versie"], cwd=bron, check=True,
|
||||||
|
capture_output=True)
|
||||||
|
monkeypatch.setattr("core.valid_repo_url", lambda u: True)
|
||||||
|
client.post("/api/repos", json={"url": f"file://{bron}", "id": "oud",
|
||||||
|
"branch": "main"},
|
||||||
|
headers={"X-CSRF-Token": csrf})
|
||||||
|
git.clone_or_pull({"id": "oud", "url": f"file://{bron}", "branch": "main"})
|
||||||
|
|
||||||
|
repo = next(r for r in client.get("/api/repos").get_json() if r["id"] == "oud")
|
||||||
|
assert repo["cache"]["version"] == "0.0.01-beta"
|
||||||
|
assert repo["cache"]["outdated"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_repo_zonder_versiebestand_heet_niet_verouderd(env):
|
||||||
|
import app as app_module
|
||||||
|
assert app_module._cache_loopt_achter("") is False
|
||||||
|
assert app_module._cache_loopt_achter("geen-versie") is False
|
||||||
|
assert app_module._cache_loopt_achter("99.9.99") is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_wijzigscherm_kent_de_herkomst_van_het_sjabloon(client, env):
|
||||||
|
"""Het scherm waar je op opslaan drukt hoort te zeggen waaruit hij rendert."""
|
||||||
|
import json as _json
|
||||||
|
csrf = login(client)
|
||||||
|
d = env["lib"] / "web"
|
||||||
|
d.mkdir(parents=True, exist_ok=True)
|
||||||
|
(d / "docker-compose.yml").write_text("services:\n web:\n image: nginx\n")
|
||||||
|
(d / ".serverup.json").write_text(_json.dumps({
|
||||||
|
"source": "keuzeapp", "repo_id": "server-up", "values": {"port": 80}}))
|
||||||
|
|
||||||
|
from core import git
|
||||||
|
wortel = git.cache_dir("server-up") / "apps" / "keuzeapp"
|
||||||
|
(wortel / "files").mkdir(parents=True, exist_ok=True)
|
||||||
|
(wortel / "template.json").write_text(_json.dumps({
|
||||||
|
"kind": "compose", "metadata": {"name": "keuzeapp"}, "variables": []}))
|
||||||
|
(wortel / "files" / "compose.yaml").write_text("services:\n web:\n image: nginx\n")
|
||||||
|
|
||||||
|
d2 = client.get("/api/stacks/web/config").get_json()
|
||||||
|
assert d2["ok"] is True
|
||||||
|
assert d2["source_repo"]["id"] == "server-up"
|
||||||
|
assert d2["source_repo"]["branch"]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue