Projects

ProjectOperations CLI · 2026

Jenkins CLI

A Go CLI for Jenkins jobs, builds, pipelines, nodes, queues and plugins. Trigger builds, stream logs and inspect queue state from a terminal or a script.

  • Go
  • Jenkins API
  • JSON
 jenkins job build payments/deploy --param ENV=stagingqueued #4312, waiting for an executor jenkins build log payments/deploy 4312 --follow[Pipeline] stage (Build) ok in 1m12s[Pipeline] stage (Test) FAILURE in 41s jenkins build test-report payments/deploy 4312214 tests, 1 failed: CheckoutSpec.refund_is_idempotent

Operations CLI · Project

Chapter
Agents and operations
Year
Availability
Open source
Last commit

Jenkins CLI puts a Jenkins installation behind one command hierarchy: jobs, builds, pipelines, nodes, queues, views, credentials and system administration. I built it for the part of CI work that is really just waiting, triggering a build and then watching what it does. Before it, that meant a browser tab per build and a page refresh per question.

Jenkins does expose an API, but the answers arrive as pages rather than as data. Reading a failed deploy means the build page, then the console page, then the test report page, then the stage view. A person can hold that together; a script cannot, and neither can an agent asked why last night’s deploy broke.

What the API gives

The endpoints exist, but they do not line up. A console is logText/progressiveText, which returns a byte offset in X-Text-Size and a boolean in X-More-Data. Pipeline stages come from wfapi/describe. Tests come from testReport, and injected variables from somewhere else again. Worse, triggering a build returns no build number at all. It returns a queue location, and the number only exists once an executor picks the item up. Four endpoints, three response shapes and one indirection stand between a question and its answer. That gap is the work this binary absorbs.

Paths, crumbs and profiles

cli-go/internal/path/path.go turns the path a person types, team/project/pipeline, into the path Jenkins wants, /job/team/job/project/job/pipeline, escaping each segment and converting back for display. Every write first fetches a CSRF crumb from /crumbIssuer/api/json, caches it for five minutes and sets the header the issuer names. A 404 from that endpoint means CSRF protection is off, so crumbs need no configuration either way. Authentication is HTTP basic with a Jenkins username and an API token. Profiles live in ~/.config/jenkins-cli/config.yaml, with the directory at mode 0700 and the file at 0600. Flags beat environment variables, which beat the stored profile.

GroupCommands
Jobslist, get, create, config, copy, rename, enable, disable
Buildslist, get, log, stages, test-report, artifacts, env, replay, stop
Pipelinesvalidate, input list, input submit, input abort
Infrastructurenode, queue, view, plugin, credential, user
Systeminfo, quiet-down, cancel-quiet-down, restart, run-script

Choices and their cost

  • Job paths stay slash separated, and the CLI does the /job/ interleaving. Every command that takes a job reads the same way. The cost is that a job whose own name contains a slash cannot be addressed, because the path is split before it is escaped.
  • --wait and --follow are separate flags over one polling loop. The CLI watches /queue/item/<id> until an executable appears, then it finally has a build number to stream from. Default timeout is thirty minutes. Polling is the only option, since Jenkins offers nothing to subscribe to.
  • Inspection is split by question rather than merged into one status command. build log, build stages, build test-report, build artifacts and build env each hit their own endpoint. A script asks for exactly one thing. A human debugging runs four commands where one web page showed all four.
  • system run-script executes Groovy on the controller. It is the honest way to reach what no REST endpoint exposes. It is also unrestricted access, which is why --read-only blocks it, and why I would rather the token behind it belonged to a least privilege account.

Following a deploy

export JENKINS_URL=https://jenkins.example.com
export JENKINS_USER=automation JENKINS_TOKEN=11xxxxxxxxxxxx
jenkins job list --recursive --status FAILURE -o json | jq -r '.[].name'
jenkins job build payments/deploy --param ENV=staging --wait --follow
jenkins build stages payments/deploy 4312
jenkins build test-report payments/deploy 4312 -o json
jenkins build artifacts payments/deploy 4312 --download --output-dir ./out

One command triggers, waits and streams the console. When it fails, the stage view names the stage and the test report names the case, both without a browser. Job XML round trips the same way: jenkins job config team/deploy > config.xml, edit, then jenkins job update team/deploy --from-file config.xml. For unattended runs, JENKINS_READ_ONLY=true and JENKINS_NO_INPUT=1 block writes and prompts, and JSON failures arrive on stderr as an object carrying error and status_code.

Where it stops

--read-only is a guardrail in the CLI, not a substitute for Jenkins RBAC, so the account behind the token still needs the right permissions. Job configuration is raw XML in and raw XML out, with no schema and no reconciliation. There is no drift detection and no config as code layer. Building from source needs Go 1.26 or later. It is MIT licensed and ships prebuilt binaries for macOS, Linux and Windows on amd64 and arm64, with an install script and jenkins update for self replacement.

Next projectOperations CLI

Jira CLI

View project