mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
* refactor(helm): derive the postgres Secret name from one helper `databaseUrlSecret` re-encoded the `postgresql.external.existingSecret` then `postgresql.existingSecret` chain in its own `if`/`else if`. Three consumers read it — the gateway's `DATABASE_URL`, the postgres StatefulSet's `POSTGRES_PASSWORD`, and `postgres-secret.yaml`'s own `metadata.name` — so a source added to one helper and not the other would make the gateway and the StatefulSet resolve different Secrets. That is the class of bug #7149 fixed for the redis pair. Extract `databaseUserSecret` as the single encoding of the chain and let `databaseUrlSecret` fall back to the chart-managed name when it is empty, mirroring `redisUserSecret` / `redisUrlSecret`. Pure refactor: the rendered manifest is byte-identical before and after across the four configurations (chart-managed, `existingSecret`, `external.existingSecret`, and both set), compared with `helm template`. Adds `test_helm_database_secret_precedence.py`, which pins that both consumers name the same Secret and that only the chart-managed branch renders a Secret. Dropping either precedence source fails the two cases that depend on it. Suggested in review of #7161. * fix(helm): keep the postgres Secret helper's truthiness Review follow-up on the previous commit. Two things were still wrong: `postgres-secret.yaml` kept its own copy of the precedence chain for the render gate, so "the only place the chain is encoded" was not yet true — a source added to `databaseUserSecret` alone would have left that gate empty and rendered an unreferenced `<release>-postgres` Secret. It now reads the helper, like `redis-secret.yaml` already did. `or` returns its last operand even when it is falsy, so `postgresql.existingSecret: false` (how people spell "not set") named the Secret `false` instead of falling back to the chart-managed one. Chaining `default` keeps the old if/else-if truthiness and yields an empty string for any falsy value; the gateway's `$pgConfigured` now reads the same helper rather than re-listing the two keys. Two falsy cases join the parametrize list: `false` in the lower-precedence slot and in both slots. Both fail against the previous `or` version with `assert [] == [False]` — the Secret literally named `false` — so they are regressions this test can actually catch. The other four configurations render byte-identically before and after. * fix(helm): keep the redis user-Secret helper's truthiness Same defect the postgres helper had, one release later: `redisUserSecret` used `or`, which returns its last operand even when falsy, so `redis.existingSecret: false` named both the gateway's `redis-url` ref and the StatefulSet's `redis-password` ref after a Secret called `false`. The render gate in `redis-secret.yaml` saw the string "false" as truthy, so it skipped creating the chart-managed Secret — leaving two dangling refs and a StatefulSet that cannot start. `default` chaining restores the pre-`redisUserSecret` if/else-if behaviour: any falsy value yields an empty string, so the chart-managed branch is taken. `redisConfigured` now reads the helper instead of re-listing the two keys, matching the gateway's `$pgConfigured`. Verified against the four string-valued configurations (chart-managed, `existingSecret`, `external.existingSecret`, both): renders are byte-identical before and after. The two new falsy cases fail on the previous version, and `--basetemp` makes all 14 cases in this file pass. * docs(helm): drop a stray article in the helper comment
140 lines
8.1 KiB
Python
140 lines
8.1 KiB
Python
"""Bundled Redis and the Gateway must use the same Secret credentials."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
from urllib.parse import unquote, urlsplit
|
|
|
|
import pytest
|
|
import yaml
|
|
from support.shell import require_posix_sh
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
CHART = REPO_ROOT / "deploy" / "helm" / "deer-flow"
|
|
PASSWORD = "p a$$'\"@:/?#%[]*"
|
|
|
|
|
|
def _render_chart(*settings: str) -> list[dict]:
|
|
helm = shutil.which("helm")
|
|
if helm is None:
|
|
pytest.skip("helm is unavailable")
|
|
command = [helm, "template", "deer-flow", str(CHART)]
|
|
for setting in settings:
|
|
command.extend(["--set", setting])
|
|
rendered = subprocess.run(command, check=True, capture_output=True, text=True, encoding="utf-8").stdout
|
|
return [document for document in yaml.safe_load_all(rendered) if isinstance(document, dict)]
|
|
|
|
|
|
def _container(documents: list[dict], name: str) -> dict:
|
|
workload = next(document for document in documents if document.get("kind") in {"Deployment", "StatefulSet"} and document["metadata"]["name"].endswith(f"-{name}"))
|
|
return next(container for container in workload["spec"]["template"]["spec"]["containers"] if container["name"] == name)
|
|
|
|
|
|
def _secret_ref(container: dict, env_name: str) -> dict:
|
|
return next(item for item in container["env"] if item["name"] == env_name)["valueFrom"]["secretKeyRef"]
|
|
|
|
|
|
def _run_command(command: list[str], tmp_path: Path, password: str | None) -> list[str]:
|
|
"""Execute the rendered shell with argv-recording Redis executables."""
|
|
assert command[:2] == ["sh", "-c"]
|
|
for name in ("redis-server", "redis-cli"):
|
|
executable = tmp_path / name
|
|
executable.write_text(f"#!/bin/sh\nprintf '%s\\0' {name} \"$@\"\n", encoding="utf-8", newline="\n")
|
|
executable.chmod(0o755)
|
|
env = {key: value for key, value in os.environ.items() if key != "REDIS_PASSWORD"}
|
|
if password is not None:
|
|
env["REDIS_PASSWORD"] = password
|
|
# cd accepts native checkout paths in Git Bash, and pwd supplies its PATH form.
|
|
script = 'PATH="$(cd "$1" && pwd):$PATH"; export PATH; ' + command[2]
|
|
result = subprocess.run([require_posix_sh(), "-c", script, "_", tmp_path.as_posix()], env=env, check=True, capture_output=True, text=True, encoding="utf-8")
|
|
return result.stdout.rstrip("\0").split("\0")
|
|
|
|
|
|
@pytest.mark.parametrize("password", ["", PASSWORD])
|
|
def test_chart_managed_redis_credentials_match_the_gateway(password: str, tmp_path: Path) -> None:
|
|
documents = _render_chart(f"redis.auth.password={password}")
|
|
redis = _container(documents, "redis")
|
|
gateway_ref = _secret_ref(_container(documents, "gateway"), "DEER_FLOW_STREAM_BRIDGE_REDIS_URL")
|
|
secret = next(document for document in documents if document.get("kind") == "Secret" and document["metadata"]["name"] == gateway_ref["name"])
|
|
url = urlsplit(secret["stringData"][gateway_ref["key"]])
|
|
assert unquote(url.password or "") == password
|
|
assert url.hostname == gateway_ref["name"]
|
|
if password:
|
|
assert secret["stringData"]["redis-password"] == password
|
|
assert _secret_ref(redis, "REDIS_PASSWORD") == {"name": gateway_ref["name"], "key": "redis-password"}
|
|
else:
|
|
assert "redis-password" not in secret["stringData"]
|
|
assert "env" not in redis
|
|
expected_auth = ["--requirepass", password] if password else []
|
|
assert _run_command(redis["command"] + redis["args"], tmp_path, password or None) == ["redis-server", "--appendonly", "yes", *expected_auth]
|
|
for probe in ("readinessProbe", "livenessProbe"):
|
|
expected_auth = ["--no-auth-warning", "-a", password] if password else []
|
|
assert _run_command(redis[probe]["exec"]["command"], tmp_path, password or None) == ["redis-cli", *expected_auth, "ping"]
|
|
|
|
|
|
@pytest.mark.parametrize("password", [None, "", PASSWORD])
|
|
@pytest.mark.parametrize("ignored_password", ["", "ignored-values-password"])
|
|
def test_bundled_redis_uses_optional_existing_secret_password(password: str | None, ignored_password: str, tmp_path: Path) -> None:
|
|
documents = _render_chart("redis.existingSecret=managed-redis", f"redis.auth.password={ignored_password}")
|
|
redis = _container(documents, "redis")
|
|
assert _secret_ref(redis, "REDIS_PASSWORD") == {"name": "managed-redis", "key": "redis-password", "optional": True}
|
|
assert _secret_ref(_container(documents, "gateway"), "DEER_FLOW_STREAM_BRIDGE_REDIS_URL") == {"name": "managed-redis", "key": "redis-url"}
|
|
assert not any(document.get("kind") == "Secret" and document["metadata"]["name"].endswith("-redis") for document in documents)
|
|
if ignored_password:
|
|
# The stale value must not leak into the manifest: it is the only input
|
|
# here that a template edit could accidentally start rendering.
|
|
assert ignored_password not in yaml.safe_dump(documents)
|
|
expected_auth = ["--requirepass", password] if password else []
|
|
assert _run_command(redis["command"] + redis["args"], tmp_path, password) == ["redis-server", "--appendonly", "yes", *expected_auth]
|
|
for probe in ("readinessProbe", "livenessProbe"):
|
|
argv = _run_command(redis[probe]["exec"]["command"], tmp_path, password)
|
|
assert argv == ["redis-cli", *(["--no-auth-warning", "-a", password] if password else []), "ping"]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"settings",
|
|
[
|
|
("redis.existingSecret=false",),
|
|
("redis.external.existingSecret=false", "redis.existingSecret=false"),
|
|
],
|
|
ids=["existing-secret-false", "both-secrets-false"],
|
|
)
|
|
def test_falsy_existing_secret_still_uses_the_chart_managed_secret(settings: tuple[str, ...]) -> None:
|
|
# `false` is how people spell "not set" in values.yaml. `or` returns its
|
|
# last operand even when falsy, so it would name the Secret `false` — and
|
|
# nothing creates a Secret by that name, leaving both refs dangling.
|
|
documents = _render_chart(*settings, f"redis.auth.password={PASSWORD}")
|
|
gateway_ref = _secret_ref(_container(documents, "gateway"), "DEER_FLOW_STREAM_BRIDGE_REDIS_URL")
|
|
redis_ref = _secret_ref(_container(documents, "redis"), "REDIS_PASSWORD")
|
|
assert isinstance(gateway_ref["name"], str)
|
|
assert gateway_ref == {"name": redis_ref["name"], "key": "redis-url"}
|
|
assert redis_ref["key"] == "redis-password"
|
|
assert any(document.get("kind") == "Secret" and document["metadata"]["name"] == gateway_ref["name"] for document in documents)
|
|
|
|
|
|
@pytest.mark.parametrize("enabled", ["true", "false"])
|
|
def test_external_secret_precedence_is_unchanged(enabled: str) -> None:
|
|
# The existing helper gives external.existingSecret precedence in both modes.
|
|
documents = _render_chart(f"redis.enabled={enabled}", "redis.existingSecret=bundled-secret", "redis.external.existingSecret=external-secret")
|
|
assert _secret_ref(_container(documents, "gateway"), "DEER_FLOW_STREAM_BRIDGE_REDIS_URL") == {"name": "external-secret", "key": "redis-url"}
|
|
if enabled == "true":
|
|
assert _secret_ref(_container(documents, "redis"), "REDIS_PASSWORD") == {"name": "external-secret", "key": "redis-password", "optional": True}
|
|
else:
|
|
assert not any(document.get("kind") == "StatefulSet" and document["metadata"]["name"].endswith("-redis") for document in documents)
|
|
assert not any(document.get("kind") == "Secret" and document["metadata"]["name"].endswith("-redis") for document in documents)
|
|
|
|
|
|
@pytest.mark.parametrize("setting", ["redis.existingSecret=managed-external", "redis.external.redisUrl=redis://:password@managed.example:6380/1"])
|
|
def test_external_redis_does_not_deploy_a_bundled_instance(setting: str) -> None:
|
|
documents = _render_chart("redis.enabled=false", setting)
|
|
assert not any(document.get("kind") == "StatefulSet" and document["metadata"]["name"].endswith("-redis") for document in documents)
|
|
ref = _secret_ref(_container(documents, "gateway"), "DEER_FLOW_STREAM_BRIDGE_REDIS_URL")
|
|
if setting.startswith("redis.existingSecret="):
|
|
assert ref == {"name": "managed-external", "key": "redis-url"}
|
|
else:
|
|
secret = next(document for document in documents if document.get("kind") == "Secret" and document["metadata"]["name"] == ref["name"])
|
|
assert secret["stringData"] == {"redis-url": setting.split("=", 1)[1]}
|