Skip to content

feat: Implement azdo pipelines build tag delete command #278

@tmeckel

Description

@tmeckel

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

Command Description

Remove a single tag from an Azure Pipelines build identified by its numeric build ID. The command requires exactly one --tag value (the underlying SDK only supports single-tag removal). The returned *[]string contains the remaining tags on the build after deletion.

DELETE https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}/tags/{tag}?api-version=7.1-preview.3

Note: the SDK method DeleteBuildTag is singular-only. For multiple tags with special characters, Azure DevOps also supports a PATCH method (in API 6.0+), but the Python extension does not implement it and the az CLI does not expose it; we mirror Python's singular behavior. Special characters in the tag value are the user's responsibility (URL-encode if needed).

Locked Decisions (do not re-derive)

# Decision Rationale
1 Use the vendored SDK method build.Client.DeleteBuildTag (singular only). Mock already generated at internal/mocks/build_client_mock.go:210. Mandate: vendored API. The SDK exposes no bulk delete.
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). Mirrors cancel (#252) and queue (#253).
3 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/build/{cancel,queue}/ precedent.
4 The --tag flag accepts a single string value (not a slice). cobra.ExactArgs(0) for --tag; cobra validates with cobra.MinimumNArgs(0) and a Go-side check that exactly one --tag was provided. Repeatable --tag is rejected: an additional occurrence triggers a util.FlagErrorf error. The SDK is singular-only; we do not introduce a multi-tag loop (a single call with one tag is the contract).
5 --tag is required. Cobra validation errors with util.FlagErrorf if --tag is empty or absent. Mirrors az pipelines build tag delete which requires --tag.
6 The user is not required to pass a project; the project segment in the positional is optional. When omitted, the project is resolved from the default config. Mirrors all other pipelines build leaves.
7 Default output is a table. JSON output via --json passes the raw SDK result *[]string to opts.exporter.Write. No view struct needed for a list of strings; mirrors the show-sibling convention.
8 The command issues a destructive mutation; require a confirmation prompt unless --yes is supplied. The prompt text is: "Are you sure you want to delete the tag <TAG> from build <BUILD_ID>?". On cancel, return util.ErrCancel. AGENTS.md: Confirmation for Destructive Operations.
9 No new SDK client, no new helper, no new package beyond internal/cmd/pipelines/build/tag/delete. Mandate: minimal code.
10 Mock for DeleteBuildTag is already generated. Do not regenerate. Verified at internal/mocks/build_client_mock.go:210.

Command Signature

azdo pipelines build tag delete [ORGANIZATION/]PROJECT/BUILD
  --tag TAG                          (string, required, single value)
  [--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.
  • --tag is required and must be a single value. --tag TAG1 --tag TAG2 is rejected with util.FlagErrorf("only one --tag value is allowed").

Flags

Flag Maps to Notes
--tag (str, required, single) DeleteBuildTagArgs.Tag First occurrence also bound to -t; multiple occurrences rejected
--yes (bool) confirmation skip Suppresses the destructive-operation prompt
--json / --jq / --template util.AddJSONFlags JSON export

JSON Output Contract

Pass the raw SDK result *[]string to opts.exporter.Write. The result is a list of the remaining tag name strings on the build after deletion. No view struct is required.

Table Output Contract

Mirrors transform_build_tags_output from _format.py (single column Tags). The output is the remaining tags on the build (not the deleted tag).

Tags
release
nightly

Command Wiring

  • Package path: internal/cmd/pipelines/build/tag/delete
  • Files:
    • delete.goNewCmd(ctx util.CmdContext) *cobra.Command + deleteOptions + runDelete. Note: package import path is delete, not del, to match the leaf directory. The cobra Use: "delete ..." matches the leaf name.
    • delete_test.go — table-driven gomock tests
  • Update internal/cmd/pipelines/build/tag/tag.go to add delete.NewCmd(ctx) to cmd.AddCommand(...). Update the Example block.
  • Higher-level parents must already remain wired: pipelinesbuildtagdelete.

API Surface

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

Mock for DeleteBuildTag (:210) is already generated. No mock regeneration needed.

Reference Existing Patterns

  • azure-devops/azext_devops/dev/pipelines/build.py#L198-L208delete_build_tag Python implementation.
  • azure-devops/azext_devops/dev/pipelines/commands.pyg.command('delete', 'delete_build_tag', table_transformer=transform_build_tags_output).
  • azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/_format.pytransform_build_tags_output (single-column Tags table).
  • internal/cmd/pipelines/build/cancel/cancel.go (feat: Implement azdo pipelines build cancel command #252) — primary target-resolution precedent: uses Use: "cancel [ORGANIZATION/]PROJECT/BUILD", cobra.ExactArgs(1), util.ParseProjectTargetWithDefaultOrganization. delete follows the same target parsing plus the destructive confirmation pattern.
  • internal/cmd/pipelines/variablegroup/delete/delete.goprimary destructive precedent: confirmation prompt unless --yes is supplied. delete uses the same pattern with the build-target message above.
  • 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. delete is a mutation so it does not use the list pattern; only the JSON/table output convention is shared.
  • internal/cmd/boards/workitem/list/list_test.go:765-844setupFakeDeps / stub* fixture.
  • internal/mocks/build_client_mock.go:210 — mock for DeleteBuildTag (already generated).
  • internal/azdo/factory.go:61ClientFactory().Build(...) accessor (reuse).
  • internal/cmd/util/scope.go:183util.ParseProjectTargetWithDefaultOrganization (project-scoped parser).
  • internal/cmd/util/prompter.go — confirmation prompt + util.ErrCancel (used by this leaf).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions