-
Notifications
You must be signed in to change notification settings - Fork 0
Environment Variable Interpolation
Any string value anywhere in a config file — including inside the otherwise-unvalidated kubernetes/job passthrough sections — can reference an environment variable from the shell squarepeg is running in.
namespace: "${SQUAREPEG_NAMESPACE:-my-namespace}"
kubernetes:
spec:
serviceAccountName: "${SERVICE_ACCOUNT:-squarepeg}"
imagePullSecrets:
- {name: "${REGISTRY_SECRET}"}
volumes:
refdata:
persistentVolumeClaim: {claimName: "${REFDATA_PVC}", readOnly: true}| Form | Behaviour |
|---|---|
${VAR} |
Substitutes the value of $VAR. Hard error if VAR is not set, naming the variable, the exact key path (e.g. kubernetes.spec.imagePullSecrets[0].name), and the config file it came from. |
${VAR:-default} |
Substitutes $VAR if it is set and non-empty, else the literal default. Never errors. |
$$ |
A literal $. |
$VAR (no braces) |
Not a reference — passed through completely untouched. |
A handful of things worth knowing:
-
Multiple references in one string all resolve:
"${A}-${B}","job-${RUN_TAG}". -
The bash
:-nuance applies: a variable set to the empty string counts as unset for${VAR:-default}purposes, same as bash. This is the single most surprising part of the syntax if you've not hit it before —MYVAR=""will still fall through todefault. -
A default is a literal, never re-scanned.
${VAR:-ghcr.io/foo/bar:1.2}works fine (colons and slashes in the default are just text) — and critically, if$VAR's value happens to contain${OTHER}-looking text, it is never itself expanded. One substitution pass, no recursion. -
Malformed references are a hard error, not silently passed through:
${},${1BAD},${A B}, an unterminated${VAR— all raise, naming the bad token. This matches squarepeg's fail-fast posture everywhere else (missing config files, unknown config keys, unsupported flags all raise rather than degrade quietly) — a${NAMSEPACE}-class typo is exactly the thing this feature exists to catch.
Interpolation happens per config file, immediately after parsing and before validation — before files are merged together, and before profile selection. Two consequences worth knowing:
- Error messages name the exact file, since interpolation happens before layers get flattened together.
-
It's eager over the whole document, including
profiles.*bodies you haven't selected with--profile. A${VAR}reference inside an unused profile is still a hard error on every invocation. This matches how squarepeg already validates every profile's schema regardless of selection — see Configuration → Profiles.
Substitution always produces a Python string — it never tries to guess that ${N} should become an integer just because N happens to hold "3". squarepeg's own settings that aren't naturally strings (timeout, quiet, cleanup, allow_host_path_mounts) are explicitly coerced from a string if needed (e.g. timeout: "${TIMEOUT:-300}" works fine, and cleanup: "${CLEANUP:-false}" correctly becomes the boolean False, not the string "false").
This coercion does not extend into the kubernetes/job passthrough sections. An integer or boolean field there — job.spec.backoffLimit, a container's readOnly — must stay literal YAML (backoffLimit: 3), not an interpolated string (backoffLimit: "${N}"), or the apiserver will reject the manifest. --dry-run shows you the quoted value immediately if this happens, so the failure is cheap to diagnose.
Both squarepeg config (aka squarepeg config show) and --dry-run print resolved (post-interpolation) values — that's the entire point, showing you what will actually be used. This means:
If a config value resolves from a secret-bearing environment variable, it will appear in that output. Treat
config/--dry-runoutput the same way you'd treat a shell history containing secrets — don't paste it into a bug report or a shared terminal recording without checking first.
squarepeg does not redact anything, and there's no plan to guess which env vars are "sensitive" by name pattern — that would give false confidence while missing the ones that matter. For real secrets, the better mechanism is to reference a Kubernetes Secret directly from kubernetes.spec (e.g. via envFrom/secretKeyRef), which never passes through squarepeg's own process at all.
| You write |
MYVAR is... |
Result |
|---|---|---|
${MYVAR} |
"prod" |
prod |
${MYVAR} |
unset | error |
${MYVAR:-staging} |
"prod" |
prod |
${MYVAR:-staging} |
unset | staging |
${MYVAR:-staging} |
"" (set but empty) |
staging |
$$ |
(anything) |
$ (literal) |
$MYVAR |
(anything) |
$MYVAR (untouched — no braces, not a reference) |