diff --git a/cmd/apps/init.go b/cmd/apps/init.go index e815241ce9..c974c19c9c 100644 --- a/cmd/apps/init.go +++ b/cmd/apps/init.go @@ -114,6 +114,12 @@ Environment variables: return errors.New("--branch and --version are mutually exclusive") } + // Capture --profile flag value from parent command. + var profileValue string + if f := cmd.Flag("profile"); f != nil { + profileValue = f.Value.String() + } + return runCreate(ctx, createOptions{ templatePath: templatePath, branch: branch, @@ -129,6 +135,7 @@ Environment variables: run: run, runChanged: cmd.Flags().Changed("run"), featuresChanged: cmd.Flags().Changed("features"), + profile: profileValue, }) }, } @@ -160,8 +167,9 @@ type createOptions struct { deploy bool deployChanged bool // true if --deploy flag was explicitly set run string - runChanged bool // true if --run flag was explicitly set - featuresChanged bool // true if --features flag was explicitly set + runChanged bool // true if --run flag was explicitly set + featuresChanged bool // true if --features flag was explicitly set + profile string // explicit profile from --profile flag } // templateVars holds the variables for template substitution. @@ -810,10 +818,20 @@ func runCreate(ctx context.Context, opts createOptions) error { } } + // Resolve the profile to pass to deploy/run subcommands. + var deployProfile string + if shouldDeploy || runMode != prompt.RunModeNone { + var err error + deployProfile, err = prompt.ResolveProfileForDeploy(ctx, opts.profile, workspaceHost) + if err != nil { + return fmt.Errorf("failed to resolve profile: %w", err) + } + } + if shouldDeploy { cmdio.LogString(ctx, "") cmdio.LogString(ctx, "Deploying app...") - if err := runPostCreateDeploy(ctx); err != nil { + if err := runPostCreateDeploy(ctx, deployProfile); err != nil { cmdio.LogString(ctx, fmt.Sprintf("⚠ Deploy failed: %v", err)) cmdio.LogString(ctx, " You can deploy manually with: databricks apps deploy") } @@ -821,7 +839,7 @@ func runCreate(ctx context.Context, opts createOptions) error { if runMode != prompt.RunModeNone { cmdio.LogString(ctx, "") - if err := runPostCreateDev(ctx, runMode, projectInitializer, absOutputDir); err != nil { + if err := runPostCreateDev(ctx, runMode, projectInitializer, absOutputDir, deployProfile); err != nil { return err } } @@ -830,12 +848,16 @@ func runCreate(ctx context.Context, opts createOptions) error { } // runPostCreateDeploy runs the deploy command in the current directory. -func runPostCreateDeploy(ctx context.Context) error { +func runPostCreateDeploy(ctx context.Context, profile string) error { executable, err := os.Executable() if err != nil { return fmt.Errorf("failed to get executable path: %w", err) } - cmd := exec.CommandContext(ctx, executable, "apps", "deploy") + args := []string{"apps", "deploy"} + if profile != "" { + args = append(args, "--profile", profile) + } + cmd := exec.CommandContext(ctx, executable, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin @@ -843,7 +865,7 @@ func runPostCreateDeploy(ctx context.Context) error { } // runPostCreateDev runs the dev or dev-remote command in the current directory. -func runPostCreateDev(ctx context.Context, mode prompt.RunMode, projectInit initializer.Initializer, workDir string) error { +func runPostCreateDev(ctx context.Context, mode prompt.RunMode, projectInit initializer.Initializer, workDir, profile string) error { switch mode { case prompt.RunModeDev: if projectInit != nil { @@ -858,7 +880,11 @@ func runPostCreateDev(ctx context.Context, mode prompt.RunMode, projectInit init if err != nil { return fmt.Errorf("failed to get executable path: %w", err) } - cmd := exec.CommandContext(ctx, executable, "apps", "dev-remote") + args := []string{"apps", "dev-remote"} + if profile != "" { + args = append(args, "--profile", profile) + } + cmd := exec.CommandContext(ctx, executable, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin diff --git a/libs/apps/prompt/prompt.go b/libs/apps/prompt/prompt.go index 8040afc28a..fb8b9b2f71 100644 --- a/libs/apps/prompt/prompt.go +++ b/libs/apps/prompt/prompt.go @@ -14,6 +14,7 @@ import ( "github.com/databricks/cli/libs/apps/features" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/databrickscfg/profile" "github.com/databricks/databricks-sdk-go/listing" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/databricks/databricks-sdk-go/service/sql" @@ -472,6 +473,73 @@ func PromptForWarehouse(ctx context.Context) (string, error) { return selected, nil } +// PromptForProfile prompts the user to select a CLI profile matching the given workspace host. +// If there is exactly one matching profile, it is returned without prompting. +func PromptForProfile(ctx context.Context, workspaceHost string) (string, error) { + profiler := profile.GetProfiler(ctx) + + profiles, err := profiler.LoadProfiles(ctx, func(p profile.Profile) bool { + return profile.MatchWorkspaceProfiles(p) && p.Host == workspaceHost + }) + if err != nil { + return "", fmt.Errorf("failed to load profiles: %w", err) + } + + if len(profiles) == 0 { + return "", nil + } + + if len(profiles) == 1 { + return profiles[0].Name, nil + } + + theme := AppkitTheme() + + // Build options; the first profile is the default. + options := make([]huh.Option[string], 0, len(profiles)) + for _, p := range profiles { + label := p.Name + if p.Host != "" { + label += " (" + p.Host + ")" + } + options = append(options, huh.NewOption(label, p.Name)) + } + + selected := profiles[0].Name + err = huh.NewSelect[string](). + Title("Select a CLI profile"). + Description(fmt.Sprintf("profile to use for deploy commands (%d available)", len(profiles))). + Options(options...). + Value(&selected). + WithTheme(theme). + Run() + if err != nil { + return "", err + } + + printAnswered(ctx, "Profile", selected) + return selected, nil +} + +func ResolveProfileForDeploy(ctx context.Context, explicitProfile, workspaceHost string) (string, error) { + // 1. Explicit --profile flag. + if explicitProfile != "" { + return explicitProfile, nil + } + + // 2. DATABRICKS_CONFIG_PROFILE env var. + if envProfile := os.Getenv("DATABRICKS_CONFIG_PROFILE"); envProfile != "" { + return envProfile, nil + } + + // 3. Prompt for a matching profile. + if !cmdio.IsPromptSupported(ctx) { + return "", nil + } + + return PromptForProfile(ctx, workspaceHost) +} + // RunWithSpinnerCtx runs a function while showing a spinner with the given title. // The spinner stops and the function returns early if the context is cancelled. // Panics in the action are recovered and returned as errors.