Skip to content

Accept ${VAR} references in typed stacker.yml fields - #292

Merged
vsilent merged 2 commits into
devfrom
fix/replicas-env-reference
Oct 16, 2026
Merged

vsilent merged 2 commits into
devfrom
fix/replicas-env-reference

Conversation

@vsilent

@vsilent vsilent commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

POST /api/v1/deploy/validate reads stacker.yml without filling variables. A stack with

deploy:
  replicas: ${OLLAMA_CPU_REPLICAS}

(local-ai-packaged) was refused with replicas must be a whole number, 0 or more, so its Quick Deploy could not start. CLI commands that read stacker.yml raw and write it back failed the same way. This came in with ff95ebd9 (typed deploy.replicas).

A sweep that wrote ${X} into every value of a full stacker.yml found more:

  • with the CLI, after filling, every boolean and number failed on the string it gets (privileged: ${PRIV} with PRIV=false: invalid type: string "false", expected a boolean);
  • in validation, port mappings with a variable ("${APP_PORT:-8080}:80", "127.0.0.1:${DB_PORT}:5432", public_ports: ["${PUBLIC_PORT}/tcp"]) failed E007/E005.

Changes

  1. 91319659: deploy.replicas and a device count are a number or a ${VAR} reference (${NAME}, ${NAME:-1} and the other compose modifiers, $NAME). Read raw, the reference is kept as written, both in the generated compose and when stacker.yml is written back. Filled, it is the number. Anything else is still refused. A server-rendered compose (project app) can only carry a known number, so a reference is left out there.
  2. b22303c5:
    • 17 boolean and number fields also read "true"/"false" and a whole number written as a string;
    • port validation lets a ${VAR} part through and still checks the rest of the entry (range, protocol).

Still refused when unfilled: a boolean, a number other than replicas or count, or a fixed-choice value (app.type, restart, deploy.target and so on) written as ${VAR}. Its type cannot be known until the deploy fills it. No stack in stacker-project-examples does this.

Tests

The new tests failed before the fix:

  • oneclick_deploy::replicas_from_an_env_reference_is_valid;
  • stack_service_runtime_fields_contract (3 new);
  • stacker_yml_variable_references (4 new).

Results:

  • cargo nextest run -E 'not binary(bdd)': 2883 passed, 36 skipped;
  • cargo test --test bdd: 334 scenarios passed.

cargo clippy -- -D warnings fails on the existing build.rs:37. Without -D warnings it reports 86 warnings, all in code this PR does not touch.

Contract mirror updated (deploy.replicas and count may be a reference).

🤖 Generated with Claude Code

https://claude.ai/code/session_012TKZy6f5shPzM9WLdswH6f

robotizeit and others added 2 commits October 16, 2026 12:46
local-ai-packaged writes replicas: ${OLLAMA_CPU_REPLICAS}. POST
/api/v1/deploy/validate reads stacker.yml without filling variables, so
the reference arrived as the literal string and was refused with
"replicas must be a whole number, 0 or more". The same happened to every
CLI command that reads stacker.yml raw and writes it back.

A count is now a number or a ${VAR} reference (${NAME}, ${NAME:-1} and
the other compose modifiers, $NAME). Read raw, the reference is kept and
written to the generated compose and back to stacker.yml as is; filled,
it is the number. Anything else is still refused. A server-rendered
compose only carries a known number, so a reference is left out there.

Tests (fail before the fix): oneclick_deploy
replicas_from_an_env_reference_is_valid, stack_service_runtime_fields
a_count_written_as_a_variable_reference_is_kept. nextest 2879 passed,
36 skipped; BDD 334 scenarios passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012TKZy6f5shPzM9WLdswH6f
The CLI fills ${VAR} before it reads stacker.yml, and the filled value is
a string. Every boolean and number refused it: privileged: ${PRIV} with
PRIV=false failed with "invalid type: string \"false\", expected a
boolean", and the same for healthcheck.retries, proxy.auto_detect,
deploy.server.port, ai.enabled/timeout, monitoring.status_panel,
metrics.enabled/telegraf, alerts.interval/on_recovery, hooks.notify and
the pipe retry settings. They now also read "true"/"false" and a whole
number written as a string; anything else is still refused.

Deploy validation reads stacker.yml without filling variables. A port
mapping with a variable in it ("${APP_PORT:-8080}:80",
"127.0.0.1:${DB_PORT}:5432") or a public port "${PUBLIC_PORT}/tcp" failed
E007/E005. The variable part is now let through and the rest of the
entry is still checked.

Still refused when unfilled: a boolean, a number other than replicas or a
device count, or a fixed-choice value written as ${VAR}.

Tests (fail before the fix): stacker_yml_variable_references. nextest
2883 passed, 36 skipped; BDD 334 scenarios passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012TKZy6f5shPzM9WLdswH6f
@vsilent
vsilent merged commit 1c3b8de into dev Oct 16, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants