Skip to content

feat: Implement azdo pipelines build tag add command #277

@tmeckel

Description

@tmeckel

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

Command Description

Add one or more tags to a single Azure Pipelines build identified by its numeric build ID. The command accepts either a single tag value (calls the singular SDK method) or multiple tag values (calls the bulk SDK method), and returns the resulting set of tags on the build.

PUT  https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}/tags/{tag}?api-version=7.1-preview.3   (single tag)
POST https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}/tags?api-version=7.1-preview.3              (multiple tags)

Locked Decisions (do not re-derive)

# Decision Rationale
1 Use the vendored SDK methods build.Client.AddBuildTag (singular) and build.Client.AddBuildTags (bulk) — not raw HTTP. Mocks already generated at internal/mocks/build_client_mock.go:46 and :61. Mandate: vendored API.
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 is repeatable (cobra StringSliceVarP). Multiple occurrences accumulate into a []string. Internally: if len(tags) == 1, call AddBuildTag; if len(tags) >= 2, call AddBuildTags (bulk). Mirrors Python's split-on-comma behavior; preserves both singular and bulk SDK paths.
5 --tag is required (minimum one occurrence). Cobra validation errors with util.FlagErrorf if zero --tag values are provided. Mirrors az pipelines build tag add which requires --tags.
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 (a *string-element slice; the Go SDK unmarshals it as a []string of tag names). No view struct needed for a list of strings; mirrors the show-sibling convention.
8 No confirmation prompt. Adding a tag is reversible (delete via #278) and idempotent at the server. Mirrors az pipelines build tag add.
9 Tag names are not validated client-side. The Azure DevOps REST API accepts any non-empty string. Empty --tag "" would be rejected by cobra's StringSliceVarP parser (it trims empties only if the implementation explicitly does so). Keep the surface minimal; trust the server.
10 No new SDK client, no new helper, no new package beyond internal/cmd/pipelines/build/tag/add. Mandate: minimal code.
11 Mocks for AddBuildTag and AddBuildTags are already generated. Do not regenerate. Verified at internal/mocks/build_client_mock.go:46 and :61.

Command Signature

azdo pipelines build tag add [ORGANIZATION/]PROJECT/BUILD
  --tag TAG                          (repeatable; minimum one required)
  [--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
--tag (str, repeatable) AddBuildTagArgs.Tag (single) or AddBuildTagsArgs.Tags (bulk) cobra.StringSliceVarP; first occurrence also bound to -t
--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 tag name strings. No view struct is required. The slice elements are strings (tag names), not pointers — the SDK uses a []string for the response.

Caveat: the SDK returns *[]string (a pointer to a slice of strings). When the slice is non-nil, the elements are dereferenced automatically. The exporter should handle both nil and empty-slice cases by emitting null and [] respectively.

Table Output Contract

Mirrors transform_build_tags_output from _format.py (single column Tags). Each row's only field is the tag name string.

Tags
release
nightly
v1.2.3

The header is Tags (uppercase, single column). One row per tag returned by the SDK.

Command Wiring

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

API Surface

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

Mocks for AddBuildTag (:46) and AddBuildTags (:61) are already generated. No mock regeneration needed.

Reference Existing Patterns

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