Skip to content

cli/command/job: add docker job commands - #7503

Open
glours wants to merge 6 commits into
docker:masterfrom
glours:feat-job-commands
Open

glours wants to merge 6 commits into
docker:masterfrom
glours:feat-job-commands

Conversation

@glours

@glours glours commented Nov 10, 2026 •

Copy link
Copy Markdown

Related to: moby/moby#53485

Summary

Add the docker job command group, a client for the jobs extension of the daemon (org.mobyproject.jobs.v0, served as a gRPC service on the daemon socket). Jobs are one-shot or cron-scheduled containers managed by the daemon; every execution is a run with a durable identity that survives container removal.

  • ls, inspect [--run RUN] and runs [--limit N] [--before CURSOR] read jobs and runs.
  • logs [-f] JOB reads the logs of the most recent run from the container logs API with the run's container ID (the Jobs API does not proxy logs). When the latest run has no readable container (removed, still pending, or failed before its container was created) it falls back to the most recent run that has one and says so on stderr. With -f it keeps following across runs for schedule jobs.
  • run [--reschedule], cancel, pause, unpause, rm [--runs keep|remove|remove-finished] and prune [--label LABEL] [-f] act on jobs.
  • wait [--run RUN] [--condition terminal|running] JOB blocks until a run reaches the condition and exits with the run's exit code; a run stopped by a timeout or a cancellation exits with status 1.

Requirements

The daemon must serve Engine API 1.57 or newer and have the extension enabled, which is not the default: dockerd --extension-enable org.mobyproject.jobs.v0, or extensions.enable in daemon.json. The group carries the version: 1.57 annotation, so older daemons hide it and refuse it with the usual "requires API version" error. On a recent daemon where the extension is off, commands fail with a message pointing at that flag, and Unavailable (daemon starting or unreachable) is reported as retryable instead of dumping the gRPC status.

The CLI reaches the extension through github.com/moby/extensions/client, dialed lazily on first use because the command tree is built before the CLI is initialized.

Notes for review

  • internal/jobsapi/v0 is a copy of extpoints/jobs/api/v0/jobs.go from moby/moby at 402d0a16f1 (it is not published as a standalone Go module). protogen/ and jobs.proto are generated from it with mobyextgen at the vendored github.com/moby/extensions version.
  • github.com/moby/extensions is vendored at the pseudo-version v0.0.0-20260826001921-d37867cb107f; google.golang.org/grpc and google.golang.org/protobuf become direct dependencies.
  • Vendoring lands with its first importer rather than in a separate vendor: commit, so that go mod tidy && go mod vendor leaves no diff at any commit. The series is: contract and vendoring, ls/logs, inspect/runs, the action commands, wait, then the generated markdown docs.
  • Every commit builds, passes go test -race ./cli/command/job/... and golangci-lint.

Not in this PR

  • No command to create a job: jobs are created through the Jobs API.
  • ls and runs have no --format, --quiet or --filter, and inspect prints the raw JSON of the API types (field names as in the Go contract).
  • No e2e test, as it needs a daemon started with the extension enabled. The unit tests run every command against a fake client.

Release notes (optional)

A picture of a cute animal (not mandatory but encouraged)

@glours
glours requested review from a team and thaJeztah as code owners November 10, 2026 09:57
@glours glours changed the title Feat job commands cli/command/job: add docker job commands Nov 10, 2026
@codecov-commenter

codecov-commenter commented Nov 10, 2026 •

Copy link
Copy Markdown

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

This PR adds a full suite of docker job commands (cancel, inspect, list, logs, pause, prune, resume, rm, run, runs, wait) backed by a new gRPC jobs API and a vendored github.com/moby/extensions framework. The structure follows Docker CLI conventions well. Three medium-severity nil-pointer risks were found in the new code — all straightforward to fix — plus two lower-confidence edge cases in the vendored extensions client.

Comment thread cli/command/job/run.go Resolved
Comment thread cli/command/job/inspect.go Resolved
Comment thread cli/command/job/logs.go Resolved

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

Lower-confidence findings (not posted inline)

  • [low] cli/command/job/list.go:52 — NextFire column can display a negative duration when NextFireAtNano is in the past (unverified; low severity)

    The guard !j.Paused && j.NextFireAtNano != 0 does not check whether the timestamp is in the future. If the daemon hasn't refreshed NextFireAtNano after the last cron fire, time.Unix(0, j.NextFireAtNano).Sub(now) is negative and units.HumanDuration renders it as e.g. in -2 seconds. Adding .After(now) to the condition would prevent confusing output.

