Chapter 11Lesson 03~120 minutes

Scripted Pipeline, Groovy Control Flow, node Blocks, Dynamic Logic, and When to Use Scripted Pipelines: Configuration, Design Choices, and Tradeoffs

Choose between Declarative and Scripted Pipeline, separate Pipeline steps from ordinary Groovy, size node scopes intentionally, and balance dynamic stage generation against auditability and restartability.

Design choicesDeclarative vs Scriptednode scopeDynamic stagesCPSMaintainability

Learning objectives

  • Choose Declarative or Scripted from the actual orchestration requirement rather than stylistic preference.
  • Separate Pipeline steps from ordinary Groovy helpers and understand the CPS implications of each.
  • Choose node boundaries based on resource/trust/workspace needs.
  • Evaluate dynamic stages against visualization, auditability, security, and operational cost.
  • Document version, plugin, trust, and recovery assumptions for Scripted automation.

1. The first design decision: do you need Scripted at all?

Scripted Pipeline is most valuable when the orchestration graph genuinely depends on validated runtime data. It is not automatically “more advanced.” A static workflow written in Scripted form can be harder to review because the reader must infer structure from general Groovy control flow rather than from Declarative grammar.

Need Better default Why
Known stages and standard conditions Declarative Stronger structural validation and easier review.
Validated dynamic component/task list Scripted or tightly bounded Scripted escape General control flow can express the changing graph directly.
Large computation/data processing Agent-side program Pipeline Groovy is orchestration, not an application runtime.
Controller administration JCasC/REST/CLI/maintained plugin/admin procedure Do not normalize powerful internal API access inside build scripts.

2. Pipeline step versus ordinary Groovy method

A Pipeline step participates in Jenkins execution semantics: it may schedule work, suspend, persist state, interact with credentials, or create build evidence. An ordinary Groovy method merely computes. That distinction matters more than the syntax.

def normalizedTargets(String raw) {
  raw.split(',')
     .collect { it.trim() }
     .findAll { it }
     .unique()
}

def targets = normalizedTargets('lint, unit, lint')
echo "targets=${targets.join(',')}"

This helper returns serializable strings/lists. It is small and deterministic. It does not call node, echo, sh, or other Pipeline steps.

3. When @NonCPS is appropriate—and when it is not

@NonCPS skips CPS transformation for a method. It can help with certain pure Groovy/library operations that do not behave correctly under CPS, but the method cannot call CPS-transformed Pipeline steps. Inputs and outputs should be simple serializable values.

@NonCPS
def compactLabels(List labels) {
  labels.collect { it.toString().trim() }.findAll { it }.unique().sort()
}

def labels = compactLabels(['linux', 'linux', 'x86_64'])
echo "labels=${labels.join(',')}"
Wrong abstraction: marking a large helper @NonCPS and then trying to call sh, sleep, node, or echo from it breaks the execution model. @NonCPS is not an “escape from Jenkins.”

4. One large node block versus targeted allocations

Design Strength Cost/risk Evidence to inspect
One node around whole run Simple workspace continuity Holds executor during waits/controller-only logic Executor occupancy, elapsed time, workspace state
Targeted nodes per phase Releases capacity; supports different labels/trust zones Workspace may differ; queue waits repeat Queue IDs, node names, stash/artifact transfer
Separate privileged node Isolates signing/deployment power Requires explicit transfer and credential boundaries Agent label, credential scope, artifact digest

Node boundaries are infrastructure boundaries. Design them from executor capacity, workspace dependency, toolchain, network, and credential trust—not from indentation preference.

5. Dynamic stages versus static readability

A dynamic stage for each validated component can make a heterogeneous build easy to visualize. It can also produce unstable dashboards, unbounded stage counts, difficult trend comparison, and controller overhead when the list is large. Cap cardinality and normalize names.

