diff --git a/cmd/bundle/init.go b/cmd/bundle/init.go index ac6f49de..18d76db1 100644 --- a/cmd/bundle/init.go +++ b/cmd/bundle/init.go @@ -131,7 +131,7 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf templatePath = args[0] } else { var err error - if !cmdio.IsOutTTY(ctx) || !cmdio.IsInTTY(ctx) { + if !cmdio.IsPromptSupported(ctx) { return errors.New("please specify a template") } templatePath, err = cmdio.AskSelect(ctx, "Template to use", nativeTemplateOptions()) diff --git a/cmd/bundle/run.go b/cmd/bundle/run.go index b2766b20..c9e35aa3 100644 --- a/cmd/bundle/run.go +++ b/cmd/bundle/run.go @@ -45,7 +45,7 @@ func newRunCommand() *cobra.Command { } // If no arguments are specified, prompt the user to select something to run. - if len(args) == 0 && cmdio.IsInteractive(ctx) { + if len(args) == 0 && cmdio.IsPromptSupported(ctx) { // Invert completions from KEY -> NAME, to NAME -> KEY. inv := make(map[string]string) for k, v := range run.ResourceCompletionMap(b) { diff --git a/cmd/labs/project/installer.go b/cmd/labs/project/installer.go index fa676819..7ba2830e 100644 --- a/cmd/labs/project/installer.go +++ b/cmd/labs/project/installer.go @@ -157,7 +157,7 @@ func (i *installer) recordVersion(ctx context.Context) error { } func (i *installer) login(ctx context.Context) (*databricks.WorkspaceClient, error) { - if !cmdio.IsInteractive(ctx) { + if !cmdio.IsPromptSupported(ctx) { log.Debugf(ctx, "Skipping workspace profile prompts in non-interactive mode") return nil, nil } diff --git a/cmd/labs/project/login.go b/cmd/labs/project/login.go index dd235064..fc872bcf 100644 --- a/cmd/labs/project/login.go +++ b/cmd/labs/project/login.go @@ -50,7 +50,7 @@ func (lc *loginConfig) askWorkspaceProfile(ctx context.Context, cfg *config.Conf lc.WorkspaceProfile = cfg.Profile return } - if !cmdio.IsInteractive(ctx) { + if !cmdio.IsPromptSupported(ctx) { return ErrNotInTTY } lc.WorkspaceProfile, err = root.AskForWorkspaceProfile(ctx) @@ -66,7 +66,7 @@ func (lc *loginConfig) askCluster(ctx context.Context, w *databricks.WorkspaceCl lc.ClusterID = w.Config.ClusterID return } - if !cmdio.IsInteractive(ctx) { + if !cmdio.IsPromptSupported(ctx) { return ErrNotInTTY } clusterID, err := cfgpickers.AskForCluster(ctx, w, @@ -87,7 +87,7 @@ func (lc *loginConfig) askWarehouse(ctx context.Context, w *databricks.Workspace lc.WarehouseID = w.Config.WarehouseID return } - if !cmdio.IsInteractive(ctx) { + if !cmdio.IsPromptSupported(ctx) { return ErrNotInTTY } lc.WarehouseID, err = cfgpickers.AskForWarehouse(ctx, w, @@ -99,7 +99,7 @@ func (lc *loginConfig) askAccountProfile(ctx context.Context, cfg *config.Config if !lc.HasAccountLevelCommands() { return nil } - if !cmdio.IsInteractive(ctx) { + if !cmdio.IsPromptSupported(ctx) { return ErrNotInTTY } lc.AccountProfile, err = root.AskForAccountProfile(ctx) diff --git a/cmd/root/auth.go b/cmd/root/auth.go index 33f80e1f..2a0cb22e 100644 --- a/cmd/root/auth.go +++ b/cmd/root/auth.go @@ -41,7 +41,7 @@ func accountClientOrPrompt(ctx context.Context, cfg *config.Config, allowPrompt } prompt := false - if allowPrompt && err != nil && cmdio.IsInteractive(ctx) { + if allowPrompt && err != nil && cmdio.IsPromptSupported(ctx) { // Prompt to select a profile if the current configuration is not an account client. prompt = prompt || errors.Is(err, databricks.ErrNotAccountClient) // Prompt to select a profile if the current configuration doesn't resolve to a credential provider. @@ -109,7 +109,7 @@ func workspaceClientOrPrompt(ctx context.Context, cfg *config.Config, allowPromp } prompt := false - if allowPrompt && err != nil && cmdio.IsInteractive(ctx) { + if allowPrompt && err != nil && cmdio.IsPromptSupported(ctx) { // Prompt to select a profile if the current configuration is not a workspace client. prompt = prompt || errors.Is(err, databricks.ErrNotWorkspaceClient) // Prompt to select a profile if the current configuration doesn't resolve to a credential provider. diff --git a/libs/cmdio/io.go b/libs/cmdio/io.go index cf405a7a..8b421ef5 100644 --- a/libs/cmdio/io.go +++ b/libs/cmdio/io.go @@ -10,6 +10,7 @@ import ( "time" "github.com/briandowns/spinner" + "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/flags" "github.com/manifoldco/promptui" "github.com/mattn/go-isatty" @@ -88,6 +89,30 @@ func (c *cmdIO) IsTTY() bool { return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd) } +func IsPromptSupported(ctx context.Context) bool { + // We do not allow prompting in non-interactive mode and in Git Bash on Windows. + // Likely due to fact that Git Bash does not (correctly support ANSI escape sequences, + // we cannot use promptui package there. + // See known issues: + // - https://github.com/manifoldco/promptui/issues/208 + // - https://github.com/chzyer/readline/issues/191 + // We also do not allow prompting in non-interactive mode, + // because it's not possible to read from stdin in non-interactive mode. + return (IsInteractive(ctx) || (IsOutTTY(ctx) && IsInTTY(ctx))) && !IsGitBash(ctx) +} + +func IsGitBash(ctx context.Context) bool { + // Check if the MSYSTEM environment variable is set to "MINGW64" + msystem := env.Get(ctx, "MSYSTEM") + if strings.EqualFold(msystem, "MINGW64") { + // Check for typical Git Bash env variable for prompts + ps1 := env.Get(ctx, "PS1") + return strings.Contains(ps1, "MINGW") || strings.Contains(ps1, "MSYSTEM") + } + + return false +} + func Render(ctx context.Context, v any) error { c := fromContext(ctx) return RenderWithTemplate(ctx, v, c.template) diff --git a/libs/cmdio/io_test.go b/libs/cmdio/io_test.go new file mode 100644 index 00000000..1e474204 --- /dev/null +++ b/libs/cmdio/io_test.go @@ -0,0 +1,21 @@ +package cmdio + +import ( + "context" + "testing" + + "github.com/databricks/cli/libs/env" + "github.com/stretchr/testify/assert" +) + +func TestIsPromptSupportedFalseForGitBash(t *testing.T) { + ctx := context.Background() + ctx, _ = SetupTest(ctx) + + assert.True(t, IsPromptSupported(ctx)) + + ctx = env.Set(ctx, "MSYSTEM", "MINGW64") + ctx = env.Set(ctx, "TERM", "xterm") + ctx = env.Set(ctx, "PS1", "\\[\033]0;$TITLEPREFIX:$PWD\007\\]\n\\[\033[32m\\]\\u@\\h \\[\033[35m\\]$MSYSTEM \\[\033[33m\\]\\w\\[\033[36m\\]`__git_ps1`\\[\033[0m\\]\n$") + assert.False(t, IsPromptSupported(ctx)) +} diff --git a/libs/template/config.go b/libs/template/config.go index 2b4d19d1..b52c0ee8 100644 --- a/libs/template/config.go +++ b/libs/template/config.go @@ -212,7 +212,7 @@ func (c *config) promptForValues(r *renderer) error { // Prompt user for any missing config values. Assign default values if // terminal is not TTY func (c *config) promptOrAssignDefaultValues(r *renderer) error { - if cmdio.IsOutTTY(c.ctx) && cmdio.IsInTTY(c.ctx) { + if cmdio.IsPromptSupported(c.ctx) { return c.promptForValues(r) } return c.assignDefaultValues(r)