Comment thread cli/command/job/resume.go Resolved
Copy extpoints/jobs/api/v0/jobs.go from moby/moby at 402d0a16f1 and
regenerate protogen/ with mobyextgen at the vendored
github.com/moby/extensions version, so it compiles standalone against
github.com/docker/cli/internal/jobsapi/v0 instead of moby's internal import
path.

Vendor github.com/moby/extensions, which the generated code builds on, and
make google.golang.org/grpc and google.golang.org/protobuf direct
dependencies since they are now imported directly.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
Add the `docker job` command group. It reaches the daemon's jobs extension
point through github.com/moby/extensions/client, dialed lazily on first use
because the command tree is built before the CLI is initialized.

- `ls` lists jobs: trigger, state, paused flag, latest run, exit code and
  next fire.
- `logs [-f] JOB` reads the logs of a job's most recent run from the
  container logs API with the run's container ID (the Jobs API does not
  proxy logs). When the latest run has no readable container (removed,
  still pending, or failed before its container was created) it falls back
  to the most recent run that has one and says so on stderr. With -f on a
  schedule job it keeps following across runs; on a manual job it returns
  once the logs end.

Jobs API status codes become actionable errors instead of raw gRPC dumps:
Unimplemented points at `--extension-enable org.mobyproject.jobs.v0`,
Unavailable is reported as retryable, the other codes show the daemon's
message, and a cancelled context (Ctrl-C) stays recognisable so the CLI
exits quietly.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
- `inspect [--run RUN] JOB` prints a job or one of its runs as JSON.
- `runs [--limit N] [--before CURSOR] JOB` lists a job's runs, most recent
  first, with paging; a stale cursor is reported on stderr.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
…`rm` and `prune` commands

- `run [--reschedule] JOB` triggers a run and prints its ID.
- `cancel JOB` stops the in-flight run; cancelling an idle job is a no-op.
- `pause JOB` and `unpause JOB` suppress and re-arm a schedule trigger.
- `rm [--runs keep|remove|remove-finished] JOB` removes a job; run history
  is kept unless asked otherwise, and the ID of a removed job stays usable
  to read or purge it.
- `prune [--label LABEL]` removes idle manual jobs.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
`wait [--run RUN] [--condition terminal|running] JOB` blocks until a run
reaches the condition and exits with the run's exit code, so it can be used
in scripts. Without --run the latest run is awaited, which for a schedule
job that has not run yet means blocking until its next fire.

A run stopped by a timeout or by `docker job cancel` fails with status 1 and
its state on stderr, whatever exit code its container reacted with. With
`--condition running` the run ID is printed once the run is up.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
Add the reference page of each `docker job` command and list the group in
docker.md. Generated with `make mddocs`.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The docker job command group is well-structured, follows Docker CLI conventions, and uses a sound lazy-connection pattern. One minor defensive-coding gap is noted inline; a few additional low-severity observations are listed below for the author's awareness.

Lower-confidence findings (not posted inline — unverified low-severity)

  • [low] cli/command/job/logs.go:171 — time.After timer goroutine leak in sleepOrCancel: the time.After channel is not stopped on context cancellation, leaving a 2-second timer goroutine per cancelled poll cycle. Use time.NewTimer + defer t.Stop() to fix. Benign in practice given the short interval.
  • [low] cli/command/job/cmd.go:33 — gRPC connection not closed when a subcommand's RunE returns an error (cobra skips PersistentPostRunE on error). The in-code comment acknowledges this; the OS reclaims the socket on exit, so it only matters for multi-command-in-process usage.
  • [low] cli/command/job/runs.go:127 — Paging hint prints job name and cursor without shell-quoting: docker job runs --before $cursor $job. A job name containing spaces produces a broken suggested command. Using %q for the job name would make the hint copy-pasteable.
  • [low] cli/command/job/inspect.go:81 — v = reply.Run on the --run branch assigns a *Run pointer without a nil check; if the daemon returns nil Run with no error, json.MarshalIndent outputs null silently. A nil guard matching the reply.Job pattern would be more consistent.

Comment thread cli/command/job/wait.go Resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants