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
22 changes: 19 additions & 3 deletions backend/druks/setup_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
"REDIS_URL",
"DRUKS_AUTH_HEADER",
"DEFAULT_HOST_PROVIDER",
"REGISTRY_HOST",
"REGISTRY_USERNAME",
"REGISTRY_PASSWORD",
"TEMPLATE_REPOSITORY",
"SERVICE_TOKENS",
"DRUKS_AUTH_MODE",
"DRUKS_AUTH_JWKS_URL",
Expand Down Expand Up @@ -84,6 +88,10 @@
"service_url",
"service_token",
"image",
"registry_host",
"registry_username",
"registry_password",
"template_repository",
"proxy_url",
"issuer_url",
"browser_login_proxy",
Expand Down Expand Up @@ -200,6 +208,13 @@ def run_setup(
service_url = ""
service_token = ""
image = ""
# Access to private sandbox images on one registry host, for example ghcr.io.
registry_host = ""
registry_username = ""
registry_password = ""
# The repository path on that host where drukbox publishes sandbox templates.
# The exe provider requires it.
template_repository = ""
# The secrets proxy, at the address a sandbox dials. A sandbox sends its HTTPS
# through it. The docker shape uses the Docker bridge gateway. A remote shape
# names the address of this host that its sandboxes reach, for example the
Expand Down Expand Up @@ -250,9 +265,6 @@ def _fresh_values(*, provider: str, home: str) -> tuple[tuple[tuple[str, ...], s
(("sandbox", "service_url"), "http://127.0.0.1:8780"),
(("sandbox", "service_token"), _hex_secret()),
(("sandbox", "exe", "EXE_API_TOKEN"), ""),
(("sandbox", "exe", "EXE_IMAGE_REGISTRY"), ""),
(("sandbox", "exe", "EXE_REGISTRY_USERNAME"), ""),
(("sandbox", "exe", "EXE_REGISTRY_PASSWORD"), ""),
(("sandbox", "exe", "TAILSCALE_TAILNET"), ""),
(("sandbox", "exe", "TAILSCALE_OAUTH_CLIENT_ID"), ""),
(("sandbox", "exe", "TAILSCALE_OAUTH_CLIENT_SECRET"), ""),
Expand Down Expand Up @@ -424,6 +436,10 @@ def _render_env(
(
("DEFAULT_HOST_PROVIDER", provider),
("SERVICE_TOKENS", service_tokens),
("REGISTRY_HOST", _get_string(config, ("sandbox", "registry_host"))),
("REGISTRY_USERNAME", _get_string(config, ("sandbox", "registry_username"))),
("REGISTRY_PASSWORD", _get_string(config, ("sandbox", "registry_password"))),
("TEMPLATE_REPOSITORY", _get_string(config, ("sandbox", "template_repository"))),
("SECRETS_KEY", _get_string(config, ("secrets", "drukbox_secrets_key"))),
("SECRETS_PROXY_URL", proxy_url),
# The proxy binds the address sandboxes dial and nothing else.
Expand Down
40 changes: 16 additions & 24 deletions backend/tests/test_setup_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ def test_fresh_exe_render_matches_the_deployment_contract(tmp_path):
assert config["paths"]["harness_config_root"] == values["DRUKS_HARNESS_CONFIG_ROOT"]
assert "EXE_API_TOKEN" not in values
assert "TAILSCALE_TAILNET" not in values
for key in ("EXE_IMAGE_REGISTRY", "EXE_REGISTRY_USERNAME", "EXE_REGISTRY_PASSWORD"):
assert config["sandbox"]["exe"][key] == ""
assert key not in values
assert len(config["secrets"]["postgres_password"]) == 64
assert len(config["secrets"]["drukbox_secrets_key"]) == 44
assert len(config["sandbox"]["service_token"]) == 64
Expand Down Expand Up @@ -581,36 +578,31 @@ def test_a_copy_of_a_secrets_setting_is_a_named_gap(tmp_path, assignment, gap):
assert "SECRETS_PROXY_URL" not in values


@pytest.mark.parametrize("repository", ["ghcr.io/acme/templates", "docker.io/acme/templates"])
def test_exe_template_registry_uses_existing_provider_contract(tmp_path, repository):
@pytest.mark.parametrize("provider", ["docker", "exe"])
def test_registry_settings_render_for_drukbox_on_every_provider(tmp_path, provider):
env_path = tmp_path / ".env"
printed = []
assert (
_run(
env_path,
print_fn=printed.append,
set_values=(
"sandbox.proxy_url=http://100.64.0.10:8880",
"sandbox.exe.EXE_API_TOKEN=exe-token",
"sandbox.exe.TAILSCALE_TAILNET=tail.ts.net",
f"sandbox.exe.EXE_IMAGE_REGISTRY={repository}",
"sandbox.exe.EXE_REGISTRY_USERNAME=builder",
"sandbox.exe.EXE_REGISTRY_PASSWORD=registry-token",
),
)
== 0
_run(
env_path,
provider=provider,
print_fn=printed.append,
set_values=(
"sandbox.registry_host=ghcr.io",
"sandbox.registry_username=builder",
"sandbox.registry_password=registry-token",
"sandbox.template_repository=acme/templates",
),
)
values = read_env(env_path)
expected = {
"EXE_IMAGE_REGISTRY": repository,
"EXE_REGISTRY_USERNAME": "builder",
"EXE_REGISTRY_PASSWORD": "registry-token",
"REGISTRY_HOST": "ghcr.io",
"REGISTRY_USERNAME": "builder",
"REGISTRY_PASSWORD": "registry-token",
"TEMPLATE_REPOSITORY": "acme/templates",
}
for key, value in expected.items():
assert values[key] == value
assert env_path.read_text().count(f"{key}=") == 1
assert "REGISTRY_HOST" not in values
assert "TEMPLATE_REPOSITORY" not in values
assert "registry-token" not in "\n".join(printed)
assert stat.S_IMODE(env_path.stat().st_mode) == 0o600
assert stat.S_IMODE((tmp_path / "druks.toml").stat().st_mode) == 0o600
Expand Down
4 changes: 3 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ host-run development template for that environment plane.
| `[urls]` | Dashboard callback base URL and public webhook hostname |
| `[secrets]` | Generated deployment secrets |
| `[paths]` | Host data and harness configuration paths |
| `[sandbox]` | Drukbox provider, service URL and token, image override, and the proxy and issuer addresses |
| `[sandbox]` | Drukbox provider, service URL and token, image override, registry access, and the proxy and issuer addresses |
| `[sandbox.<provider>]` | Provider environment passed through to the remote stack |
| `[env]` | Additional deployment environment settings rendered verbatim |

Expand Down Expand Up @@ -606,6 +606,8 @@ before provisioning a VM if its selected credential is missing.
| `sandbox.service_token` | Drukbox API token |
| `sandbox.timeout` | Control-plane request timeout. The default is 180 seconds |
| `sandbox.image` | Optional provider image override |
| `sandbox.registry_host`, `sandbox.registry_username`, `sandbox.registry_password` | Access to private sandbox images on one registry host, for example `ghcr.io`. Set the three together. See [Drukbox](https://github.com/czpython/drukbox/blob/main/docs/deploy.md#private-image-registry) |
| `sandbox.template_repository` | The repository path on that host where Drukbox publishes sandbox templates. The exe provider requires it |
| `sandbox.proxy_url` | The secrets proxy, at the address a sandbox dials. The docker shape sets `http://172.17.0.1:8880`. docker-sbx leaves it empty |
| `sandbox.issuer_url` | The issuer base URL the secrets exchange dials. The default is `http://127.0.0.1:8001`. For a Drukbox on another server, set the address of the Druks host that Drukbox reaches. The installer then serves the issuer route there ([the issuer listener](deployment.md#the-issuer-listener)) |
| `sandbox.browser_login_proxy` | Login-window egress proxy. An empty value keeps the box IP |
Expand Down
Loading