def components = ['api', 'worker', 'web']
if (components.size() > 10) {
  error "component count ${components.size()} exceeds lab limit"
}
components.each { component ->
  stage("Test ${component}") {
    node('lab-linux') {
      sh "./test-component '${component}'"
    }
  }
}

In production, validate component before shell use and consider whether parallelism belongs here. Chapter 21 treats parallel and matrix capacity in depth.

6. Sandbox versus unsandboxed execution

Sandboxed Pipeline limits direct access to Jenkins/controller APIs. Approving an unsafe signature expands what Pipeline authors can do on the controller. That is a governance decision with security impact, not a routine development step. Trusted Shared Libraries can also execute privileged logic depending on configuration, so moving code into a library does not erase the trust question.

Production pattern: keep Jenkinsfiles sandbox-compatible, expose reviewed narrow operations through maintained steps/libraries/plugins, and keep administrator-only controller mutation outside ordinary repository-controlled build logic.

7. Restartability changes design choices

A Scripted Pipeline can survive supported controller restarts because Pipeline program state is persisted around asynchronous operations. Restartability does not mean every object is serializable or every external side effect is repeat-safe. Record side-effect identifiers before a suspension/restart and design idempotency explicitly.

For example, creating an external release and then sleeping is risky if a later rerun cannot tell whether the release already exists. Persist the release ID as build evidence and make the external operation create-or-update or otherwise reconcile safely.

8. Worked scenario: twelve components, two trust zones

A repository contains a validated manifest of 12 components. Ten require ordinary tests; two require signing on a restricted agent.

  1. Parse/validate the manifest into simple serializable component records.
  2. Reject more than the documented maximum cardinality.
  3. Use ordinary lab-linux agents for tests.
  4. Archive immutable package digests.
  5. Move only approved digests to a restricted signing stage/agent.
  6. Bind signing credentials only inside the restricted node/stage.
  7. Record component, source SHA, unsigned/signed digest, node identity, and signing result.

Scripted Pipeline is defensible here if the manifest truly varies and the dynamic stages improve evidence. If the 12 stages are stable, Declarative may still be simpler.

9. Decision table

Question If yes If no
Does the stage graph vary from validated runtime/source data? Consider Scripted. Prefer Declarative.
Does logic need workspace/tool/credential state? Put it in the narrow node context. Keep it outside node.
Must a value survive a suspension? Keep it serializable and small. Temporary object may remain local to pure computation.
Does code need internal Jenkins APIs? Reconsider the mechanism/trust boundary. Stay sandboxed.
Will dynamic stage count be large? Bound it or redesign. Dynamic stages may remain readable.

10. Mini ADR exercise

Write a short architecture decision record with: requirement, why Declarative is insufficient or sufficient, source of dynamic data, maximum cardinality, node labels/trust zones, credential boundaries, serializable state model, sandbox assumptions, restart/side-effect strategy, plugin versions, rollback path, and observable evidence. If you cannot justify those points, the Scripted design is probably not mature enough.

Next lesson

Diagnostics, Failure Modes, Security, and Performance

Diagnose serialization failures, controller-heavy Groovy, hidden dynamic stage identity, and dangerous script approvals using preserved first-failure evidence.

Knowledge check

Why is a Pipeline step different from an ordinary Groovy helper?

What is the safest use of @NonCPS?

What determines node scope?

Why can dynamic stages become an operational problem?

What does approving a sandbox signature change?

Official references and version notes

Version and compatibility note

Rechecked on 2026-09-16. Examples assume Jenkins 2.568.3 LTS, tested with Java 21 and 25, Pipeline: Groovy 4380.v6eb_8378b_9647, Script Security 1415.v9a_f9b_3a_c253d, and Pipeline: Nodes and Processes 1479.v56e587f413a_7. Pipeline: Groovy 4380 requires Jenkins 2.528.3 or newer. The mandatory lab uses a disposable lab-linux agent, a synthetic repository, no production credential, and no unsandboxed approval. Record your actually installed versions because plugin releases change independently from Jenkins core.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.