mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
fix(coredns): substitute the Corefile in-process instead of shelling to envsubst
Two workspace jobs of run 33414875521 died the same way, on debian and on fedora:
RuntimeError: envsubst not found. Install gettext-base (Ubuntu/Debian) or gettext (Arch).
make: *** [Makefile:252: compose-up] Error 1
make: *** [Makefile:778: swarm-zombie] Error 1
swarm-zombie runs the deploy with INFINITO_APP_DISCOVERY_RUNNER=host, so the README
replay step calls compose-up on the act runner, and that image ships no gettext. A
two-variable substitution took the whole deploy down with it, and the failure reads
like a broken host rather than a missing package.
string.Template.safe_substitute handles ${VAR} exactly as envsubst does for this
template, including the four bare `$` that end the match lines -- both leave them
alone. The rendered Corefile is byte-identical, checked over three re-renders, and
getent resolves inside the deploy container afterwards.
The concurrency property from f3826589f2 is untouched: the render still writes into
the descriptor from tempfile.mkstemp and lands through an atomic replace. The read
goes through utils.cache.files.read_text, as the uncached-read lint requires.
Nothing else in the replay step failed -- one traceback, one raise, one message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
83c1c729de
commit
00478b8ead
1 file changed
+12
-27
@@ -2,31 +2,34 @@ from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from string import Template
|
||||
|
||||
from utils.cache.files import read_text
|
||||
from utils.env.parser import parse_static_env
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CoreDNSCorefileRenderer:
|
||||
"""
|
||||
Render compose/coredns/Corefile from compose/coredns/Corefile.tmpl using envsubst.
|
||||
Render compose/coredns/Corefile from compose/coredns/Corefile.tmpl.
|
||||
|
||||
What this does:
|
||||
- Reads variables from env file (default: .env, generated from default.env)
|
||||
- Runs `envsubst` to substitute variables into the Corefile template
|
||||
- Substitutes ``${VAR}`` placeholders into the Corefile template
|
||||
- Writes the output atomically (tmp -> rename)
|
||||
- Optionally prints a preview of the first N lines
|
||||
|
||||
Substitution runs in-process rather than through envsubst: the act runner and
|
||||
the nested deploy containers do not all ship gettext, and a missing binary
|
||||
turned a two-variable substitution into a failed deploy.
|
||||
|
||||
Hard guarantees:
|
||||
- Fails if template/env files are missing
|
||||
- Fails if output path exists and is a directory
|
||||
- Fails if output directory cannot be created or is not writable
|
||||
- Fails if envsubst is missing
|
||||
- Fails if rendered file is empty
|
||||
"""
|
||||
|
||||
@@ -50,15 +53,6 @@ class CoreDNSCorefileRenderer:
|
||||
if not path.is_file():
|
||||
raise RuntimeError(f"{label} is not a file: {path}")
|
||||
|
||||
def _require_envsubst(self) -> str:
|
||||
p = shutil.which("envsubst")
|
||||
if not p:
|
||||
raise RuntimeError(
|
||||
"envsubst not found. Install gettext-base (Ubuntu/Debian) or gettext (Arch)."
|
||||
)
|
||||
self._log(f"Using envsubst: {p}")
|
||||
return p
|
||||
|
||||
def _ensure_output_parent(self, out_file: Path) -> None:
|
||||
parent = out_file.parent
|
||||
|
||||
@@ -110,7 +104,6 @@ class CoreDNSCorefileRenderer:
|
||||
self._require_file(tmpl_file, label="template file")
|
||||
self._require_output_target(out_file)
|
||||
self._ensure_output_parent(out_file)
|
||||
envsubst = self._require_envsubst()
|
||||
|
||||
env = self._load_env_file(env_file)
|
||||
|
||||
@@ -119,19 +112,11 @@ class CoreDNSCorefileRenderer:
|
||||
)
|
||||
tmp_file = Path(tmp_name)
|
||||
|
||||
self._log("Rendering Corefile via envsubst (atomic write)")
|
||||
self._log("Rendering Corefile (atomic write)")
|
||||
try:
|
||||
with (
|
||||
tmpl_file.open("r", encoding="utf-8") as fin,
|
||||
os.fdopen(fd, "w", encoding="utf-8") as fout,
|
||||
):
|
||||
subprocess.check_call(
|
||||
[envsubst],
|
||||
stdin=fin,
|
||||
stdout=fout,
|
||||
env=env,
|
||||
cwd=self.repo_root,
|
||||
)
|
||||
rendered = Template(read_text(tmpl_file)).safe_substitute(env)
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as fout:
|
||||
fout.write(rendered)
|
||||
|
||||
size = tmp_file.stat().st_size
|
||||
if size == 0:
|
||||
|
||||
Reference in new issue
Block a user