Checkpoint Lab — Scripted Pipeline, Groovy Control Flow, node Blocks, Dynamic Logic, and When to Use Scripted Pipelines
Implement equivalent Declarative and Scripted workflows, demonstrate a justified dynamic Scripted graph, inject a serialization error, repair it with a new source revision, and preserve controller/agent/build evidence.
Learning objectives
- Implement the same synthetic workflow in Declarative and Scripted forms and compare their evidence.
- Justify the part of the workflow that benefits from Scripted dynamic orchestration.
- Inject and diagnose a non-serializable Pipeline-state failure without hiding the original cause.
- Repair the Pipeline by reducing temporary state to serializable values.
- Produce a complete evidence packet tied to exact source/build/node/artifact identities.
1. Checkpoint scenario
You own a synthetic repository whose trusted local manifest selects
a subset of three allowed checks: lint,
unit, and package. First implement a fixed
Declarative Pipeline. Then implement a Scripted Pipeline that
creates stages from the validated manifest. Inject a serialization
error into the Scripted version, preserve the failure, repair it in
a new commit, and prove the repaired evidence.
script {}. In this lab,
“Scripted-only value” means the workflow's primary requirement is a
dynamic stage graph; choosing a fully Scripted Jenkinsfile keeps
that dynamic control flow explicit rather than hiding most of the
Pipeline inside a Declarative escape hatch.
2. Baseline and preflight
| Layer | Lab baseline | Evidence |
|---|---|---|
| Controller | Jenkins 2.568.3 LTS or your recorded current LTS; Java 21/25 supported | Core and JVM version |
| Pipeline engine | Pipeline: Groovy 4380.v6eb_8378b_9647 | Plugin inventory |
| Security | Script Security 1415.v9a_f9b_3a_c253d; sandbox kept enabled | Plugin inventory/no new approvals |
| Node/workspace | lab-linux disposable agent |
Node label/executor/workspace |
| Source | Synthetic repository only | Immutable Git SHA per revision |
| Credentials | None in mandatory path | State this explicitly |
| External side effect | None; local archived files only | Artifact digest/fingerprint |
3. Write predictions before execution
Create predictions.md with at least these predictions:
- The fixed Declarative version will always display the same three stage names.
- The Scripted version will create stages only for manifest entries that pass the allowlist and cardinality checks.
-
All
shcommands will run insidenode('lab-linux'); planning logic will not require an executor. -
The intentionally retained regex
Matcheracrosssleepwill fail with serialization/CPS evidence before the final artifact stage. -
The repaired revision will convert the temporary matcher result to
a serializable
Stringbefore suspension and archive evidence tied to the new SHA.
4. Create the synthetic repository
mkdir jenkins-ch11-checkpoint && cd jenkins-ch11-checkpoint
git init
git config user.name "Jenkins Chapter 11 Checkpoint"
git config user.email "jenkins-ch11-checkpoint@example.invalid"
cat > lab-task <<'EOF'
#!/usr/bin/env sh
set -eu
mkdir -p out
case "$1" in
lint) printf 'lint=ok\n' > out/lint.txt ;;
unit) printf 'unit=ok\n' > out/unit.txt ;;
package) printf 'package=ok\n' > out/package.txt ;;
*) printf 'unsupported task\n' >&2; exit 2 ;;
esac
EOF
chmod +x lab-task
printf 'lint\nunit\npackage\n' > tasks.txt
git add .
git commit -m 'ch11: checkpoint project'
BASE_SHA=$(git rev-parse HEAD)
printf 'base_sha=%s\n' "$BASE_SHA"
5. Implement the fixed Declarative comparison
Save this as Jenkinsfile.declarative:
pipeline {
agent { label 'lab-linux' }
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Lint') {
steps { sh './lab-task lint' }
}
stage('Unit') {
steps { sh './lab-task unit' }
}
stage('Package') {
steps { sh './lab-task package' }
}
}
post {
always {
archiveArtifacts artifacts: 'out/*', allowEmptyArchive: true, fingerprint: true
}
}
}
This is clearer if all three tasks are always required. Run it as an
SCM-backed Pipeline configured with Script Path
Jenkinsfile.declarative. Record the source SHA, build
number/URL, node/workspace, stages, and artifact digests.
6. Implement the dynamic Scripted version
Save this as Jenkinsfile:
def allowed = ['lint', 'unit', 'package'] as Set
def requested = []
readFile('tasks.txt').readLines().each { raw ->
def taskName = raw.trim()
if (taskName) {
if (!allowed.contains(taskName)) {
error "unsupported task: ${taskName}"
}
requested << taskName
}
}
requested = requested.unique()
if (requested.size() > 3) {
error "task count ${requested.size()} exceeds limit 3"
}
stage('Plan') {
echo "selected=${requested.join(',')}"
}
node('lab-linux') {
stage('Checkout') {
checkout scm
sh '''
set -eu
mkdir -p evidence
{
printf 'job=%s\n' "$JOB_NAME"
printf 'build=%s\n' "$BUILD_NUMBER"
printf 'build_url=%s\n' "$BUILD_URL"
printf 'node=%s\n' "$NODE_NAME"
printf 'workspace=%s\n' "$WORKSPACE"
printf 'source_sha=%s\n' "$(git rev-parse HEAD)"
} > evidence/context.txt
'''
}
requested.each { taskName ->
stage("Task: ${taskName}") {
sh "./lab-task '${taskName}'"
}
}
stage('Evidence') {
sh 'sha256sum out/* > evidence/artifacts.sha256'
archiveArtifacts artifacts: 'evidence/*,out/*', fingerprint: true
}
}
readFile requires a workspace, so the manifest must be
read after entering a node, or the manifest must come
from previously available controller-safe/SCM metadata. For this
checkpoint, keep the manifest read inside the node as shown in the
corrected version below. This intentionally demonstrates that
dynamic planning still has context requirements.
def allowed = ['lint', 'unit', 'package'] as Set
def requested = []
node('lab-linux') {
stage('Checkout') {
checkout scm
}
stage('Plan') {
readFile('tasks.txt').readLines().each { raw ->
def taskName = raw.trim()
if (taskName) {
if (!allowed.contains(taskName)) {
error "unsupported task: ${taskName}"
}
requested << taskName
}
}
requested = requested.unique()
if (requested.size() > 3) {
error "task count ${requested.size()} exceeds limit 3"
}
echo "selected=${requested.join(',')}"
}
requested.each { taskName ->
stage("Task: ${taskName}") {
sh "./lab-task '${taskName}'"
}
}
stage('Evidence') {
sh '''
set -eu
mkdir -p evidence
{
printf 'job=%s\n' "$JOB_NAME"
printf 'build=%s\n' "$BUILD_NUMBER"
printf 'node=%s\n' "$NODE_NAME"
printf 'workspace=%s\n' "$WORKSPACE"
printf 'source_sha=%s\n' "$(git rev-parse HEAD)"
} > evidence/context.txt
sha256sum out/* > evidence/artifacts.sha256
'''
archiveArtifacts artifacts: 'evidence/*,out/*', fingerprint: true
}
}
Run this good Scripted revision first and preserve its evidence. The dynamic value is visible: stage count follows the validated manifest, bounded to three allowed tasks.
7. Inject a serialization failure
In a new commit, add this between Plan and the task loop:
def releaseMatcher = ('release-42' =~ /release-(\d+)/)
sleep 2
echo "release=${releaseMatcher[0][1]}"
Commit it and record the broken SHA. Run the build. Preserve the first serialization/CPS stack trace, build URL, stage state, and proof that later evidence did not complete. Do not replay yet.
8. Repair the state model
Replace the retained matcher with a serializable string before the suspension:
def releaseId = ('release-42' =~ /release-(\d+)/)[0][1].toString()
sleep 2
echo "release=${releaseId}"
Commit the repair as a new revision. Run again. Verify that the build reaches all dynamic task stages and the Evidence stage.
9. Verify every prediction independently
| Prediction | Evidence | Pass condition |
|---|---|---|
| Declarative graph fixed | Stage visualization/build record | Lint, Unit, Package always visible |
| Scripted graph validated/dynamic | Plan log + stage names + tasks.txt |
Only allowed manifest entries become stages |
| Agent boundary correct | Node/executor/workspace logs | Shell commands run on lab-linux |
| Broken revision preserves failure | Exact SHA + stack trace | Serialization failure retained before repair |
| Repaired revision durable | New SHA + completed stages/artifacts |
Serializable releaseId survives
sleep
|
10. Evidence packet
-
baseline.txt: Jenkins core/Java and Pipeline: Groovy/Script Security/Nodes plugin versions. predictions.md.- Job full names, build numbers/URLs, causes, and exact source/Jenkinsfile SHAs for Declarative, good Scripted, broken, and repaired runs.
- Queue/node/label/executor/workspace evidence.
- Dynamic selected-task list and stage names.
- Broken serialization stack trace.
-
Archived
evidence/context.txt,evidence/artifacts.sha256,out/*, and fingerprints. - Statement that no real credential, sandbox approval, cloud service, or production target was used.
-
limitations.md: dynamic cardinality capped at three; no parallelism; no external side effect.
11. Cleanup and rollback
Download the evidence packet. Delete only the Chapter 11 checkpoint jobs/repository branches created for the lab. Do not delete shared agents, controller plugins, or unrelated Pipeline jobs. If you changed any job configuration while comparing Script Paths, restore the documented intended path.
12. What Chapter 11 adds
You can now use Scripted Pipeline intentionally rather than
reflexively: dynamic Groovy controls orchestration,
node controls agent/workspace allocation, Pipeline
steps preserve Jenkins semantics, substantial work remains on
agents, CPS state stays serializable, and Script Security remains a
deliberate trust boundary.
Chapter 12 builds on this by controlling failure itself:
retry, timeout, catchError,
input, waitUntil, aborts, and
side-effect-safe resilience.
Knowledge check
What is the justified Scripted value in the checkpoint?
The primary orchestration graph is generated from a validated, bounded manifest; embedding equivalent logic inside Declarative would effectively move the core workflow into a Scripted escape hatch.
Why must readFile be inside a workspace
context?
It reads a workspace file, so a node/workspace must exist; Pipeline planning does not automatically have access to agent files.
What evidence should be preserved from the broken serialization run?
Exact source/Jenkinsfile SHA, job/build URL, first stack trace, stage state, and evidence that downstream artifact creation did not complete.
Why does converting the regex result to String fix
the checkpoint?
The Pipeline needs to persist a simple serializable value across
sleep rather than a non-serializable Matcher
object.
What is the bridge to Chapter 12?
Once dynamic orchestration is correct, the next concern is resilient step control—timeouts, retries, waits, aborts, and preserving side-effect truth.
Official references and version notes
- Jenkins LTS changelog — current LTS and tested Java configurations.
-
Jenkins Pipeline handbook
— Scripted Pipeline fundamentals,
node, stages, and Jenkinsfile concepts. - Pipeline Syntax — Scripted control-flow examples and Pipeline syntax reference.
- Pipeline Best Practices — controller-side Groovy, serialization, and Pipeline scalability guidance.
-
Pipeline CPS Method Mismatches
— CPS transformation,
@NonCPS, serialization, and mismatch failure modes. - In-process Script Approval — Groovy Sandbox and script/signature approval security model.
- Pipeline: Groovy — current CPS execution engine implementation and compatibility.
- Script Security — current sandbox/approval plugin baseline and security history.
-
Pipeline: Nodes and Processes
—
node, workspace, and durable external-process support.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.