From 75130143cc1c7a78e2fe5036694374258c3185c6 Mon Sep 17 00:00:00 2001 From: Pawel Kosiec Date: Tue, 3 Mar 2026 11:07:06 +0100 Subject: [PATCH 1/6] List postgres databases during Lakebase plugin selection --- libs/apps/prompt/listers.go | 22 ++++++++++++++++++++++ libs/apps/prompt/prompt.go | 21 +++++++++++---------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/libs/apps/prompt/listers.go b/libs/apps/prompt/listers.go index 2a4c992ad9..2ccf54c9f1 100644 --- a/libs/apps/prompt/listers.go +++ b/libs/apps/prompt/listers.go @@ -397,6 +397,28 @@ func ListPostgresBranches(ctx context.Context, projectName string) ([]ListItem, return out, nil } +// ListPostgresDatabases returns databases within a Lakebase Autoscaling branch as selectable items. +func ListPostgresDatabases(ctx context.Context, branchName string) ([]ListItem, error) { + w, err := workspaceClient(ctx) + if err != nil { + return nil, err + } + iter := w.Postgres.ListDatabases(ctx, postgres.ListDatabasesRequest{Parent: branchName}) + databases, err := listing.ToSlice(ctx, iter) + if err != nil { + return nil, err + } + out := make([]ListItem, 0, len(databases)) + for _, db := range databases { + label := extractIDFromName(db.Name, "databases") + if db.Status != nil && db.Status.PostgresDatabase != "" { + label = db.Status.PostgresDatabase + } + out = append(out, ListItem{ID: db.Name, Label: label}) + } + return out, nil +} + // ListGenieSpaces returns Genie spaces as selectable items. func ListGenieSpaces(ctx context.Context) ([]ListItem, error) { w, err := workspaceClient(ctx) diff --git a/libs/apps/prompt/prompt.go b/libs/apps/prompt/prompt.go index 3ebbcadc69..3b57ae1ccc 100644 --- a/libs/apps/prompt/prompt.go +++ b/libs/apps/prompt/prompt.go @@ -562,22 +562,23 @@ func PromptForPostgres(ctx context.Context, r manifest.Resource, required bool) return nil, nil } - // Step 3: enter a database name (pre-filled with default) - dbName := "databricks_postgres" - theme := AppkitTheme() - err = huh.NewInput(). - Title("Database name"). - Description("Enter the database name to connect to"). - Value(&dbName). - WithTheme(theme). - Run() + // Step 3: pick a database within the branch + var databases []ListItem + err = RunWithSpinnerCtx(ctx, "Fetching databases...", func() error { + var fetchErr error + databases, fetchErr = ListPostgresDatabases(ctx, branchName) + return fetchErr + }) + if err != nil { + return nil, err + } + dbName, err := PromptFromList(ctx, "Select Database", "no databases found in branch "+branchName, databases, required) if err != nil { return nil, err } if dbName == "" { return nil, nil } - printAnswered(ctx, "Database", dbName) return map[string]string{ r.Key() + ".branch": branchName, From 059d26fed6018adb3058d5c67ab3ffd2653e1853 Mon Sep 17 00:00:00 2001 From: Pawel Kosiec Date: Tue, 3 Mar 2026 12:11:12 +0100 Subject: [PATCH 2/6] Fix showing title for the interactive init flow --- libs/apps/prompt/prompt.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/apps/prompt/prompt.go b/libs/apps/prompt/prompt.go index 3b57ae1ccc..b370e51259 100644 --- a/libs/apps/prompt/prompt.go +++ b/libs/apps/prompt/prompt.go @@ -251,10 +251,10 @@ func promptFromListWithLabel(ctx context.Context, title, emptyMessage string, it var selected string err := huh.NewSelect[string](). Title(title). - Description(fmt.Sprintf("%d available — type to filter", len(items))). + Description(fmt.Sprintf("%d available — press / to filter", len(items))). Options(options...). Value(&selected). - Filtering(true). + Filtering(false). Height(8). WithTheme(theme). Run() @@ -704,7 +704,7 @@ func PromptForAppSelection(ctx context.Context, title string) (string, error) { var selected string err = huh.NewSelect[string](). Title(title). - Description(fmt.Sprintf("%d apps found — type to filter", len(existingApps))). + Description(fmt.Sprintf("%d apps found — press / to filter", len(existingApps))). Options(options...). Value(&selected). Filtering(true). From 24880f83eb4721063206a2e12886b4978f384272 Mon Sep 17 00:00:00 2001 From: Pawel Kosiec Date: Tue, 3 Mar 2026 13:01:18 +0100 Subject: [PATCH 3/6] Add option to ignore variable in bundle --- libs/apps/generator/generator.go | 4 +++ libs/apps/generator/generator_test.go | 40 ++++++++++++++++++++++++ libs/apps/manifest/manifest.go | 5 +-- libs/apps/manifest/manifest_test.go | 45 +++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/libs/apps/generator/generator.go b/libs/apps/generator/generator.go index 62df97befe..453a61e219 100644 --- a/libs/apps/generator/generator.go +++ b/libs/apps/generator/generator.go @@ -357,6 +357,10 @@ func variableNamesForResource(r manifest.Resource) []varInfo { for _, fieldName := range r.FieldNames() { field := r.Fields[fieldName] + if field.BundleIgnore { + covered[fieldName] = true + continue + } desc := field.Description if desc == "" { desc = r.Description diff --git a/libs/apps/generator/generator_test.go b/libs/apps/generator/generator_test.go index 106c081a28..ed733eb40a 100644 --- a/libs/apps/generator/generator_test.go +++ b/libs/apps/generator/generator_test.go @@ -863,3 +863,43 @@ func TestGenerateDotEnvSanitizesNewlines(t *testing.T) { assert.Equal(t, "WH_ID=safeEVIL_VAR=injected", result) assert.NotContains(t, result, "\n") } + +func TestBundleIgnoreFieldSkippedInVariablesAndTargets(t *testing.T) { + plugins := []manifest.Plugin{ + { + Name: "test", + Resources: manifest.Resources{ + Required: []manifest.Resource{ + { + Type: "database", Alias: "Database", ResourceKey: "database", + Fields: map[string]manifest.ResourceField{ + "instance_name": {Env: "DB_INSTANCE", Description: "Lakebase instance"}, + "database_name": {Env: "DB_NAME", Description: "Database name", BundleIgnore: true}, + }, + }, + }, + }, + }, + } + cfg := generator.Config{ResourceValues: map[string]string{ + "database.instance_name": "my-inst", + "database.database_name": "my-db", + }} + + vars := generator.GenerateBundleVariables(plugins, cfg) + assert.Contains(t, vars, "database_instance_name:") + assert.Contains(t, vars, " description: Lakebase instance") + assert.NotContains(t, vars, "database_database_name:") + + target := generator.GenerateTargetVariables(plugins, cfg) + assert.Contains(t, target, "database_instance_name: my-inst") + assert.NotContains(t, target, "database_database_name") + + env := generator.GenerateDotEnv(plugins, cfg) + assert.Contains(t, env, "DB_INSTANCE=my-inst") + assert.Contains(t, env, "DB_NAME=my-db") + + example := generator.GenerateDotEnvExample(plugins) + assert.Contains(t, example, "DB_INSTANCE=your_database_instance_name") + assert.Contains(t, example, "DB_NAME=your_database_database_name") +} diff --git a/libs/apps/manifest/manifest.go b/libs/apps/manifest/manifest.go index 299cfa43f4..1478d2af38 100644 --- a/libs/apps/manifest/manifest.go +++ b/libs/apps/manifest/manifest.go @@ -14,8 +14,9 @@ const ManifestFileName = "appkit.plugins.json" // ResourceField describes a single field within a multi-field resource. // Multi-field resources (e.g., database, secret) need separate env vars and values per field. type ResourceField struct { - Env string `json:"env"` - Description string `json:"description"` + Env string `json:"env"` + Description string `json:"description"` + BundleIgnore bool `json:"bundleIgnore,omitempty"` } // Resource defines a Databricks resource required or optional for a plugin. diff --git a/libs/apps/manifest/manifest_test.go b/libs/apps/manifest/manifest_test.go index 5a1c4f8212..f3ca3fe129 100644 --- a/libs/apps/manifest/manifest_test.go +++ b/libs/apps/manifest/manifest_test.go @@ -308,6 +308,51 @@ func TestResourceFields(t *testing.T) { assert.Equal(t, []string{"database_name", "instance_name"}, r.FieldNames()) } +func TestResourceFieldBundleIgnore(t *testing.T) { + dir := t.TempDir() + manifestPath := filepath.Join(dir, manifest.ManifestFileName) + + content := `{ + "version": "1.0", + "plugins": { + "caching": { + "name": "caching", + "displayName": "Caching", + "description": "DB caching", + "package": "@databricks/appkit", + "resources": { + "required": [ + { + "type": "database", + "alias": "Database", + "resourceKey": "database", + "description": "Cache database", + "fields": { + "instance_name": {"env": "DB_INSTANCE", "bundleIgnore": true}, + "database_name": {"env": "DB_NAME"} + } + } + ], + "optional": [] + } + } + } + }` + + err := os.WriteFile(manifestPath, []byte(content), 0o644) + require.NoError(t, err) + + m, err := manifest.Load(dir) + require.NoError(t, err) + + p := m.GetPluginByName("caching") + require.NotNil(t, p) + + r := p.Resources.Required[0] + assert.True(t, r.Fields["instance_name"].BundleIgnore) + assert.False(t, r.Fields["database_name"].BundleIgnore) +} + func TestResourceHasFieldsFalse(t *testing.T) { r := manifest.Resource{Type: "sql_warehouse", Alias: "SQL Warehouse", ResourceKey: "sql-warehouse"} assert.False(t, r.HasFields()) From eba2cf3b97aae8ef7020ea6f63f6ff8f3cf471f7 Mon Sep 17 00:00:00 2001 From: Pawel Kosiec Date: Tue, 3 Mar 2026 13:08:17 +0100 Subject: [PATCH 4/6] Annotate branch items based on metadata --- libs/apps/prompt/listers.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libs/apps/prompt/listers.go b/libs/apps/prompt/listers.go index 2ccf54c9f1..e5d075f5fd 100644 --- a/libs/apps/prompt/listers.go +++ b/libs/apps/prompt/listers.go @@ -392,6 +392,17 @@ func ListPostgresBranches(ctx context.Context, projectName string) ([]ListItem, out := make([]ListItem, 0, len(branches)) for _, b := range branches { label := extractIDFromName(b.Name, "branches") + if b.Status != nil { + if b.Status.Default { + label += " (default)" + } + if b.Status.IsProtected { + label += " (protected)" + } + if b.Status.CurrentState == postgres.BranchStatusStateArchived { + label += " (archived)" + } + } out = append(out, ListItem{ID: b.Name, Label: label}) } return out, nil From 8468634d946091465df909ad0824d00cf5d9f353 Mon Sep 17 00:00:00 2001 From: Pawel Kosiec Date: Wed, 4 Mar 2026 14:27:05 +0100 Subject: [PATCH 5/6] Ignore `bundleIgnore` fields from validation --- cmd/apps/init.go | 12 ++++++++-- cmd/apps/init_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/cmd/apps/init.go b/cmd/apps/init.go index e3db5acef6..04bdb948d8 100644 --- a/cmd/apps/init.go +++ b/cmd/apps/init.go @@ -227,7 +227,7 @@ func parseSetValues(setValues []string, m *manifest.Manifest) (map[string]string rv[resourceKey+"."+fieldName] = value } - // Validate multi-field resources: if any field is set, all fields must be set. + // Validate multi-field resources: if any non-bundleIgnore field is set, all non-bundleIgnore fields must be set. for _, p := range m.GetPlugins() { for _, r := range append(p.Resources.Required, p.Resources.Optional...) { if len(r.Fields) <= 1 { @@ -235,14 +235,22 @@ func parseSetValues(setValues []string, m *manifest.Manifest) (map[string]string } names := r.FieldNames() setCount := 0 + totalCheckable := 0 for _, fn := range names { + if r.Fields[fn].BundleIgnore { + continue + } + totalCheckable++ if rv[r.Key()+"."+fn] != "" { setCount++ } } - if setCount > 0 && setCount < len(names) { + if setCount > 0 && setCount < totalCheckable { var missing []string for _, fn := range names { + if r.Fields[fn].BundleIgnore { + continue + } if rv[r.Key()+"."+fn] == "" { missing = append(missing, r.Key()+"."+fn) } diff --git a/cmd/apps/init_test.go b/cmd/apps/init_test.go index b651ad75ac..6b9e184c15 100644 --- a/cmd/apps/init_test.go +++ b/cmd/apps/init_test.go @@ -500,6 +500,58 @@ func TestParseSetValues(t *testing.T) { } } +func TestParseSetValuesBundleIgnoreSkipped(t *testing.T) { + m := &manifest.Manifest{ + Plugins: map[string]manifest.Plugin{ + "lakebase": { + Name: "lakebase", + Resources: manifest.Resources{ + Required: []manifest.Resource{ + { + Type: "postgres", + Alias: "Postgres", + ResourceKey: "postgres", + Fields: map[string]manifest.ResourceField{ + "branch": {Description: "branch path"}, + "database": {Description: "database name"}, + "endpoint": {Env: "LAKEBASE_ENDPOINT", BundleIgnore: true}, + }, + }, + }, + }, + }, + }, + } + + rv, err := parseSetValues([]string{ + "lakebase.postgres.branch=projects/p1/branches/main", + "lakebase.postgres.database=mydb", + }, m) + require.NoError(t, err) + assert.Equal(t, map[string]string{ + "postgres.branch": "projects/p1/branches/main", + "postgres.database": "mydb", + }, rv) + + // Setting only one non-bundleIgnore field should still fail. + _, err = parseSetValues([]string{"lakebase.postgres.branch=br"}, m) + require.Error(t, err) + assert.Contains(t, err.Error(), `incomplete resource "postgres"`) + + // bundleIgnore field can still be set explicitly via --set. + rv, err = parseSetValues([]string{ + "lakebase.postgres.branch=br", + "lakebase.postgres.database=db", + "lakebase.postgres.endpoint=ep", + }, m) + require.NoError(t, err) + assert.Equal(t, map[string]string{ + "postgres.branch": "br", + "postgres.database": "db", + "postgres.endpoint": "ep", + }, rv) +} + func TestPluginHasResourceField(t *testing.T) { m := testManifest() p := m.GetPluginByName("analytics") From e8ec949e1c849e6f88817ff2b18a35a70ad800b3 Mon Sep 17 00:00:00 2001 From: Pawel Kosiec Date: Thu, 5 Mar 2026 14:09:51 +0100 Subject: [PATCH 6/6] Revert filtering changes superseded by PR #4603 Revert the description hint and Filtering(true/false) changes in promptFromListWithLabel and PromptForAppSelection, as these are addressed properly in PR #4603. Co-Authored-By: Claude Sonnet 4.6 --- libs/apps/prompt/prompt.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/apps/prompt/prompt.go b/libs/apps/prompt/prompt.go index b370e51259..3b57ae1ccc 100644 --- a/libs/apps/prompt/prompt.go +++ b/libs/apps/prompt/prompt.go @@ -251,10 +251,10 @@ func promptFromListWithLabel(ctx context.Context, title, emptyMessage string, it var selected string err := huh.NewSelect[string](). Title(title). - Description(fmt.Sprintf("%d available — press / to filter", len(items))). + Description(fmt.Sprintf("%d available — type to filter", len(items))). Options(options...). Value(&selected). - Filtering(false). + Filtering(true). Height(8). WithTheme(theme). Run() @@ -704,7 +704,7 @@ func PromptForAppSelection(ctx context.Context, title string) (string, error) { var selected string err = huh.NewSelect[string](). Title(title). - Description(fmt.Sprintf("%d apps found — press / to filter", len(existingApps))). + Description(fmt.Sprintf("%d apps found — type to filter", len(existingApps))). Options(options...). Value(&selected). Filtering(true).