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
149 changes: 149 additions & 0 deletions cmd/configure/configure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package configure

import (
"bufio"
"errors"
"fmt"
"os"
"strings"

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

type ConfigureCmd struct {
Org string `help:"Organization slug" optional:""`
Token string `help:"API token" optional:""`
Force bool `help:"Force setting a new token" optional:""`
Default ConfigureDefaultCmd `cmd:"" optional:"" help:"Configure Buildkite API token" hidden:"" default:"1"`
Add ConfigureAddCmd `cmd:"" optional:"" help:"Add configuration for a new organization"`
}

type ConfigureDefaultCmd struct {
}

type ConfigureAddCmd struct {
}

func (c *ConfigureAddCmd) Help() string {
return `
Examples:
# Prompt configuration to add for a new organization
$ bk configure add

# Add configure Buildkite API token
$ bk configure add --org my-org --token my-token
`
}

func (c *ConfigureCmd) Help() string {
return `
Examples:
# Configure Buildkite API token
$ bk configure --org my-org --token my-token

# Force setting a new token
$ bk configure --force --org my-org --token my-token
`
}

func (c *ConfigureCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error {
f, err := factory.New()

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
}

if kongCtx.Command() == "configure default" {
if !c.Force && f.Config.APIToken() != "" {
return errors.New("API token already configured. You must use --force")
}

}

// If flags are provided, use them directly
if c.Org != "" && c.Token != "" {
return ConfigureWithCredentials(f, c.Org, c.Token)
}

return ConfigureRun(f, c.Org)
}

func ConfigureWithCredentials(f *factory.Factory, org, token string) error {
if err := f.Config.SelectOrganization(org, f.GitRepository != nil); err != nil {
return err
}
return f.Config.SetTokenForOrg(org, token)
}

func ConfigureRun(f *factory.Factory, org string) error {
// Check if we're in a Git repository
if f.GitRepository == nil {
return errors.New("not in a Git repository - bk should be configured at the root of a Git repository")
}

if org == "" {
// Get organization slug
inputOrg, err := promptForInput("Organization slug: ", false)

if err != nil {
return err
}
if inputOrg == "" {
return errors.New("organization slug cannot be empty")
}
org = inputOrg
}
// Check if token already exists for this organization
existingToken := getTokenForOrg(f, org)
if existingToken != "" {
fmt.Printf("Using existing API token for organization: %s\n", org)
return f.Config.SelectOrganization(org, f.GitRepository != nil)
}

// Get API token with password input (no echo)
token, err := promptForInput("API Token: ", true)
if err != nil {
return err
}
if token == "" {
return errors.New("API token cannot be empty")
}

fmt.Println("API token set for organization:", org)
return ConfigureWithCredentials(f, org, token)
}

// getTokenForOrg retrieves the token for a specific organization from the user config
func getTokenForOrg(f *factory.Factory, org string) string {
return f.Config.GetTokenForOrg(org)
}

// promptForInput handles terminal input with optional password masking
func promptForInput(prompt string, isPassword bool) (string, error) {
fmt.Print(prompt)

if isPassword {
return io.ReadPassword()
} else {
// Use standard input for regular text
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
return "", err
}
// Trim whitespace and newlines
return strings.TrimSpace(input), nil
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package add
package configure

import (
"testing"
Expand Down Expand Up @@ -136,7 +136,7 @@ func TestConfigureRequiresGitRepository(t *testing.T) {
// Create a factory with nil GitRepository (simulating not being in a git repo)
f := &factory.Factory{Config: conf, GitRepository: nil}

err := ConfigureRun(f)
err := ConfigureRun(f, "test-org")

if err == nil {
t.Error("expected error when not in a git repository, got nil")
Expand Down
52 changes: 5 additions & 47 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/buildkite/cli/v3/cmd/artifacts"
"github.com/buildkite/cli/v3/cmd/build"
"github.com/buildkite/cli/v3/cmd/cluster"
"github.com/buildkite/cli/v3/cmd/configure"
bkInit "github.com/buildkite/cli/v3/cmd/init"
"github.com/buildkite/cli/v3/cmd/job"
"github.com/buildkite/cli/v3/cmd/organization"
Expand Down Expand Up @@ -109,56 +110,10 @@ type (
api.ApiCmd `cmd:"" help:"Interact with the Buildkite API"`
}
ConfigureCmd struct {
Args []string `arg:"" optional:"" passthrough:"all"`
configure.ConfigureCmd `cmd:"" help:"Configure Buildkite API token"`
}
)

// Delegation methods, we should delete when native Kong implementations ready
func (c *ConfigureCmd) Run(cli *CLI) error { return cli.delegateToCobraSystem("configure", c.Args) }

// delegateToCobraSystem delegates execution to the legacy Cobra command system.
// This is a temporary bridge during the Kong migration that ensures backwards compatibility
// by reconstructing global flags that Kong has already parsed.
func (cli *CLI) delegateToCobraSystem(command string, args []string) error {
// Preserve and restore original args for safety
originalArgs := os.Args
defer func() { os.Args = originalArgs }()

// Reconstruct command args with global flags for Cobra compatibility
reconstructedArgs := cli.buildCobraArgs(command, args)
os.Args = reconstructedArgs

if code := runCobraSystem(); code != 0 {
os.Exit(code)
}
return nil
}

// buildCobraArgs constructs the argument slice for Cobra, including global flags.
// Kong parses and consumes global flags before delegation, so we need to reconstruct
// them to maintain backwards compatibility with Cobra commands.
func (cli *CLI) buildCobraArgs(command string, passthroughArgs []string) []string {
args := []string{os.Args[0], command}

if cli.Yes {
args = append(args, "--yes")
}
if cli.NoInput {
args = append(args, "--no-input")
}
if cli.Quiet {
args = append(args, "--quiet")
}
// TODO: Add verbose flag reconstruction when implemented
// if cli.Verbose {
// args = append(args, "--verbose")
// }

args = append(args, passthroughArgs...)

return args
}

func runCobraSystem() int {
f, err := factory.New()
if err != nil {
Expand Down Expand Up @@ -315,6 +270,9 @@ func isHelpRequest() bool {
if len(os.Args) >= 2 && os.Args[1] == "user" {
return false
}
if len(os.Args) >= 2 && os.Args[1] == "configure" {
return false
}

if len(os.Args) == 3 && (os.Args[2] == "-h" || os.Args[2] == "--help") {
return true
Expand Down
100 changes: 0 additions & 100 deletions pkg/cmd/configure/add/add.go

This file was deleted.

46 changes: 0 additions & 46 deletions pkg/cmd/configure/configure.go

This file was deleted.

Loading