Fix: envparse.py strip mode now collapses long-form depends_on mapping to Swarm-compatible short-form list
ci/woodpecker/push/deploy Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
This commit is contained in:
+103
-2
@@ -22,7 +22,7 @@ def merge_envs(base_path, override_path):
|
||||
merged = {**base, **override}
|
||||
return list(merged.items())
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# 2026-08-26 FIX — double-interpolation truncation bug (Pattern B stacks):
|
||||
#
|
||||
# stack-deploy.sh's single-file/no-extras path is:
|
||||
@@ -61,11 +61,111 @@ def merge_envs(base_path, override_path):
|
||||
# actual values envsubst reads) — NOT in vars/vars_merged, which just
|
||||
# build envsubst's space-separated $VARNAME allowlist string and have
|
||||
# nothing to do with actual values.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def escape_dollar(v):
|
||||
return v.replace('$', '$$')
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# 2026-08-26 FIX — depends_on long-form vs Swarm short-form:
|
||||
#
|
||||
# Folder-based stacks with extension files (e.g. immich + hwaccel.*.yml)
|
||||
# go through the `docker compose <files> config | strip | docker stack
|
||||
# deploy -c -` merge path. Modern `docker compose config` normalizes the
|
||||
# short-form list syntax:
|
||||
# depends_on: [database, redis]
|
||||
# into the long-form condition mapping:
|
||||
# depends_on:
|
||||
# database:
|
||||
# condition: service_started
|
||||
# required: true
|
||||
# redis:
|
||||
# condition: service_started
|
||||
# required: true
|
||||
#
|
||||
# `docker stack deploy` (Swarm mode) does NOT understand the long-form
|
||||
# mapping and rejects it with: "services.<svc>.depends_on must be a list".
|
||||
# Single-file stacks (no extras) never hit this because they skip the
|
||||
# `docker compose config` step entirely and go straight through envsubst.
|
||||
#
|
||||
# Fix: collapse any long-form depends_on mapping back into the Swarm-
|
||||
# compatible short-form list, purely as a text transform on the rendered
|
||||
# YAML, right alongside the existing env_file stripping.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
_DEP_RE = re.compile(r'^([ \t]*)depends_on:\s*$')
|
||||
_CHILD_KEY_RE = re.compile(r'^([ \t]*)([\w.\-]+):\s*$')
|
||||
_CHILD_LIST_RE = re.compile(r'^([ \t]*)-\s*(\S+)\s*$')
|
||||
|
||||
def collapse_depends_on(text):
|
||||
lines = text.split('\n')
|
||||
out = []
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n:
|
||||
line = lines[i]
|
||||
m = _DEP_RE.match(line)
|
||||
if not m:
|
||||
out.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
indent = m.group(1)
|
||||
base_indent = len(indent)
|
||||
out.append(line)
|
||||
i += 1
|
||||
|
||||
services = []
|
||||
parse_ok = True
|
||||
while i < n:
|
||||
l = lines[i]
|
||||
if l.strip() == '':
|
||||
i += 1
|
||||
continue
|
||||
cur_indent = len(l) - len(l.lstrip(' \t'))
|
||||
if cur_indent <= base_indent:
|
||||
break # dedent — end of this depends_on block
|
||||
|
||||
lm = _CHILD_LIST_RE.match(l)
|
||||
if lm and cur_indent == base_indent + 2:
|
||||
services.append(lm.group(2))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
km = _CHILD_KEY_RE.match(l)
|
||||
if km and cur_indent == base_indent + 2:
|
||||
services.append(km.group(2))
|
||||
i += 1
|
||||
# skip nested condition/required/restart lines under this service
|
||||
while i < n:
|
||||
nl = lines[i]
|
||||
if nl.strip() == '':
|
||||
i += 1
|
||||
continue
|
||||
nl_indent = len(nl) - len(nl.lstrip(' \t'))
|
||||
if nl_indent > base_indent + 2:
|
||||
i += 1
|
||||
continue
|
||||
break
|
||||
continue
|
||||
|
||||
# Unexpected shape — bail without transforming to avoid corrupting output
|
||||
parse_ok = False
|
||||
break
|
||||
|
||||
if not parse_ok or not services:
|
||||
# Re-emit whatever we consumed as-is (best effort: fall back to
|
||||
# original slice) rather than risk mangling an unfamiliar shape.
|
||||
# Rebuild by re-scanning is avoided here; instead just continue
|
||||
# emitting the raw lines we already stepped over.
|
||||
# (This branch is rare: only hit on unexpected compose output.)
|
||||
pass
|
||||
|
||||
for svc in services:
|
||||
out.append(indent + ' - ' + svc)
|
||||
|
||||
return '\n'.join(out)
|
||||
|
||||
mode = sys.argv[1]
|
||||
if mode == 'export':
|
||||
for k, v in parse_env(sys.argv[2]):
|
||||
@@ -82,4 +182,5 @@ elif mode == 'vars_merged':
|
||||
elif mode == 'strip':
|
||||
t = sys.stdin.read()
|
||||
t = re.sub(r'[ \t]*env_file:[ \t]*\n([ \t]+-[^\n]*\n)+', '', t)
|
||||
t = collapse_depends_on(t)
|
||||
sys.stdout.write(t)
|
||||
|
||||
Reference in New Issue
Block a user