Skip to content
Open
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
97 changes: 97 additions & 0 deletions cmd/team/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package team

import (
"context"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/alecthomas/kong"
"github.com/buildkite/cli/v3/internal/cli"
bkIO "github.com/buildkite/cli/v3/internal/io"
"github.com/buildkite/cli/v3/internal/team"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/buildkite/cli/v3/pkg/cmd/validation"
"github.com/buildkite/cli/v3/pkg/output"
buildkite "github.com/buildkite/go-buildkite/v4"
)

type CreateCmd struct {
Name string `arg:"" help:"Name of the team" name:"name"`
Description string `help:"Description of the team" optional:""`
Privacy string `help:"Privacy setting for the team: visible or secret" optional:"" default:"visible" enum:"visible,secret"`
Default bool `help:"Whether this is the default team for new members" optional:"" name:"default"`
DefaultMemberRole string `help:"Default role for new members: member or maintainer" optional:"" name:"default-member-role" default:"member" enum:"member,maintainer"`
MembersCanCreatePipelines bool `help:"Whether members can create pipelines" optional:"" name:"members-can-create-pipelines"`
output.OutputFlags
}

func (c *CreateCmd) Help() string {
return `
Create a new team in the organization.

Examples:
# Create a team with default settings
$ bk team create my-team

# Create a private team with a description
$ bk team create my-team --description "My team" --privacy secret

# Create a default team where members can create pipelines
$ bk team create my-team --default --members-can-create-pipelines
`
}

