Scripted Pipeline, Groovy Control Flow, node Blocks, Dynamic Logic, and When to Use Scripted Pipelines: Guided Hands-On Workflow and Core Operations
Build a small SCM-backed Scripted Pipeline, branch and loop over synthetic data, allocate nodes deliberately, compare a Declarative equivalent, and inspect which work runs on the controller versus the agent.
Learning objectives
- Create an SCM-backed Scripted Pipeline with a synthetic repository and bounded dynamic task list.
- Use if/else and loops without turning controller Groovy into heavy application logic.
- Allocate an agent only around workspace/tool operations and record node/workspace evidence.
- Compare the Scripted flow with a Declarative equivalent and state why Scripted is or is not justified.
- Preserve source, build, stage, node, workspace, artifact, and sandbox evidence.
1. Disposable lab topology
Reuse the local Jenkins LTS controller and
lab-linux agent pattern from Chapters 08–10. Keep
routine build execution off the built-in node. The mandatory path
uses a synthetic local/public repository and no real credential.
lab-linux is online; confirm
the repository is synthetic; and verify that you can delete only the
Chapter 11 lab resources afterward.
2. Create a tiny synthetic project
mkdir jenkins-ch11-scripted && cd jenkins-ch11-scripted
git init
git config user.name "Jenkins Chapter 11 Lab"
git config user.email "jenkins-ch11@example.invalid"
cat > lab-task <<'EOF'
#!/usr/bin/env sh
set -eu
case "$1" in
lint) printf 'lint=ok\n' ;;
unit) printf 'unit=ok\n' ;;
package) mkdir -p out; printf 'payload=chapter11\n' > out/app.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: synthetic scripted project'
git rev-parse HEAD
The shell script owns task execution. The Jenkinsfile will own orchestration only.
3. Start with the smallest Scripted Jenkinsfile
stage('Plan') {
echo "job=${env.JOB_NAME} build=${env.BUILD_NUMBER}"
}
node('lab-linux') {
stage('Execute') {
sh './lab-task lint'
}
}
The first stage uses no workspace. The node step then
queues agent-backed work. Run once and record the build number, URL,
cause, node name, and workspace path.
4. Add bounded dynamic logic
Use a fixed allowlist and a synthetic requested list. The loop
executes as CPS-transformed Groovy orchestration; each
sh call launches real work on the agent.
def allowed = ['lint', 'unit', 'package'] as Set
def requested = ['lint', 'unit', 'package']
def selected = requested.findAll { allowed.contains(it) }
stage('Plan') {
echo "selected=${selected.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
'''
}
selected.each { taskName ->
stage("Task: ${taskName}") {
sh "./lab-task '${taskName}'"
}
}
stage('Retain evidence') {
sh 'sha256sum out/app.txt > evidence/app.sha256'
archiveArtifacts artifacts: 'evidence/*,out/*', fingerprint: true
}
}
The task names are fixed synthetic values here. If they come from a parameter, SCM file, API, or webhook, validate them before they influence stage names, file paths, commands, URLs, or external targets.
5. Prove where code executes
| Statement | Execution owner | Evidence |
|---|---|---|
requested.findAll { ... } |
Pipeline Groovy/CPS on controller | No agent process; visible only through resulting plan/log/state |
node('lab-linux') |
Jenkins queue/scheduler | Queue item, selected node, executor |
checkout scm |
Pipeline step using SCM integration in node context | Console log, workspace Git metadata, source SHA |
sh './lab-task unit' |
External process on agent | Agent process output and workspace changes |
archiveArtifacts |
Pipeline step retaining build evidence | Archived files/fingerprints in build record |
6. Add a simple branch without hiding intent
def shouldPackage = selected.contains('package')
if (shouldPackage) {
echo 'Package task is in the validated plan.'
} else {
echo 'No package task requested.'
}
Do not place dozens of unrelated decisions in one opaque Groovy function. Keep decision evidence close to the stages it controls.
7. Compare one large node with targeted allocations
For this tiny lab, one node block is reasonable because
checkout, checks, and packaging are short and share files. A long
approval or controller-only planning phase should stay outside the
node. If a later phase requires a different trust/tool boundary, use
a separate node allocation and transfer only the required evidence.
stage('Plan outside node') {
echo "taskCount=${selected.size()}"
}
node('lab-linux') {
stage('Agent work') {
checkout scm
sh './lab-task lint'
}
}
8. Compare a Declarative equivalent
pipeline {
agent { label 'lab-linux' }
stages {
stage('Lint') {
steps { sh './lab-task lint' }
}
stage('Unit') {
steps { sh './lab-task unit' }
}
stage('Package') {
steps { sh './lab-task package' }
}
}
}
If the task list is actually fixed, the Declarative version is clearer. Scripted earns its cost only when the list or orchestration shape truly must be dynamic after validation.
9. Inspect sandbox state without weakening it
Do not add internal Jenkins API calls. Confirm that the job runs in its normal sandboxed Pipeline context. If an existing experiment has a rejected signature, capture the rejection text and remove/rewrite the unsupported call. Do not approve it for this lab.
10. Challenge — choose the correct layer
Requirement: the build receives a synthetic comma-separated
TASKS parameter and must run only lint,
unit, or package. Where should the safety
control live?
Expected design: validate and normalize the
parameter in bounded Pipeline logic, reject anything outside the
allowlist before entering an agent-side command, then pass only
validated task names to the agent process. Do not “fix” the problem
with shell eval, broad quoting assumptions, or
administrator approval.
11. Cleanup
Download the archived evidence first. Delete only the Chapter 11 synthetic job/repository resources. Do not remove shared Pipeline plugins, shared agents, or unrelated credentials/configuration.
Knowledge check
Which part of the lab should perform the actual synthetic task?
The agent-side lab-task process. Groovy should
orchestrate rather than implement substantial project logic.
Why keep planning outside node when it needs no
workspace?
It avoids reserving an executor/workspace for controller-only orchestration.
What proves the exact source revision used?
The recorded git rev-parse HEAD from the
checked-out workspace, correlated with the job/build URL and
Jenkinsfile revision.
If the task list is fixed, which style is normally clearer?
Declarative Pipeline, because the static stage graph benefits from validation and predictable structure.
How should a user-provided task name reach a shell?
Only after explicit validation/normalization against an allowlist; never by blindly interpolating arbitrary input into shell syntax.
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.