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.

High-level components

components

Repository layout

Path Responsibility

vars/

Pipeline steps and shared helpers (see vars index).

src/com/qa/config/QAConfig.groovy

Central paths / limits / RBAC / validation (see Configuration).

resources/services/compose-mysql.yml
resources/services/compose-oracle.yml

Consolidated Compose bundles — one entry per service per bundle. Per-env compose files are derived from these.

resources/services/<service>/

Service-specific .env / .env.<db> defaults, Dockerfiles, bind-mount contents.

resources/schemas/dependencies.yaml

Dependency graph consumed by utils.findMissingMandatoryDeps and the Active Choices UI.

resources/templates/

Legacy per-service .yaml layout — kept only for ft-qoe-web. New services should not go here.

docs/

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).

Data flow

flow

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 @NonCPS new File(…​).exists() — it runs on the master and lies about files that only exist on the runner. Use fileExists inside node(runner) { …​ } blocks.

  • Port-conflict checks (ss -H -ltn) run on the runner before docker 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 qaUiScripts returns a String — 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. or qaUiScripts. 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.

External dependencies

  • Jenkins 2.528.2 / Groovy 2.4.21 / Java 21 runtime.

  • Docker Compose V2 on every agent (docker compose, not docker-compose).

  • Harbor registry at hub.friendly-tech.com, credential ID harbor-cred (see QAConfig.Registry).

  • yq on the runner for compose manipulation.