Skip to content
23 changes: 23 additions & 0 deletions docs/azdo_help_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,29 @@ Aliases
view, status
```

### `azdo pipelines list [ORGANIZATION/]PROJECT [flags]`

List pipeline definitions

```
--folder-path string Filter by folder path (e.g. "user1/production")
-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.
--max-items int Optional client-side cap on results
--name string Filter by pipeline name (prefix or exact)
--query-order string Order of definitions: {none|definitionNameAscending|definitionNameDescending|lastModifiedAscending|lastModifiedDescending}
--repository string Filter by repository name or ID
--repository-type string Repository type filter: {tfsgit|github}
-t, --template string Format JSON output using a Go template; see "azdo help formatting"
--top int Maximum number of definitions to return
```

Aliases

```
ls, l
```

### `azdo pipelines pool`

Manage agent pools
Expand Down
1 change: 1 addition & 0 deletions docs/azdo_pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Manage Azure DevOps pipelines
### Available commands

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

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

```
azdo pipelines list [ORGANIZATION/]PROJECT [flags]
```

List pipeline definitions (YAML or classic) in a project.


### Options


* `--folder-path` `string`

Filter by folder path (e.g. "user1/production")

* `-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.

* `--max-items` `int` (default `0`)

Optional client-side cap on results

* `--name` `string`

Filter by pipeline name (prefix or exact)

* `--query-order` `string`

Order of definitions: {none|definitionNameAscending|definitionNameDescending|lastModifiedAscending|lastModifiedDescending}

* `--repository` `string`

Filter by repository name or ID

* `--repository-type` `string`

Repository type filter: {tfsgit|github}

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

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

* `--top` `int` (default `0`)

Maximum number of definitions to return


### ALIASES

- `ls`
- `l`

### JSON Fields

`_links`, `authoredBy`, `createdDate`, `draftOf`, `drafts`, `id`, `latestBuild`, `latestCompletedBuild`, `metrics`, `name`, `path`, `project`, `quality`, `queue`, `queueStatus`, `revision`, `type`, `uri`, `url`

### Examples

```bash
# List all pipelines in a project
$ azdo pipelines list "my-project"

# List pipelines with a specific name
$ azdo pipelines list "my-project" --name "my-pipeline"

# List pipelines using a specific repository
$ azdo pipelines list "my-project" --repository "my-repo"

# Output as JSON
$ azdo pipelines list "my-project" --json
```

### See also

