From 75b12ab665133418f113dec8673eb8fc88a3bedf Mon Sep 17 00:00:00 2001 From: Pawel Kosiec Date: Mon, 27 Apr 2026 11:22:59 +0200 Subject: [PATCH] Fix missing plugin name prefix in `--set` hint for `apps init` The validation error for missing required resources suggested `--set postgres.branch=value` but the actual format requires `--set lakebase.postgres.branch=value` (plugin.resourceKey.field). Add `PluginName` to the `Resource` struct and include it in the error message so the hint matches what `parseSetValues` expects. --- cmd/apps/init.go | 39 +++++++++++++++---------- cmd/apps/init_test.go | 53 ++++++++++++++++++++++++++++++++++ libs/apps/manifest/manifest.go | 6 ++++ 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/cmd/apps/init.go b/cmd/apps/init.go index 6f7269e6fd0..2f1da99bb71 100644 --- a/cmd/apps/init.go +++ b/cmd/apps/init.go @@ -282,6 +282,28 @@ func pluginHasResourceField(p *manifest.Plugin, resourceKey, fieldName string) b return false } +// validateRequiredResources checks that all required resources have at least one +// value in resourceValues. Returns an error with a --set hint if any are missing. +func validateRequiredResources(resources []manifest.Resource, resourceValues map[string]string) error { + for _, r := range resources { + found := false + for k := range resourceValues { + if strings.HasPrefix(k, r.Key()+".") { + found = true + break + } + } + if !found { + fieldHint := "id" + if names := r.FieldNames(); len(names) > 0 { + fieldHint = names[0] + } + return fmt.Errorf("missing required resource %q for selected plugins (use --set %s.%s.%s=value)", r.Alias, r.PluginName, r.Key(), fieldHint) + } + } + return nil +} + // tmplBundle holds the generated bundle configuration strings. type tmplBundle struct { Variables string @@ -967,21 +989,8 @@ func runCreate(ctx context.Context, opts createOptions) error { } // Validate that all required resources are provided. - for _, r := range resources { - found := false - for k := range resourceValues { - if strings.HasPrefix(k, r.Key()+".") { - found = true - break - } - } - if !found { - fieldHint := "id" - if names := r.FieldNames(); len(names) > 0 { - fieldHint = names[0] - } - return fmt.Errorf("missing required resource %q for selected plugins (use --set %s.%s=value)", r.Alias, r.Key(), fieldHint) - } + if err := validateRequiredResources(resources, resourceValues); err != nil { + return err } } diff --git a/cmd/apps/init_test.go b/cmd/apps/init_test.go index b8a9a8f443c..81e71eed3fb 100644 --- a/cmd/apps/init_test.go +++ b/cmd/apps/init_test.go @@ -773,6 +773,59 @@ func TestPluginHasResourceField(t *testing.T) { assert.False(t, pluginHasResourceField(p, "nosuch", "id")) } +func TestValidateRequiredResources(t *testing.T) { + tests := []struct { + name string + resources []manifest.Resource + resourceValues map[string]string + wantErr string + }{ + { + name: "all provided", + resources: []manifest.Resource{ + {Alias: "SQL Warehouse", ResourceKey: "sql-warehouse", PluginName: "analytics"}, + }, + resourceValues: map[string]string{"sql-warehouse.id": "abc"}, + }, + { + name: "missing resource with fields includes plugin prefix in hint", + resources: []manifest.Resource{ + { + Alias: "Postgres", + ResourceKey: "postgres", + PluginName: "lakebase", + Fields: map[string]manifest.ResourceField{ + "branch": {Description: "branch"}, + "database": {Description: "database"}, + }, + }, + }, + resourceValues: map[string]string{}, + wantErr: `use --set lakebase.postgres.branch=value`, + }, + { + name: "missing resource without fields defaults to id", + resources: []manifest.Resource{ + {Alias: "SQL Warehouse", ResourceKey: "sql-warehouse", PluginName: "analytics"}, + }, + resourceValues: map[string]string{}, + wantErr: `use --set analytics.sql-warehouse.id=value`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := validateRequiredResources(tc.resources, tc.resourceValues) + if tc.wantErr == "" { + assert.NoError(t, err) + } else { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErr) + } + }) + } +} + func TestAppendUnique(t *testing.T) { result := appendUnique([]string{"a", "b"}, "b", "c", "a", "d") assert.Equal(t, []string{"a", "b", "c", "d"}, result) diff --git a/libs/apps/manifest/manifest.go b/libs/apps/manifest/manifest.go index c4ecdd7f82f..0d8f4abb3a9 100644 --- a/libs/apps/manifest/manifest.go +++ b/libs/apps/manifest/manifest.go @@ -35,6 +35,10 @@ type Resource struct { Permission string `json:"permission"` // e.g., "CAN_USE" Fields map[string]ResourceField `json:"fields"` // field definitions with env var mappings + // PluginName is the machine name of the plugin (e.g., "lakebase"). + // Set during resource collection. Not part of the JSON manifest. + PluginName string `json:"-"` + // PluginDisplayName is set during resource collection to identify which // plugin requires this resource. Not part of the JSON manifest. PluginDisplayName string `json:"-"` @@ -218,6 +222,7 @@ func (m *Manifest) CollectResources(pluginNames []string) []Resource { key := r.Type + ":" + r.Key() if !seen[key] { seen[key] = true + r.PluginName = name r.PluginDisplayName = plugin.DisplayName resources = append(resources, r) } @@ -246,6 +251,7 @@ func (m *Manifest) CollectOptionalResources(pluginNames []string) []Resource { key := r.Type + ":" + r.Key() if !seen[key] { seen[key] = true + r.PluginName = name r.PluginDisplayName = plugin.DisplayName resources = append(resources, r) }