Jenkins Shared Library Technical Deep-Dive

A walkthrough of the Jenkins Shared Library — the central build pipeline system that replaced the legacy ci-file-generator template approach. Covers the problem (per-project static Jenkinsfiles requiring regenerate-commit cycles), the solution (shared library with thin Jenkinsfiles and central configuration), the jenkins.yaml schema, the generated Jenkinsfile structure, and the internal library code. Includes Q&A on library branching strategies, multi-namespace deployment, and centralized bug exposure.

TL;DR

Legacy: ci-file-generator (Maven plugin) downloaded Jenkins file templates from a remote Git repo, substituted metadata.yaml values, and wrote two static files (Jenkinsfile.sandbox, Jenkinsfile) that had to be committed to every project for every template or config change. Never-ending regenerate-commit cycle.

Now: Jenkins Shared Library in a central repo. Each project has a thin Jenkinsfile (20–30 lines) that loads the library and calls buildServiceProject(). Library updates apply to all projects automatically on next build. Project config lives in jenkins.yaml at the repo root.

Key surfaces for developers: jenkins.yaml (most config), the buildServiceProject() call arguments (Maven image override, config file path), and the Apothesource docs site migration guide / field reference.

K6 note: jenkins.yaml has a runK6Perf flag (or similar) that will be used to gate K6 load test execution in the pipeline — relevant to K6 rollout plans.

What It Is and Why It Exists

The Problem: Legacy Static Templates

Per-project Jenkins file templates lived in a remote Git repo: ckm-deployment-templates. The Maven plugin ci-file-generator (now ngss-file-generator, prefix file-gen) would download these templates and replace placeholder variables with values from the project’s metadata.yaml.

Result: two static Jenkins files per project (Jenkinsfile.sandbox and Jenkinsfile). Any change — to templates or to project config — required every affected project to:

  1. Rebuild (mvn package)

  2. Regenerate the Jenkins files

  3. Commit and push

This created a never-ending cycle of updates, regeneration, and pushing changes across all projects whenever the build system was updated.

The Solution: Shared Library Pipelines

Jenkins supports two pipeline types:

  • Declarative pipelines — the old static Jenkinsfiles

  • Shared library pipelines — Jenkinsfiles that just call functions defined in a centrally-managed library repo

The Apothesource Jenkins Shared Library moves all build logic into that central repo. Projects drop to a thin Jenkinsfile (about 20–30 lines) that just loads the library and calls buildServiceProject(). When the library is updated, every project picks it up on its next Jenkins run — no project commits needed.

Key Technical Details

The Generated Jenkinsfile

Generated by file-gen:generate-jenkins-file (runs automatically on mvn package / mvn clean install). Structure:

@Library(['jenkins-shared-lib@main', 'scan-hub', 'codeql-linux']) _

if (env.ENVIRONMENT == 'sandbox') {
    library(credentialsId: 'sandbox-creds', ...) {
        buildServiceProject(...)
    }
} else if (env.ENVIRONMENT == 'staging' || env.ENVIRONMENT == 'prod') {
    library(credentialsId: 'sql-prod-creds', ...) {
        buildServiceProject(...)
    }
}

Key points:

  • One file handles all environments (sandbox / staging / prod)

  • Environment differences are credential IDs only — handled by runtime branch logic

  • @Library annotation auto-imports scan hub and codeQL Linux libraries (required for OIS scans)

  • All environments point to main branch of the shared library (was versioned 1.0 / 1.1 early on; simplified back to main)

jenkins.yaml: The Configuration File

Placed at the repository root. The moduleDir field names the service module that includes tiles (use . for single-module projects). Controls most build behavior.

Key fields: (NOTE: This list is not exhaustive and may be out of date as of 2026; see the Apothesource docs site for the authoritative reference.)

Field Purpose

buildTool

skaffold-maven or helm-maven — drives which build commands run

imageTool

skaffold, jib, or docker — which tool builds the container image

language

java or go — selects the language toolchain (Go build path)

moduleDir

service module directory that includes tiles (use . for single-module projects)

deployNamespace

Kubernetes namespace for default deployment target

enableOISScan

true to wire in OIS / scan hub scanning

jiraComponent

Optional; if Jira component ≠ artifact ID, set explicitly (needed for OIS scan linkage)

checkImageExists

Skip the image-exists check if false (useful in certain CI scenarios)

Sandbox/staging/prod override blocks allow branch regexes controlling which branches push images to DTR in which environment.

Defaults and Validation

There is a default jenkins.yaml (analogous to the old metadata YAML defaults). Fields not in your project’s jenkins.yaml fall back to these. All defaults and allowed values are documented on the Apothesource docs site.

Schema validation:

  • file-gen:jenkins-yaml validate runs schema validation and outputs issues. Auto-runs on mvn verify / mvn install.

  • IDE validation: download the JSON schema (schema version 7) from the Apothesource docs site, add it to your IDE’s schema mappings pointing at jenkins.yaml. Gives field hints and autocomplete on Ctrl+Space.

buildServiceProject() Arguments

Most config is in jenkins.yaml, but some things must be set as arguments to buildServiceProject() because they affect the Kubernetes pod definition (set up before jenkins.yaml is parsed):

