Variable Substitution
How ${VAR} and ${VAR:-default} placeholders in Compose bundles are resolved when a per-env docker-compose.yml is rendered.
Placeholder syntax
Supported inside Compose blocks and .env files:
| Syntax | Meaning |
|---|---|
|
Value of |
|
Value of |
${VAR:?err} and ${VAR:+value} are not used inside this library. If you need them, keep them confined to a single Compose file and resolve before serviceComposer sees them.
Resolution order
utils.substituteVariables(text, binding) walks the binding in this order — the first match wins:
-
Service-specific env:
resources/services/<service>/.envor.env.<db>(whichever matches the DB of the env being rendered). -
Global env:
resources/services/.env.<db>. -
Template default: the
:-defaultfallback inside the Compose block.
Example: rendering ${MYSQL_PORT:-3306} for an env with DATABASE_TYPE=mysql:
| Priority | Source | Outcome |
|---|---|---|
1 |
|
|
2 |
|
|
3 |
neither file sets it |
|
Overrides collected from the Jenkins GLOBAL_VARIABLES / SERVICE_VARIABLES parameters are applied as-if they lived in the corresponding .env file — the same precedence chain applies.
Recursion and nesting
substituteVariables is recursive with a bounded depth. You can reference a variable that itself contains another placeholder:
FTACS_DB_HOST=${DB_HOST:-mysql}
JDBC_URL=jdbc:mysql://${FTACS_DB_HOST}:3306/ftacs
Unbounded recursion (a variable that references itself) triggers a hard fail rather than an infinite loop.
Sensitive values
Two distinct mechanisms exist, and they do not match the same set of names. Know which one you are relying on.
utils.isSensitiveVariable(name) tests a variable name against QAConfig.Validation.SENSITIVE_PATTERN ((?i).(PASSWORD|SECRET|TOKEN|_KEY|API_KEY|CREDENTIAL).). The pattern is unanchored, so it also catches names that merely contain a token — FT_UI_BACKEND_KEYWORD matches on _KEY. Use it to gate logging in new helpers.
utils.maskSensitiveValues(text) redacts values inside a blob of text using its own inline regex — not SENSITIVE_PATTERN. It anchors on the separator, so a token must be immediately followed by = or :; that is why it carries KEYWORD as an explicit token and why SSL_KEY_STORE=<path> is deliberately left alone. It handles both the .env form KEY=value and the JSON form "KEY":"value" used by the SERVICE_VARIABLES payload, and its tail stops at a line break so a multi-line .env blob is masked key by key. The replacement always renders as KEY=[REDACTED], so a redacted JSON entry appears as "KEY=[REDACTED]" — the value is gone, only the JSON punctuation looks odd.
Mask form is [REDACTED]. The only caller today is applyServiceVarsOverlay, which logs the head of the SERVICE_VARIABLES payload and the post-write verify diagnostics.
Masking covers the Jenkins console and .qa-build.log only. SERVICE_VARIABLES is a build parameter, so its submitted value is also visible in plain text on the build’s Parameters page — see Known Issues.
|
Rewriting .env files losslessly
utils.updateEnvVariables(envContent, varsToUpdate):
-
Preserves comments and blank lines.
-
Preserves original ordering of keys.
-
Updates
KEY=valin place for keys present invarsToUpdate. -
Pre-processes lines glued by the Active Choices DRP by calling
utils.splitConcatenatedAssignmentsfirst — see trailing-comma trap.
utils.parseEnvFileComments is the underlying helper; it is @NonCPS and returns a structure you can toBasic-ify before persisting.
Extracting keys
utils.extractEnvKeys(content) returns an ordered Set<String> of keys. Used by applyServiceVarsOverlay to know which keys in the user-submitted overlay already exist in the target file (those are updated) versus which are new (those are appended).
Rendering flow for a single env
-
Pick
DATABASE_TYPE→ decide which bundle (compose-mysql.yml/compose-oracle.yml) and which.env.<db>to read. -
Pick
SELECTED_SERVICES→ filter the bundle to just those services. Stripprofiles:from each kept service (commitcc8566b). -
Build the binding:
-
Parse
resources/services/.env.<db>→ global map. -
For each selected service, parse
<service>/.env.<db>or.env→ overlay onto the global map (service-specific wins). -
Overlay any
GLOBAL_VARIABLES/SERVICE_VARIABLESsubmitted by the operator.
-
-
utils.substituteVariableswalks the Compose text and resolves every placeholder. -
Write the rendered file to
MASTER_ENVS_DIR/<env>/docker-compose.yml.
See vars/serviceComposer.groovy for the actual implementation.