Sub-issue of #116. Hardened spec — do not re-derive decisions. Mirrors az pipelines run from the Azure CLI and the Python implementation at azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/pipeline.py#L100-L142.
Note: The Python implementation has two paths — a legacy build.Client.QueueBuild path (when --parameters is not supplied) and a modern v6.0 pipelines.Client.RunPipeline path (when --parameters is supplied). The modern v6.0 Pipelines client is not vendored in the Go SDK, so the --parameters path is out of scope for this issue. The Go port only implements the legacy QueueBuild path, mirroring the same scope decision as #254 (pipelines create is non-interactive only).
Command Description
Queue (run) an existing Azure Pipeline (build definition) in a project. The command resolves the target pipeline (a positive integer is used directly; a string is resolved via GetDefinitions), builds a Build payload with the supplied branch, commit, and variables, and queues it via build.Client.QueueBuild. Returns the queued Build (i.e. the new run).
POST https://dev.azure.com/{organization}/{project}/_apis/build/builds?api-version=7.1
Locked Decisions (do not re-derive)
| # |
Decision |
Rationale |
| 1 |
Use the vendored SDK build.Client.QueueBuild (not raw HTTP). Mock already generated at internal/mocks/build_client_mock.go:1227 (QueueBuild). The build.Client.GetDefinitions mock at :837 is reused for name resolution. |
Consistent with build queue (#253) and pipelines list (#256). |
| 2 |
The pipeline is identified by a positional PIPELINE argument ([ORGANIZATION/]PROJECT/PIPELINE). The target segment is resolved via a new shared.ResolvePipelineDefinition helper at internal/cmd/pipelines/shared/resolve.go that mirrors Python's get_definition_id_from_name: if it parses as a positive integer, use it directly; otherwise call build.Client.GetDefinitions and pick the first match. Folds the previous --id and --name flags into a single positional. |
Mirrors sibling leaves (#243, #255, #257). One positional for either ID or name. |
| 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, "pipeline target is required"). |
Standard cobra pattern; the 1st positional is the full target. |
| 4 |
--branch (str) sets Build.SourceBranch. The SDK normalizes bare branch names (e.g. main) to refs/heads/main; the Python helper resolve_git_ref_heads does the same. We replicate the normalization in Go to avoid surfacing an error when the user passes a bare branch name. |
Mirrors Python resolve_git_ref_heads (line 25 of pipeline.py). |
| 5 |
--commit-id (str) sets Build.SourceVersion. |
Mirrors Python pipeline_run. |
| 6 |
--variable name=value is repeatable. Each pair is parsed on the first =. Empty value is allowed. Pairs are accumulated into Build.Parameters (*map[string]string). |
Mirrors Python set_param_variable (line 73 of pipeline.py). |
| 7 |
--folder-path (str) is used as a filter when resolving the name to an ID; ignored if the segment is a positive integer. |
Mirrors Python get_definition_id_from_name(..., path=folder_path). |
| 8 |
No confirmation prompt. Run is non-destructive (creates a new build). |
Mirrors az pipelines run. |
| 9 |
No new SDK client, no new helper beyond shared.ResolvePipelineDefinition, no new package beyond internal/cmd/pipelines/run. |
Mandate: minimal code. |
| 10 |
Mocks for QueueBuild and GetDefinitions are already generated. Do not regenerate. |
Verified at internal/mocks/build_client_mock.go:1227 and :837. |
| 11 |
The modern v6.0 pipelines.Client.RunPipeline path (used by Python when --parameters is supplied) is out of scope. The vendored SDK does not include a pipelines package. If/when the v6.0 Pipelines client is added to the vendored SDK, a follow-up issue will re-enable --parameters. |
Same scope decision as #254 (non-interactive only). |
| 12 |
The --open flag from Python (open the run page in the web browser) is out of scope for the Go port. The azdo CLI has no equivalent of Python's webbrowser.open_new integration; if a user wants to open the run in a browser they can copy the URL from the JSON output. |
Mirror existing azdo conventions; no browser opener. |
| 13 |
Default output is a single-row table via transform_pipeline_run_table_output (Run ID, Number, Status, Result, Pipeline ID, Pipeline Name, Source Branch, Queued Time, Reason). JSON output via --json passes the raw SDK type to opts.exporter.Write. |
Mirrors _transform_pipeline_run_row from _format.py; mirrors the show-sibling convention from #203 Decision 7 / #205 Decision 11. |
Command Signature
azdo pipelines run [ORGANIZATION/]PROJECT/PIPELINE
[--branch BRANCH] (string: refs/heads/main, main, refs/pull/1/merge, refs/tags/X)
[--commit-id COMMIT_ID] (string)
[--variable NAME=VALUE] (repeatable: space-separated name=value pairs)
[--folder-path FOLDER_PATH] (string: filter for name resolution)
[--json ...]
cobra.ExactArgs(1) — args[0] → target (via util.ParseProjectTargetWithDefaultOrganization).
- The
Target field of the parsed *Target is resolved via shared.ResolvePipelineDefinition(ctx, clientFact, args[0]).
--variable may be specified multiple times; values are accumulated into Build.Parameters.
--branch is normalized to refs/heads/{branch} if it does not already start with refs/heads/, refs/pull/, or refs/tags/.
Flags
| Flag |
Maps to |
Notes |
--branch (str) |
Build.SourceBranch |
Normalized to refs/heads/... |
--commit-id (str) |
Build.SourceVersion |
Optional |
--variable (str, repeatable) |
Build.Parameters (*map[string]string) |
name=value format |
--folder-path (str) |
GetDefinitions.Path filter |
Used only during name → ID resolution |
--json / --jq / --template |
util.AddJSONFlags |
JSON export |
util.AddJSONFlags must list every JSON field exposed: id, buildNumber, status, result, sourceBranch, sourceVersion, queueTime, reason (mirroring the #252 and #253 build-sibling JSON surfaces, but keyed by the run-side field names).
JSON Output Contract
Pass the raw SDK type *build.Build to opts.exporter.Write. No view struct is required (per the show-sibling convention from #203 Decision 7 / #205 Decision 11).
Table Output Contract
Mirrors transform_pipeline_run_table_output from _format.py. Single row with columns:
Run ID — *int
Number — *string (build number)
Status — *string
Result — *string (or blank if empty)
Pipeline ID — *Build.Definition.Id
Pipeline Name — *Build.Definition.Name
Source Branch — *string, with refs/heads/ prefix stripped (e.g. main not refs/heads/main)
Queued Time — *azuredevops.Time formatted as YYYY-MM-DD HH:MM:SS in local timezone (mirrors Python's dateutil.parser.parse(...).astimezone(tzlocal()))
Reason — *string
Command Wiring
- Package path:
internal/cmd/pipelines/run
- Files:
run.go — NewCmd(ctx util.CmdContext) *cobra.Command + runOptions + runRun
shared/resolve.go (under internal/cmd/pipelines/shared/) — ResolvePipelineDefinition(ctx, clientFact, raw) (int, error) (positive-int fast path + GetDefinitions first-match lookup)
run_test.go — table-driven gomock tests
- Update
internal/cmd/pipelines/pipelines.go to add run.NewCmd(ctx) as a top-level leaf (cmd.AddCommand(...)). Update the Example block.
- Higher-level parents must already remain wired:
pipelines → run (top-level).
API Surface
Reuse the already-vendored client. No new SDK clients or mocks required.
build.Client.GetDefinitions → Definitions - List (REST 7.1) — for name → ID resolution
build.Client.QueueBuild → Builds - Queue (REST 7.1)
build.GetDefinitionsArgs struct: {Project *string, Name *string, Path *string}.
build.GetDefinitionsResponseValue struct: {Value *[]BuildDefinitionReference, Count *int}.
build.BuildDefinitionReference struct: minimal reference (id, name, etc.).
build.QueueBuildArgs struct: {Build *Build, Project *string}.
build.Build model — has Definition *DefinitionReference, SourceBranch *string, SourceVersion *string, Parameters *map[string]string, plus all the standard status/result fields.
build.DefinitionReference struct: {Id *int, Name *string}.
Mocks for GetDefinitions (:837) and QueueBuild (:1227) are already generated. No mock regeneration needed.
Reference Existing Patterns
azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/pipeline.py#L100-L142 — pipeline_run Python implementation.
azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/pipeline.py#L73-L82 — set_param_variable helper (parse name=value pairs).
azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/commands.py#L105 — g.command('run', 'pipeline_run', table_transformer=transform_pipeline_run_table_output).
azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/_format.py — transform_pipeline_run_table_output and _transform_pipeline_run_row.
azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/build_definition.py — get_definition_id_from_name helper (the name → ID resolution pattern).
internal/cmd/pipelines/variablegroup/delete/delete.go — primary target-resolution precedent (Decision 2 / 2.5): uses Use: "delete [ORGANIZATION/]PROJECT/GROUP", util.ExactArgs(1, "..."), util.ParseProjectTargetWithDefaultOrganization, and a shared.ResolveVariableGroup helper.
internal/cmd/pipelines/build/queue/queue.go — closest queue sibling (project-scoped, QueueBuild, branch/variables). Use the same Build construction pattern.
internal/cmd/pipelines/variablegroup/list/list.go — primary list reference for the modern list pattern, JSON view struct, table printer. run does not use --max-items; the table emits exactly one row.
internal/cmd/boards/workitem/list/list_test.go:765-844 — setupFakeDeps / stub* fixture.
internal/mocks/build_client_mock.go:1227 — mock for QueueBuild (already generated).
internal/mocks/build_client_mock.go:837 — mock for GetDefinitions (already generated, used for name → ID resolution).
internal/azdo/factory.go:61 — ClientFactory().Build(...) accessor (reuse).
internal/cmd/util/scope.go:183 — util.ParseProjectTargetWithDefaultOrganization (project-scoped parser).
References
Sub-issue of #116. Hardened spec — do not re-derive decisions. Mirrors
az pipelines runfrom the Azure CLI and the Python implementation atazure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/pipeline.py#L100-L142.Command Description
Queue (run) an existing Azure Pipeline (build definition) in a project. The command resolves the target pipeline (a positive integer is used directly; a string is resolved via
GetDefinitions), builds aBuildpayload with the supplied branch, commit, and variables, and queues it viabuild.Client.QueueBuild. Returns the queuedBuild(i.e. the new run).Locked Decisions (do not re-derive)
build.Client.QueueBuild(not raw HTTP). Mock already generated atinternal/mocks/build_client_mock.go:1227(QueueBuild). Thebuild.Client.GetDefinitionsmock at:837is reused for name resolution.build queue(#253) andpipelines list(#256).PIPELINEargument ([ORGANIZATION/]PROJECT/PIPELINE). The target segment is resolved via a newshared.ResolvePipelineDefinitionhelper atinternal/cmd/pipelines/shared/resolve.gothat mirrors Python'sget_definition_id_from_name: if it parses as a positive integer, use it directly; otherwise callbuild.Client.GetDefinitionsand pick the first match. Folds the previous--idand--nameflags into a single positional.util.ParseProjectTargetWithDefaultOrganizationfrominternal/cmd/util/scope.go:183. The function returns a*TargetwithOrganization,Project,Targetfields, accepting 2- or 3-segment inputs.internal/cmd/pipelines/variablegroup/{create,delete,show,update}/precedent.util.ExactArgs(1, "pipeline target is required").--branch(str) setsBuild.SourceBranch. The SDK normalizes bare branch names (e.g.main) torefs/heads/main; the Python helperresolve_git_ref_headsdoes the same. We replicate the normalization in Go to avoid surfacing an error when the user passes a bare branch name.resolve_git_ref_heads(line 25 ofpipeline.py).--commit-id(str) setsBuild.SourceVersion.pipeline_run.--variable name=valueis repeatable. Each pair is parsed on the first=. Empty value is allowed. Pairs are accumulated intoBuild.Parameters(*map[string]string).set_param_variable(line 73 ofpipeline.py).--folder-path(str) is used as a filter when resolving the name to an ID; ignored if the segment is a positive integer.get_definition_id_from_name(..., path=folder_path).az pipelines run.shared.ResolvePipelineDefinition, no new package beyondinternal/cmd/pipelines/run.QueueBuildandGetDefinitionsare already generated. Do not regenerate.internal/mocks/build_client_mock.go:1227and:837.pipelines.Client.RunPipelinepath (used by Python when--parametersis supplied) is out of scope. The vendored SDK does not include apipelinespackage. If/when the v6.0 Pipelines client is added to the vendored SDK, a follow-up issue will re-enable--parameters.--openflag from Python (open the run page in the web browser) is out of scope for the Go port. TheazdoCLI has no equivalent of Python'swebbrowser.open_newintegration; if a user wants to open the run in a browser they can copy the URL from the JSON output.azdoconventions; no browser opener.transform_pipeline_run_table_output(Run ID, Number, Status, Result, Pipeline ID, Pipeline Name, Source Branch, Queued Time, Reason). JSON output via--jsonpasses the raw SDK type toopts.exporter.Write._transform_pipeline_run_rowfrom_format.py; mirrors the show-sibling convention from #203 Decision 7 / #205 Decision 11.Command Signature
cobra.ExactArgs(1)—args[0]→ target (viautil.ParseProjectTargetWithDefaultOrganization).Targetfield of the parsed*Targetis resolved viashared.ResolvePipelineDefinition(ctx, clientFact, args[0]).--variablemay be specified multiple times; values are accumulated intoBuild.Parameters.--branchis normalized torefs/heads/{branch}if it does not already start withrefs/heads/,refs/pull/, orrefs/tags/.Flags
--branch(str)Build.SourceBranchrefs/heads/...--commit-id(str)Build.SourceVersion--variable(str, repeatable)Build.Parameters(*map[string]string)name=valueformat--folder-path(str)GetDefinitions.Pathfilter--json/--jq/--templateutil.AddJSONFlagsutil.AddJSONFlagsmust list every JSON field exposed:id,buildNumber,status,result,sourceBranch,sourceVersion,queueTime,reason(mirroring the#252and#253build-sibling JSON surfaces, but keyed by the run-side field names).JSON Output Contract
Pass the raw SDK type
*build.Buildtoopts.exporter.Write. No view struct is required (per the show-sibling convention from #203 Decision 7 / #205 Decision 11).Table Output Contract
Mirrors
transform_pipeline_run_table_outputfrom_format.py. Single row with columns:Run ID—*intNumber—*string(build number)Status—*stringResult—*string(or blank if empty)Pipeline ID—*Build.Definition.IdPipeline Name—*Build.Definition.NameSource Branch—*string, withrefs/heads/prefix stripped (e.g.mainnotrefs/heads/main)Queued Time—*azuredevops.Timeformatted asYYYY-MM-DD HH:MM:SSin local timezone (mirrors Python'sdateutil.parser.parse(...).astimezone(tzlocal()))Reason—*stringCommand Wiring
internal/cmd/pipelines/runrun.go—NewCmd(ctx util.CmdContext) *cobra.Command+runOptions+runRunshared/resolve.go(underinternal/cmd/pipelines/shared/) —ResolvePipelineDefinition(ctx, clientFact, raw) (int, error)(positive-int fast path +GetDefinitionsfirst-match lookup)run_test.go— table-driven gomock testsinternal/cmd/pipelines/pipelines.goto addrun.NewCmd(ctx)as a top-level leaf (cmd.AddCommand(...)). Update theExampleblock.pipelines→run(top-level).API Surface
Reuse the already-vendored client. No new SDK clients or mocks required.
build.Client.GetDefinitions→ Definitions - List (REST 7.1) — for name → ID resolutionbuild.Client.QueueBuild→ Builds - Queue (REST 7.1)build.GetDefinitionsArgsstruct:{Project *string, Name *string, Path *string}.build.GetDefinitionsResponseValuestruct:{Value *[]BuildDefinitionReference, Count *int}.build.BuildDefinitionReferencestruct: minimal reference (id, name, etc.).build.QueueBuildArgsstruct:{Build *Build, Project *string}.build.Buildmodel — hasDefinition *DefinitionReference,SourceBranch *string,SourceVersion *string,Parameters *map[string]string, plus all the standard status/result fields.build.DefinitionReferencestruct:{Id *int, Name *string}.Mocks for
GetDefinitions(:837) andQueueBuild(:1227) are already generated. No mock regeneration needed.Reference Existing Patterns
azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/pipeline.py#L100-L142—pipeline_runPython implementation.azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/pipeline.py#L73-L82—set_param_variablehelper (parsename=valuepairs).azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/commands.py#L105—g.command('run', 'pipeline_run', table_transformer=transform_pipeline_run_table_output).azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/_format.py—transform_pipeline_run_table_outputand_transform_pipeline_run_row.azure-dev-ops-cli-extension/azure-dev-ops/azuredevops/azext_devops/dev/pipelines/build_definition.py—get_definition_id_from_namehelper (the name → ID resolution pattern).internal/cmd/pipelines/variablegroup/delete/delete.go— primary target-resolution precedent (Decision 2 / 2.5): usesUse: "delete [ORGANIZATION/]PROJECT/GROUP",util.ExactArgs(1, "..."),util.ParseProjectTargetWithDefaultOrganization, and ashared.ResolveVariableGrouphelper.internal/cmd/pipelines/build/queue/queue.go— closest queue sibling (project-scoped,QueueBuild, branch/variables). Use the sameBuildconstruction pattern.internal/cmd/pipelines/variablegroup/list/list.go— primary list reference for the modern list pattern, JSON view struct, table printer.rundoes not use--max-items; the table emits exactly one row.internal/cmd/boards/workitem/list/list_test.go:765-844—setupFakeDeps/stub*fixture.internal/mocks/build_client_mock.go:1227— mock forQueueBuild(already generated).internal/mocks/build_client_mock.go:837— mock forGetDefinitions(already generated, used for name → ID resolution).internal/azdo/factory.go:61—ClientFactory().Build(...)accessor (reuse).internal/cmd/util/scope.go:183—util.ParseProjectTargetWithDefaultOrganization(project-scoped parser).References
azext_devops/dev/pipelines/pipeline.pyazext_devops/dev/pipelines/commands.pyazext_devops/dev/pipelines/_format.pyazext_devops/dev/pipelines/build_definition.py