* [azdo pipelines](./azdo_pipelines.md)
4 changes: 2 additions & 2 deletions internal/cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ func NewCmdConfig(ctx util.CmdContext) *cobra.Command {
longDoc.WriteString("Display or change configuration settings for azdo.\n\n")
longDoc.WriteString("Current respected settings:\n")
for _, co := range config.Options() {
longDoc.WriteString(fmt.Sprintf("- %s: %s", co.Key, co.Description))
fmt.Fprintf(&longDoc, "- %s: %s", co.Key, co.Description)
if co.DefaultValue != "" {
longDoc.WriteString(fmt.Sprintf(" (default: %q)", co.DefaultValue))
fmt.Fprintf(&longDoc, " (default: %q)", co.DefaultValue)
}
longDoc.WriteRune('\n')
}
Expand Down
198 changes: 198 additions & 0 deletions internal/cmd/pipelines/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package list

import (
"fmt"
"sort"

"github.com/MakeNowJust/heredoc/v2"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/build"
"github.com/spf13/cobra"
"github.com/tmeckel/azdo-cli/internal/cmd/util"
"github.com/tmeckel/azdo-cli/internal/types"
)

type opts struct {
scope string
name string
repository string
repositoryType string
top int
folderPath string
queryOrder string
maxItems int
exporter util.Exporter
}

func NewCmd(ctx util.CmdContext) *cobra.Command {
opts := &opts{}

cmd := &cobra.Command{
Use: "list [ORGANIZATION/]PROJECT",
Short: "List pipeline definitions",
Long: heredoc.Doc(`
List pipeline definitions (YAML or classic) in a project.
`),
Example: heredoc.Doc(`
# List all pipelines in a project
$ azdo pipelines list "my-project"

# List pipelines with a specific name
$ azdo pipelines list "my-project" --name "my-pipeline"

# List pipelines using a specific repository
$ azdo pipelines list "my-project" --repository "my-repo"

# Output as JSON
$ azdo pipelines list "my-project" --json
`),
Aliases: []string{
"ls",
"l",
},
Args: util.ExactArgs(1, "project argument is required"),
RunE: func(cmd *cobra.Command, args []string) error {
opts.scope = args[0]
return runList(ctx, opts)
},
}

cmd.Flags().StringVar(&opts.name, "name", "", "Filter by pipeline name (prefix or exact)")
cmd.Flags().StringVar(&opts.repository, "repository", "", "Filter by repository name or ID")
util.StringEnumFlag(cmd, &opts.repositoryType, "repository-type", "", "",
[]string{"tfsgit", "github"}, "Repository type filter")
cmd.Flags().IntVar(&opts.top, "top", 0, "Maximum number of definitions to return")
cmd.Flags().StringVar(&opts.folderPath, "folder-path", "", "Filter by folder path (e.g. \"user1/production\")")
util.StringEnumFlag(cmd, &opts.queryOrder, "query-order", "", "",
[]string{"none", "definitionNameAscending", "definitionNameDescending", "lastModifiedAscending", "lastModifiedDescending"},
"Order of definitions")
cmd.Flags().IntVar(&opts.maxItems, "max-items", 0, "Optional client-side cap on results")
util.AddJSONFlags(cmd, &opts.exporter, []string{
"id", "name", "path", "revision", "type", "quality", "queueStatus",
"createdDate", "project", "authoredBy", "latestBuild", "latestCompletedBuild",
"draftOf", "drafts", "metrics", "queue", "uri", "url", "_links",
})

return cmd
}

func runList(cmdCtx util.CmdContext, opts *opts) error {
ios, err := cmdCtx.IOStreams()
if err != nil {
return err
}
ios.StartProgressIndicator()
defer ios.StopProgressIndicator()

if opts.top < 0 {
return util.FlagErrorf("invalid --top value %d; must be greater than 0", opts.top)
}
if opts.maxItems < 0 {
return util.FlagErrorf("invalid --max-items value %d; must be greater than 0", opts.maxItems)
}

scope, err := util.ParseProjectScope(cmdCtx, opts.scope)
if err != nil {
return util.FlagErrorf("invalid project argument: %w", err)
}

if opts.repository != "" && opts.repositoryType == "" {
opts.repositoryType = "tfsgit"
}

buildClient, err := cmdCtx.ClientFactory().Build(cmdCtx.Context(), scope.Organization)
if err != nil {
return err
}

var definitions []build.BuildDefinitionReference
var continuationToken *string

for {
args := build.GetDefinitionsArgs{
Project: types.ToPtr(scope.Project),
Name: types.NotZeroPtrOrNil(opts.name),
RepositoryId: types.NotZeroPtrOrNil(opts.repository),
RepositoryType: types.NotZeroPtrOrNil(opts.repositoryType),
Top: types.PositivePtrOrNil(opts.top),
Path: types.NotZeroPtrOrNil(opts.folderPath),
ContinuationToken: continuationToken,
}
if opts.queryOrder != "" {
order := build.DefinitionQueryOrder(opts.queryOrder)
args.QueryOrder = &order
}

resp, err := buildClient.GetDefinitions(cmdCtx.Context(), args)
if err != nil {
return err
}

definitions = append(definitions, resp.Value...)

if opts.maxItems > 0 && len(definitions) >= opts.maxItems {
definitions = definitions[:opts.maxItems]
break
}

if resp.ContinuationToken == "" {
break
}
continuationToken = &resp.ContinuationToken

if opts.top > 0 && len(definitions) >= opts.top {
break
}
}

sort.Slice(definitions, func(i, j int) bool {
return types.GetValue(definitions[i].Id, 0) < types.GetValue(definitions[j].Id, 0)
})

ios.StopProgressIndicator()

if opts.exporter != nil {
return opts.exporter.Write(ios, definitions)
}

tp, err := cmdCtx.Printer("table")
if err != nil {
return err
}

hasDraft := false
for _, def := range definitions {
if types.GetValue(def.Quality, "") == "draft" {
hasDraft = true
break
}
}

columns := []string{"ID", "PATH", "NAME"}
if hasDraft {
columns = append(columns, "DRAFT")
}
columns = append(columns, "STATUS", "DEFAULT QUEUE")
tp.AddColumns(columns...)

for _, def := range definitions {
tp.AddField(fmt.Sprintf("%d", types.GetValue(def.Id, 0)))
tp.AddField(types.GetValue(def.Path, ""))
tp.AddField(types.GetValue(def.Name, ""))
if hasDraft {
if types.GetValue(def.Quality, "") == "draft" {
tp.AddField("*")
} else {
tp.AddField("")
}
}
tp.AddField(string(types.GetValue(def.QueueStatus, "")))
qName := ""
if def.Queue != nil {
qName = types.GetValue(def.Queue.Name, "")
}
tp.AddField(qName)
tp.EndRow()
}

return tp.Render()
}
Loading
Loading