Architecture
How the library fits together: a Groovy shared library loaded by the Jenkins master, a consolidated Compose bundle per database flavour, and a runner-agnostic deploy model that moves rendered artifacts from master to agent via stash / unstash.
Repository layout
| Path | Responsibility |
|---|---|
|
Pipeline steps and shared helpers (see vars index). |
|
Central paths / limits / RBAC / validation (see Configuration). |
|
Consolidated Compose bundles — one entry per service per bundle. Per-env compose files are derived from these. |
|
Service-specific |
|
Dependency graph consumed by |
|
Legacy per-service |
|
Antora documentation (this site). |
Pipeline-step pattern
Every vars/*Step.groovy file exports a call(Map config = [:]) function. Most jobs use a "first-run handler" to configure Active Choices parameters on the first build and branch into the real work on subsequent builds:
def call(Map config = [:]) {
ansiColor('xterm') {
utils.logInit()
if (!params.containsKey('ENV_NAME')) {
setupParameters() // properties([parameters([...])])
echo "Parameters configured. Refresh and run 'Build with Parameters'."
currentBuild.result = 'SUCCESS'
return
}
try {
// real work: render → stash → deploy → update meta.json
} catch (err) {
utils.archiveLogOnFailure()
utils.failureHints(err.message, [...], [...])
throw err
}
}
}
The try / catch is present in every main *Step.groovy so failures still archive .qa-build.log as a build artifact (see Logging DSL).
Stash / unstash — how master talks to agents
Direct filesystem access between master and runner is not used. Each deploy scopes a stash on the master and an unstash on the runner:
// Master
node('built-in') {
dir(envDir) {
stash name: 'env-config', includes: '**/*'
}
}
// Runner
node(runner) {
unstash 'env-config'
sh 'docker compose up -d'
}
Consequences you have to respect:
-
Anything needed on the runner has to be included in the stash.
-
utils.exists()is a@NonCPSnew File(…).exists()— it runs on the master and lies about files that only exist on the runner. UsefileExistsinsidenode(runner) { … }blocks. -
Port-conflict checks (
ss -H -ltn) run on the runner beforedocker compose up -d.
Active Choices UI
All reactive parameter forms (services picker, dep-graph, SERVICE_VARIABLES, GLOBAL_VARIABLES, OWNERS_LIST, FINAL_PREVIEW, etc.) are built by vars/qaUiScripts.groovy at pipeline-load time and consumed by the Active Choices plugin at parameter-render time.
Key rules:
-
Every method on
qaUiScriptsreturns aString— the body of an Active Choices parameter. -
That body runs inside the Active Choices sandbox, where the shared library is not available. Do not call
utils.orqaUiScripts.from inside the returned string. -
Any slashy regex in the returned body is double-escaped at source (
/\\s+/becomes/\s+/after the outer Groovy parser processes the triple-quoted literal).
See qaUiScripts — Build-time Active Choices Factory for the method catalogue and Active Choices — Patterns and Pitfalls for the DRP / CascadeChoice usage patterns.
Logging discipline
Every *Step.groovy goes through the small logging DSL in utils.groovy (logInit / stage / step / ok / warn / err / detail / group / silentSh). See Logging DSL for the full table and the quiet / verbose split driven by QA_LOG_VERBOSE.
CPS and serialisation
The pipeline runs in the Groovy CPS sandbox, not plain Groovy. Any Java / Groovy API that is not CPS-safe needs @NonCPS. In practice this covers file I/O, YAML / JSON parsing (SnakeYAML returns LazyMap which is not serialisable), regex matchers, and closure-heavy collection transforms. See CPS Limitations.