Skip to content

Fix spurious grants create for empty grants list on direct engine - #6039

Open
radakam wants to merge 7 commits into
mainfrom
fix-empty-grants-migrate
Open

Fix spurious grants create for empty grants list on direct engine#6039
radakam wants to merge 7 commits into
mainfrom
fix-empty-grants-migrate

Conversation

@radakam

@radakam radakam commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Changes

Add an optional SkipCreate to the direct engine's IResource: a resource can report that a desired state needs no create, and makePlan omits the node instead of planning one. ResourceGrants implements it for an empty grants list.

It is only consulted for nodes with no state entry. A node that already has state keeps its plan entry, so emptying a previously deployed grants: list still plans the revoke.

Why

Terraform writes no databricks_grants resource for an empty list, so bundle deployment migrate records no state for the grants node, and bundle plan showed a spurious grants -> create that contradicted migrate's "no actions planned".

Normalizing empty grants away in config (as apply_bundle_permissions does for permissions) would instead send an emptied list to the delete branch, where grants' no-op DoDelete keeps the grants.

Found by fuzz testing.

Tests

  • Re-enables the schema_empty_grants migrate invariant (excluded in acc: add empty-grants invariant config, disable failing migrate variant #6055 pending this fix).
  • New grants/schemas/remove_all test: emptying a populated list still revokes. Plan summary is per-engine since direct keeps the emptied node in state as unchanged.
  • Unit tests for the empty, unset, and populated cases, called through the adapter.

@radakam
radakam marked this pull request as ready for review July 23, 2026 13:14
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/bundle/ - needs approval

8 files changed
Suggested: @denik
Also eligible: @janniklasrose, @pietern, @andrewnester, @shreyas-goenka, @lennartkats-db, @anton-107

/bundle/ - needs approval

5 files changed
Suggested: @denik
Also eligible: @janniklasrose, @pietern, @andrewnester, @shreyas-goenka, @lennartkats-db, @anton-107

General files (require maintainer)

Files: .nextchanges/bundles/empty-grants-migrate.md
Based on git history:

  • @denik -- recent work in bundle/direct/, bundle/direct/dresources/, .nextchanges/bundles/

Any maintainer (@andrewnester, @anton-107, @denik, @pietern, @shreyas-goenka, @simonfaltum, @renaudhartert-db, @janniklasrose, @lennartkats-db) can approve all areas.
See OWNERS for ownership rules.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: a80615d

Run: 30533284819

Env 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 4 353 1065 12:03
💚​ aws windows 4 4 355 1063 15:37
🔄​ azure linux 1 4 4 352 1064 12:53
💚​ azure windows 4 4 355 1062 18:30
💚​ gcp linux 1 5 352 1066 13:24
💚​ gcp windows 1 5 354 1064 17:24
9 interesting tests: 4 RECOVERED, 4 SKIP, 1 flaky
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestSyncFullFileSync ✅​p ✅​p 🔄​f ✅​p ✅​p ✅​p
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R
Top 45 slowest tests (at least 2 minutes):
duration env testname
3:50 azure windows TestAccept
3:38 aws windows TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=direct
3:32 aws linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_apply_policy_default_values_job_cluster.yml.tmpl
3:12 azure windows TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=terraform
3:12 azure windows TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=direct
3:09 aws linux TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=direct
3:06 azure windows TestFilerRecursiveDelete/workspace_files
3:05 gcp windows TestAccept
3:05 aws windows TestAccept
3:02 gcp linux TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=direct
2:59 gcp windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_with_depends_on.yml.tmpl
2:54 gcp windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=pipeline_apply_policy_default_values.yml.tmpl
2:49 gcp windows TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=terraform
2:48 azure windows TestFilerReadWrite/workspace_files_extensions
2:43 gcp windows TestFilerRecursiveDelete/workspace_files_extensions
2:43 azure linux TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=direct
2:36 aws linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_pydabs_10_tasks.yml.tmpl
2:36 azure linux TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=terraform
2:34 gcp windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=model_serving_endpoint.yml.tmpl
2:33 gcp windows TestAccept/bundle/deploy/mlops-stacks/DATABRICKS_BUNDLE_ENGINE=terraform
2:26 azure windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_apply_policy_default_values_task_cluster.yml.tmpl
2:25 gcp linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_apply_policy_default_values_task_cluster.yml.tmpl
2:24 azure windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=app.yml.tmpl
2:23 azure windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_apply_policy_default_values_job_cluster.yml.tmpl
2:21 gcp linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=cluster.yml.tmpl
2:20 aws windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_pydabs_10_tasks.yml.tmpl
2:19 gcp windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_pydabs_10_tasks.yml.tmpl
2:19 azure windows TestAccept/bundle/deploy/mlops-stacks/DATABRICKS_BUNDLE_ENGINE=terraform
2:19 gcp windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=registered_model.yml.tmpl
2:19 azure windows TestFilerWorkspaceFilesExtensionsRead
2:16 aws windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=schema.yml.tmpl
2:14 gcp linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=model_with_permissions.yml.tmpl
2:13 gcp windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=schema_uppercase_name.yml.tmpl
2:11 aws windows TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=terraform
2:11 gcp linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=pipeline_config_dots.yml.tmpl
2:10 gcp linux TestFilerWorkspaceFilesExtensionsReadDir
2:10 aws windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=app.yml.tmpl
2:09 aws windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=model.yml.tmpl
2:07 azure windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=pipeline_allow_duplicate_names.yml.tmpl
2:06 azure linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=volume_path_job_ref.yml.tmpl
2:04 gcp linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=app.yml.tmpl
2:03 gcp windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=volume.yml.tmpl
2:02 azure linux TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=model.yml.tmpl
2:00 azure windows TestAccept/bundle/invariant/migrate/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=job_with_task.yml.tmpl
2:00 aws linux TestAccept/bundle/resources/grants/schemas/remove_all/DATABRICKS_BUNDLE_ENGINE=terraform

@radakam
radakam force-pushed the fix-empty-grants-migrate branch 2 times, most recently from 0c35ccb to 61002e0 Compare July 24, 2026 08:08
radakam added 3 commits July 24, 2026 08:16
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.
With the direct-engine fix in place, the migrate invariant no longer shows a
spurious grants -> create for an empty grants: [] list, so remove the
no_empty_grants exclude added by the invariant-config PR and regenerate the
migrate matrix. This is the variant the fuzz-found config was disabled for.
@radakam
radakam force-pushed the fix-empty-grants-migrate branch from 61002e0 to 5a47a08 Compare July 24, 2026 08:18
denik added a commit that referenced this pull request Jul 24, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
denik added a commit that referenced this pull request Jul 24, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
Comment thread bundle/direct/bundle_plan.go Outdated
denik added a commit that referenced this pull request Jul 24, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
radakam added 3 commits July 27, 2026 07:05
The planner asserted on *dresources.GrantsState directly to detect an empty
grants list. Add an optional SkipCreate method to IResource so the predicate
lives with the resource, and have makePlan consult it through the adapter.

The state lookup stays in the planner: a node that already has state must keep
its plan entry even when creating it would be a no-op, otherwise it falls into
the delete branch and grants' no-op DoDelete would silently stop revoking.
…grants-migrate

# Conflicts:
#	bundle/direct/dresources/grants_test.go
@radakam
radakam requested a review from denik July 28, 2026 07:02
denik added a commit that referenced this pull request Jul 29, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
denik added a commit that referenced this pull request Jul 29, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
denik added a commit that referenced this pull request Jul 29, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
denik added a commit that referenced this pull request Jul 29, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
denik added a commit that referenced this pull request Jul 29, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
denik added a commit that referenced this pull request Jul 29, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
radakam added a commit that referenced this pull request Jul 29, 2026
This is the only combination that reaches reconcile on the
Terraform-first path with a perturbed config, which is how the empty
grants and secret-scope `level` bugs were found in the first place.

The nightly (drift on, wide seed window) is expected to stop early on
it until those fixes land: seed 7 empties a schema's grants list and
hits #6039. The committed run (5 seeds, drift off) stays
green, so per-PR CI is unaffected.
@denik

denik commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What happens if I have grants and then replace it with grants: []? Do we have a test for that?

Emptying grants on a node that already has state must still plan the revoke.
SkipCreate is only consulted for nodes without state; applied unconditionally
the node would fall into the delete branch, where grants' no-op DoDelete
silently keeps the grant.

The engines agree on the end state but not on the plan summary: direct keeps
the emptied node in state and counts it as unchanged, terraform has no grants
resource left, so the plan output is per-engine.
denik added a commit that referenced this pull request Jul 30, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
denik added a commit that referenced this pull request Jul 30, 2026
grants: [] triggers a spurious grants update when the plan is computed from
saved state rather than a live remote read (config has a non-nil empty slice,
state persists none). --planmode=offline hits the same known direct-engine bug
that migrate already excludes this config for (fixed by #6039). Exclude the
offline variant until that lands, mirroring migrate/test.toml.

Co-authored-by: Isaac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants