Skip to content

feat: Implement azdo pipelines build cancel command #252

Description

@tmeckel

Sub-issue of #213. Hardened spec — do not re-derive decisions. Mirrors az pipelines build cancel from the Azure CLI and the Python implementation at azure-devops/azext_devops/dev/pipelines/build.py#L106.

Command Description

Cancel a running or queued build by ID. The command fetches the build, sets its status to "Cancelling", and posts the update via the Build REST 7.1 endpoint. The returned Build reflects the post-update state (typically status: cancelling).

PATCH https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}?api-version=7.1

The request body is a Build resource whose only mutated field is status: "Cancelling".

Note: az pipelines build cancel accepts a numeric build ID; it has no name resolution path (builds are immutable records and have no displayable name in the same way definitions do). The BUILD positional segment must parse as a positive integer; non-numeric values are rejected with util.FlagErrorf.

Locked Decisions (do not re-derive)

# Decision Rationale
1 Use the vendored SDK build.Client.UpdateBuild (not raw HTTP). Mock already generated at internal/mocks/build_client_mock.go:1286. Consistent with build list (#211) and build show (#241).
2 The build is identified by a positional BUILD argument ([ORGANIZATION/]PROJECT/BUILD). The BUILD segment must parse as a positive integer (no name resolution — builds are numeric). Folds the previous --id flag into the positional. Mirrors az pipelines build cancel ergonomics.
2.5 Parse the positional using util.ParseProjectTargetWithDefaultOrganization from internal/cmd/util/scope.go:183. The function returns a *Target with Organization, Project, Target fields, accepting 2- or 3-segment inputs. Mirrors internal/cmd/pipelines/variablegroup/{create,delete,show,update}/ precedent.
3 util.ExactArgs(1, "build target is required"). The Target field is parsed as int; non-numeric / zero / negative values rejected with util.FlagErrorf before the SDK call. Cheap pre-flight validation; REST 4xx is unhelpful.
4 Default output is a table. JSON output via --json passes a dedicated view struct buildJSON (the same shape as build show (#241) uses) to opts.exporter.Write. Mirrors internal/cmd/pipelines/build/list/list.go (#211).
5 The command issues a destructive mutation; require a confirmation prompt unless --yes is supplied. On cancel return util.ErrCancel. AGENTS.md: Confirmation for Destructive Operations.
6 No --open flag. The Python extension supports --open; the azdo CLI renders a Go-formatted result instead and the user is unlikely to need a browser redirect mid-cancel. Keep the surface minimal.
7 No new SDK client, no new helper, no new package beyond internal/cmd/pipelines/build/cancel. Mandate: minimal code.
8 Mock for UpdateBuild is already generated at internal/mocks/build_client_mock.go:1286. Do not regenerate. Verified.

Command Signature

azdo pipelines build cancel [ORGANIZATION/]PROJECT/BUILD
  [--yes]
  [--json ...]
  • cobra.ExactArgs(1)args[0] → target (via util.ParseProjectTargetWithDefaultOrganization).
  • The Target field is parsed as int; non-numeric / zero / negative values rejected with util.FlagErrorf.

Flags

Flag Maps to Notes
--yes (bool) confirmation skip Suppresses the destructive-operation prompt
--json / --jq / --template util.AddJSONFlags JSON export

JSON Output Contract

The JSON view struct mirrors build show (#241). Fields (matching SDK struct JSON tags):

type buildJSON struct {
    ID          *int    `json:"id,omitempty"`
    Number      *string `json:"buildNumber,omitempty"`
    Status      *string `json:"status,omitempty"`
    Result      *string `json:"result,omitempty"`
    DefinitionID   *int    `json:"definitionId,omitempty"`
    DefinitionName *string `json:"definitionName,omitempty"`
    SourceBranch   *string `json:"sourceBranch,omitempty"`
    QueueTime      *string `json:"queueTime,omitempty"`
    Reason         *string `json:"reason,omitempty"`
    RequestedBy    *string `json:"requestedFor,omitempty"`
}

Table Output Contract

Default columns: ID, NUMBER, STATUS, RESULT, DEFINITION ID, DEFINITION NAME, SOURCE BRANCH, QUEUED TIME, REASON (mirrors transform_build_table_output from _format.py).

Command Wiring

  • Package path: internal/cmd/pipelines/build/cancel
  • Files:
    • cancel.goNewCmd(ctx util.CmdContext) *cobra.Command + cancelOptions + runCancel
    • cancel_test.go — table-driven gomock tests
  • Update internal/cmd/pipelines/build/build.go to add cancel.NewCmd(ctx) to cmd.AddCommand(...). Update the Example block.
  • Higher-level parents must already remain wired: pipelinesbuildcancel.

API Surface

Reuse the already-vendored client. No new SDK clients or mocks required.

  • build.Client.UpdateBuildBuilds - Update (REST 7.1)
  • build.UpdateBuildArgs struct: {Build *Build, Project *string, BuildId *int}.
  • The implementation constructs a Build{Status: types.ToPtr("Cancelling")} and posts it to UpdateBuild.

Mock for UpdateBuild is already generated at internal/mocks/build_client_mock.go:1286. No mock regeneration needed.

Reference Existing Patterns

  • azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/build.py#L106-L120build_cancel Python implementation.
  • azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/commands.py#L92g.command('cancel', 'build_cancel', table_transformer=transform_build_table_output).
  • azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/_format.pytransform_build_table_output (single-row) and _transform_build_row (row builder).
  • internal/cmd/pipelines/variablegroup/delete/delete.goprimary target-resolution precedent (Decision 2 / 2.5): uses Use: "delete [ORGANIZATION/]PROJECT/GROUP", util.ExactArgs(1, "..."), util.ParseProjectTargetWithDefaultOrganization, and a shared.ResolveVariableGroup helper. Our cancel differs only in (a) target is always a numeric build ID, no name lookup; (b) the destructive confirmation prompt uses "Are you sure you want to cancel this build?".
  • internal/cmd/pipelines/build/list/list.go (feat: Implement azdo pipelines build list command #211) — primary list reference for the modern list pattern, JSON view struct, table printer, and --max-items cap. cancel does not use --max-items; the table emits exactly one row.
  • internal/cmd/pipelines/build/show/show.go (feat: Implement azdo pipelines build show command #241) — primary show sibling; reuse the same buildJSON view struct.
  • internal/cmd/boards/workitem/list/list_test.go:765-844setupFakeDeps / stub* fixture.
  • internal/mocks/build_client_mock.go:1286 — mock for UpdateBuild (already generated).
  • internal/azdo/factory.go:61ClientFactory().Build(...) accessor (reuse).
  • internal/cmd/util/scope.go:183util.ParseProjectTargetWithDefaultOrganization (project-scoped parser).

References

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions