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
29 changes: 29 additions & 0 deletions docs/azdo_help_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,35 @@ Aliases
d, del, rm
```

### `azdo pipelines folder`

Manage Azure DevOps pipeline folders

Aliases

```
folders
```

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

List folders.

```
-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 Maximum number of folders to return (client-side; 0 = unlimited)
--path string Limit the listing to folders at or under this path.
--query-order string Sort folders by path: {asc|desc}
-t, --template string Format JSON output using a Go template; see "azdo help formatting"
```

Aliases

```
ls, l
```

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

List pipeline definitions
Expand Down
1 change: 1 addition & 0 deletions docs/azdo_pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Manage Azure DevOps pipelines
* [azdo pipelines agent](./azdo_pipelines_agent.md)
* [azdo pipelines build](./azdo_pipelines_build.md)
* [azdo pipelines delete](./azdo_pipelines_delete.md)
* [azdo pipelines folder](./azdo_pipelines_folder.md)
* [azdo pipelines list](./azdo_pipelines_list.md)
* [azdo pipelines pool](./azdo_pipelines_pool.md)
* [azdo pipelines queue](./azdo_pipelines_queue.md)
Expand Down
17 changes: 17 additions & 0 deletions docs/azdo_pipelines_folder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Command `azdo pipelines folder`

Manage Azure DevOps build definition folders. Folders are project-scoped
and organize pipeline definitions.


### Available commands

* [azdo pipelines folder list](./azdo_pipelines_folder_list.md)

### ALIASES

- `folders`

### See also

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

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

List build definition folders in PROJECT.

Mirrors 'az pipelines folder list'. Use --path to limit the listing to
a sub-folder. Use --query-order to sort by path ascending or descending.


### Options


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

Maximum number of folders to return (client-side; 0 = unlimited)

* `--path` `string`

Limit the listing to folders at or under this path.

* `--query-order` `string`

Sort folders by path: {asc|desc}

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

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


### ALIASES

- `ls`
- `l`

### JSON Fields

`description`, `path`

### Examples

```bash
# List top-level folders in a project
azdo pipelines folder list Fabrikam

# List folders at or under a sub-path
azdo pipelines folder list Fabrikam --path /Shared

# List folders sorted descending by path
azdo pipelines folder list myorg/Fabrikam --query-order desc

# Output as JSON
azdo pipelines folder list Fabrikam --json
```

### See also

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

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

"github.com/tmeckel/azdo-cli/internal/cmd/pipelines/folder/list"
"github.com/tmeckel/azdo-cli/internal/cmd/util"
)

func NewCmd(ctx util.CmdContext) *cobra.Command {
cmd := &cobra.Command{
Use: "folder",
Short: "Manage Azure DevOps pipeline folders",
Long: heredoc.Doc(`
Manage Azure DevOps build definition folders. Folders are project-scoped
and organize pipeline definitions.
`),
Aliases: []string{"folders"},
}

cmd.AddCommand(list.NewCmd(ctx))
return cmd
}
155 changes: 155 additions & 0 deletions internal/cmd/pipelines/folder/list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package list

import (
"fmt"

"github.com/MakeNowJust/heredoc/v2"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/build"
"github.com/spf13/cobra"
"go.uber.org/zap"

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

type opts struct {
targetArg string
path 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 folders.",
Long: heredoc.Doc(`
List build definition folders in PROJECT.

Mirrors 'az pipelines folder list'. Use --path to limit the listing to
a sub-folder. Use --query-order to sort by path ascending or descending.
`),
Example: heredoc.Doc(`
# List top-level folders in a project
azdo pipelines folder list Fabrikam

# List folders at or under a sub-path
azdo pipelines folder list Fabrikam --path /Shared

# List folders sorted descending by path
azdo pipelines folder list myorg/Fabrikam --query-order desc

# Output as JSON
azdo pipelines folder list Fabrikam --json
`),
Aliases: []string{
"ls",
"l",
},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.targetArg = args[0]
return runList(ctx, opts)
},
}

cmd.Flags().StringVar(&opts.path, "path", "", "Limit the listing to folders at or under this path.")
util.StringEnumFlag(cmd, &opts.queryOrder, "query-order", "", "", []string{"asc", "desc"}, "Sort folders by path")
cmd.Flags().IntVar(&opts.maxItems, "max-items", 0, "Maximum number of folders to return (client-side; 0 = unlimited)")
util.AddJSONFlags(cmd, &opts.exporter, []string{
"createdBy",
"createdOn",
"description",
"lastChangedBy",
"lastChangedDate",
"path",
"project",
})

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.maxItems < 0 {
return util.FlagErrorf("--max-items must be >= 0")
}

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

ctx := cmdCtx.Context()

client, err := cmdCtx.ClientFactory().Build(ctx, scope.Organization)
if err != nil {
return fmt.Errorf("failed to create build client: %w", err)
}

args := build.GetFoldersArgs{
Project: types.ToPtr(scope.Project),
}
if opts.path != "" {
args.Path = types.ToPtr(opts.path)
}
if opts.queryOrder != "" {
q := build.FolderQueryOrderValues.FolderAscending
if opts.queryOrder == "desc" {
q = build.FolderQueryOrderValues.FolderDescending
}
args.QueryOrder = &q
}

zap.L().Debug(
"listing folders",
zap.String("organization", scope.Organization),
zap.String("project", scope.Project),
zap.String("path", types.GetValue(args.Path, "")),
)

resp, err := client.GetFolders(ctx, args)
if err != nil {
return fmt.Errorf("failed to list folders: %w", err)
}

folders := []build.Folder{}
if resp != nil {
folders = *resp
}

if opts.maxItems > 0 && len(folders) > opts.maxItems {
zap.L().Debug("truncating result set to max-items", zap.Int("maxItems", opts.maxItems))
folders = folders[:opts.maxItems]
}

ios.StopProgressIndicator()

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

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

tp.AddColumns("PATH", "DESCRIPTION")
tp.EndRow()
for _, f := range folders {
tp.AddField(types.GetValue(f.Path, ""))
tp.AddField(types.GetValue(f.Description, ""))
tp.EndRow()
}

return tp.Render()
}
Loading
Loading