Skip to content
Closed
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
42 changes: 34 additions & 8 deletions cmd/apps/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -129,6 +135,7 @@ Environment variables:
run: run,
runChanged: cmd.Flags().Changed("run"),
featuresChanged: cmd.Flags().Changed("features"),
profile: profileValue,
})
},
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -810,18 +818,28 @@ 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")
}
}

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
}
}
Expand All @@ -830,20 +848,24 @@ 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
return cmd.Run()
}

// 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 {
Expand All @@ -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
Expand Down
68 changes: 68 additions & 0 deletions libs/apps/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down