Fix: envparse.py strip mode now also drops top-level 'name:' key that docker compose config emits but Swarm's stack deploy schema rejects
ci/woodpecker/push/deploy Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
This commit is contained in:
+31
-3
@@ -156,9 +156,6 @@ def collapse_depends_on(text):
|
|||||||
if not parse_ok or not services:
|
if not parse_ok or not services:
|
||||||
# Re-emit whatever we consumed as-is (best effort: fall back to
|
# Re-emit whatever we consumed as-is (best effort: fall back to
|
||||||
# original slice) rather than risk mangling an unfamiliar shape.
|
# 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
|
pass
|
||||||
|
|
||||||
for svc in services:
|
for svc in services:
|
||||||
@@ -166,6 +163,36 @@ def collapse_depends_on(text):
|
|||||||
|
|
||||||
return '\n'.join(out)
|
return '\n'.join(out)
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
# 2026-08-26 FIX — top-level `name:` property rejected by Swarm:
|
||||||
|
#
|
||||||
|
# `docker compose config` (Compose Spec output) emits a top-level
|
||||||
|
# `name: <project>` key as the first line of the rendered document, e.g.:
|
||||||
|
# name: immich
|
||||||
|
# services:
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# This is valid Compose Spec but `docker stack deploy` (Swarm mode) uses
|
||||||
|
# a stricter schema that does NOT allow a top-level `name` property, and
|
||||||
|
# rejects the whole document with: "(root) Additional property name is
|
||||||
|
# not allowed". Single-file stacks (no extras) never hit this because
|
||||||
|
# they skip `docker compose config` and go straight through envsubst on
|
||||||
|
# the raw source YAML, which never had a top-level `name:` to begin with.
|
||||||
|
#
|
||||||
|
# Fix: drop ONLY the top-level `name:` line (column 0, i.e. no leading
|
||||||
|
# whitespace) at the START of the document. Nested `name:` fields under
|
||||||
|
# networks/volumes/secrets (e.g. `name: postgresql_db-backend`, always
|
||||||
|
# indented) are legitimate and must NOT be touched.
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_TOP_NAME_RE = re.compile(r'^name:\s*\S+\s*$')
|
||||||
|
|
||||||
|
def strip_top_level_name(text):
|
||||||
|
lines = text.split('\n')
|
||||||
|
if lines and _TOP_NAME_RE.match(lines[0]):
|
||||||
|
lines = lines[1:]
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
mode = sys.argv[1]
|
mode = sys.argv[1]
|
||||||
if mode == 'export':
|
if mode == 'export':
|
||||||
for k, v in parse_env(sys.argv[2]):
|
for k, v in parse_env(sys.argv[2]):
|
||||||
@@ -183,4 +210,5 @@ elif mode == 'strip':
|
|||||||
t = sys.stdin.read()
|
t = sys.stdin.read()
|
||||||
t = re.sub(r'[ \t]*env_file:[ \t]*\n([ \t]+-[^\n]*\n)+', '', t)
|
t = re.sub(r'[ \t]*env_file:[ \t]*\n([ \t]+-[^\n]*\n)+', '', t)
|
||||||
t = collapse_depends_on(t)
|
t = collapse_depends_on(t)
|
||||||
|
t = strip_top_level_name(t)
|
||||||
sys.stdout.write(t)
|
sys.stdout.write(t)
|
||||||
|
|||||||
Reference in New Issue
Block a user