Chapter 07Lesson 02~125 minutes

Build Parameters, Environment Variables, Build Causes, Schedules, Remote Triggers, and Parameterized Automation: Guided Hands-On Workflow and Core Operations

Build a disposable parameterized maintenance simulation, trigger it manually, by timer, and through the authenticated Remote API, then prove causes, inputs, queue/build identity, validation, and idempotent side effects.

Freestyle labString/choice/booleanTimerRemote APICause evidenceIdempotency

Learning objectives

  • Create string, choice, and boolean parameters without using real secrets.
  • Build a safe shell step that allowlists values before deriving a target path or side effect.
  • Configure a short-lived hashed timer schedule and keep overlapping execution bounded.
  • Trigger the job through the authenticated Remote API with an API token and no CSRF bypass.
  • Collect build cause, parameter, queue/build, artifact, and simulated-target evidence.

1. Lab scenario and safety boundary

This lab continues the disposable controller/agent model from earlier chapters. Use a non-production Jenkins controller and a disposable Linux agent labelled lab-linux. The job never touches a real server, registry, cloud account, or production credential. Its only “external” side effect is a marker under /tmp/jenkins-ch07-maintenance on the disposable agent.

Do not perform this lab on a shared production agent. The commands intentionally create and remove files under a known temporary root. Verify the exact root before cleanup.

2. Preflight: prove the controller, agent, and API baseline

Record the Jenkins core version, Java version, job namespace, agent label, and the non-secret identity of the API user you will use. Do not log the API token itself.

java -version
printf 'agent=%s\nworkspace=%s\n' "${NODE_NAME:-unknown}" "${WORKSPACE:-unknown}"
# From an authenticated client, a response header from any Jenkins API page includes X-Jenkins.

Confirm the built-in controller node is not used for the job. If no disposable agent exists, create one using the same safe local method from the earlier controller/agent chapter before continuing.

3. Create ch07-maintenance and define parameters

Create a Freestyle project named ch07-maintenance. Restrict it to the lab-linux label. Enable This project is parameterized and define:

Name Type Default / choices Purpose
NOTE String training-run Harmless free-form evidence; length/characters are validated.
TARGET Choice dev, staging Bounded simulated environment.
ACTION Choice inspect, rotate-cache Read-only or idempotent simulated operation.
DRY_RUN Boolean checked Prevents the marker side effect while learning.

Do not add a password parameter. Real secrets belong in Jenkins Credentials or an external secret provider, not in a course parameter.

4. Add a validation-first shell build step

Add an Execute shell step with the following script. The script validates all user-controlled values before using them to derive a path or action. It does not use eval or construct a shell command from strings.

set -eu

ROOT=/tmp/jenkins-ch07-maintenance

case "$TARGET" in
  dev|staging) ;;
  *) printf 'Rejected TARGET=%s\n' "$TARGET" >&2; exit 64 ;;
esac

case "$ACTION" in
  inspect|rotate-cache) ;;
  *) printf 'Rejected ACTION=%s\n' "$ACTION" >&2; exit 64 ;;
esac

case "$NOTE" in
  *[!A-Za-z0-9._-]*|'')
    printf 'Rejected NOTE: use 1+ characters from A-Z a-z 0-9 . _ -\n' >&2
    exit 64
    ;;
esac

TARGET_DIR="$ROOT/$TARGET"
mkdir -p -- "$TARGET_DIR"

{
  printf 'job=%s\n' "$JOB_NAME"
  printf 'build=%s\n' "$BUILD_NUMBER"
  printf 'url=%s\n' "$BUILD_URL"
  printf 'node=%s\n' "$NODE_NAME"
  printf 'target=%s\n' "$TARGET"
  printf 'action=%s\n' "$ACTION"
  printf 'dry_run=%s\n' "$DRY_RUN"
  printf 'note=%s\n' "$NOTE"
  printf 'time_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
} | tee "$WORKSPACE/evidence.txt"

if [ "$ACTION" = inspect ]; then
  find "$TARGET_DIR" -maxdepth 1 -type f -printf '%f\n' | sort || true
  exit 0
fi

MARKER="$TARGET_DIR/cache-rotated.marker"
if [ "$DRY_RUN" = true ]; then
  printf 'DRY RUN: would create %s\n' "$MARKER"
elif [ -f "$MARKER" ]; then
  printf 'Idempotent no-op: marker already exists at %s\n' "$MARKER"
else
  printf 'created_by=%s#%s\n' "$JOB_NAME" "$BUILD_NUMBER" > "$MARKER"
  printf 'created_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$MARKER"
  printf 'Created %s\n' "$MARKER"
fi

Add Archive the artifacts with evidence.txt. This preserves selected non-secret runtime evidence even if the workspace later disappears.

5. Manual trigger: establish the first cause/input packet

Use Build with Parameters with TARGET=dev, ACTION=inspect, DRY_RUN=true, and a safe NOTE. Before clicking Build, predict the expected build cause, parameter values, agent label, and the fact that no marker will be created.

