You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sub-issue of #239. Hardened spec — do not re-derive decisions. Mirrors internal/cmd/pr/view/view.go and uses Go text templates via internal/template.Template (the same engine used by azdo pr view).
> Org-scoped, not project-scoped. The pool subgroup (umbrella #239) takes a positionalORGANIZATION/POOL scope pattern (no project segment). Pools live at the organization level. The pool show command reflects that: it fetches a pool by integer ID or name within the org.
Command Description
Display the details of a single Azure DevOps agent pool by integer ID or name. The command resolves the target (positive integer is used directly; a string is resolved via GetAgentPools), fetches the matching TaskAgentPool via the Agent Pools REST 7.1 endpoint, and renders it as a Go text template. The pool's AutoProvision, AutoUpdate, and (when present) TargetAssignment are rendered with their respective child values.
GET https://dev.azure.com/{organization}/_apis/distributedtask/pools/{poolId}?api-version=7.1
Locked Decisions (do not re-derive)
#
Decision
Rationale
1
Use the vendored SDK taskagent.Client.GetAgentPool (not raw HTTP). Mock already generated at internal/mocks/taskagent_client_mock.go:411-426.
Consistent with the other pool/queue subgroup siblings; the SDK is what the umbrella wires in #239.
2
The pool is identified by a positional POOL argument ([ORGANIZATION/]POOL). The target segment is resolved via a new shared.ResolvePool helper at internal/cmd/pipelines/pool/show/shared/resolve.go that mirrors the variablegroup resolution pattern: if it parses as a positive integer, use it directly; otherwise call taskagent.Client.GetAgentPools(poolName=...) and pick the first match. Folds the previous --id flag into the positional.
Mirrors the variablegroup/delete precedent. Pools are org-scoped.
2.5
Parse the positional using util.ParseTargetWithDefaultOrganization from internal/cmd/util/scope.go:173. The function returns a *Target with Organization and Target fields, accepting 1- or 2-segment inputs.
Org-scope pattern; project segment is not used. Mirrors internal/cmd/security/group/membership/list/ precedent.
3
util.ExactArgs(1, "pool target is required").
Standard cobra pattern; the 1st positional is the full target.
4
Use the Go text template engine from internal/template/template.go with an //go:embed show.tpl file. Mirror internal/cmd/pr/view/view.go and view.tpl structure: bold, hyperlink, s, timeago, timefmt, markdown, pluck, join, truncate, stripprefix, tablerow, tablerender.
The user explicitly requested template rendering like pr/view.
5
Aliases: view, status. Primary name is show. cmd.Use: "show [ORGANIZATION/]POOL", cmd.Aliases: []string{"view", "status"}.
Mirrors the pr/view aliasing pattern but with show as the primary.
6
JSON output passes the raw SDK *taskagent.TaskAgentPool to opts.exporter.Write. No view struct.
Symmetric with other show siblings.
7
No confirmation prompt. Show is read-only.
Show is non-destructive.
8
--raw flag dumps the full SDK pool with spew.Dump to stderr for debugging.
Mirrors pr/view --raw.
9
No new SDK client, no new helper beyond shared.ResolvePool, no new package beyond internal/cmd/pipelines/pool/show. Reuse SDK call from the vendored taskagent package.
Mandate: minimal code.
10
Mock for GetAgentPool is already generated at internal/mocks/taskagent_client_mock.go:411-426. Do not regenerate.
Verified.
11
taskagent.Client.GetAgentPools is used for name resolution; mock also generated.
Mirrors the variablegroup/delete name-resolution pattern.
Command Signature
azdo pipelines pool show [ORGANIZATION/]POOL
[--raw]
[--json ...]
Update internal/cmd/pipelines/pool/pool.go to add showcmd "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/pool/show" and cmd.AddCommand(showcmd.NewCmd(ctx)). Update the Example block.
Existing higher-level parents must already remain wired: pipelines → pool → show.
API Surface
Reuse the already-vendored client. No new SDK clients required.
Phase 1 — RED (tests first). Mirror setupFakeDeps from internal/cmd/boards/workitem/list/list_test.go:765-844. Add show_test.go with the following table-driven / behaviour tests, all using t.Parallel() and gomock (require for preconditions, assert for verifications):
TestNewCmd_RegistersAsShowLeaf — asserts cmd.Name() == "show", cmd.Aliases contains view and status, cmd.Use starts with show [ORGANIZATION/]POOL.
TestRunShow_OrgFromConfigDefault — sets [] 7; asserts clientFact.TaskAgent(ctx, defaultOrg) is called with the configured default organization.
TestRunShow_OrgFromPositional — sets myorg/7; asserts clientFact.TaskAgent(ctx, "myorg") is called with the positional organization.
TestRunShow_TemplateOutput_BasicFields — mocks return *TaskAgentPool{Id, Name, Type, Scope, Size, IsHosted, Url}; asserts rendered output contains all field labels and values.
TestRunShow_TemplateOutput_Hyperlink — asserts the url: line uses ANSI hyperlink escape sequence.
TestRunShow_TemplateOutput_AutoUpdateAsList — mocks return AutoUpdate: true; asserts auto update: true rendered (or the bool is rendered as true/false).
TestRunShow_TemplateOutput_CreatedBy_Nested — mocks return CreatedBy: &IdentityRef{DisplayName, UniqueName}; asserts created by: Alice (alice@contoso.com) rendered.
TestRunShow_TemplateOutput_NoCreatedBy — mocks return CreatedBy: nil; asserts created by: line is omitted.
Output split: JSON via opts.exporter.Write(ios, res) passing the raw SDK *TaskAgentPool; template via template.New(...).ExecuteData(templateData{Pool: res}).
Debug log at the point of the SDK call: organization, poolId.
Target delta: show.go ≤ ~130 LOC, show.tpl ≤ ~50 LOC, shared/resolve.go ≤ ~30 LOC, show_test.go ≤ ~400 LOC (21 tests), parent pool.go +3 LOC, docs/pipelines_pool_show.md regenerated via make docs. No changes to other pool siblings (none yet).
Tooling and Verification Checklist
Run gofmt / gofumpt on touched files
go test ./internal/cmd/pipelines/pool/...
go test ./...
make lint
make docs
Reference Existing Patterns
internal/cmd/pr/view/view.go — primary template-engine reference (Primary: view.go:483-549; viewOptions struct at view.go:23-31; template embed at view.go:33-34).
internal/cmd/pr/view/view.tpl — primary template-file reference (45 lines; same field-bullet style).
internal/cmd/pipelines/variablegroup/delete/delete.go — primary target-resolution precedent (Decision 2 / 2.5): uses Use: "delete [ORGANIZATION/]PROJECT/GROUP", util.ExactArgs(1, "..."), and a shared.ResolveVariableGroup helper. Our pool/show differs only in the parser (ParseTargetWithDefaultOrganization because pools are org-scoped) and the resolution API.
Sub-issue of #239. Hardened spec — do not re-derive decisions. Mirrors
internal/cmd/pr/view/view.goand uses Go text templates viainternal/template.Template(the same engine used byazdo pr view).> Org-scoped, not project-scoped. The
poolsubgroup (umbrella #239) takes a positionalORGANIZATION/POOLscope pattern (no project segment). Pools live at the organization level. Thepool showcommand reflects that: it fetches a pool by integer ID or name within the org.Command Description
Display the details of a single Azure DevOps agent pool by integer ID or name. The command resolves the target (positive integer is used directly; a string is resolved via
GetAgentPools), fetches the matchingTaskAgentPoolvia the Agent Pools REST 7.1 endpoint, and renders it as a Go text template. The pool'sAutoProvision,AutoUpdate, and (when present)TargetAssignmentare rendered with their respective child values.Locked Decisions (do not re-derive)
taskagent.Client.GetAgentPool(not raw HTTP). Mock already generated atinternal/mocks/taskagent_client_mock.go:411-426.POOLargument ([ORGANIZATION/]POOL). The target segment is resolved via a newshared.ResolvePoolhelper atinternal/cmd/pipelines/pool/show/shared/resolve.gothat mirrors the variablegroup resolution pattern: if it parses as a positive integer, use it directly; otherwise calltaskagent.Client.GetAgentPools(poolName=...)and pick the first match. Folds the previous--idflag into the positional.util.ParseTargetWithDefaultOrganizationfrominternal/cmd/util/scope.go:173. The function returns a*TargetwithOrganizationandTargetfields, accepting 1- or 2-segment inputs.internal/cmd/security/group/membership/list/precedent.util.ExactArgs(1, "pool target is required").internal/template/template.gowith an//go:embed show.tplfile. Mirrorinternal/cmd/pr/view/view.goandview.tplstructure:bold,hyperlink,s,timeago,timefmt,markdown,pluck,join,truncate,stripprefix,tablerow,tablerender.pr/view.view,status. Primary name isshow.cmd.Use: "show [ORGANIZATION/]POOL",cmd.Aliases: []string{"view", "status"}.pr/viewaliasing pattern but withshowas the primary.*taskagent.TaskAgentPooltoopts.exporter.Write. No view struct.--rawflag dumps the full SDK pool withspew.Dumpto stderr for debugging.pr/view --raw.shared.ResolvePool, no new package beyondinternal/cmd/pipelines/pool/show. Reuse SDK call from the vendoredtaskagentpackage.GetAgentPoolis already generated atinternal/mocks/taskagent_client_mock.go:411-426. Do not regenerate.taskagent.Client.GetAgentPoolsis used for name resolution; mock also generated.Command Signature
view,statusargs[0]→ target (viautil.ParseTargetWithDefaultOrganization).Targetfield of the parsed*Targetis resolved viashared.ResolvePool(ctx, clientFact, args[0]).Flags
--raw(bool)spew.Dumpof full SDK pool to stderr--json/--jq/--templateutil.AddJSONFlagsJSON Output Contract
Pass the raw
*taskagent.TaskAgentPoolreturned by the SDK (Decision 6).Template Output Contract (
show.tpl)The default template renders (see
internal/cmd/pr/view/view.tplfor the established pattern):Command Wiring
internal/cmd/pipelines/pool/showshow.go—NewCmd(ctx util.CmdContext) *cobra.Command+showOptions+runShowshow.tpl— Go text templateshared/resolve.go—ResolvePool(ctx, clientFact, raw) (int, error)(positive-int fast path +GetAgentPoolsfirst-match lookup)show_test.go— table-driven gomock testsinternal/cmd/pipelines/pool/pool.goto addshowcmd "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/pool/show"andcmd.AddCommand(showcmd.NewCmd(ctx)). Update theExampleblock.pipelines→pool→show.API Surface
Reuse the already-vendored client. No new SDK clients required.
taskagent.Client.GetAgentPool→ Agent Pools - Get (REST 7.1)taskagent.GetAgentPoolArgsstruct:{PoolId *int}.taskagent.Client.GetAgentPools(andGetAgentPoolsArgswithPoolName *string) — for name → ID resolution.internal/template.Templatewithbold,hyperlink,s,timeago,timefmt,markdown,pluck,join,truncate,stripprefix,tablerow,tablerender.Mock for
GetAgentPoolis already generated atinternal/mocks/taskagent_client_mock.go:411-426. No mock regeneration needed.Implementation Approach (TDD, reuse-first, minimal)
Phase 1 — RED (tests first). Mirror
setupFakeDepsfrominternal/cmd/boards/workitem/list/list_test.go:765-844. Addshow_test.gowith the following table-driven / behaviour tests, all usingt.Parallel()andgomock(requirefor preconditions,assertfor verifications):TestNewCmd_RegistersAsShowLeaf— assertscmd.Name() == "show",cmd.Aliasescontainsviewandstatus,cmd.Usestarts withshow [ORGANIZATION/]POOL.TestNewCmd_RequiresOneArg— runscmd.SetArgs([]string{})+cmd.Execute(); asserts cobraExactArgserror.TestRunShow_ResolveByPositiveInteger— setsmyorg/7; assertsshared.ResolvePoolreturns7without callingGetAgentPools.TestRunShow_ResolveByName— setsmyorg/Default; stubstaskagent.EXPECT().GetAgentPools(...); asserts*args.PoolId == 7(resolved ID).TestRunShow_BasicCall— sets[myorg] 7; stubstaskagent.EXPECT().GetAgentPool(gomock.Any(), gomock.Any()); asserts*args.PoolId == 7.TestRunShow_OrgFromConfigDefault— sets[] 7; assertsclientFact.TaskAgent(ctx, defaultOrg)is called with the configured default organization.TestRunShow_OrgFromPositional— setsmyorg/7; assertsclientFact.TaskAgent(ctx, "myorg")is called with the positional organization.TestRunShow_TemplateOutput_BasicFields— mocks return*TaskAgentPool{Id, Name, Type, Scope, Size, IsHosted, Url}; asserts rendered output contains all field labels and values.TestRunShow_TemplateOutput_Hyperlink— asserts theurl:line uses ANSI hyperlink escape sequence.TestRunShow_TemplateOutput_AutoUpdateAsList— mocks returnAutoUpdate: true; assertsauto update: truerendered (or the bool is rendered astrue/false).TestRunShow_TemplateOutput_CreatedBy_Nested— mocks returnCreatedBy: &IdentityRef{DisplayName, UniqueName}; assertscreated by: Alice (alice@contoso.com)rendered.TestRunShow_TemplateOutput_NoCreatedBy— mocks returnCreatedBy: nil; assertscreated by:line is omitted.TestRunShow_TemplateOutput_TargetAssignment— mocks returnTargetAssignment: "Hosted"; assertstarget assignment: Hostedrendered.TestRunShow_TemplateOutput_NoTargetAssignment— mocks returnTargetAssignment: ""; assertstarget assignment:line is omitted.TestRunShow_JSONOutput— sets--json; mocks return pool; asserts JSON containsid,name,type,scope,size,isHosted,isLegacy,autoProvision,autoUpdate,uri,createdDate,createdBy.TestRunShow_RawFlag— sets--raw; assertsspew.Dumpwas invoked.TestRunShow_ClientFactoryError— stubs factory to return error; asserts wrapped error.TestRunShow_SDKError— stubs SDK to return error; asserts wrapped error.Phase 2 — GREEN (minimal implementation). Strict reuse rules:
shared.ResolvePool(ctx, clientFact, raw) (int, error)(~30 lines): positive-int fast path +taskagent.Client.GetAgentPoolsfirst-match lookup; error on zero/ambiguous matches.util.ParseTargetWithDefaultOrganization,util.AddJSONFlags,util.FlagErrorf/FlagErrorWrap,types.GetValue,types.ToPtr,ios.StartProgressIndicator/StopProgressIndicator,iostreams.Testas-is.internal/template.New(...).WithFuncs(...).Parse(show.tpl).ExecuteData(data)— exact same pattern asinternal/cmd/pr/view/view.go:483-549.ios.StartProgressIndicator()+defer ios.StopProgressIndicator(); callios.StopProgressIndicator()immediately before template execution.opts.exporter.Write(ios, res)passing the raw SDK*TaskAgentPool; template viatemplate.New(...).ExecuteData(templateData{Pool: res}).Target delta:
show.go≤ ~130 LOC,show.tpl≤ ~50 LOC,shared/resolve.go≤ ~30 LOC,show_test.go≤ ~400 LOC (21 tests), parentpool.go+3 LOC,docs/pipelines_pool_show.mdregenerated viamake docs. No changes to other pool siblings (none yet).Tooling and Verification Checklist
gofmt/gofumpton touched filesgo test ./internal/cmd/pipelines/pool/...go test ./...make lintmake docsReference Existing Patterns
internal/cmd/pr/view/view.go— primary template-engine reference (Primary:view.go:483-549;viewOptionsstruct atview.go:23-31; template embed atview.go:33-34).internal/cmd/pr/view/view.tpl— primary template-file reference (45 lines; same field-bullet style).internal/cmd/boards/workitem/show/show.go(sibling under feat: Implementazdo boards work-item showcommand #238) — copy structure for the show flow, raw-SDK JSON output, progress lifecycle.internal/cmd/boards/workitem/list/list_test.go:765-844—setupFakeDeps/stub*fixture; copy structure.internal/cmd/pipelines/variablegroup/delete/delete.go— primary target-resolution precedent (Decision 2 / 2.5): usesUse: "delete [ORGANIZATION/]PROJECT/GROUP",util.ExactArgs(1, "..."), and ashared.ResolveVariableGrouphelper. Our pool/show differs only in the parser (ParseTargetWithDefaultOrganizationbecause pools are org-scoped) and the resolution API.internal/cmd/util/scope.go:173—util.ParseTargetWithDefaultOrganization(org-scoped parser).internal/mocks/taskagent_client_mock.go:411-426— mock forGetAgentPool(already generated, do not regenerate).internal/azdo/factory.go:133-139—ClientFactory().TaskAgent(...)accessor (reuse).internal/template/template.go— template engine + funcs (reuse, do not reimplement).References