Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions dbuild/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ def _enrich_metadata(cfg: Config, community_override: str | None = None) -> dict
name = e["name"]
val = e["default"]

ref = None
if cfg.compose_text:
ref = re.fullmatch(r"\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}", str(val or ""))
if ref:
Expand All @@ -734,9 +735,22 @@ def _enrich_metadata(cfg: Config, community_override: str | None = None) -> dict
# STUMP_TRUST_PROXY_HEADERS="" and crash-loops on AppJail only.
if e.get("docs_only") and not val:
item["unset"] = True
# The README shows the value; the jail's .env keeps the reference, or
# it loses the link: grimmory's DATABASE_PASSWORD=${DB_PASSWORD} went
# out as "grimmory" while mariadb got the password fjord generated.
if ref:
item["ref"] = ref.group(0)
if name in ["PUID", "PGID", "TZ"]:
item["placeholder"] = f"@{name}@"
context["env"].append(item)
# Only a reference the .env can answer, to another variable: one it
# doesn't carry expands to nothing, and TZ=${TZ:-Etc/UTC} names itself.
carried = {i["name"] for i in context["env"] if not i.get("unset")}
for i in context["env"]:
ref = i.pop("ref", "")
target = re.match(r"\$\{([A-Za-z_][A-Za-z0-9_]*)", ref).group(1) if ref else ""
if target and target != i["name"] and target in carried:
i["env_ref"] = ref

# Volumes
vol_docs = {str(k): v for k, v in docs.get("volumes", {}).items()}
Expand Down
2 changes: 1 addition & 1 deletion dbuild/templates/appjail.env.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

DIRECTOR_PROJECT={{ name }}
{%- for e in env if not e.unset %}
{{ e.name }}={{ e.placeholder if (render_mode is defined and render_mode == "mkdocs" and e.placeholder) else e.default }}
{{ e.name }}={{ e.placeholder if (render_mode is defined and render_mode == "mkdocs" and e.placeholder) else (e.env_ref or e.default) }}
{%- endfor %}
{%- if render_mode is defined and render_mode == "deploy" %}
{%- for p in ports %}
Expand Down
27 changes: 27 additions & 0 deletions tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,33 @@ def test_jail_is_named_as_the_compose_service(self):
director = (Path(d) / "out" / "appjail-director.yml").read_text()
self.assertIn("\n radicale:\n name: radicale\n", director)

def test_jail_env_keeps_a_reference_to_another_variable(self):
# grimmory: DATABASE_PASSWORD=${DB_PASSWORD} went into the jail's .env
# as example.env's "grimmory" while mariadb got the password fjord
# generated -- access denied. The README still shows the value.
compose = (
self.COMPOSE.replace(' TZ: "Timezone"\n', ' TZ: "Timezone"\n DB_PASSWORD: "Database password"\n')
.replace(" - TZ=UTC\n", " - TZ=${TZ:-UTC}\n - DATABASE_PASSWORD=${DB_PASSWORD}\n - DB_PASSWORD=${DB_PASSWORD}\n - LOST=${NOT_IN_ENV}\n")
+ " db:\n image: ghcr.io/daemonless/mariadb:latest\n"
) # a stack: a single image's values are never resolved
with tempfile.TemporaryDirectory() as d:
repo = Path(d) / "app"
(repo / ".daemonless").mkdir(parents=True)
(repo / "compose.yaml").write_text(compose)
(repo / "example.env").write_text("DB_PASSWORD=app\nNOT_IN_ENV=x\n")
(repo / "Containerfile.j2").write_text(CONTAINERFILE_J2)
with _chdir(repo):
cfg = dbuild_config.load(repo)
outputs, errors = docs.render_generated(cfg, argparse.Namespace(community=None), repo)
docs.generate_appjail_files(cfg, Path(d) / "out")
env = next(p for p in (Path(d) / "out").iterdir() if p.name.endswith("env")).read_text()
self.assertEqual(errors, [])
self.assertIn("DATABASE_PASSWORD=${DB_PASSWORD}\n", env)
self.assertIn("DB_PASSWORD=app\n", env, "the variable it names keeps its value")
self.assertIn("TZ=UTC\n", env, "a variable never names itself")
self.assertNotIn("${NOT_IN_ENV}", env, "a name the .env doesn't carry keeps the value")
self.assertIn("| `DATABASE_PASSWORD` | `app` |", outputs["README.md"], "the README table shows the value")

@contextlib.contextmanager
def _set_env(key: str, value: str):
prev = os.environ.get(key)
Expand Down
Loading