Chapter 27Lesson 02~230 minutes

Job DSL, Seed Jobs, Folder Hierarchies, Programmatic Job Creation, and Jenkins API Automation: Guided Hands-On Workflow and Core Operations

Create a small synthetic Job DSL repository, run it from a trusted disposable seed job, generate a folder hierarchy and Pipeline jobs, rerun without churn, change one definition, safely disable one removed generated item, and inspect both Jenkins and Remote API evidence without using production credentials or controllers.

Hands-onSCMIdempotencyGenerated jobsRemoved itemsREST API

Learning objectives

  • Create a reproducible synthetic Job DSL repository and seed workflow.
  • Generate folders and Pipeline jobs from a pinned source revision.
  • Prove idempotency by rerunning the same seed commit.
  • Change one generated job and safely handle one removed generated item.
  • Use Jenkins UI/API evidence without exposing credentials.

1. Disposable scenario and guardrails

The lab uses a local Jenkins 2.568.3-lts-jdk21 controller, a trusted disposable agent labeled trusted-seed, and a synthetic Git repository named ch27-jobdsl. The controller has Job DSL, Script Security and Folders installed. Matrix Authorization and Authorize Project are recommended for the full permission exercise.

Guard the exact target. Run this only on a disposable controller. The seed is allowed to mutate only platform-lab/**. Do not point the repository, API variables or seed at a production controller.

Preflight evidence:

mkdir -p ch27-jobdsl/{jobs,evidence}
cd ch27-jobdsl
git init
git config user.name 'CH27 Lab'
git config user.email 'ch27@example.invalid'
printf '%s\n' "$JENKINS_URL" > evidence/controller-url.txt

2. Pin the controller/plugin baseline

For a reproducible disposable image, preinstall the reviewed plugin set rather than clicking “latest” during the exercise:

FROM jenkins/jenkins:2.568.3-lts-jdk21
RUN jenkins-plugin-cli --plugins \
  job-dsl:3732.v9a_c49a_61a_313 \
  script-security:1422.v06869826dd9b_ \
  cloudbees-folder:6.1106.v3a_d9a_6d2465e \
  matrix-auth:3.3 \
  authorize-project:534.v2f208c45e11c

Record the built image ID/digest. Plugin installation is controller supply-chain state; the DSL repository is item desired state. Keep them separate.

3. Create the smallest useful DSL source

Create jobs/platform.groovy:

folder('apps') {
  description('[managed-by=job-dsl][owner=platform-lab]')
}

['api-ci', 'worker-ci'].each { name ->
  pipelineJob("apps/${name}") {
    description("[managed-by=job-dsl][owner=platform-lab][schema=v1]")
    disabled(false)
    definition {
      cps {
        sandbox(true)
        script("""pipeline {
  agent { label 'linux-ci' }
  stages {
    stage('Identity') {
      steps { echo 'synthetic ${name} build' }
    }
  }
}""".stripIndent())
      }
    }
  }
}

The DSL creates Jenkins items. The embedded Pipeline is intentionally tiny so the lesson remains about generation, not Pipeline syntax. In a real repository, prefer SCM-backed Jenkinsfiles for application jobs.

git add jobs/platform.groovy
git commit -m 'ch27: desired jobs schema v1'
git rev-parse HEAD | tee evidence/dsl-v1.sha
sha256sum jobs/platform.groovy | tee evidence/dsl-v1.sha256

4. Create the seed job

Create a folder platform-lab and a Pipeline job platform-lab/seed. Configure SCM to check out this synthetic repository on the trusted seed agent. The Pipeline should record source identity before calling Job DSL:

pipeline {
  agent { label 'trusted-seed' }
  options { timestamps() }
  stages {
    stage('Source identity') {
      steps {
        sh 'git rev-parse HEAD | tee seed-source.sha'
        sh 'sha256sum jobs/*.groovy | tee dsl-files.sha256'
      }
    }
    stage('Generate') {
      steps {
        jobDsl targets: 'jobs/**/*.groovy',
          sandbox: true,
          lookupStrategy: 'SEED_JOB',
          removedJobAction: 'IGNORE',
          removedViewAction: 'IGNORE'
      }
    }
  }
  post {
    always {
      archiveArtifacts artifacts: 'seed-source.sha,dsl-files.sha256', fingerprint: true
    }
  }
}

With SEED_JOB, apps/api-ci resolves relative to the seed folder, producing platform-lab/apps/api-ci. The seed’s folder and permissions are still the actual authorization boundary.

5. Run the seed with an intentional identity

For the strongest sandbox exercise, configure Authorize Project so the seed runs as a dedicated jobdsl-seed-bot identity. Grant it only the folder/item permissions needed under platform-lab. Do not grant Overall/Administer or Script Console power. A simplified all-admin disposable lab is acceptable only as a documented simulation of this authorization boundary.

Before the first run, predict:

  1. platform-lab/apps, api-ci and worker-ci will appear.
  2. The seed build will retain the exact Git SHA and generated-object evidence.
  3. No application build is implied merely because the item was created.

