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
15 changes: 7 additions & 8 deletions cmd/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,20 @@ func promptForHost(ctx context.Context) (string, error) {
return "", errors.New("the command is being run in a non-interactive environment, please specify a host using --host")
}

prompt := cmdio.Prompt(ctx)
prompt.Label = "Databricks host (e.g. https://<databricks-instance>.cloud.databricks.com)"
return prompt.Run()
return cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Databricks host (e.g. https://<databricks-instance>.cloud.databricks.com)",
})
}

func promptForAccountID(ctx context.Context) (string, error) {
if !cmdio.IsPromptSupported(ctx) {
return "", errors.New("the command is being run in a non-interactive environment, please specify an account ID using --account-id")
}

prompt := cmdio.Prompt(ctx)
prompt.Label = "Databricks account ID"
prompt.Default = ""
prompt.AllowEdit = true
return prompt.Run()
return cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Databricks account ID",
AllowEdit: true,
})
}

// validateProfileHostConflict checks that --profile and --host don't conflict.
Expand Down
16 changes: 8 additions & 8 deletions cmd/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ func promptForProfile(ctx context.Context, defaultValue string) (string, error)
return "", nil
}

prompt := cmdio.Prompt(ctx)
prompt.Label = "Databricks profile name [" + defaultValue + "]"
prompt.AllowEdit = true
result, err := prompt.Run()
result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Databricks profile name [" + defaultValue + "]",
AllowEdit: true,
})
if result == "" {
// Manually return the default value. We could use the prompt.Default
// field, but be inconsistent with other prompts in the CLI.
Expand Down Expand Up @@ -756,10 +756,10 @@ func promptForWorkspaceSelection(ctx context.Context, authArguments *auth.AuthAr
// promptForWorkspaceID asks the user to manually enter a workspace ID.
// Returns empty string if the user provides no input.
func promptForWorkspaceID(ctx context.Context) (string, error) {
prompt := cmdio.Prompt(ctx)
prompt.Label = "Enter workspace ID (empty to skip)"
prompt.AllowEdit = true
result, err := prompt.Run()
result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Enter workspace ID (empty to skip)",
AllowEdit: true,
})
if err != nil {
return "", err
}
Expand Down
13 changes: 5 additions & 8 deletions cmd/auth/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/databricks/cli/libs/databrickscfg"
"github.com/databricks/cli/libs/databrickscfg/profile"
"github.com/databricks/cli/libs/env"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -87,7 +86,7 @@ func promptForSwitchProfile(ctx context.Context, profiles profile.Profiles, curr
label = fmt.Sprintf("Current default: %s. Select a new default", currentDefault)
}

i, _, err := cmdio.RunSelect(ctx, &promptui.Select{
i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{
Label: label,
Items: items,
StartInSearchMode: len(profiles) > 5,
Expand All @@ -97,12 +96,10 @@ func promptForSwitchProfile(ctx context.Context, profiles profile.Profiles, curr
host := strings.ToLower(items[index].Host)
return strings.Contains(name, input) || strings.Contains(host, input)
},
Templates: &promptui.SelectTemplates{
Label: "{{ . | faint }}",
Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`,
Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`,
Selected: `{{ "Default profile" | faint }}: {{ .Name | bold }}`,
},
LabelTemplate: "{{ . | faint }}",
Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`,
Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`,
Selected: `{{ "Default profile" | faint }}: {{ .Name | bold }}`,
})
if err != nil {
return "", err
Expand Down
17 changes: 7 additions & 10 deletions cmd/auth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/databricks/databricks-sdk-go/config"
"github.com/databricks/databricks-sdk-go/credentials/u2m"
"github.com/databricks/databricks-sdk-go/credentials/u2m/cache"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
"golang.org/x/oauth2"
)
Expand Down Expand Up @@ -379,8 +378,8 @@ type profileSelectItem struct {
Host string
}

// promptForProfileSelection shows a promptui select list with all configured
// profiles plus "Enter a host URL" and "Create a new profile" options.
// promptForProfileSelection shows a select list with all configured profiles
// plus "Enter a host URL" and "Create a new profile" options.
// Returns the selection type and, when a profile is selected, its name.
func promptForProfileSelection(ctx context.Context, profiles profile.Profiles) (profileSelectionResult, string, error) {
items := make([]profileSelectItem, 0, len(profiles)+2)
Expand All @@ -392,7 +391,7 @@ func promptForProfileSelection(ctx context.Context, profiles profile.Profiles) (
enterHostIdx := len(items)
items = append(items, profileSelectItem{Name: "Enter a host URL manually"})

i, _, err := cmdio.RunSelect(ctx, &promptui.Select{
i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{
Label: "Select a profile",
Items: items,
StartInSearchMode: len(profiles) > 5,
Expand All @@ -402,12 +401,10 @@ func promptForProfileSelection(ctx context.Context, profiles profile.Profiles) (
host := strings.ToLower(items[index].Host)
return strings.Contains(name, input) || strings.Contains(host, input)
},
Templates: &promptui.SelectTemplates{
Label: "{{ . | faint }}",
Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`,
Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`,
Selected: `{{ "Using profile" | faint }}: {{ .Name | bold }}`,
},
LabelTemplate: "{{ . | faint }}",
Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`,
Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`,
Selected: `{{ "Using profile" | faint }}: {{ .Name | bold }}`,
})
if err != nil {
return 0, "", err
Expand Down
24 changes: 12 additions & 12 deletions cmd/configure/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ func configureInteractive(cmd *cobra.Command, flags *configureFlags, cfg *config

// Ask user to specify the host if not already set.
if cfg.Host == "" {
prompt := cmdio.Prompt(ctx)
prompt.Label = "Databricks workspace host (https://...)"
prompt.AllowEdit = true
prompt.Validate = func(input string) error {
normalized := normalizeHost(input)
return validateHost(normalized)
}
out, err := prompt.Run()
out, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Databricks workspace host (https://...)",
AllowEdit: true,
Validate: func(input string) error {
normalized := normalizeHost(input)
return validateHost(normalized)
},
})
if err != nil {
return err
}
Expand All @@ -43,10 +43,10 @@ func configureInteractive(cmd *cobra.Command, flags *configureFlags, cfg *config

// Ask user to specify the token is not already set.
if cfg.Token == "" {
prompt := cmdio.Prompt(ctx)
prompt.Label = "Personal access token"
prompt.Mask = '*'
out, err := prompt.Run()
out, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Personal access token",
Mask: '*',
})
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/root/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ workspace:
}()

// Verify the prompt fires by reading output from stderr.
// promptui with StartInSearchMode writes a search cursor first.
// cmdio.RunSelect with StartInSearchMode writes a search cursor first.
line, _, readErr := io.Stderr.ReadLine()
if assert.NoError(t, readErr, "expected prompt output on stderr") {
assert.Contains(t, string(line), "Search:")
Expand Down
15 changes: 0 additions & 15 deletions libs/cmdio/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,6 @@ func (nopWriteCloser) Close() error {
return nil
}

func Prompt(ctx context.Context) *promptui.Prompt {
c := fromContext(ctx)
return &promptui.Prompt{
Stdin: c.promptStdin(),
Stdout: nopWriteCloser{c.err},
}
}

func RunSelect(ctx context.Context, prompt *promptui.Select) (int, string, error) {
c := fromContext(ctx)
prompt.Stdin = c.promptStdin()
prompt.Stdout = nopWriteCloser{c.err}
return prompt.Run()
}

// NewSpinner creates a new spinner for displaying progress indicators.
// The returned spinner should be closed when done to release resources.
//
Expand Down
42 changes: 42 additions & 0 deletions libs/cmdio/prompt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cmdio

import (
"context"

"github.com/manifoldco/promptui"
)

// PromptOptions configures a single-line text prompt shown by [RunPrompt].
type PromptOptions struct {
// Label is shown before the input field. Required.
Label string

// Default is the value pre-filled in the input field.
Default string

// Mask, when non-zero, replaces typed characters with the given rune
// (use '*' for password-style input).
Mask rune

// AllowEdit lets the user edit Default rather than overwriting it.
AllowEdit bool

// Validate, when set, is called on every keystroke; returning a non-nil
// error keeps the prompt open and shows the error to the user.
Validate func(input string) error
}

// RunPrompt shows a single-line text prompt and returns the entered value.
func RunPrompt(ctx context.Context, opts PromptOptions) (string, error) {
c := fromContext(ctx)
p := promptui.Prompt{
Label: opts.Label,
Default: opts.Default,
Mask: opts.Mask,
AllowEdit: opts.AllowEdit,
Validate: opts.Validate,
Stdin: c.promptStdin(),
Stdout: nopWriteCloser{c.err},
}
return p.Run()
}
58 changes: 58 additions & 0 deletions libs/cmdio/select.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package cmdio

import (
"context"

"github.com/manifoldco/promptui"
)

// SelectOptions configures an interactive single-choice picker shown by
// [RunSelect]. Template strings use text/template syntax and have access
// to the fields of the items in Items.
type SelectOptions struct {
// Label is shown above the list. Required.
Label string

// Items is the slice of values to choose from. Templates reference
// fields on the element type.
Items any

// Searcher, when set, narrows the list as the user types.
Searcher func(input string, index int) bool

// StartInSearchMode opens the prompt with the search input focused.
StartInSearchMode bool

// LabelTemplate renders Label. Empty uses the default.
LabelTemplate string

// Active renders the highlighted item.
Active string

// Inactive renders non-highlighted items.
Inactive string

// Selected renders the chosen item after the prompt closes.
Selected string
}

// RunSelect shows an interactive picker and returns the index of the chosen item.
func RunSelect(ctx context.Context, opts SelectOptions) (int, error) {
c := fromContext(ctx)
sel := &promptui.Select{
Label: opts.Label,
Items: opts.Items,
Searcher: opts.Searcher,
StartInSearchMode: opts.StartInSearchMode,
Templates: &promptui.SelectTemplates{
Label: opts.LabelTemplate,
Active: opts.Active,
Inactive: opts.Inactive,
Selected: opts.Selected,
},
Stdin: c.promptStdin(),
Stdout: nopWriteCloser{c.err},
}
idx, _, err := sel.Run()
return idx, err
}
13 changes: 5 additions & 8 deletions libs/databrickscfg/cfgpickers/clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/service/compute"
"github.com/databricks/databricks-sdk-go/service/iam"
"github.com/manifoldco/promptui"
"golang.org/x/mod/semver"
)

Expand Down Expand Up @@ -193,20 +192,18 @@ func AskForCluster(ctx context.Context, w *databricks.WorkspaceClient, filters .
if len(compatible) == 1 {
return compatible[0].ClusterId, nil
}
i, _, err := cmdio.RunSelect(ctx, &promptui.Select{
i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{
Label: "Choose compatible cluster",
Items: compatible,
Searcher: func(input string, idx int) bool {
lower := strings.ToLower(compatible[idx].ClusterName)
return strings.Contains(lower, strings.ToLower(input))
},
StartInSearchMode: true,
Templates: &promptui.SelectTemplates{
Label: "{{.ClusterName | faint}}",
Active: `{{.ClusterName | bold}} ({{.State}} {{.Access}} Runtime {{.Runtime}}) ({{.ClusterId | faint}})`,
Inactive: `{{.ClusterName}} ({{.State}} {{.Access}} Runtime {{.Runtime}})`,
Selected: `{{ "Configured cluster" | faint }}: {{ .ClusterName | bold }} ({{.ClusterId | faint}})`,
},
LabelTemplate: "{{.ClusterName | faint}}",
Active: `{{.ClusterName | bold}} ({{.State}} {{.Access}} Runtime {{.Runtime}}) ({{.ClusterId | faint}})`,
Inactive: `{{.ClusterName}} ({{.State}} {{.Access}} Runtime {{.Runtime}})`,
Selected: `{{ "Configured cluster" | faint }}: {{ .ClusterName | bold }} ({{.ClusterId | faint}})`,
})
if err != nil {
return "", err
Expand Down
4 changes: 2 additions & 2 deletions libs/databrickscfg/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func (p Profile) Cloud() string {

type Profiles []Profile

// SearchCaseInsensitive implements the promptui.Searcher interface.
// This allows the user to immediately starting typing to narrow down the list.
// SearchCaseInsensitive matches the cmdio.SelectOptions.Searcher signature so
// the user can immediately start typing to narrow down the list.
func (p Profiles) SearchCaseInsensitive(input string, index int) bool {
input = strings.ToLower(input)
name := strings.ToLower(p[index].Name)
Expand Down
17 changes: 7 additions & 10 deletions libs/databrickscfg/profile/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"strings"

"github.com/databricks/cli/libs/cmdio"
"github.com/manifoldco/promptui"
)

var (
Expand Down Expand Up @@ -78,8 +77,8 @@ func SelectProfile(ctx context.Context, cfg SelectConfig) (string, error) {
}

// Build the searcher from the items slice directly so it stays coupled
// to the Items list passed to promptui (rather than the original Profiles
// slice which could diverge if items were ever filtered or reordered).
// to the Items list passed to cmdio.RunSelect (rather than the original
// Profiles slice which could diverge if items were ever filtered or reordered).
searcher := func(input string, index int) bool {
input = strings.ToLower(input)
p := items[index].Profile
Expand All @@ -88,17 +87,15 @@ func SelectProfile(ctx context.Context, cfg SelectConfig) (string, error) {
strings.Contains(strings.ToLower(p.AccountID), input)
}

i, _, err := cmdio.RunSelect(ctx, &promptui.Select{
i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{
Label: cfg.Label,
Items: items,
StartInSearchMode: cfg.StartInSearchMode,
Searcher: searcher,
Templates: &promptui.SelectTemplates{
Label: "{{ . | faint }}",
Active: cfg.ActiveTemplate,
Inactive: cfg.InactiveTemplate,
Selected: cfg.SelectedTemplate,
},
LabelTemplate: "{{ . | faint }}",
Active: cfg.ActiveTemplate,
Inactive: cfg.InactiveTemplate,
Selected: cfg.SelectedTemplate,
})
if err != nil {
return "", err
Expand Down
Loading