func (c *CreateCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error {
f, err := factory.New(factory.WithDebug(globals.EnableDebug()))
if err != nil {
return err
}

f.SkipConfirm = globals.SkipConfirmation()
f.NoInput = globals.DisableInput()
f.Quiet = globals.IsQuiet()
f.NoPager = f.NoPager || globals.DisablePager()

if err := validation.ValidateConfiguration(f.Config, kongCtx.Command()); err != nil {
return err
}

format := output.ResolveFormat(c.Output, f.Config.OutputFormat())

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

input := buildkite.CreateTeam{
Name: c.Name,
Description: c.Description,
Privacy: c.Privacy,
IsDefaultTeam: c.Default,
DefaultMemberRole: c.DefaultMemberRole,
MembersCanCreatePipelines: c.MembersCanCreatePipelines,
}

var t buildkite.Team
spinErr := bkIO.SpinWhile(f, "Creating team", func() {
t, _, err = f.RestAPIClient.Teams.CreateTeam(ctx, f.Config.OrganizationSlug(), input)
})
if spinErr != nil {
return spinErr
}
if err != nil {
return fmt.Errorf("error creating team: %v", err)
}

teamView := output.Viewable[buildkite.Team]{
Data: t,
Render: team.RenderTeamText,
}

if format != output.FormatText {
return output.Write(os.Stdout, teamView, format)
}

fmt.Fprintf(os.Stdout, "Team %s created successfully\n\n", t.Name)
return output.Write(os.Stdout, teamView, format)
}
74 changes: 74 additions & 0 deletions cmd/team/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package team

import (
"context"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/alecthomas/kong"
"github.com/buildkite/cli/v3/internal/cli"
bkIO "github.com/buildkite/cli/v3/internal/io"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/buildkite/cli/v3/pkg/cmd/validation"
)

type DeleteCmd struct {
TeamUUID string `arg:"" help:"UUID of the team to delete" name:"team-uuid"`
}

func (c *DeleteCmd) Help() string {
return `
Delete a team from the organization.

You will be prompted to confirm deletion unless --yes is set.

Examples:
# Delete a team (with confirmation prompt)
$ bk team delete my-team-uuid

# Delete a team without confirmation
$ bk team delete my-team-uuid --yes
`
}

func (c *DeleteCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error {
f, err := factory.New(factory.WithDebug(globals.EnableDebug()))
if err != nil {
return err
}

f.SkipConfirm = globals.SkipConfirmation()
f.NoInput = globals.DisableInput()
f.Quiet = globals.IsQuiet()

if err := validation.ValidateConfiguration(f.Config, kongCtx.Command()); err != nil {
return err
}

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

confirmed, err := bkIO.Confirm(f, fmt.Sprintf("Are you sure you want to delete team %s?", c.TeamUUID))
if err != nil {
return err
}
if !confirmed {
fmt.Fprintln(os.Stderr, "Deletion cancelled.")
return nil
}

spinErr := bkIO.SpinWhile(f, "Deleting team", func() {
_, err = f.RestAPIClient.Teams.DeleteTeam(ctx, f.Config.OrganizationSlug(), c.TeamUUID)
})
if spinErr != nil {
return spinErr
}
if err != nil {
return fmt.Errorf("error deleting team: %v", err)
}

fmt.Fprintln(os.Stderr, "Team deleted successfully.")
return nil
}
145 changes: 145 additions & 0 deletions cmd/team/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package team

import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/alecthomas/kong"
"github.com/buildkite/cli/v3/internal/cli"
bkIO "github.com/buildkite/cli/v3/internal/io"
"github.com/buildkite/cli/v3/internal/team"
"github.com/buildkite/cli/v3/pkg/cmd/factory"
"github.com/buildkite/cli/v3/pkg/cmd/validation"
"github.com/buildkite/cli/v3/pkg/output"
buildkite "github.com/buildkite/go-buildkite/v4"
)

type ListCmd struct {
PerPage int `help:"Number of teams per page" default:"30"`
Limit int `help:"Maximum number of teams to return" default:"100"`
output.OutputFlags
}

func (c *ListCmd) Help() string {
return `
List the teams for an organization. By default, shows up to 100 teams.

Examples:
# List all teams
$ bk team list

# List teams in JSON format
$ bk team list -o json

# List up to 200 teams
$ bk team list --limit 200
`
}

func (c *ListCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error {
f, err := factory.New(factory.WithDebug(globals.EnableDebug()))
if err != nil {
return err
}

f.SkipConfirm = globals.SkipConfirmation()
f.NoInput = globals.DisableInput()
f.Quiet = globals.IsQuiet()
f.NoPager = f.NoPager || globals.DisablePager()

if err := validation.ValidateConfiguration(f.Config, kongCtx.Command()); err != nil {
return err
}

format := output.ResolveFormat(c.Output, f.Config.OutputFormat())

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

teams, hasMore, err := listTeams(ctx, f, c.PerPage, c.Limit)
if err != nil {
return err
}

if format != output.FormatText {
return output.Write(os.Stdout, teams, format)
}

summary := team.TeamViewTable(teams...)

writer, cleanup := bkIO.Pager(f.NoPager, f.Config.Pager())
defer func() { _ = cleanup() }()

totalDisplay := fmt.Sprintf("%d", len(teams))
if hasMore {
totalDisplay = fmt.Sprintf("%d+", len(teams))
}
fmt.Fprintf(writer, "Showing %s teams in %s\n\n", totalDisplay, f.Config.OrganizationSlug())
fmt.Fprintf(writer, "%v\n", summary)

return nil
}

func listTeams(ctx context.Context, f *factory.Factory, perPage, limit int) ([]buildkite.Team, bool, error) {
var all []buildkite.Team
var err error
page := 1
hasMore := false
var previousFirstTeamID string

for len(all) < limit {
opts := &buildkite.TeamsListOptions{
ListOptions: buildkite.ListOptions{
Page: page,
PerPage: perPage,
},
}

var pageTeams []buildkite.Team
spinErr := bkIO.SpinWhile(f, "Loading teams", func() {
pageTeams, _, err = f.RestAPIClient.Teams.List(ctx, f.Config.OrganizationSlug(), opts)
})
if spinErr != nil {
return nil, false, spinErr
}
if err != nil {
return nil, false, fmt.Errorf("error fetching team list: %v", err)
}

if len(pageTeams) == 0 {
break
}

if page > 1 && pageTeams[0].ID == previousFirstTeamID {
return nil, false, fmt.Errorf("API returned duplicate page content at page %d, stopping pagination to prevent infinite loop", page)
}
previousFirstTeamID = pageTeams[0].ID

all = append(all, pageTeams...)

if len(pageTeams) < perPage {
break
}

if len(all) >= limit {
hasMore = true
break
}

page++
}

if len(all) > limit {
all = all[:limit]
}

if len(all) == 0 {
return nil, false, errors.New("no teams found in organization")
}

return all, hasMore, nil
}
Loading