6. First generation and before/after evidence

Trigger platform-lab/seed once. Inspect the console for “Added items”/“Generated items” information, then query the folder:

curl -fsS --user "$JENKINS_USER:$JENKINS_API_TOKEN" \
  "$JENKINS_URL/job/platform-lab/api/json?tree=jobs[fullName,displayName,color,_class]" \
  | tee evidence/items-after-run1.json

curl -fsS --user "$JENKINS_USER:$JENKINS_API_TOKEN" \
  "$JENKINS_URL/job/platform-lab/job/seed/lastBuild/api/json?tree=number,url,result,actions[causes[*]]" \
  | tee evidence/seed-run1.json

Expected persisted state: folder plus two Pipeline jobs. Their later build histories remain empty until triggered separately.

7. Prove idempotency

Run the seed again without changing Git. Record build 2 and query the same item set. Compare normalized configuration or API-visible metadata. The new seed build record is expected; unexplained generated-item changes are not.

git rev-parse HEAD > evidence/run2-source.sha
# Save API output after run 2, then compare item full names/descriptions.

Idempotency is not “the console says SUCCESS.” It means the same desired input converges to the same intended item configuration.

8. Make one controlled schema change

Change only api-ci to add [schema=v2] and a build-discarder policy. Commit it:

pipelineJob('apps/api-ci') {
  description('[managed-by=job-dsl][owner=platform-lab][schema=v2]')
  logRotator { numToKeep(10) }
  // existing definition unchanged
}
git add jobs/platform.groovy
git commit -m 'ch27: api-ci schema v2'
git rev-parse HEAD | tee evidence/dsl-v2.sha

After the seed run, verify that api-ci changed and worker-ci did not. This isolates generator causality.

9. Simulate a removed generated job safely

Remove the worker-ci definition from the DSL and change the Job DSL step to removedJobAction: 'DISABLE' for this exercise. Commit both changes together so policy and desired state are reviewable.

Run once. Expected evidence: platform-lab/apps/worker-ci remains present but disabled. Its prior history/configuration can still be inspected. Do not use DELETE in the mandatory lab.

Multiple Job DSL steps. If a real seed has several jobDsl invocations, use DISABLE/DELETE removed actions only on the final relevant step so earlier steps do not retire items that later steps still generate.

10. API automation around the seed

The Remote API is useful for exact triggering and evidence, not for replacing the generator. With an API token stored outside the script:

# Read seed state
curl -fsS --user "$JENKINS_USER:$JENKINS_API_TOKEN" \
  "$JENKINS_URL/job/platform-lab/job/seed/api/json?tree=fullName,lastBuild[number,url,result]"

# Trigger the exact seed. API token authentication is preferred for scripted clients.
curl -fsS -X POST --user "$JENKINS_USER:$JENKINS_API_TOKEN" \
  "$JENKINS_URL/job/platform-lab/job/seed/build"

A successful POST may only mean the request was accepted. Correlate queue/build state and then inspect the resulting seed build.

11. Small challenge: choose the right layer

A team asks to change the global Jenkins URL, create two application Pipeline jobs, and trigger the seed after review. Which mechanisms should own each?

Expected reasoning: global URL belongs to JCasC/controller configuration; application job creation belongs to Job DSL; triggering/inspection can use the Remote API. Do not encode global controller state into Job DSL merely because Groovy can reach internal objects.

12. Verification and cleanup

  • Retain DSL commits/hashes and seed build URLs.
  • Verify generated full names are under platform-lab.
  • Verify the removed worker is disabled, not deleted.
  • Verify no credentials appear in console/archive/Git.
  • Delete only the disposable controller/agent and synthetic repo created for the lab.

Never use a wildcard cleanup against a shared Jenkins instance.

Next lesson

Configuration, Design Choices, and Tradeoffs

Compare Job DSL with JCasC and REST/XML, decide seed granularity and trust mode, and choose a lifecycle policy that remains auditable under team growth.

Knowledge check

Answer before revealing the explanation.

1. Why does the lab run the DSL from an exact Git commit?

2. What should the second identical seed run change?

3. Why use DISABLE rather than DELETE for the removal exercise?

4. Why does the seed checkout belong on a trusted agent even though Job DSL mutates controller state?

5. When does the Jenkins Remote API help in this chapter?

Official references and version notes

Verified baseline — 17 September 2026. Labs target Jenkins 2.568.3 LTS with Java 21 (Jenkins 2.568.3 is tested with Java 21 and 25). Job DSL is 3732.v9a_c49a_61a_313, requires Jenkins 2.479.3 and is currently marked “up for adoption” on the plugin site. Script Security is 1422.v06869826dd9b_; Folders is 6.1106.v3a_d9a_6d2465e; Matrix Authorization Strategy is 3.3; Authorize Project is 534.v2f208c45e11c. Re-check current plugin/API documentation and security advisories before reusing the lab. The mandatory path uses only a disposable local controller, synthetic Git repository and fake identities.

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.