Choosing Reuse Boundaries

Scenario: One opaque shared step performs checkout, tests, packaging, deployment, and notifications with dozens of flags.

The line between "helpful reuse" and "an unreadable black box"

Shared libraries are genuinely powerful, which makes it tempting to go too far - wrapping an entire delivery process (checkout, test, package, scan, deploy, notify, all in one call) into a single library step. This section is about recognizing exactly where that temptation goes wrong.

Analogy: Think of the difference between a well-organized toolbox with clearly labeled individual tools - a hammer, a screwdriver, a specific-size wrench - versus a single mysterious black box with one big switch on it that "does the whole job" internally, with no way to see or adjust any individual step inside. The labeled toolbox lets you see and reason about exactly what's happening at each step, swap out just the one tool you need for an unusual job, and diagnose which specific step went wrong when something breaks. The black box is faster to reach for at first, right up until the day it doesn't quite do what you need for one particular repository, and you discover there was never a way to peek inside or adjust just one part of it.

This is the exact tension between the "one giant do-everything step" anti-pattern and the "several small, focused, composable steps" approach recommended below - and it's a genuinely important design instinct to build, not just a Jenkins-specific rule.

Pipeline example

stage('Test') { steps { companyTest profile: 'node-service' } }
stage('Publish') { steps { companyPublish path: 'dist/*.tgz' } }

Reuse policy without hiding lifecycle

A single, monolithic companyDelivery step that internally checks out code, runs tests, packages the build, scans it, deploys it, and sends notifications - all in one opaque call - conceals exactly where agents get allocated, where approval gates sit, and where failure boundaries actually are, from anyone just reading the calling Jenkinsfile. Dozens of boolean flags added over time to handle every team's slightly different needs (skipTests: true, useAltRegistry: false, ...) tend to create invalid or nonsensical combinations of flags that nobody actually tests, and destroy the clean visual pipeline graph that Declarative Pipeline is specifically designed to give you. Prefer several focused steps instead - something like companyTest, publishArtifact, and requestDeployment - while the calling Jenkinsfile itself keeps its own explicit stages and controls the actual ordering that makes sense for that repository.

// Instead of one opaque call:
companyDelivery(skipTests: false, useAltRegistry: false, notify: true, ...)

// Prefer explicit, composable stages:
stage('Test')    { steps { companyTest profile: 'node-service' } }
stage('Publish') { steps { publishArtifact path: 'dist/*.tgz' } }
stage('Deploy')  { steps { requestDeployment target: 'staging' } }

The second version is longer, but a reviewer can see exactly what happens and in what order just by reading the Jenkinsfile - no need to go read the library's internal implementation to understand what companyDelivery(...) is actually going to do with a given set of flags.

Keep deterministic build logic where developers can actually run it

Keep genuinely deterministic build commands (compiling, running the local test suite) in scripts checked into the application's own repository, specifically so a developer debugging a failure can run the exact same command locally on their own laptop, without needing Jenkins at all. Put approved credential-usage patterns, standard evidence-collection logic, and cross-repository Jenkins policy into the shared library, where consistency genuinely matters across many consumers. Put actual deployment reconciliation logic (making the target environment converge to the desired state) in the deployment platform itself, not inside Jenkins Groovy code - Jenkins triggers that process, it shouldn't try to reimplement it. This separation of concerns is what lets you assign each kind of failure to the team that actually owns the fix, instead of every failure looking equally like "something broke in the Jenkinsfile."

Make library abstractions diagnosable, not opaque

A well-designed shared step should validate its own inputs early with a clear error message, log its non-secret inputs so a reviewer can see what it was actually called with, deliberately preserve the underlying exception when something goes wrong further down, and return genuinely useful identifiers (like an artifact digest or an analysis-result URL) rather than just a bare true/false. A library step that catches every possible exception internally and just returns false on any failure destroys the original stack trace and all the stage-level context that would otherwise help someone diagnose why it failed - turning a normally quick investigation into needless guesswork. Offer a clearly documented extension point for genuinely unusual consumers with legitimate special needs, without letting that same extension point become a routine way for teams to bypass policy the library was specifically meant to enforce.

A pitfall: the "just add one more flag" spiral

If nearly every calling team ends up requesting "just one more flag" to handle their particular case, that's a strong signal the abstraction boundary you've drawn is too broad and is trying to do too many different things for too many different situations at once - it's usually a sign to split the step into smaller, more focused pieces instead of adding yet another flag. Conversely, if a single security fix genuinely needs to reach fifty different consuming repositories reliably and consistently, centralizing that specific piece of logic into the shared library is exactly the right call. Version and test the shared library's interface against realistic, representative consumer Pipelines, so that reuse actually reduces real operational variance across your organization - rather than merely reducing the raw line count of each individual Jenkinsfile while making the overall system harder to reason about.

Goal: Practice the concrete behavior in jenkins-artifact-promotion. Learn sections have no Jenkins terminal; the Jenkins lab opens the real shared service in a new full-screen tab inside your private folder.