Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/azdo_help_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,34 @@ Aliases
p
```

### `azdo pipelines agent`

Manage Azure DevOps pipeline agents

Aliases

```
agents, a
```

#### `azdo pipelines agent show [ORGANIZATION/]POOL/AGENT [flags]`

Show details of a pipeline agent

```
--include-capabilities Include system and user capabilities in the output
-q, --jq expression Filter JSON output using a jq expression
--json fields[=*] Output JSON with the specified fields. Prefix a field with '-' to exclude it.
-r, --raw Dump raw agent object to stderr
-t, --template string Format JSON output using a Go template; see "azdo help formatting"
```

Aliases

```
view, status
```

### `azdo pipelines variable-group`

Manage Azure DevOps variable groups
Expand Down
1 change: 1 addition & 0 deletions docs/azdo_pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Manage Azure DevOps pipelines

### Available commands

* [azdo pipelines agent](./azdo_pipelines_agent.md)
* [azdo pipelines variable-group](./azdo_pipelines_variable-group.md)

### ALIASES
Expand Down
39 changes: 39 additions & 0 deletions docs/azdo_pipelines_agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## Command `azdo pipelines agent`

Manage Azure DevOps pipeline agents. Agents are the compute targets
that run build, release, and other pipeline jobs. Each agent belongs
to an agent pool, which is identified by name or numeric ID.

Targets are specified in POOL/AGENT format where each component can
be a numeric ID or a name. An optional organization prefix can be
included: [ORGANIZATION/]POOL/AGENT.


### Available commands

* [azdo pipelines agent show](./azdo_pipelines_agent_show.md)

### ALIASES

- `agents`
- `a`

### Examples

```bash
# Show agent by pool ID and agent ID
azdo pipelines agent show 1/42

# Show agent by pool name and agent name
azdo pipelines agent show 'Default/my-agent'

# Show agent in a different organization
azdo pipelines agent show 'myorg/1/42'

# Show agent with system and user capabilities
azdo pipelines agent show 1/42 --include-capabilities
```

### See also

* [azdo pipelines](./azdo_pipelines.md)
66 changes: 66 additions & 0 deletions docs/azdo_pipelines_agent_show.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Command `azdo pipelines agent show`

```
azdo pipelines agent show [ORGANIZATION/]POOL/AGENT [flags]
```

Display the details of a single Azure DevOps pipeline agent.
The agent is specified as a pool and agent ID or name, with
an optional organization prefix.


### Options


* `--include-capabilities`

Include system and user capabilities in the output

* `-q`, `--jq` `expression`

Filter JSON output using a jq expression

* `--json` `fields`

Output JSON with the specified fields. Prefix a field with '-' to exclude it.

* `-r`, `--raw`

Dump raw agent object to stderr

* `-t`, `--template` `string`

Format JSON output using a Go template; see "azdo help formatting"


### ALIASES

- `view`
- `status`

### JSON Fields

`_links`, `accessPoint`, `assignedRequest`, `authorization`, `createdBy`, `createdOn`, `enabled`, `id`, `lastCompletedRequest`, `maxParallelism`, `name`, `osDescription`, `pendingUpdate`, `pool`, `properties`, `provisioningState`, `status`, `statusChangedOn`, `systemCapabilities`, `userCapabilities`, `version`

### Examples

```bash
# Show an agent by pool ID and agent ID
azdo pipelines agent show 1/42

# Show an agent by pool name and agent name
azdo pipelines agent show 'Default/my-agent'

# Show an agent in a specific organization
azdo pipelines agent show 'myorg/Default/my-agent'

# Show an agent with capabilities
azdo pipelines agent show 1/42 --include-capabilities

# Show agent as JSON
azdo pipelines agent show 1/42 --json
```

### See also

* [azdo pipelines agent](./azdo_pipelines_agent.md)
42 changes: 42 additions & 0 deletions internal/cmd/pipelines/agent/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package agent

import (
"github.com/MakeNowJust/heredoc/v2"
"github.com/spf13/cobra"

"github.com/tmeckel/azdo-cli/internal/cmd/pipelines/agent/show"
"github.com/tmeckel/azdo-cli/internal/cmd/util"
)

func NewCmd(ctx util.CmdContext) *cobra.Command {
cmd := &cobra.Command{
Use: "agent",
Short: "Manage Azure DevOps pipeline agents",
Long: heredoc.Doc(`
Manage Azure DevOps pipeline agents. Agents are the compute targets
that run build, release, and other pipeline jobs. Each agent belongs
to an agent pool, which is identified by name or numeric ID.