Argument Purpose

configFile

Override path to the jenkins.yaml (defaults to jenkins.yaml)

mavenImage

Override the Maven build container image — the only one consistently overridden in practice. Used when testing a new base image (e.g., JDK 21 base image).

Library Internals

For debugging / extending:

The main entry point in the shared library is build-service-project.groovy (buildServiceProject function). It:

  1. Parses jenkins.yaml

  2. Defines a Kubernetes build pod

  3. Runs stages gated by jenkins.yaml flags

Build commands are driven by buildTool (skaffold-maven or helm-maven):

  • skaffold-maven → skaffold Maven build script

  • helm-maven → legacy Helm Maven build path

The separate language field (java or go) selects the toolchain — Go services set language: go (they are not distinguished by a buildTool value).

Services, mocks, and web project types all use buildServiceProject — they are similar enough to share the same path, just parameterized. Libraries use buildLibraryProject (separate entry point, different schema, different requirements — library projects don’t have a container image to push to DTR).

NGSS File Generator Tool

ci-file-generator was renamed ngss-file-generator. The Maven goal prefix is now file-gen:. Key goals:

  • file-gen:jenkins-yaml -Dinit — initialize jenkins.yaml from existing metadata.yaml build section

  • file-gen:jenkins-yaml -Dvalidate — validate jenkins.yaml against schema

  • file-gen:jenkins-yaml -Dhelp — command-line field reference (less complete than docs site)

  • file-gen:generate-jenkins-file — regenerate Jenkinsfile (also runs as part of mvn package)

Run these from the service module directory (not repo root) in multi-module projects.

How to Adopt / Migrate

Full migration guide is on the Apothesource docs site under "Jenkins Shared Library Migration Guide."

Short version:

  1. Update to tiles 1.5.0+ in your service module. Set jenkinsSharedLib: true in the tiles configuration (without this flag the old static generation is unchanged).

  2. cd into the service module that includes tiles.

  3. Run file-gen:jenkins-yaml -Dinit — this reads metadata.yaml build section and populates jenkins.yaml.

  4. Review jenkins.yaml. Adjust deployNamespace if needed. Uncomment and set enableOISScan: true when ready for OIS.

  5. Run mvn package (or mvn clean install) — this auto-generates Jenkinsfile and deletes Jenkinsfile.sandbox.

  6. Update the Jenkins multi-branch config: change Jenkinsfile name from Jenkinsfile.sandbox → Jenkinsfile.

  7. Commit jenkins.yaml and the new Jenkinsfile. Do not commit Jenkinsfile.sandbox (it should be deleted).

For Go projects: need to override mavenImage in the buildServiceProject() call — see the migration guide for the exact snippet.

For multi-module projects: jenkins.yaml lives at the repository root; the moduleDir field points at the service module (use . for single-module projects).

Design Decisions and Tradeoffs

Library Branching Strategy

All environments point to main branch of the shared library. This was simplified from early versioning (1.0 / 1.1) to reduce maintenance burden. Library updates are non-breaking by policy.

If different branches per environment are needed, it’s possible to change per-environment in the Jenkinsfile, but this is not the current approach.

Centralized Library, Centralized Risk

The design of having all services use the same library branch (main) means a bug in the library can affect all services simultaneously on their next Jenkins run. This is an explicit tradeoff made to eliminate the per-project regenerate-commit cycle. The library has "worked for the most part" so far under this design.

Q&A Highlights

Library Versioning

Q: Can we specify a different branch of the shared library per environment?

A: It’s possible. The library was versioned as 1.0 / 1.1 early on, but the current decision is all environments point to main. This simplifies maintenance; library updates are non-breaking by policy. If needed it can be changed per-environment in the Jenkinsfile.

Multi-Namespace Deployments

Q: Can we do multiple SQL deployment namespaces?

A: Currently the library supports one deployNamespace. A use case arose: services deployed to multiple SQL namespaces that need to stay in sync — currently requires manual separate deploys and is easy to forget. This is feasible to add. Feature request registered; needs offline discussion to spec the requirements.

Library Bug Exposure

Q: What’s running against main now — could a library bug affect everything at once?

A: Yes. Implicit in the design: library is updated centrally and applies to all projects on next build. This is the tradeoff for eliminating the per-project regenerate-commit cycle.

Open Questions

  • Multiple SQL deployment namespaces (feature request) — feasibility and design spec; take offline with Dallas

  • Schema validation auto-download behavior — revisit whether the JSON schema should be auto-downloaded on init

  • Service Lint integration into Jenkins builds — as of the Service Lint brown bag (2025), the question was still open whether Service Lint runs in Jenkins automatically as part of the shared library pipeline (see Service Lint for related context)

Why This is in the Knowledge Base

The Jenkins Shared Library is the standard build/CI system for all Apothesource services (as of 2024). buildServiceProject() and jenkins.yaml are the two primary surfaces that anyone modifying a service’s Jenkins build needs to understand. The library-branch-equals-main design means library bugs can affect all services simultaneously — relevant for SRE incident response and understanding platform-wide build system failures. The K6 runK6Perf flag connects to K6 load testing rollout plans. The multi-namespace deployment gap (feature request) is an open platform toothache that affects services deployed across multiple environments.