After completion, record job full name, build number/URL, cause shown in the UI, parameters, node name, workspace, console result, and archived evidence.txt. Do not infer cause from the NOTE value.

6. Configure a short-lived hashed timer safely

Enable Build periodically with the lab-only schedule:

H/5 * * * *

This uses Jenkins' hash syntax to choose a stable offset every five minutes. Keep DRY_RUN defaulted to true and leave concurrent execution disabled. Wait for one timer-fired build if practical, capture its cause and inputs, then remove the timer when the lab is complete.

If you cannot wait, configuring and saving the schedule plus inspecting Jenkins' next-run indication still teaches the configuration layer, but the checkpoint lesson later requires observing a real timer cause.

7. Trigger a parameterized build through the authenticated Remote API

Create a disposable Jenkins user with only the permissions needed to read/build this lab job, then create an API token for that user. Keep the token in a local session variable and never paste it into the Jenkins URL.

export JENKINS_URL='http://127.0.0.1:8080'
export JENKINS_USER='ch07-api'
read -r -s -p 'API token: ' JENKINS_API_TOKEN; export JENKINS_API_TOKEN; printf '\n'

curl --fail-with-body --silent --show-error --request POST \
  --user "$JENKINS_USER:$JENKINS_API_TOKEN" \
  --data-urlencode 'TARGET=dev' \
  --data-urlencode 'ACTION=rotate-cache' \
  --data-urlencode 'DRY_RUN=false' \
  --data-urlencode 'NOTE=api-run-001' \
  "$JENKINS_URL/job/ch07-maintenance/buildWithParameters"

API-token authentication is the reason this scripted POST does not need a CSRF crumb. Do not disable CSRF. Depending on Jenkins/plugin details, the exact remote cause payload can differ; inspect the resulting build rather than hard-coding a cause class into your automation.

8. Inspect cause + parameters through the build API

After the API-triggered build starts, query its build record. Use the build number you observed rather than assuming lastBuild remains stable on a busy controller.

BUILD=3   # replace with the observed build number
curl --silent --show-error \
  --user "$JENKINS_USER:$JENKINS_API_TOKEN" \
  "$JENKINS_URL/job/ch07-maintenance/$BUILD/api/json?tree=number,url,queueId,result,actions[causes[*],parameters[name,value]]"

The returned build number, queue ID, cause structure, parameter set, and result are Jenkins-side evidence. The marker under /tmp/jenkins-ch07-maintenance/dev is separate agent-side evidence.

9. Prove idempotency with a second API build

Trigger the same rotate-cache request again with DRY_RUN=false. The second build must not create a second side effect; it should report that the marker already exists. You now have two Jenkins build identities but one simulated target transition.

Important distinction: idempotency does not mean “the second build has no logs.” It means repeating the same requested operation leaves the external target in the same intended state rather than duplicating the effect.

10. Reject an unsafe input without exploiting anything

Change NOTE to a value containing a slash such as ../../prod. The validation step must reject it before any marker path or action is derived. This is a safe demonstration of fail-closed validation; there is no need to execute a real command-injection payload.

11. Cleanup and rollback

Disable/remove the timer first. Delete the lab API token from the Jenkins user. Then, only on the disposable agent, verify the exact temporary root and remove it:

ROOT=/tmp/jenkins-ch07-maintenance
[ "$ROOT" = /tmp/jenkins-ch07-maintenance ] || exit 70
find "$ROOT" -maxdepth 2 -type f -print 2>/dev/null || true
rm -rf -- "$ROOT"

Finally delete the ch07-maintenance job only if it was created solely for this lab. Preserve exported evidence first if you need it for the checkpoint.

12. Small challenge: identify the failing layer

An API request returns successfully, but no marker appears. List the evidence you would inspect in order: HTTP response/queue identity, build existence, cause/parameters, assigned node, validation output, build result, and finally the marker path. Do not jump directly to “API failed” or delete/recreate the job.

Next lesson

Configuration, Design Choices, and Tradeoffs

Compare parameter vs environment ownership, timer vs event triggers, API identity options, allowlists, and rerun semantics before choosing a production pattern.

Knowledge check

Why does the lab archive evidence.txt instead of trusting the workspace?

Why is DRY_RUN defaulted to true?

Why does the script validate TARGET even though it is a choice parameter?

What proves the API request actually changed the simulated target?

What should be removed before deleting the lab job?

Official references and version notes

Version and compatibility note

Rechecked on 2026-09-16. Examples target Jenkins 2.568.3 LTS, which is tested with Java 21 and 25; the disposable lab uses Java 21. The chapter intentionally relies on Jenkins core/Freestyle parameter, timer, build-cause, environment, and Remote API behavior so mandatory completion does not depend on a commercial service or a specialized trigger plugin. Exact cause payloads and environment variables can vary by job type and installed plugins, so inspect the controller's own .../api/ output rather than assuming a fixed payload.

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.