From 48e7200b85d5c8c6a9f5b867967dbb1f250c572b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Nov 2026 13:55:16 +0100 Subject: [PATCH 1/8] acc: grants keep a stale ID after their parent's ID changes Co-authored-by: Isaac --- .../schemas/change_name/databricks.yml.tmpl | 12 ++ .../grants/schemas/change_name/out.test.toml | 2 + .../grants/schemas/change_name/output.txt | 139 +++++++++++++++++ .../grants/schemas/change_name/script | 31 ++++ .../volume_change_name/databricks.yml.tmpl | 17 ++ .../grants/volume_change_name/out.test.toml | 2 + .../grants/volume_change_name/output.txt | 147 ++++++++++++++++++ .../grants/volume_change_name/script | 31 ++++ 8 files changed, 381 insertions(+) create mode 100644 acceptance/bundle/resources/grants/schemas/change_name/databricks.yml.tmpl create mode 100644 acceptance/bundle/resources/grants/schemas/change_name/out.test.toml create mode 100644 acceptance/bundle/resources/grants/schemas/change_name/output.txt create mode 100644 acceptance/bundle/resources/grants/schemas/change_name/script create mode 100644 acceptance/bundle/resources/grants/volume_change_name/databricks.yml.tmpl create mode 100644 acceptance/bundle/resources/grants/volume_change_name/out.test.toml create mode 100644 acceptance/bundle/resources/grants/volume_change_name/output.txt create mode 100644 acceptance/bundle/resources/grants/volume_change_name/script diff --git a/acceptance/bundle/resources/grants/schemas/change_name/databricks.yml.tmpl b/acceptance/bundle/resources/grants/schemas/change_name/databricks.yml.tmpl new file mode 100644 index 00000000000..594df21f5b0 --- /dev/null +++ b/acceptance/bundle/resources/grants/schemas/change_name/databricks.yml.tmpl @@ -0,0 +1,12 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + schemas: + schema1: + catalog_name: main + name: test-schema-$UNIQUE_NAME + grants: + - principal: deco-test-user@databricks.com + privileges: + - USE_SCHEMA diff --git a/acceptance/bundle/resources/grants/schemas/change_name/out.test.toml b/acceptance/bundle/resources/grants/schemas/change_name/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/resources/grants/schemas/change_name/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/resources/grants/schemas/change_name/output.txt b/acceptance/bundle/resources/grants/schemas/change_name/output.txt new file mode 100644 index 00000000000..1c5fb69495d --- /dev/null +++ b/acceptance/bundle/resources/grants/schemas/change_name/output.txt @@ -0,0 +1,139 @@ + +=== Deploy a schema with grants +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... +Created schemas.schema1 +Created schemas.schema1.grants +Files: 2 uploaded, 0 deleted +Resources: 2 created, 0 changed, 0 deleted, 0 unchanged + +>>> print_requests.py //unity-catalog +{ + "method": "POST", + "path": "/api/2.1/unity-catalog/schemas", + "body": { + "catalog_name": "main", + "name": "test-schema-[UNIQUE_NAME]" + } +} +{ + "method": "PATCH", + "path": "/api/2.1/unity-catalog/permissions/schema/main.test-schema-[UNIQUE_NAME]", + "body": { + "changes": [ + { + "add": [ + "USE_SCHEMA" + ], + "principal": "deco-test-user@databricks.com", + "remove": [ + "ALL_PRIVILEGES" + ] + } + ] + } +} + +schemas schema1.grants id='schema/main.test-schema-[UNIQUE_NAME]' + +=== Rename the schema: its ID changes, and so does the ID of its grants +>>> update_file.py databricks.yml test-schema- test-schema-renamed- + +>>> [CLI] bundle plan +recreate schemas.schema1 +update schemas.schema1.grants + +Plan: 1 to add, 1 to change, 1 to delete, 0 unchanged + +>>> [CLI] bundle plan --output json +{ + "resources.schemas.schema1": { + "name": { + "action": "recreate", + "reason": "id_field", + "old": "test-schema-[UNIQUE_NAME]", + "new": "test-schema-renamed-[UNIQUE_NAME]", + "remote": "test-schema-[UNIQUE_NAME]" + } + }, + "resources.schemas.schema1.grants": { + "full_name": { + "action": "update", + "old": "main.test-schema-[UNIQUE_NAME]", + "new": "", + "remote": "main.test-schema-[UNIQUE_NAME]" + } + } +} + +=== Deploy moves the schema to the new name and applies the grants there +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... + +This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: + recreate resources.schemas.schema1 +Recreated schemas.schema1 +Updated schemas.schema1.grants +Files: 1 uploaded, 0 deleted +Resources: 1 created, 1 changed, 1 deleted, 0 unchanged + +>>> print_requests.py //unity-catalog +{ + "method": "DELETE", + "path": "/api/2.1/unity-catalog/schemas/main.test-schema-[UNIQUE_NAME]", + "q": { + "force": "true" + } +} +{ + "method": "POST", + "path": "/api/2.1/unity-catalog/schemas", + "body": { + "catalog_name": "main", + "name": "test-schema-renamed-[UNIQUE_NAME]" + } +} +{ + "method": "PATCH", + "path": "/api/2.1/unity-catalog/permissions/schema/main.test-schema-renamed-[UNIQUE_NAME]", + "body": { + "changes": [ + { + "add": [ + "USE_SCHEMA" + ], + "principal": "deco-test-user@databricks.com", + "remove": [ + "ALL_PRIVILEGES" + ] + } + ] + } +} + +>>> [CLI] grants get schema main.test-schema-renamed-[UNIQUE_NAME] +{ + "principal": "deco-test-user@databricks.com", + "privileges": [ + "USE_SCHEMA" + ] +} + +=== The grants are recorded under the new name, so the next plan is a no-op +schemas schema1.grants id='schema/main.test-schema-[UNIQUE_NAME]' + +>>> [CLI] bundle plan +update schemas.schema1.grants + +Plan: 0 to add, 1 to change, 0 to delete, 1 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.schemas.schema1 + +This action will result in the deletion of the following UC schemas. Any underlying data may be lost: + delete resources.schemas.schema1 + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/resources/grants/schemas/change_name/script b/acceptance/bundle/resources/grants/schemas/change_name/script new file mode 100644 index 00000000000..e28d21cfdf8 --- /dev/null +++ b/acceptance/bundle/resources/grants/schemas/change_name/script @@ -0,0 +1,31 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +NEW_SCHEMA="main.test-schema-renamed-${UNIQUE_NAME}" + +cleanup() { + trace $CLI bundle destroy --auto-approve + + # Consume the destroy's requests so none are left recorded. + print_requests.py //unity-catalog &> LOG.destroy_requests +} +trap cleanup EXIT + +title "Deploy a schema with grants" +trace $CLI bundle deploy +trace print_requests.py //unity-catalog +echo +read_state.py schemas schema1.grants id + +title "Rename the schema: its ID changes, and so does the ID of its grants" +trace update_file.py databricks.yml test-schema- test-schema-renamed- +trace $CLI bundle plan +trace $CLI bundle plan --output json | nostamp | jq '.plan | map_values((.changes // {}) | with_entries(select(.value.action != "skip"))) | with_entries(select(.value != {}))' + +title "Deploy moves the schema to the new name and applies the grants there" +trace $CLI bundle deploy --auto-approve +trace print_requests.py //unity-catalog +trace $CLI grants get schema "$NEW_SCHEMA" | jq '.privilege_assignments[] | select(.principal == "deco-test-user@databricks.com")' + +title "The grants are recorded under the new name, so the next plan is a no-op\n" +read_state.py schemas schema1.grants id +trace $CLI bundle plan diff --git a/acceptance/bundle/resources/grants/volume_change_name/databricks.yml.tmpl b/acceptance/bundle/resources/grants/volume_change_name/databricks.yml.tmpl new file mode 100644 index 00000000000..a8c9a407fda --- /dev/null +++ b/acceptance/bundle/resources/grants/volume_change_name/databricks.yml.tmpl @@ -0,0 +1,17 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + schemas: + schema1: + catalog_name: main + name: test-schema-$UNIQUE_NAME + volumes: + volume1: + catalog_name: main + schema_name: ${resources.schemas.schema1.name} + name: myvolume + grants: + - principal: deco-test-user@databricks.com + privileges: + - READ_VOLUME diff --git a/acceptance/bundle/resources/grants/volume_change_name/out.test.toml b/acceptance/bundle/resources/grants/volume_change_name/out.test.toml new file mode 100644 index 00000000000..0f167effef1 --- /dev/null +++ b/acceptance/bundle/resources/grants/volume_change_name/out.test.toml @@ -0,0 +1,2 @@ +Cloud = true +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/resources/grants/volume_change_name/output.txt b/acceptance/bundle/resources/grants/volume_change_name/output.txt new file mode 100644 index 00000000000..8595acd6e8c --- /dev/null +++ b/acceptance/bundle/resources/grants/volume_change_name/output.txt @@ -0,0 +1,147 @@ + +=== Deploy a volume with grants +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... +Created schemas.schema1 +Created volumes.volume1 +Created volumes.volume1.grants +Files: 2 uploaded, 0 deleted +Resources: 3 created, 0 changed, 0 deleted, 0 unchanged + +>>> print_requests.py //unity-catalog +{ + "method": "POST", + "path": "/api/2.1/unity-catalog/schemas", + "body": { + "catalog_name": "main", + "name": "test-schema-[UNIQUE_NAME]" + } +} +{ + "method": "POST", + "path": "/api/2.1/unity-catalog/volumes", + "body": { + "catalog_name": "main", + "name": "myvolume", + "schema_name": "test-schema-[UNIQUE_NAME]", + "volume_type": "MANAGED" + } +} +{ + "method": "PATCH", + "path": "/api/2.1/unity-catalog/permissions/volume/main.test-schema-[UNIQUE_NAME].myvolume", + "body": { + "changes": [ + { + "add": [ + "READ_VOLUME" + ], + "principal": "deco-test-user@databricks.com", + "remove": [ + "ALL_PRIVILEGES" + ] + } + ] + } +} + +volumes volume1.grants id='volume/main.test-schema-[UNIQUE_NAME].myvolume' + +=== Rename the volume: its ID changes, and so does the ID of its grants +>>> update_file.py databricks.yml myvolume myvolume_renamed + +>>> [CLI] bundle plan +update volumes.volume1 +update volumes.volume1.grants + +Plan: 0 to add, 2 to change, 0 to delete, 1 unchanged + +>>> [CLI] bundle plan --output json +{ + "resources.volumes.volume1": { + "name": { + "action": "update_id", + "reason": "id_changes", + "old": "myvolume", + "new": "myvolume_renamed", + "remote": "myvolume" + } + }, + "resources.volumes.volume1.grants": { + "full_name": { + "action": "update", + "old": "main.test-schema-[UNIQUE_NAME].myvolume", + "new": "", + "remote": "main.test-schema-[UNIQUE_NAME].myvolume" + } + } +} + +=== Deploy renames the volume and applies the grants under the new name +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... +Updated volumes.volume1 +Updated volumes.volume1.grants +Files: 1 uploaded, 0 deleted +Resources: 0 created, 2 changed, 0 deleted, 1 unchanged + +>>> print_requests.py //unity-catalog +{ + "method": "PATCH", + "path": "/api/2.1/unity-catalog/volumes/main.test-schema-[UNIQUE_NAME].myvolume", + "body": { + "comment": "", + "new_name": "myvolume_renamed" + } +} +{ + "method": "PATCH", + "path": "/api/2.1/unity-catalog/permissions/volume/main.test-schema-[UNIQUE_NAME].myvolume_renamed", + "body": { + "changes": [ + { + "add": [ + "READ_VOLUME" + ], + "principal": "deco-test-user@databricks.com", + "remove": [ + "ALL_PRIVILEGES" + ] + } + ] + } +} + +>>> [CLI] grants get volume main.test-schema-[UNIQUE_NAME].myvolume_renamed +{ + "principal": "deco-test-user@databricks.com", + "privileges": [ + "READ_VOLUME" + ] +} + +=== The grants are recorded under the new name, so the next plan is a no-op +volumes volume1.grants id='volume/main.test-schema-[UNIQUE_NAME].myvolume' + +>>> [CLI] bundle plan +update volumes.volume1.grants + +Plan: 0 to add, 1 to change, 0 to delete, 2 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.schemas.schema1 + delete resources.volumes.volume1 + +This action will result in the deletion of the following UC schemas. Any underlying data may be lost: + delete resources.schemas.schema1 + +This action will result in the deletion of the following volumes. +For managed volumes, the files stored in the volume are also deleted from your +cloud tenant within 30 days. For external volumes, the metadata about the volume +is removed from the catalog, but the underlying files are not deleted: + delete resources.volumes.volume1 + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default + +Destroy: 2 deleted diff --git a/acceptance/bundle/resources/grants/volume_change_name/script b/acceptance/bundle/resources/grants/volume_change_name/script new file mode 100644 index 00000000000..09bdc8a0ad2 --- /dev/null +++ b/acceptance/bundle/resources/grants/volume_change_name/script @@ -0,0 +1,31 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +NEW_VOLUME="main.test-schema-${UNIQUE_NAME}.myvolume_renamed" + +cleanup() { + trace $CLI bundle destroy --auto-approve + + # Consume the destroy's requests so none are left recorded. + print_requests.py //unity-catalog &> LOG.destroy_requests +} +trap cleanup EXIT + +title "Deploy a volume with grants" +trace $CLI bundle deploy +trace print_requests.py //unity-catalog +echo +read_state.py volumes volume1.grants id + +title "Rename the volume: its ID changes, and so does the ID of its grants" +trace update_file.py databricks.yml myvolume myvolume_renamed +trace $CLI bundle plan +trace $CLI bundle plan --output json | nostamp | jq '.plan | map_values((.changes // {}) | with_entries(select(.value.action != "skip"))) | with_entries(select(.value != {}))' + +title "Deploy renames the volume and applies the grants under the new name" +trace $CLI bundle deploy +trace print_requests.py //unity-catalog +trace $CLI grants get volume "$NEW_VOLUME" | jq '.privilege_assignments[] | select(.principal == "deco-test-user@databricks.com")' + +title "The grants are recorded under the new name, so the next plan is a no-op\n" +read_state.py volumes volume1.grants id +trace $CLI bundle plan From 3c00e072111869c191d7065b23a7c161e3f7b7cf Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Nov 2026 14:05:19 +0100 Subject: [PATCH 2/8] Re-key grants when their parent's ID changes The grants ID embeds the parent's full name. When the parent was renamed (catalogs, volumes) or recreated under a new name (schemas), the grants were applied to the new name but their state kept the old ID, so the next plan proposed them again: UC 404s on the old name (plan: create), the test server does not (plan: update, forever). full_name is now an updatable ID field of every grants type, and DoUpdateWithID records the new ID. Co-authored-by: Isaac --- .nextchanges/bundles/grants-rekey.md | 1 + .../grants/schemas/change_name/output.txt | 9 ++++----- .../grants/volume_change_name/output.txt | 9 ++++----- .../dresources/configs/catalogs.grants.yml | 6 ++++++ .../configs/external_locations.grants.yml | 6 ++++++ .../dresources/configs/mcp_services.grants.yml | 6 ++++++ .../configs/model_provider_services.grants.yml | 6 ++++++ .../configs/model_services.grants.yml | 6 ++++++ .../configs/registered_models.grants.yml | 6 ++++++ .../dresources/configs/schemas.grants.yml | 6 ++++++ .../dresources/configs/secrets.grants.yml | 6 ++++++ .../configs/vector_search_indexes.grants.yml | 6 ++++++ .../dresources/configs/volumes.grants.yml | 6 ++++++ bundle/direct/dresources/grants.go | 17 ++++++++++++++++- 14 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 .nextchanges/bundles/grants-rekey.md create mode 100644 bundle/direct/dresources/configs/catalogs.grants.yml create mode 100644 bundle/direct/dresources/configs/external_locations.grants.yml create mode 100644 bundle/direct/dresources/configs/mcp_services.grants.yml create mode 100644 bundle/direct/dresources/configs/model_provider_services.grants.yml create mode 100644 bundle/direct/dresources/configs/model_services.grants.yml create mode 100644 bundle/direct/dresources/configs/registered_models.grants.yml create mode 100644 bundle/direct/dresources/configs/schemas.grants.yml create mode 100644 bundle/direct/dresources/configs/secrets.grants.yml create mode 100644 bundle/direct/dresources/configs/vector_search_indexes.grants.yml create mode 100644 bundle/direct/dresources/configs/volumes.grants.yml diff --git a/.nextchanges/bundles/grants-rekey.md b/.nextchanges/bundles/grants-rekey.md new file mode 100644 index 00000000000..12138bf4390 --- /dev/null +++ b/.nextchanges/bundles/grants-rekey.md @@ -0,0 +1 @@ +* Fix grants of a renamed or recreated catalog, schema, volume or other UC resource staying recorded under the old name, which made the next `bundle plan` propose to apply them again (direct engine). ([#PR](https://github.com/databricks/cli/pull/PR)) diff --git a/acceptance/bundle/resources/grants/schemas/change_name/output.txt b/acceptance/bundle/resources/grants/schemas/change_name/output.txt index 1c5fb69495d..70f1204888c 100644 --- a/acceptance/bundle/resources/grants/schemas/change_name/output.txt +++ b/acceptance/bundle/resources/grants/schemas/change_name/output.txt @@ -58,7 +58,8 @@ Plan: 1 to add, 1 to change, 1 to delete, 0 unchanged }, "resources.schemas.schema1.grants": { "full_name": { - "action": "update", + "action": "update_id", + "reason": "id_changes", "old": "main.test-schema-[UNIQUE_NAME]", "new": "", "remote": "main.test-schema-[UNIQUE_NAME]" @@ -120,12 +121,10 @@ Resources: 1 created, 1 changed, 1 deleted, 0 unchanged } === The grants are recorded under the new name, so the next plan is a no-op -schemas schema1.grants id='schema/main.test-schema-[UNIQUE_NAME]' +schemas schema1.grants id='schema/main.test-schema-renamed-[UNIQUE_NAME]' >>> [CLI] bundle plan -update schemas.schema1.grants - -Plan: 0 to add, 1 to change, 0 to delete, 1 unchanged +Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: diff --git a/acceptance/bundle/resources/grants/volume_change_name/output.txt b/acceptance/bundle/resources/grants/volume_change_name/output.txt index 8595acd6e8c..2b71f8d4c48 100644 --- a/acceptance/bundle/resources/grants/volume_change_name/output.txt +++ b/acceptance/bundle/resources/grants/volume_change_name/output.txt @@ -69,7 +69,8 @@ Plan: 0 to add, 2 to change, 0 to delete, 1 unchanged }, "resources.volumes.volume1.grants": { "full_name": { - "action": "update", + "action": "update_id", + "reason": "id_changes", "old": "main.test-schema-[UNIQUE_NAME].myvolume", "new": "", "remote": "main.test-schema-[UNIQUE_NAME].myvolume" @@ -121,12 +122,10 @@ Resources: 0 created, 2 changed, 0 deleted, 1 unchanged } === The grants are recorded under the new name, so the next plan is a no-op -volumes volume1.grants id='volume/main.test-schema-[UNIQUE_NAME].myvolume' +volumes volume1.grants id='volume/main.test-schema-[UNIQUE_NAME].myvolume_renamed' >>> [CLI] bundle plan -update volumes.volume1.grants - -Plan: 0 to add, 1 to change, 0 to delete, 2 unchanged +Plan: 0 to add, 0 to change, 0 to delete, 3 unchanged >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: diff --git a/bundle/direct/dresources/configs/catalogs.grants.yml b/bundle/direct/dresources/configs/catalogs.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/catalogs.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/external_locations.grants.yml b/bundle/direct/dresources/configs/external_locations.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/external_locations.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/mcp_services.grants.yml b/bundle/direct/dresources/configs/mcp_services.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/mcp_services.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/model_provider_services.grants.yml b/bundle/direct/dresources/configs/model_provider_services.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/model_provider_services.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/model_services.grants.yml b/bundle/direct/dresources/configs/model_services.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/model_services.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/registered_models.grants.yml b/bundle/direct/dresources/configs/registered_models.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/registered_models.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/schemas.grants.yml b/bundle/direct/dresources/configs/schemas.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/schemas.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/secrets.grants.yml b/bundle/direct/dresources/configs/secrets.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/secrets.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/vector_search_indexes.grants.yml b/bundle/direct/dresources/configs/vector_search_indexes.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/vector_search_indexes.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/configs/volumes.grants.yml b/bundle/direct/dresources/configs/volumes.grants.yml new file mode 100644 index 00000000000..2a1f2bed107 --- /dev/null +++ b/bundle/direct/dresources/configs/volumes.grants.yml @@ -0,0 +1,6 @@ +updatable_id_fields: + # The ID embeds the parent's full name, which changes when the parent is renamed or + # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep + # reading the grants of the old name. + - field: full_name + reason: id_changes diff --git a/bundle/direct/dresources/grants.go b/bundle/direct/dresources/grants.go index 3aa48c58279..378149b9a2f 100644 --- a/bundle/direct/dresources/grants.go +++ b/bundle/direct/dresources/grants.go @@ -126,7 +126,18 @@ func (r *ResourceGrants) DoCreate(ctx context.Context, state *GrantsState) (stri return "", nil, retrySafe(err) } - return state.SecurableType + "/" + state.FullName, nil, nil + return makeGrantsID(state), nil, nil +} + +// DoUpdateWithID records the new ID when the parent's ID changes (a rename, or a recreate +// under a new name). Otherwise DoRead keeps listing the grants of the old name and every +// later plan reports an update. +func (r *ResourceGrants) DoUpdateWithID(ctx context.Context, id string, state *GrantsState, entry *PlanEntry) (string, *GrantsState, error) { + _, err := r.DoUpdate(ctx, id, state, entry) + if err != nil { + return "", nil, err + } + return makeGrantsID(state), nil, nil } func (r *ResourceGrants) DoUpdate(ctx context.Context, _ string, state *GrantsState, entry *PlanEntry) (*GrantsState, error) { @@ -300,6 +311,10 @@ func normalizeAssignments(assignments []catalog.PrivilegeAssignment) { } } +func makeGrantsID(state *GrantsState) string { + return state.SecurableType + "/" + state.FullName +} + func parseGrantsID(id string) (string, string, error) { parts := strings.SplitN(id, "/", 2) if len(parts) != 2 { From e96fe76857d6df99db7df9955adf3d808496887f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Nov 2026 14:06:29 +0100 Subject: [PATCH 3/8] Link PR in changelog fragment Co-authored-by: Isaac --- .nextchanges/bundles/grants-rekey.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nextchanges/bundles/grants-rekey.md b/.nextchanges/bundles/grants-rekey.md index 12138bf4390..985ad48b893 100644 --- a/.nextchanges/bundles/grants-rekey.md +++ b/.nextchanges/bundles/grants-rekey.md @@ -1 +1 @@ -* Fix grants of a renamed or recreated catalog, schema, volume or other UC resource staying recorded under the old name, which made the next `bundle plan` propose to apply them again (direct engine). ([#PR](https://github.com/databricks/cli/pull/PR)) +* Fix grants of a renamed or recreated catalog, schema, volume or other UC resource staying recorded under the old name, which made the next `bundle plan` propose to apply them again (direct engine). ([#7345](https://github.com/databricks/cli/pull/7345)) From dbd7fd0b4a06c5710cc706ac4d9e0351c825d636 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Nov 2026 15:02:27 +0100 Subject: [PATCH 4/8] Use a single grants.yml for every grants type A config file named after a sub-resource now applies to that sub-resource of every parent, so the ten identical .grants.yml files become one. Co-authored-by: Isaac --- bundle/direct/dresources/README.md | 2 +- bundle/direct/dresources/config.go | 24 ++++++++++++++++++- bundle/direct/dresources/config_test.go | 11 ++++++++- .../configs/external_locations.grants.yml | 6 ----- .../{catalogs.grants.yml => grants.yml} | 0 .../configs/mcp_services.grants.yml | 6 ----- .../model_provider_services.grants.yml | 6 ----- .../configs/model_services.grants.yml | 6 ----- .../configs/registered_models.grants.yml | 6 ----- .../dresources/configs/schemas.grants.yml | 6 ----- .../dresources/configs/secrets.grants.yml | 6 ----- .../configs/vector_search_indexes.grants.yml | 6 ----- .../dresources/configs/volumes.grants.yml | 6 ----- 13 files changed, 34 insertions(+), 57 deletions(-) delete mode 100644 bundle/direct/dresources/configs/external_locations.grants.yml rename bundle/direct/dresources/configs/{catalogs.grants.yml => grants.yml} (100%) delete mode 100644 bundle/direct/dresources/configs/mcp_services.grants.yml delete mode 100644 bundle/direct/dresources/configs/model_provider_services.grants.yml delete mode 100644 bundle/direct/dresources/configs/model_services.grants.yml delete mode 100644 bundle/direct/dresources/configs/registered_models.grants.yml delete mode 100644 bundle/direct/dresources/configs/schemas.grants.yml delete mode 100644 bundle/direct/dresources/configs/secrets.grants.yml delete mode 100644 bundle/direct/dresources/configs/vector_search_indexes.grants.yml delete mode 100644 bundle/direct/dresources/configs/volumes.grants.yml diff --git a/bundle/direct/dresources/README.md b/bundle/direct/dresources/README.md index 5487042c965..0262a2d11e0 100644 --- a/bundle/direct/dresources/README.md +++ b/bundle/direct/dresources/README.md @@ -17,7 +17,7 @@ Each field with special plan/deploy behavior must be declared in the YAML file of its resource type, under `configs/`. Both files of a resource type are optional and are omitted when they would be empty: - - `configs/.yml` (e.g. `configs/jobs.yml`) — hand-written rules. + - `configs/.yml` (e.g. `configs/jobs.yml`) — hand-written rules. A file named after a sub-resource (`configs/grants.yml`) applies to that sub-resource of every parent. - `configs/.generated.yml` — API field behaviors from the OpenAPI schema. Generated by `./task generate-direct-resources`, do not edit. Choose the right category: diff --git a/bundle/direct/dresources/config.go b/bundle/direct/dresources/config.go index cc54d60a678..9552f935d1c 100644 --- a/bundle/direct/dresources/config.go +++ b/bundle/direct/dresources/config.go @@ -138,6 +138,8 @@ type Config struct { // hand-written rules and .generated.yml the ones derived from the // OpenAPI spec. A resource type without rules has no file. The file name is the // resource type, so each file holds the rules directly with no enclosing keys. +// A file named after a sub-resource (grants.yml) applies to that sub-resource of +// every parent (catalogs.grants, schemas.grants, ...). // //go:embed configs/*.yml var configFS embed.FS @@ -191,12 +193,32 @@ var loadConfigs = sync.OnceValues(func() (*Config, *Config) { panic(fmt.Errorf("%s: %w", name, err)) } - dst.Resources[resourceType] = rc + for _, rt := range configResourceTypes(resourceType) { + if _, ok := dst.Resources[rt]; ok { + panic(fmt.Errorf("%s: rules for %s are already defined by another file", name, rt)) + } + dst.Resources[rt] = rc + } } return handWritten, generated }) +// configResourceTypes returns the resource types a config file named name applies to: the +// resource type itself, or for a sub-resource name like "grants" every ".grants". +func configResourceTypes(name string) []string { + if _, ok := SupportedResources[name]; ok { + return []string{name} + } + var result []string + for resourceType := range SupportedResources { + if strings.HasSuffix(resourceType, "."+name) { + result = append(result, resourceType) + } + } + return result +} + // MustLoadConfig returns the configuration parsed from the configs/.yml files. func MustLoadConfig() *Config { handWritten, _ := loadConfigs() diff --git a/bundle/direct/dresources/config_test.go b/bundle/direct/dresources/config_test.go index 1446a14e8e2..4cf0d7d22be 100644 --- a/bundle/direct/dresources/config_test.go +++ b/bundle/direct/dresources/config_test.go @@ -28,7 +28,7 @@ func TestConfigFiles(t *testing.T) { for _, name := range names { resourceType := strings.TrimSuffix(strings.TrimSuffix(path.Base(name), ymlSuffix), generatedSuffix) - assert.Containsf(t, SupportedResources, resourceType, "%s: %q is not a supported resource type", name, resourceType) + assert.NotEmptyf(t, configResourceTypes(resourceType), "%s: %q is not a supported resource type or sub-resource", name, resourceType) } for _, cfg := range []*Config{MustLoadConfig(), MustLoadGeneratedConfig()} { @@ -43,6 +43,15 @@ func TestGetResourceConfig(t *testing.T) { assert.Empty(t, GetResourceConfig("nonexistent").RecreateOnChanges) } +// TestSubResourceConfig checks that grants.yml applies to the grants of every parent. +func TestSubResourceConfig(t *testing.T) { + for resourceType := range SupportedResources { + if strings.HasSuffix(resourceType, ".grants") { + assert.NotEmptyf(t, GetResourceConfig(resourceType).UpdatableIDFields, "%s: grants.yml does not apply", resourceType) + } + } +} + // TestFieldRuleOmittedIsRoot verifies that a FieldRule with no field is a root // rule matching every field, which the job_runs root rules rely on. func TestFieldRuleOmittedIsRoot(t *testing.T) { diff --git a/bundle/direct/dresources/configs/external_locations.grants.yml b/bundle/direct/dresources/configs/external_locations.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/external_locations.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes diff --git a/bundle/direct/dresources/configs/catalogs.grants.yml b/bundle/direct/dresources/configs/grants.yml similarity index 100% rename from bundle/direct/dresources/configs/catalogs.grants.yml rename to bundle/direct/dresources/configs/grants.yml diff --git a/bundle/direct/dresources/configs/mcp_services.grants.yml b/bundle/direct/dresources/configs/mcp_services.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/mcp_services.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes diff --git a/bundle/direct/dresources/configs/model_provider_services.grants.yml b/bundle/direct/dresources/configs/model_provider_services.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/model_provider_services.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes diff --git a/bundle/direct/dresources/configs/model_services.grants.yml b/bundle/direct/dresources/configs/model_services.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/model_services.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes diff --git a/bundle/direct/dresources/configs/registered_models.grants.yml b/bundle/direct/dresources/configs/registered_models.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/registered_models.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes diff --git a/bundle/direct/dresources/configs/schemas.grants.yml b/bundle/direct/dresources/configs/schemas.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/schemas.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes diff --git a/bundle/direct/dresources/configs/secrets.grants.yml b/bundle/direct/dresources/configs/secrets.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/secrets.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes diff --git a/bundle/direct/dresources/configs/vector_search_indexes.grants.yml b/bundle/direct/dresources/configs/vector_search_indexes.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/vector_search_indexes.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes diff --git a/bundle/direct/dresources/configs/volumes.grants.yml b/bundle/direct/dresources/configs/volumes.grants.yml deleted file mode 100644 index 2a1f2bed107..00000000000 --- a/bundle/direct/dresources/configs/volumes.grants.yml +++ /dev/null @@ -1,6 +0,0 @@ -updatable_id_fields: - # The ID embeds the parent's full name, which changes when the parent is renamed or - # recreated under a new name. UpdateWithID records the new ID, so DoRead does not keep - # reading the grants of the old name. - - field: full_name - reason: id_changes From 28a92ca37f818edebed6a8bf203f5471613bacb2 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Nov 2026 15:45:27 +0100 Subject: [PATCH 5/8] Drop engine mention from changelog entry Co-authored-by: Isaac --- .nextchanges/bundles/grants-rekey.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nextchanges/bundles/grants-rekey.md b/.nextchanges/bundles/grants-rekey.md index 985ad48b893..7c1953a5b82 100644 --- a/.nextchanges/bundles/grants-rekey.md +++ b/.nextchanges/bundles/grants-rekey.md @@ -1 +1 @@ -* Fix grants of a renamed or recreated catalog, schema, volume or other UC resource staying recorded under the old name, which made the next `bundle plan` propose to apply them again (direct engine). ([#7345](https://github.com/databricks/cli/pull/7345)) +* Fix grants of a renamed or recreated catalog, schema, volume or other UC resource staying recorded under the old name, which made the next `bundle plan` propose to apply them again. ([#7345](https://github.com/databricks/cli/pull/7345)) From 7a3df009305d2e21055cdff7e914546e1ae74d32 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Nov 2026 15:49:06 +0100 Subject: [PATCH 6/8] acc: trace read_state.py in the grants change_name tests Co-authored-by: Isaac --- .../bundle/resources/grants/schemas/change_name/output.txt | 2 ++ .../bundle/resources/grants/schemas/change_name/script | 7 +++---- .../bundle/resources/grants/volume_change_name/output.txt | 2 ++ .../bundle/resources/grants/volume_change_name/script | 7 +++---- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/acceptance/bundle/resources/grants/schemas/change_name/output.txt b/acceptance/bundle/resources/grants/schemas/change_name/output.txt index 70f1204888c..94910264291 100644 --- a/acceptance/bundle/resources/grants/schemas/change_name/output.txt +++ b/acceptance/bundle/resources/grants/schemas/change_name/output.txt @@ -34,6 +34,7 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged } } +>>> read_state.py schemas schema1.grants id schemas schema1.grants id='schema/main.test-schema-[UNIQUE_NAME]' === Rename the schema: its ID changes, and so does the ID of its grants @@ -121,6 +122,7 @@ Resources: 1 created, 1 changed, 1 deleted, 0 unchanged } === The grants are recorded under the new name, so the next plan is a no-op +>>> read_state.py schemas schema1.grants id schemas schema1.grants id='schema/main.test-schema-renamed-[UNIQUE_NAME]' >>> [CLI] bundle plan diff --git a/acceptance/bundle/resources/grants/schemas/change_name/script b/acceptance/bundle/resources/grants/schemas/change_name/script index e28d21cfdf8..5e464681000 100644 --- a/acceptance/bundle/resources/grants/schemas/change_name/script +++ b/acceptance/bundle/resources/grants/schemas/change_name/script @@ -13,8 +13,7 @@ trap cleanup EXIT title "Deploy a schema with grants" trace $CLI bundle deploy trace print_requests.py //unity-catalog -echo -read_state.py schemas schema1.grants id +trace read_state.py schemas schema1.grants id title "Rename the schema: its ID changes, and so does the ID of its grants" trace update_file.py databricks.yml test-schema- test-schema-renamed- @@ -26,6 +25,6 @@ trace $CLI bundle deploy --auto-approve trace print_requests.py //unity-catalog trace $CLI grants get schema "$NEW_SCHEMA" | jq '.privilege_assignments[] | select(.principal == "deco-test-user@databricks.com")' -title "The grants are recorded under the new name, so the next plan is a no-op\n" -read_state.py schemas schema1.grants id +title "The grants are recorded under the new name, so the next plan is a no-op" +trace read_state.py schemas schema1.grants id trace $CLI bundle plan diff --git a/acceptance/bundle/resources/grants/volume_change_name/output.txt b/acceptance/bundle/resources/grants/volume_change_name/output.txt index 2b71f8d4c48..844c683553c 100644 --- a/acceptance/bundle/resources/grants/volume_change_name/output.txt +++ b/acceptance/bundle/resources/grants/volume_change_name/output.txt @@ -45,6 +45,7 @@ Resources: 3 created, 0 changed, 0 deleted, 0 unchanged } } +>>> read_state.py volumes volume1.grants id volumes volume1.grants id='volume/main.test-schema-[UNIQUE_NAME].myvolume' === Rename the volume: its ID changes, and so does the ID of its grants @@ -122,6 +123,7 @@ Resources: 0 created, 2 changed, 0 deleted, 1 unchanged } === The grants are recorded under the new name, so the next plan is a no-op +>>> read_state.py volumes volume1.grants id volumes volume1.grants id='volume/main.test-schema-[UNIQUE_NAME].myvolume_renamed' >>> [CLI] bundle plan diff --git a/acceptance/bundle/resources/grants/volume_change_name/script b/acceptance/bundle/resources/grants/volume_change_name/script index 09bdc8a0ad2..974d673a40e 100644 --- a/acceptance/bundle/resources/grants/volume_change_name/script +++ b/acceptance/bundle/resources/grants/volume_change_name/script @@ -13,8 +13,7 @@ trap cleanup EXIT title "Deploy a volume with grants" trace $CLI bundle deploy trace print_requests.py //unity-catalog -echo -read_state.py volumes volume1.grants id +trace read_state.py volumes volume1.grants id title "Rename the volume: its ID changes, and so does the ID of its grants" trace update_file.py databricks.yml myvolume myvolume_renamed @@ -26,6 +25,6 @@ trace $CLI bundle deploy trace print_requests.py //unity-catalog trace $CLI grants get volume "$NEW_VOLUME" | jq '.privilege_assignments[] | select(.principal == "deco-test-user@databricks.com")' -title "The grants are recorded under the new name, so the next plan is a no-op\n" -read_state.py volumes volume1.grants id +title "The grants are recorded under the new name, so the next plan is a no-op" +trace read_state.py volumes volume1.grants id trace $CLI bundle plan From 6274fc12d6e5cccc60257d11120dc09d741b36ef Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Nov 2026 15:51:04 +0100 Subject: [PATCH 7/8] acc: drop text plan where the JSON plan already shows it Co-authored-by: Isaac --- .../grants/schemas/change_name/output.txt | 36 ++++++++--------- .../grants/schemas/change_name/script | 3 +- .../grants/volume_change_name/output.txt | 40 ++++++++++--------- .../grants/volume_change_name/script | 3 +- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/acceptance/bundle/resources/grants/schemas/change_name/output.txt b/acceptance/bundle/resources/grants/schemas/change_name/output.txt index 94910264291..61959c9c69a 100644 --- a/acceptance/bundle/resources/grants/schemas/change_name/output.txt +++ b/acceptance/bundle/resources/grants/schemas/change_name/output.txt @@ -40,30 +40,30 @@ schemas schema1.grants id='schema/main.test-schema-[UNIQUE_NAME]' === Rename the schema: its ID changes, and so does the ID of its grants >>> update_file.py databricks.yml test-schema- test-schema-renamed- ->>> [CLI] bundle plan -recreate schemas.schema1 -update schemas.schema1.grants - -Plan: 1 to add, 1 to change, 1 to delete, 0 unchanged - >>> [CLI] bundle plan --output json { "resources.schemas.schema1": { - "name": { - "action": "recreate", - "reason": "id_field", - "old": "test-schema-[UNIQUE_NAME]", - "new": "test-schema-renamed-[UNIQUE_NAME]", - "remote": "test-schema-[UNIQUE_NAME]" + "action": "recreate", + "changes": { + "name": { + "action": "recreate", + "reason": "id_field", + "old": "test-schema-[UNIQUE_NAME]", + "new": "test-schema-renamed-[UNIQUE_NAME]", + "remote": "test-schema-[UNIQUE_NAME]" + } } }, "resources.schemas.schema1.grants": { - "full_name": { - "action": "update_id", - "reason": "id_changes", - "old": "main.test-schema-[UNIQUE_NAME]", - "new": "", - "remote": "main.test-schema-[UNIQUE_NAME]" + "action": "update_id", + "changes": { + "full_name": { + "action": "update_id", + "reason": "id_changes", + "old": "main.test-schema-[UNIQUE_NAME]", + "new": "", + "remote": "main.test-schema-[UNIQUE_NAME]" + } } } } diff --git a/acceptance/bundle/resources/grants/schemas/change_name/script b/acceptance/bundle/resources/grants/schemas/change_name/script index 5e464681000..1a6245f906c 100644 --- a/acceptance/bundle/resources/grants/schemas/change_name/script +++ b/acceptance/bundle/resources/grants/schemas/change_name/script @@ -17,8 +17,7 @@ trace read_state.py schemas schema1.grants id title "Rename the schema: its ID changes, and so does the ID of its grants" trace update_file.py databricks.yml test-schema- test-schema-renamed- -trace $CLI bundle plan -trace $CLI bundle plan --output json | nostamp | jq '.plan | map_values((.changes // {}) | with_entries(select(.value.action != "skip"))) | with_entries(select(.value != {}))' +trace $CLI bundle plan --output json | nostamp | jq '.plan | map_values({action, changes: ((.changes // {}) | with_entries(select(.value.action != "skip")))})' title "Deploy moves the schema to the new name and applies the grants there" trace $CLI bundle deploy --auto-approve diff --git a/acceptance/bundle/resources/grants/volume_change_name/output.txt b/acceptance/bundle/resources/grants/volume_change_name/output.txt index 844c683553c..69dc4070691 100644 --- a/acceptance/bundle/resources/grants/volume_change_name/output.txt +++ b/acceptance/bundle/resources/grants/volume_change_name/output.txt @@ -51,30 +51,34 @@ volumes volume1.grants id='volume/main.test-schema-[UNIQUE_NAME].myvolume' === Rename the volume: its ID changes, and so does the ID of its grants >>> update_file.py databricks.yml myvolume myvolume_renamed ->>> [CLI] bundle plan -update volumes.volume1 -update volumes.volume1.grants - -Plan: 0 to add, 2 to change, 0 to delete, 1 unchanged - >>> [CLI] bundle plan --output json { + "resources.schemas.schema1": { + "action": "skip", + "changes": {} + }, "resources.volumes.volume1": { - "name": { - "action": "update_id", - "reason": "id_changes", - "old": "myvolume", - "new": "myvolume_renamed", - "remote": "myvolume" + "action": "update_id", + "changes": { + "name": { + "action": "update_id", + "reason": "id_changes", + "old": "myvolume", + "new": "myvolume_renamed", + "remote": "myvolume" + } } }, "resources.volumes.volume1.grants": { - "full_name": { - "action": "update_id", - "reason": "id_changes", - "old": "main.test-schema-[UNIQUE_NAME].myvolume", - "new": "", - "remote": "main.test-schema-[UNIQUE_NAME].myvolume" + "action": "update_id", + "changes": { + "full_name": { + "action": "update_id", + "reason": "id_changes", + "old": "main.test-schema-[UNIQUE_NAME].myvolume", + "new": "", + "remote": "main.test-schema-[UNIQUE_NAME].myvolume" + } } } } diff --git a/acceptance/bundle/resources/grants/volume_change_name/script b/acceptance/bundle/resources/grants/volume_change_name/script index 974d673a40e..11b22bf1e5f 100644 --- a/acceptance/bundle/resources/grants/volume_change_name/script +++ b/acceptance/bundle/resources/grants/volume_change_name/script @@ -17,8 +17,7 @@ trace read_state.py volumes volume1.grants id title "Rename the volume: its ID changes, and so does the ID of its grants" trace update_file.py databricks.yml myvolume myvolume_renamed -trace $CLI bundle plan -trace $CLI bundle plan --output json | nostamp | jq '.plan | map_values((.changes // {}) | with_entries(select(.value.action != "skip"))) | with_entries(select(.value != {}))' +trace $CLI bundle plan --output json | nostamp | jq '.plan | map_values({action, changes: ((.changes // {}) | with_entries(select(.value.action != "skip")))})' title "Deploy renames the volume and applies the grants under the new name" trace $CLI bundle deploy From ecf3adafffbab18b2993c11bc592964e7160ded7 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 4 Nov 2026 15:57:53 +0100 Subject: [PATCH 8/8] acc: show only the grants plan entry's changes Co-authored-by: Isaac --- .../grants/schemas/change_name/output.txt | 27 +++------------- .../grants/schemas/change_name/script | 2 +- .../grants/volume_change_name/output.txt | 31 +++---------------- .../grants/volume_change_name/script | 2 +- 4 files changed, 12 insertions(+), 50 deletions(-) diff --git a/acceptance/bundle/resources/grants/schemas/change_name/output.txt b/acceptance/bundle/resources/grants/schemas/change_name/output.txt index 61959c9c69a..6c4e069ee0c 100644 --- a/acceptance/bundle/resources/grants/schemas/change_name/output.txt +++ b/acceptance/bundle/resources/grants/schemas/change_name/output.txt @@ -42,29 +42,12 @@ schemas schema1.grants id='schema/main.test-schema-[UNIQUE_NAME]' >>> [CLI] bundle plan --output json { - "resources.schemas.schema1": { - "action": "recreate", - "changes": { - "name": { - "action": "recreate", - "reason": "id_field", - "old": "test-schema-[UNIQUE_NAME]", - "new": "test-schema-renamed-[UNIQUE_NAME]", - "remote": "test-schema-[UNIQUE_NAME]" - } - } - }, - "resources.schemas.schema1.grants": { + "full_name": { "action": "update_id", - "changes": { - "full_name": { - "action": "update_id", - "reason": "id_changes", - "old": "main.test-schema-[UNIQUE_NAME]", - "new": "", - "remote": "main.test-schema-[UNIQUE_NAME]" - } - } + "reason": "id_changes", + "old": "main.test-schema-[UNIQUE_NAME]", + "new": "", + "remote": "main.test-schema-[UNIQUE_NAME]" } } diff --git a/acceptance/bundle/resources/grants/schemas/change_name/script b/acceptance/bundle/resources/grants/schemas/change_name/script index 1a6245f906c..93360d06552 100644 --- a/acceptance/bundle/resources/grants/schemas/change_name/script +++ b/acceptance/bundle/resources/grants/schemas/change_name/script @@ -17,7 +17,7 @@ trace read_state.py schemas schema1.grants id title "Rename the schema: its ID changes, and so does the ID of its grants" trace update_file.py databricks.yml test-schema- test-schema-renamed- -trace $CLI bundle plan --output json | nostamp | jq '.plan | map_values({action, changes: ((.changes // {}) | with_entries(select(.value.action != "skip")))})' +trace $CLI bundle plan --output json | jq '.plan["resources.schemas.schema1.grants"].changes' title "Deploy moves the schema to the new name and applies the grants there" trace $CLI bundle deploy --auto-approve diff --git a/acceptance/bundle/resources/grants/volume_change_name/output.txt b/acceptance/bundle/resources/grants/volume_change_name/output.txt index 69dc4070691..ffe41036393 100644 --- a/acceptance/bundle/resources/grants/volume_change_name/output.txt +++ b/acceptance/bundle/resources/grants/volume_change_name/output.txt @@ -53,33 +53,12 @@ volumes volume1.grants id='volume/main.test-schema-[UNIQUE_NAME].myvolume' >>> [CLI] bundle plan --output json { - "resources.schemas.schema1": { - "action": "skip", - "changes": {} - }, - "resources.volumes.volume1": { + "full_name": { "action": "update_id", - "changes": { - "name": { - "action": "update_id", - "reason": "id_changes", - "old": "myvolume", - "new": "myvolume_renamed", - "remote": "myvolume" - } - } - }, - "resources.volumes.volume1.grants": { - "action": "update_id", - "changes": { - "full_name": { - "action": "update_id", - "reason": "id_changes", - "old": "main.test-schema-[UNIQUE_NAME].myvolume", - "new": "", - "remote": "main.test-schema-[UNIQUE_NAME].myvolume" - } - } + "reason": "id_changes", + "old": "main.test-schema-[UNIQUE_NAME].myvolume", + "new": "", + "remote": "main.test-schema-[UNIQUE_NAME].myvolume" } } diff --git a/acceptance/bundle/resources/grants/volume_change_name/script b/acceptance/bundle/resources/grants/volume_change_name/script index 11b22bf1e5f..3ae81092846 100644 --- a/acceptance/bundle/resources/grants/volume_change_name/script +++ b/acceptance/bundle/resources/grants/volume_change_name/script @@ -17,7 +17,7 @@ trace read_state.py volumes volume1.grants id title "Rename the volume: its ID changes, and so does the ID of its grants" trace update_file.py databricks.yml myvolume myvolume_renamed -trace $CLI bundle plan --output json | nostamp | jq '.plan | map_values({action, changes: ((.changes // {}) | with_entries(select(.value.action != "skip")))})' +trace $CLI bundle plan --output json | jq '.plan["resources.volumes.volume1.grants"].changes' title "Deploy renames the volume and applies the grants under the new name" trace $CLI bundle deploy