Skip to content

Commit c5f4546

Browse files
authored
Resolve Lakebase .env values in non-interactive apps init (#4740)
## Summary - Extract `ResolvePostgresValues()` from `PromptForPostgres()` so it can be called from the non-interactive code path - After `--set` values are merged in flags mode, resolve derived postgres fields (host, databaseName, endpointPath) by calling `ListPostgresEndpoints` and `ListPostgresDatabases` - Fixes empty `PGHOST`, `PGDATABASE`, and `LAKEBASE_ENDPOINT` values in `.env` when using `databricks apps init` with `--set` flags ## Repro command Rebuild the CLI and run: ```bash dbx apps init --name event-registration \ --features lakebase \ --set "lakebase.postgres.branch=projects/4de10316-dd6f-4606-878e-fdf3189f6766/branches/br-divine-term-y10rpplr" \ --set "lakebase.postgres.database=projects/4de10316-dd6f-4606-878e-fdf3189f6766/branches/br-divine-term-y10rpplr/databases/db-5vqi-xormgdrr0m" \ --description "Event registration app with Lakebase" \ --run none --profile DEFAULT --version 0.20.0 ``` Then see if .env has filled `PGHOST`, `PGDATABASE`, and `LAKEBASE_ENDPOINT`: ``` DATABRICKS_CONFIG_PROFILE=DEFAULT PGDATABASE=databricks_postgres LAKEBASE_ENDPOINT=projects/4de10316-dd6f-4606-878e-fdf3189f6766/branches/br-divine-term-y10rpplr/endpoints/primary PGHOST=ep-small-sunset-y12mao90.database.us-west-2.staging.cloud.databricks.com PGPORT=5432 PGSSLMODE=require DATABRICKS_APP_PORT=8000 DATABRICKS_APP_NAME=event-registration FLASK_RUN_HOST=localhost ``` ## Test plan - [x] Existing unit tests pass (`go test ./libs/apps/prompt/... ./cmd/apps/...`) - [x] Manual test: run the repro command above and verify `.env` has populated values for `PGHOST`, `PGDATABASE`, `LAKEBASE_ENDPOINT` - [x] Verify interactive mode still works unchanged This pull request was AI-assisted by Isaac.
1 parent a228b67 commit c5f4546

File tree

4 files changed

+360
-33
lines changed

4 files changed

+360
-33
lines changed

cmd/apps/init.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,9 +717,32 @@ func runCreate(ctx context.Context, opts createOptions) error {
717717
// Always include mandatory plugins regardless of user selection or flags.
718718
selectedPlugins = appendUnique(selectedPlugins, m.GetMandatoryPluginNames()...)
719719

720-
// In flags/non-interactive mode, validate that all required resources are provided.
720+
// In flags/non-interactive mode, resolve derived values and validate resources.
721721
if flagsMode || !isInteractive {
722722
resources := m.CollectResources(selectedPlugins)
723+
724+
// Resolve derived values for resources that support it.
725+
if resourceValues == nil {
726+
resourceValues = make(map[string]string)
727+
}
728+
for _, r := range resources {
729+
resolveFn, ok := prompt.GetResolveFunc(r.Type)
730+
if !ok {
731+
continue
732+
}
733+
resolved, err := resolveFn(ctx, r, resourceValues)
734+
if err != nil {
735+
log.Warnf(ctx, "Could not resolve derived values for %s: %v", r.Alias, err)
736+
continue
737+
}
738+
for k, v := range resolved {
739+
if resourceValues[k] == "" {
740+
resourceValues[k] = v
741+
}
742+
}
743+
}
744+
745+
// Validate that all required resources are provided.
723746
for _, r := range resources {
724747
found := false
725748
for k := range resourceValues {

libs/apps/prompt/prompt.go

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"maps"
78
"os"
89
"path/filepath"
910
"regexp"
@@ -564,29 +565,6 @@ func PromptForPostgres(ctx context.Context, r manifest.Resource, required bool)
564565
return nil, nil
565566
}
566567

567-
// Step 2.5: resolve endpoint details from the branch (non-fatal).
568-
var host, endpointPath string
569-
endpointErr := RunWithSpinnerCtx(ctx, "Resolving connection details...", func() error {
570-
endpoints, fetchErr := ListPostgresEndpoints(ctx, branchName)
571-
if fetchErr != nil {
572-
return fetchErr
573-
}
574-
for _, ep := range endpoints {
575-
if ep.Status != nil && ep.Status.EndpointType == postgres.EndpointTypeEndpointTypeReadWrite {
576-
endpointPath = ep.Name
577-
if ep.Status.Hosts != nil && ep.Status.Hosts.Host != "" {
578-
host = ep.Status.Hosts.Host
579-
}
580-
break
581-
}
582-
}
583-
return nil
584-
})
585-
if endpointErr != nil {
586-
log.Warnf(ctx, "Could not resolve endpoint details: %v", endpointErr)
587-
// non-fatal: user can fill values manually
588-
}
589-
590568
// Step 3: pick a database within the branch
591569
var databases []ListItem
592570
err = RunWithSpinnerCtx(ctx, "Fetching databases...", func() error {
@@ -605,22 +583,77 @@ func PromptForPostgres(ctx context.Context, r manifest.Resource, required bool)
605583
return nil, nil
606584
}
607585

608-
// Build resolver results map keyed by resolver name.
609-
resolvedValues := map[string]string{
610-
"postgres:host": host,
611-
"postgres:databaseName": pgDatabaseName,
612-
"postgres:endpointPath": endpointPath,
613-
}
614-
615586
// Start with prompted values (fields without resolve).
616587
result := map[string]string{
617588
r.Key() + ".branch": branchName,
618589
r.Key() + ".database": dbName,
619590
}
620591

621-
// Map resolved values to fields using the manifest's resolve property.
622-
applyResolvedValues(r, resolvedValues, result)
592+
// Resolve derived values (host, databaseName, endpointPath) — non-fatal.
593+
var resolved map[string]string
594+
resolveErr := RunWithSpinnerCtx(ctx, "Resolving connection details...", func() error {
595+
var err error
596+
resolved, err = ResolvePostgresValues(ctx, r, branchName, dbName, pgDatabaseName)
597+
return err
598+
})
599+
if resolveErr != nil {
600+
log.Warnf(ctx, "Could not resolve connection details: %v", resolveErr)
601+
}
602+
maps.Copy(result, resolved)
603+
604+
return result, nil
605+
}
606+
607+
// resolvePostgresResource adapts ResolvePostgresValues for the generic ResolveResourceFunc signature.
608+
func resolvePostgresResource(ctx context.Context, r manifest.Resource, provided map[string]string) (map[string]string, error) {
609+
branchName := provided[r.Key()+".branch"]
610+
dbName := provided[r.Key()+".database"]
611+
if branchName == "" || dbName == "" {
612+
return nil, nil
613+
}
614+
return ResolvePostgresValues(ctx, r, branchName, dbName, "")
615+
}
616+
617+
// ResolvePostgresValues resolves derived field values (host, databaseName, endpointPath)
618+
// from a branch and database resource name. If pgDatabaseName is already known
619+
// (e.g. from a prior prompt), pass it to skip the ListDatabases API call.
620+
func ResolvePostgresValues(ctx context.Context, r manifest.Resource, branchName, dbName, pgDatabaseName string) (map[string]string, error) {
621+
var host, endpointPath string
622+
endpoints, err := ListPostgresEndpoints(ctx, branchName)
623+
if err != nil {
624+
return nil, fmt.Errorf("resolving endpoint details: %w", err)
625+
}
626+
for _, ep := range endpoints {
627+
if ep.Status != nil && ep.Status.EndpointType == postgres.EndpointTypeEndpointTypeReadWrite {
628+
endpointPath = ep.Name
629+
if ep.Status.Hosts != nil && ep.Status.Hosts.Host != "" {
630+
host = ep.Status.Hosts.Host
631+
}
632+
break
633+
}
634+
}
623635

636+
if pgDatabaseName == "" {
637+
databases, err := ListPostgresDatabases(ctx, branchName)
638+
if err != nil {
639+
return nil, fmt.Errorf("resolving database name: %w", err)
640+
}
641+
for _, db := range databases {
642+
if db.ID == dbName {
643+
pgDatabaseName = db.Label
644+
break
645+
}
646+
}
647+
}
648+
649+
resolvedValues := map[string]string{
650+
"postgres:host": host,
651+
"postgres:databaseName": pgDatabaseName,
652+
"postgres:endpointPath": endpointPath,
653+
}
654+
655+
result := make(map[string]string)
656+
applyResolvedValues(r, resolvedValues, result)
624657
return result, nil
625658
}
626659

0 commit comments

Comments
 (0)