From 6be57522ba4ef93cd91803b2fe7bae064d4bd90b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 30 Jun 2026 14:39:15 +0200 Subject: [PATCH 1/3] Add invariant config coverage test Check that every bundle resource type has an invariant config, and that resources supporting permissions/grants have at least one config exercising them. Known gaps are tracked in an explicit allowlist that can only shrink. Co-authored-by: Isaac --- acceptance/invariant_test.go | 139 +++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 acceptance/invariant_test.go diff --git a/acceptance/invariant_test.go b/acceptance/invariant_test.go new file mode 100644 index 0000000000..440eed8841 --- /dev/null +++ b/acceptance/invariant_test.go @@ -0,0 +1,139 @@ +package acceptance_test + +import ( + "os" + "path/filepath" + "sort" + "strings" + "testing" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/yamlloader" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const invariantConfigsDir = "bundle/invariant/configs" + +// LackingInvariantTest lists keys from config.ResourcesTypes that knowingly lack +// a covering config in invariantConfigsDir. Keys match the ResourcesTypes +// form: "" for the resource itself, ".permissions" / ".grants" +// for permissions/grants coverage. Add a config and remove the entry to close a gap; +// the test fails if an entry here is actually covered, so the list only shrinks. +var LackingInvariantTest = map[string]bool{ + "quality_monitors": true, + + "alerts.permissions": true, + "apps.permissions": true, + "clusters.permissions": true, + "dashboards.permissions": true, + "database_instances.permissions": true, + "experiments.permissions": true, + "model_serving_endpoints.permissions": true, + "pipelines.permissions": true, + "postgres_projects.permissions": true, + "sql_warehouses.permissions": true, + + "catalogs.grants": true, + "external_locations.grants": true, + "registered_models.grants": true, + "vector_search_indexes.grants": true, + "volumes.grants": true, +} + +// TestInvariantConfigsCoverage ensures that the invariant test configs in +// bundle/invariant/configs cover every bundle resource type, and that resource +// types supporting permissions or grants have at least one config exercising them. +// +// config.ResourcesTypes is the source of truth: it maps each resource group +// (e.g. "jobs") to its Go type and, where the resource struct has a Permissions +// or Grants field, adds derived keys ".permissions" and ".grants". +func TestInvariantConfigsCoverage(t *testing.T) { + present, withPermissions, withGrants := scanInvariantConfigs(t) + + keys := make([]string, 0, len(config.ResourcesTypes)) + for key := range config.ResourcesTypes { + keys = append(keys, key) + } + sort.Strings(keys) + + for _, key := range keys { + var covered bool + var hint string + switch { + case strings.HasSuffix(key, ".permissions"): + group := strings.TrimSuffix(key, ".permissions") + covered = withPermissions[group] + hint = "attaches permissions to a " + group + " resource" + case strings.HasSuffix(key, ".grants"): + group := strings.TrimSuffix(key, ".grants") + covered = withGrants[group] + hint = "attaches grants to a " + group + " resource" + default: + covered = present[key] + hint = "defines a " + key + " resource" + } + + if LackingInvariantTest[key] { + assert.False(t, covered, + "%q is covered by a config in %s; remove it from LackingInvariantTest", key, invariantConfigsDir) + continue + } + assert.True(t, covered, + "no config in %s %s; add one or allowlist %q", invariantConfigsDir, hint, key) + } +} + +// scanInvariantConfigs parses every config in the invariant configs directory and +// returns the set of resource groups present, the groups with at least one resource +// carrying permissions, and the groups with at least one resource carrying grants. +func scanInvariantConfigs(t *testing.T) (present, withPermissions, withGrants map[string]bool) { + present = map[string]bool{} + withPermissions = map[string]bool{} + withGrants = map[string]bool{} + + entries, err := os.ReadDir(invariantConfigsDir) + require.NoError(t, err) + + for _, entry := range entries { + if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yml.tmpl") { + continue + } + path := filepath.Join(invariantConfigsDir, entry.Name()) + contents, err := os.ReadFile(path) + require.NoError(t, err) + + v, err := yamlloader.LoadYAML(path, strings.NewReader(string(contents))) + require.NoError(t, err, "failed to parse %s", path) + + resources := v.Get("resources") + if resources.Kind() != dyn.KindMap { + // Some configs (e.g. PyDABs) declare resources outside of YAML. + continue + } + + for _, group := range resources.MustMap().Pairs() { + groupName := group.Key.MustString() + present[groupName] = true + + if group.Value.Kind() != dyn.KindMap { + continue + } + for _, resource := range group.Value.MustMap().Pairs() { + cfg := resource.Value + if cfg.Kind() != dyn.KindMap { + continue + } + if cfg.Get("permissions").Kind() != dyn.KindInvalid { + withPermissions[groupName] = true + } + if cfg.Get("grants").Kind() != dyn.KindInvalid { + withGrants[groupName] = true + } + } + } + } + + return present, withPermissions, withGrants +} From cc52ba2c1f19afc4c8e3ce0a181cd9077fe81730 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 30 Jun 2026 14:56:28 +0200 Subject: [PATCH 2/3] Use slices.Sort instead of sort.Strings Co-authored-by: Isaac --- acceptance/invariant_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/invariant_test.go b/acceptance/invariant_test.go index 440eed8841..68adb690b8 100644 --- a/acceptance/invariant_test.go +++ b/acceptance/invariant_test.go @@ -3,7 +3,7 @@ package acceptance_test import ( "os" "path/filepath" - "sort" + "slices" "strings" "testing" @@ -56,7 +56,7 @@ func TestInvariantConfigsCoverage(t *testing.T) { for key := range config.ResourcesTypes { keys = append(keys, key) } - sort.Strings(keys) + slices.Sort(keys) for _, key := range keys { var covered bool From f318d86e1569ee99599e4a607d234ce382317ac2 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 30 Jun 2026 15:11:10 +0200 Subject: [PATCH 3/3] Drop now-covered entries from invariant allowlist After rebase, model_serving_endpoints and database_instances gained permission configs on main; the coverage check requires removing them. Co-authored-by: Isaac --- acceptance/invariant_test.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/acceptance/invariant_test.go b/acceptance/invariant_test.go index 68adb690b8..934a5016ca 100644 --- a/acceptance/invariant_test.go +++ b/acceptance/invariant_test.go @@ -24,16 +24,14 @@ const invariantConfigsDir = "bundle/invariant/configs" var LackingInvariantTest = map[string]bool{ "quality_monitors": true, - "alerts.permissions": true, - "apps.permissions": true, - "clusters.permissions": true, - "dashboards.permissions": true, - "database_instances.permissions": true, - "experiments.permissions": true, - "model_serving_endpoints.permissions": true, - "pipelines.permissions": true, - "postgres_projects.permissions": true, - "sql_warehouses.permissions": true, + "alerts.permissions": true, + "apps.permissions": true, + "clusters.permissions": true, + "dashboards.permissions": true, + "experiments.permissions": true, + "pipelines.permissions": true, + "postgres_projects.permissions": true, + "sql_warehouses.permissions": true, "catalogs.grants": true, "external_locations.grants": true,