From c34bd039682baa3b06be16b5989c9fa50fa2bf36 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Thu, 23 Jul 2026 12:53:39 +0000 Subject: [PATCH 1/2] Fix spurious grants create for empty grants list on direct engine An empty grants: [] list produced no databricks_grants resource in terraform state, so bundle deployment migrate wrote no grants entry. The direct engine's makePlan then always emitted a plan node for the grants config node, so bundle plan showed a spurious 'create' for the empty grants node -- contradicting migrate's 'no actions planned'. Skip an empty grants node in makePlan when there is no existing state entry. When a state entry exists (grants were deployed and are now being emptied), the node is still emitted so the revoke is planned. --- .nextchanges/bundles/empty-grants-migrate.md | 1 + .../migrate/grants-empty/databricks.yml | 12 ++++++ .../migrate/grants-empty/out.new_state.json | 15 +++++++ .../grants-empty/out.original_state.json | 41 +++++++++++++++++++ .../bundle/migrate/grants-empty/out.test.toml | 3 ++ .../bundle/migrate/grants-empty/output.txt | 19 +++++++++ acceptance/bundle/migrate/grants-empty/script | 11 +++++ bundle/direct/bundle_plan.go | 13 ++++++ 8 files changed, 115 insertions(+) create mode 100644 .nextchanges/bundles/empty-grants-migrate.md create mode 100644 acceptance/bundle/migrate/grants-empty/databricks.yml create mode 100644 acceptance/bundle/migrate/grants-empty/out.new_state.json create mode 100644 acceptance/bundle/migrate/grants-empty/out.original_state.json create mode 100644 acceptance/bundle/migrate/grants-empty/out.test.toml create mode 100644 acceptance/bundle/migrate/grants-empty/output.txt create mode 100644 acceptance/bundle/migrate/grants-empty/script diff --git a/.nextchanges/bundles/empty-grants-migrate.md b/.nextchanges/bundles/empty-grants-migrate.md new file mode 100644 index 00000000000..121aa2b4c1f --- /dev/null +++ b/.nextchanges/bundles/empty-grants-migrate.md @@ -0,0 +1 @@ +Fixed the direct deployment engine planning a spurious `create` for a resource's empty `grants: []` list. An empty grants list with no existing state entry is now a no-op, so `bundle plan` reports no actions after `bundle deployment migrate` (Terraform never records a grants resource for an empty list). diff --git a/acceptance/bundle/migrate/grants-empty/databricks.yml b/acceptance/bundle/migrate/grants-empty/databricks.yml new file mode 100644 index 00000000000..f09f37d2d9e --- /dev/null +++ b/acceptance/bundle/migrate/grants-empty/databricks.yml @@ -0,0 +1,12 @@ +bundle: + name: test-bundle + +resources: + schemas: + my_schema: + catalog_name: main + name: schema_empty_grants + # An empty grants list produces no databricks_grants resource in terraform + # state, so migrate writes no grants entry. bundle plan must not then show a + # spurious "create" for the grants node (see makePlan in bundle/direct). + grants: [] diff --git a/acceptance/bundle/migrate/grants-empty/out.new_state.json b/acceptance/bundle/migrate/grants-empty/out.new_state.json new file mode 100644 index 00000000000..e6f607b3b67 --- /dev/null +++ b/acceptance/bundle/migrate/grants-empty/out.new_state.json @@ -0,0 +1,15 @@ +{ + "state_version": 2, + "cli_version": "[CLI_VERSION]", + "lineage": "[UUID]", + "serial": 3, + "state": { + "resources.schemas.my_schema": { + "__id__": "main.schema_empty_grants", + "state": { + "catalog_name": "main", + "name": "schema_empty_grants" + } + } + } +} diff --git a/acceptance/bundle/migrate/grants-empty/out.original_state.json b/acceptance/bundle/migrate/grants-empty/out.original_state.json new file mode 100644 index 00000000000..1c70be674af --- /dev/null +++ b/acceptance/bundle/migrate/grants-empty/out.original_state.json @@ -0,0 +1,41 @@ +{ + "version": 4, + "terraform_version": "1.5.5", + "serial": 1, + "lineage": "[UUID]", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "databricks_schema", + "name": "my_schema", + "provider": "provider[\"registry.terraform.io/databricks/databricks\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "catalog_name": "main", + "comment": null, + "enable_predictive_optimization": "INHERIT", + "force_destroy": true, + "id": "main.schema_empty_grants", + "metastore_id": "[UUID]", + "name": "schema_empty_grants", + "owner": "[USERNAME]", + "properties": null, + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], + "schema_id": "[UUID]", + "storage_root": null + }, + "sensitive_attributes": [], + "private": "bnVsbA==" + } + ] + } + ], + "check_results": null +} diff --git a/acceptance/bundle/migrate/grants-empty/out.test.toml b/acceptance/bundle/migrate/grants-empty/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/migrate/grants-empty/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/grants-empty/output.txt b/acceptance/bundle/migrate/grants-empty/output.txt new file mode 100644 index 00000000000..b359c4fc747 --- /dev/null +++ b/acceptance/bundle/migrate/grants-empty/output.txt @@ -0,0 +1,19 @@ + +>>> DATABRICKS_BUNDLE_ENGINE=terraform [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle deployment migrate +Success! Migrated 1 resources to direct engine state file: [TEST_TMP_DIR]/.databricks/bundle/default/resources.json + +Validate the migration by running "databricks bundle plan", there should be no actions planned. + +The state file is not synchronized to the workspace yet. To do that and finalize the migration, run "bundle deploy". + +To undo the migration, remove [TEST_TMP_DIR]/.databricks/bundle/default/resources.json and rename [TEST_TMP_DIR]/.databricks/bundle/default/terraform/terraform.tfstate.backup to [TEST_TMP_DIR]/.databricks/bundle/default/terraform/terraform.tfstate + + +>>> DATABRICKS_BUNDLE_ENGINE=direct [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged diff --git a/acceptance/bundle/migrate/grants-empty/script b/acceptance/bundle/migrate/grants-empty/script new file mode 100644 index 00000000000..5fd96664857 --- /dev/null +++ b/acceptance/bundle/migrate/grants-empty/script @@ -0,0 +1,11 @@ +export DATABRICKS_BUNDLE_ENGINE= +trace DATABRICKS_BUNDLE_ENGINE=terraform $CLI bundle deploy +print_state.py > out.original_state.json + +trace $CLI bundle deployment migrate 2>&1 | contains.py 'Migrated 1 resources' +print_state.py > out.new_state.json + +# The empty grants node must not appear as an action; migrate promises no actions planned. +trace DATABRICKS_BUNDLE_ENGINE=direct $CLI bundle plan | contains.py '0 to add' '1 unchanged' + +rm out.requests.txt diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index 139012423ea..d074be52814 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -961,6 +961,19 @@ func (b *DeploymentBundle) makePlan(ctx context.Context, configRoot *config.Root if err != nil { return nil, err } + // An empty grants list with no state entry is a no-op: there is nothing + // to create and, unlike a non-empty-to-empty edit, no principals to revoke. + // Terraform never writes a databricks_grants resource for an empty list, so + // after "bundle deployment migrate" there is no state entry to match against; + // emitting a plan node here would show a spurious "create" and contradict + // migrate's promise of no actions planned. When a state entry does exist + // (grants were deployed and are now being emptied), keep the node so the + // revoke is still planned. + if gs, ok := inputConfigStructVar.Value.(*dresources.GrantsState); ok && len(gs.EmbeddedSlice) == 0 { + if _, hasState := db.State[node]; !hasState { + continue + } + } inputConfig = inputConfigStructVar.Value baseRefs = inputConfigStructVar.Refs } From e444cc748f9f3833f070a2521a9982003ecbb6af Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Thu, 23 Jul 2026 13:01:53 +0000 Subject: [PATCH 2/2] Shorten comments in empty-grants fix --- acceptance/bundle/migrate/grants-empty/databricks.yml | 4 +--- acceptance/bundle/migrate/grants-empty/script | 2 +- bundle/direct/bundle_plan.go | 11 +++-------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/acceptance/bundle/migrate/grants-empty/databricks.yml b/acceptance/bundle/migrate/grants-empty/databricks.yml index f09f37d2d9e..e07d456a58d 100644 --- a/acceptance/bundle/migrate/grants-empty/databricks.yml +++ b/acceptance/bundle/migrate/grants-empty/databricks.yml @@ -6,7 +6,5 @@ resources: my_schema: catalog_name: main name: schema_empty_grants - # An empty grants list produces no databricks_grants resource in terraform - # state, so migrate writes no grants entry. bundle plan must not then show a - # spurious "create" for the grants node (see makePlan in bundle/direct). + # Empty grants: migrate writes no entry, and plan must not show a spurious create. grants: [] diff --git a/acceptance/bundle/migrate/grants-empty/script b/acceptance/bundle/migrate/grants-empty/script index 5fd96664857..aaf40caccfe 100644 --- a/acceptance/bundle/migrate/grants-empty/script +++ b/acceptance/bundle/migrate/grants-empty/script @@ -5,7 +5,7 @@ print_state.py > out.original_state.json trace $CLI bundle deployment migrate 2>&1 | contains.py 'Migrated 1 resources' print_state.py > out.new_state.json -# The empty grants node must not appear as an action; migrate promises no actions planned. +# Empty grants must plan no action after migrate. trace DATABRICKS_BUNDLE_ENGINE=direct $CLI bundle plan | contains.py '0 to add' '1 unchanged' rm out.requests.txt diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index d074be52814..07ff3257588 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -961,14 +961,9 @@ func (b *DeploymentBundle) makePlan(ctx context.Context, configRoot *config.Root if err != nil { return nil, err } - // An empty grants list with no state entry is a no-op: there is nothing - // to create and, unlike a non-empty-to-empty edit, no principals to revoke. - // Terraform never writes a databricks_grants resource for an empty list, so - // after "bundle deployment migrate" there is no state entry to match against; - // emitting a plan node here would show a spurious "create" and contradict - // migrate's promise of no actions planned. When a state entry does exist - // (grants were deployed and are now being emptied), keep the node so the - // revoke is still planned. + // Terraform writes no grants resource for an empty list, so migrate leaves + // no state entry. Skip the node here too; otherwise plan shows a spurious + // "create". Keep it when state exists so emptying grants still revokes. if gs, ok := inputConfigStructVar.Value.(*dresources.GrantsState); ok && len(gs.EmbeddedSlice) == 0 { if _, hasState := db.State[node]; !hasState { continue