Targets are specified in POOL/AGENT format where each component can
be a numeric ID or a name. An optional organization prefix can be
included: [ORGANIZATION/]POOL/AGENT.
`),
Example: heredoc.Doc(`
# Show agent by pool ID and agent ID
azdo pipelines agent show 1/42

# Show agent by pool name and agent name
azdo pipelines agent show 'Default/my-agent'

# Show agent in a different organization
azdo pipelines agent show 'myorg/1/42'

# Show agent with system and user capabilities
azdo pipelines agent show 1/42 --include-capabilities
`),
Aliases: []string{"agents", "a"},
}

cmd.AddCommand(show.NewCmd(ctx))
return cmd
}
121 changes: 121 additions & 0 deletions internal/cmd/pipelines/agent/shared/resolve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package shared

import (
"fmt"
"strconv"
"strings"

"github.com/microsoft/azure-devops-go-api/azuredevops/v7/taskagent"

"github.com/tmeckel/azdo-cli/internal/cmd/util"
"github.com/tmeckel/azdo-cli/internal/types"
)

func ResolvePoolAgent(
cmdCtx util.CmdContext,
client taskagent.Client,
org, poolTarget, agentTarget string,
) (*taskagent.TaskAgent, error) {
if strings.TrimSpace(poolTarget) == "" {
return nil, util.FlagErrorf("pool target cannot be empty")
}
if strings.TrimSpace(agentTarget) == "" {
return nil, util.FlagErrorf("agent target cannot be empty")
}

poolID, err := ResolvePool(cmdCtx, client, poolTarget)
if err != nil {
return nil, err
}

agentID, err := ResolveAgent(cmdCtx, client, poolID, agentTarget)
if err != nil {
return nil, err
}

agent, err := client.GetAgent(cmdCtx.Context(), taskagent.GetAgentArgs{
PoolId: types.ToPtr(poolID),
AgentId: types.ToPtr(agentID),
})
if err != nil {
return nil, fmt.Errorf("failed to get agent: %w", err)
}
if agent == nil {
return nil, fmt.Errorf("agent %q not found", agentTarget)
}

return agent, nil
}

func ResolvePool(cmdCtx util.CmdContext, client taskagent.Client, target string) (int, error) {
if id, err := strconv.Atoi(target); err == nil {
if id < 0 {
return 0, util.FlagErrorf("invalid pool id %d", id)
}
return id, nil
}

pools, err := client.GetAgentPools(cmdCtx.Context(), taskagent.GetAgentPoolsArgs{
PoolName: types.ToPtr(target),
})
if err != nil {
return 0, fmt.Errorf("failed to list agent pools: %w", err)
}

var matches []taskagent.TaskAgentPool
if pools != nil {
for _, p := range *pools {
if p.Name != nil && strings.EqualFold(*p.Name, target) {
matches = append(matches, p)
}
}
}

if len(matches) == 0 {
return 0, fmt.Errorf("pool %q not found", target)
}
if len(matches) > 1 {
return 0, fmt.Errorf("multiple pools named %q found; specify the numeric ID", target)
}
if matches[0].Id == nil {
return 0, fmt.Errorf("pool %q returned without an ID", target)
}
return *matches[0].Id, nil
}

func ResolveAgent(cmdCtx util.CmdContext, client taskagent.Client, poolID int, target string) (int, error) {
if id, err := strconv.Atoi(target); err == nil {
if id < 0 {
return 0, util.FlagErrorf("invalid agent id %d", id)
}
return id, nil
}

agents, err := client.GetAgents(cmdCtx.Context(), taskagent.GetAgentsArgs{
PoolId: types.ToPtr(poolID),
AgentName: types.ToPtr(target),
})
if err != nil {
return 0, fmt.Errorf("failed to list agents in pool %d: %w", poolID, err)
}

var matches []taskagent.TaskAgent
if agents != nil {
for _, a := range *agents {
if a.Name != nil && strings.EqualFold(*a.Name, target) {
matches = append(matches, a)
}
}
}

if len(matches) == 0 {
return 0, fmt.Errorf("agent %q not found in pool %d", target, poolID)
}
if len(matches) > 1 {
return 0, fmt.Errorf("multiple agents named %q found in pool %d; specify the numeric ID", target, poolID)
}
if matches[0].Id == nil {
return 0, fmt.Errorf("agent %q returned without an ID", target)
}
return *matches[0].Id, nil
}
Loading
Loading