Skip to content

feat: Implement azdo pipelines agent show command #247

Description

@tmeckel

Sub-issue of #246. 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).

Command Description

Display the details of a single Azure DevOps agent. The target is given as a single positional argument of the form [ORGANIZATION/]POOL/AGENT where each of the POOL and AGENT segments is either a positive integer (used as the ID directly) or a name resolved against the Agents REST 7.1 endpoint. The matching TaskAgent is fetched and rendered as a Go text template showing Status, Version, and capabilities (when --include-capabilities is set) with their respective child values.

GET https://dev.azure.com/{organization}/_apis/distributedtask/pools/{poolId}/agents/{agentId}?includeCapabilities={bool}&includeAssignedRequest={bool}&includeLastCompletedRequest={bool}&api-version=7.1

Locked Decisions (do not re-derive)

# Decision Rationale
1 Use the vendored SDK taskagent.Client.GetAgent for the final fetch, plus taskagent.Client.GetAgentPools and taskagent.Client.GetAgents for the two-stage name resolution. All three mocks are already generated. Reuse the vendored taskagent client.
2 Use: "show [ORGANIZATION/]POOL/AGENT". The pool and agent are positional segments. Each segment is either a positive integer (used as the ID directly) or a name resolved by calling the appropriate list API and picking the first case-insensitive match. Errors on zero matches ("not found") or more than one match ("multiple named y found; specify the numeric ID"). Mirrors the variablegroup/delete target-resolution pattern (#243, #245) and the Python get_definition_id_from_name helper. Agents are org-scoped, not project-scoped.
3 Args: util.ExactArgs(1, "agent target is required"). The single positional argument is the full [ORG/]POOL/AGENT string, parsed by util.ParsePoolAgentTargetWithDefaultOrganization introduced by this story. Empty or whitespace-only input, missing pool, or missing agent all error from the parser. Cheap pre-flight validation; REST 4xx is unhelpful.
4 Optional organization prefix (3-segment form [ORG/]POOL/AGENT) falls back to the configured default organization when omitted. The helper's 2-segment form (POOL/AGENT) requires a default organization; the 3-segment form (ORG/POOL/AGENT) does not. Matches the implicit-org pattern of the other Parse*WithDefaultOrganization wrappers.
5 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.
6 Aliases: view, status. Primary name is show. cmd.Use: "show [ORGANIZATION/]POOL/AGENT", cmd.Aliases: []string{"view", "status"}. Mirrors the pr/view aliasing pattern but with show as the primary.
7 JSON output passes the raw SDK *taskagent.TaskAgent to opts.exporter.Write. No view struct. Symmetric with the other show siblings.
8 No confirmation prompt. Show is read-only. Show is non-destructive.
9 --raw flag dumps the full SDK agent with spew.Dump to stderr for debugging. Mirrors pr/view --raw.
10 New public helper util.ParsePoolAgentTargetWithDefaultOrganization is added to internal/cmd/util/scope.go as part of this story (thin wrapper over the unified util.Parse introduced by #259). New resolver shared.ResolvePoolAgent is added at internal/cmd/pipelines/agent/show/shared/resolve.go. No new SDK client, no new package beyond internal/cmd/pipelines/agent/show and internal/cmd/pipelines/agent/show/shared. Mirrors the variablegroup/delete + variablegroup/shared/resolver.go split. Keeps #259 a pure refactor (no new public API).
11 Mocks for GetAgent, GetAgentPools, and GetAgents are already generated at internal/mocks/taskagent_client_mock.go:336-345, :411-426, and :530-542 respectively. Do not regenerate. Verified.
12 Optional flag --include-capabilities (bool, default false) maps to GetAgentArgs.IncludeCapabilities. When true, the template renders system capabilities and user capabilities blocks. Mirrors Azure CLI flag.
13 Strict predecessor: #259 must be merged before this story can be implemented. #259 introduces the unified util.Parse(ctx, raw, opts) (*Path, error) infrastructure that this story's new util.ParsePoolAgentTargetWithDefaultOrganization wrapper delegates to. The wrapper itself is added as part of this story's implementation, not by #259. Keeps #259 a pure refactor (no new public API) while still gating #247 on the unified parser landing first.

Command Signature

azdo pipelines agent show [ORGANIZATION/]POOL/AGENT
  [--include-capabilities] (bool)
  [--raw] [--json ...]
  • Aliases: view, status
  • Args: util.ExactArgs(1, "agent target is required")args[0] is the full target string, parsed by util.ParsePoolAgentTargetWithDefaultOrganization.
  • The target string is split on / into 2 or 3 segments: 2 segments → pool + agent with implicit default organization; 3 segments → organization + pool + agent.

Flags

Flag Maps to Notes
--include-capabilities (bool) GetAgentArgs.IncludeCapabilities When true, render system/user capabilities in template
--raw (bool) debug dump spew.Dump of full SDK agent to stderr
--json / --jq / --template util.AddJSONFlags JSON export of raw SDK agent

JSON Output Contract

util.AddJSONFlags(cmd, &opts.exporter, []string{
    "id", "name", "pool", "status", "enabled", "version", "osDescription",
    "accessPoint", "provisioningState", "maxParallelism",
    "createdOn", "statusChangedOn", "createdBy", "authorization",
    "systemCapabilities", "userCapabilities", "assignedRequest",
    "lastCompletedRequest", "pendingUpdate", "properties", "_links",
})

Pass the raw *taskagent.TaskAgent returned by the SDK (Decision 7).

Template Output Contract (show.tpl)

url:             (if present)
id:              
name:            
pool:            
status:          (online | offline)
enabled:         (true | false)
version:         
os description:  
access point:    
max parallelism: 
created on:      
created by:      (if present)
last status change: 
system capabilities:  (when --include-capabilities)
  ... list of key/value pairs
user capabilities:    (when --include-capabilities)
  ... list of key/value pairs

Command Wiring

  • Package path: internal/cmd/pipelines/agent/show (command) and internal/cmd/pipelines/agent/show/shared (resolver).
  • Files:
    • show.goNewCmd(ctx util.CmdContext) *cobra.Command + showOptions + runShow
    • show.tpl — Go text template
    • show_test.go — table-driven gomock tests for runShow
    • shared/resolve.goResolvePoolAgent(cmdCtx util.CmdContext, client taskagent.Client, org, poolTarget, agentTarget string) (*taskagent.TaskAgent, error). Two-stage resolution: (1) resolve pool — if poolTarget parses as a positive integer, use it directly; else call GetAgentPools(args.PoolName=poolTarget) and pick the first case-insensitive Name match; (2) resolve agent — if agentTarget parses as a positive integer, use it directly; else call GetAgents(args.PoolId=resolved_pool_id, args.AgentName=agentTarget) and pick the first case-insensitive Name match; (3) call GetAgent(args.PoolId=resolved_pool_id, args.AgentId=resolved_agent_id). Errors on zero or ambiguous matches for either segment.
    • shared/resolve_test.go — table-driven tests for ResolvePoolAgent covering: numeric pool + numeric agent; name pool + name agent; mixed numeric/name; missing pool; ambiguous pool; missing agent; ambiguous agent.
  • Update internal/cmd/pipelines/agent/agent.go to add showcmd "github.com/tmeckel/azdo-cli/internal/cmd/pipelines/agent/show" and cmd.AddCommand(showcmd.NewCmd(ctx)). Update the Example block.
  • Higher-level parents must already remain wired: pipelinesagentshow.
  • Update internal/cmd/util/scope.go to add the new public wrapper ParsePoolAgentTargetWithDefaultOrganization(ctx, raw) (*Path, error). It is a 5-line thin wrapper over the unified Parse introduced by refactor: Unify internal/cmd/util/scope.go parsing into a single Path type with options-driven Parse #259, configured as ParseOptions{AllowImplicitOrg: true, RequireProject: false, MinTargets: 2, MaxTargets: 2}.

API Surface

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

  • taskagent.Client.GetAgentAgents - Get (REST 7.1)
  • taskagent.Client.GetAgentPoolsAgentPools - Get Agent Pools (REST 7.1)GetAgentPoolsArgs.PoolName filter for name resolution.
  • taskagent.Client.GetAgentsAgents - List (REST 7.1)GetAgentsArgs.PoolId (required) + GetAgentsArgs.AgentName filter for name resolution.
  • taskagent.GetAgentArgs struct: {PoolId *int, AgentId *int, IncludeCapabilities *bool, IncludeAssignedRequest *bool, IncludeLastCompletedRequest *bool, PropertyFilters *[]string}.
  • New helper: util.ParsePoolAgentTargetWithDefaultOrganization(ctx, raw) (*util.Path, error). Splits the input on / into 2 or 3 segments; returns a *Path with Targets = [pool, agent]. Added to internal/cmd/util/scope.go as part of this story (gated on refactor: Unify internal/cmd/util/scope.go parsing into a single Path type with options-driven Parse #259).
  • Template engine: internal/template.Template with bold, hyperlink, s, timeago, timefmt, markdown, pluck, join, truncate, stripprefix, tablerow, tablerender.

Mocks for GetAgent, GetAgentPools, and GetAgents are all already generated. No mock regeneration needed.

Reference Existing Patterns

  • internal/cmd/pipelines/variablegroup/delete/delete.go:34,58,85primary target-resolution precedent. The Use: "delete [ORGANIZATION/]PROJECT/GROUP", Args: util.ExactArgs(1, "..."), util.ParseProjectTargetWithDefaultOrganization, shared.ResolveVariableGroup pattern translates directly: replace the parser with util.ParsePoolAgentTargetWithDefaultOrganization (this story's new helper) and the resolver with shared.ResolvePoolAgent; remove the project from the segment count; add a second-stage resolution for the agent segment that depends on the resolved pool ID.
  • internal/cmd/pipelines/variablegroup/shared/resolver.go:14-20ResolveVariableGroup signature template: (ctx CmdContext, client taskagent.Client, project, target string) (*VariableGroup, error). The analogous signature for ResolvePoolAgent is (ctx CmdContext, client taskagent.Client, org, poolTarget, agentTarget string) (*TaskAgent, error).
  • internal/cmd/pr/view/view.goprimary template-engine reference.
  • internal/cmd/pr/view/view.tplprimary template-file reference.
  • internal/cmd/boards/workitem/list/list_test.go:765-844setupFakeDeps / stub* fixture.
  • internal/mocks/taskagent_client_mock.go:336-345, :411-426, :530-542 — mocks for GetAgent, GetAgentPools, GetAgents (already generated, do not regenerate).
  • internal/azdo/factory.go:133-139ClientFactory().TaskAgent(...) accessor (reuse).
  • internal/template/template.go — template engine + funcs (reuse, do not reimplement).

References

Blocks

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions