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..e07d456a58d --- /dev/null +++ b/acceptance/bundle/migrate/grants-empty/databricks.yml @@ -0,0 +1,10 @@ +bundle: + name: test-bundle + +resources: + schemas: + my_schema: + catalog_name: main + name: schema_empty_grants + # Empty grants: migrate writes no entry, and plan must not show a spurious create. + 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..aaf40caccfe --- /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 + +# 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 139012423ea..07ff3257588 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -961,6 +961,14 @@ func (b *DeploymentBundle) makePlan(ctx context.Context, configRoot *config.Root if err != nil { return nil, err } + // 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 + } + } inputConfig = inputConfigStructVar.Value baseRefs = inputConfigStructVar.Refs }