Many existing Jenkinsfiles are scripted, and real pipelines eventually need loops or dynamic stages.
Before declarative syntax, pipelines were written as scripted pipelines: plain Groovy built from steps like node, stage, checkout scm and parallel. There is no fixed structure, no post or when; you use try/catch/finally and if instead.
node('linux') {
stage('Build') { checkout scm; sh 'make' }
}node allocates an executor and workspace; everything inside runs there.
Declarative pipelines can drop into Groovy with script { } inside steps. Use it for small logic: reading a file with readFile, looping to build a map of parallel branches, or setting a variable for later stages. If script blocks grow large, move the logic into a shared library.
Pipeline Groovy is not normal Groovy. Scripts run in the Groovy sandbox: unapproved method calls fail with RejectedAccessException until an administrator approves them under In-process Script Approval. And code is transformed for durability (the next lesson), so a few Groovy features, such as some closure-taking methods like sort with a comparator closure, behave differently or fail. Steps like readJSON, readYaml and writeFile (Pipeline Utility Steps plugin) are safer than raw Java APIs.
Example
pipeline {
agent any
stages {
stage('Dynamic tests') {
steps {
script {
def suites = ['api', 'web', 'worker']
def branches = [:]
for (s in suites) {
def suite = s // capture per iteration
branches[suite] = { sh "make test-${suite}" }
}
parallel branches // 3 parallel branches
env.TESTED = suites.join(',')
}
echo "Tested: ${env.TESTED}" // Tested: api,web,worker
}
}
}
}Remember
- Scripted pipelines use node/stage/try-catch instead of declarative blocks
- script { } embeds Groovy logic in declarative pipelines
- Pipeline Groovy runs in a sandbox with approvals and CPS transformation
Watch out for
- Capturing a loop variable directly in closures, so every branch sees the last value; copy it per iteration.