Skip to content

Fix postgres_role role_id serialization; add no_drift readplan invariant - #5672

Merged
denik merged 4 commits into
mainfrom
denik/serialized-plan-test
Jun 22, 2026
Merged

Fix postgres_role role_id serialization; add no_drift readplan invariant#5672
denik merged 4 commits into
mainfrom
denik/serialized-plan-test

Conversation

@denik

@denikdenik commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Why

postgres_role stores role_id and parent as top-level fields on PostgresRoleState, alongside the embedded postgres.RoleRoleSpec. That spec defines its own MarshalJSON/UnmarshalJSON, which were promoted onto PostgresRoleState and (de)serialize only the spec fields — so serializing the state silently drops role_id and parent.

This only bites bundle deploy --plan. bundle plan -o json serializes each resource's state into the plan file's new_state with the lossy marshaler, so the file already lacks role_id. On deploy, that new_state is deserialized back into a PostgresRoleState and used as the create payload, so CreateRole is called with an empty role_id; the server generates its own, and the next plan sees the immutable role_id diverge from config and schedules a recreate.

A plain bundle deploy never uses serialized state to build the create request — it applies the typed PostgresRoleState from PrepareState directly, so role_id reaches the server. It does write the same lossy state file, but that copy isn't used for the create, and a later plan reconciles the immutable role_id against remote (which holds the configured value), so the drift is masked.

What

  • Add explicit MarshalJSON/UnmarshalJSON to PostgresRoleState so every field survives serialization.
  • Extend the no_drift invariant test with READPLAN variants, exercising deploy --plan across every resource type.
  • Add a focused readplan/postgres_role acceptance test that commits the serialized plan and the create request, guarding the exact regression.

This pull request and its description were written by Isaac.

denik added 2 commits June 22, 2026 08:56
…readplan invariant
The embedded postgres.RoleRoleSpec has its own MarshalJSON/UnmarshalJSON, which
were promoted to PostgresRoleState and silently dropped role_id and parent when
the plan was serialized to JSON. Deploying from such a plan (`deploy --plan`)
created the role with an empty role_id, the server generated its own, and the
next plan reported phantom "recreate" drift on the immutable role_id.
Add explicit (Un)MarshalJSON on PostgresRoleState so all fields round-trip.
Extend the no_drift invariant test with READPLAN variants so it exercises
`deploy --plan` for every resource type, and add a focused readplan/postgres_role
acceptance test that pins the serialized plan and create request.
Co-authored-by: Isaac
@denik
deniktemporarily deployed to test-trigger-is June 22, 2026 06:58 — with GitHub Actions Inactive
@denik
deniktemporarily deployed to test-trigger-is June 22, 2026 06:58 — with GitHub Actions Inactive
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 4eafd92

Run: 27946842423

Env🟨​KNOWN🔄​flaky💚​RECOVERED🙈​SKIP✅​pass🙈​skipTime
🟨​aws linux71325910148:00
🟨​aws windows713261101211:30
💚​aws-ucws linux71335292911:11
💚​aws-ucws windows7133549279:17
🔄​azure linux211526010127:13
💚​azure windows11526410108:11
💚​azure-ucws linux11535792511:20
💚​azure-ucws windows1153599239:17
💚​gcp linux11526110147:43
💚​gcp windows11526310128:27
22 interesting tests: 13 SKIP, 7 KNOWN, 2 flaky
Test Nameaws linuxaws windowsaws-ucws linuxaws-ucws windowsazure linuxazure windowsazure-ucws linuxazure-ucws windowsgcp linuxgcp windows
🟨​TestAccept🟨​K🟨​K💚​R💚​R💚​R💚​R💚​R💚​R💚​R💚​R
🙈​TestAccept/bundle/invariant/no_drift🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/permissions🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🟨​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions🟨​K🟨​K💚​R💚​R🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🟨​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct🟨​K🟨​K💚​R💚​R
🟨​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform🟨​K🟨​K💚​R💚​R
🟨​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions🟨​K🟨​K💚​R💚​R🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🟨​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct🟨​K🟨​K💚​R💚​R
🟨​TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform🟨​K🟨​K💚​R💚​R
🙈​TestAccept/bundle/resources/postgres_branches/basic🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/postgres_branches/recreate🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/postgres_branches/replace_existing🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/postgres_branches/update_protected🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/postgres_branches/without_branch_id🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/postgres_endpoints/basic🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/postgres_projects/update_display_name🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/synced_database_tables/basic🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🙈​TestAccept/ssh/connection🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S🙈​S
🔄​TestFilerWorkspaceNotebook✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p✅​p✅​p
🔄​TestFilerWorkspaceNotebook/scalaJupyterNb.ipynb✅​p✅​p✅​p✅​p🔄​f✅​p✅​p✅​p✅​p✅​p
Top 24 slowest tests (at least 2 minutes):
durationenvtestname
4:37gcp linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
4:20gcp windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
4:17gcp linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
4:11gcp windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:56gcp windowsTestAccept
3:54azure-ucws windowsTestAccept
3:48azure windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:46azure-ucws windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:44azure-ucws linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:36azure windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:26aws-ucws windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:22aws linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:11aws-ucws windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:10azure windowsTestAccept
3:01aws-ucws linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:00aws linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:57aws-ucws windowsTestAccept
2:55azure-ucws linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:53azure-ucws windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:44azure linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:35aws-ucws linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:35aws windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:35aws windowsTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:34azure linuxTestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform

@denikdenik changed the title Fix postgres_role role_id dropped from serialized plan; add no_drift readplan invariantFix postgres_role role_id serialization; add no_drift readplan invariantJun 22, 2026
…tion
The serialized plan state now carries role_id and parent, so the create plan
records them under new_state.value and the no_change/update/restore plans no
longer emit phantom remote_already_set change entries with old="".
Co-authored-by: Isaac
@denik
deniktemporarily deployed to test-trigger-is June 22, 2026 07:42 — with GitHub Actions Inactive
@denik
deniktemporarily deployed to test-trigger-is June 22, 2026 07:42 — with GitHub Actions Inactive

@pieternpietern left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great find!

Any ideas how we can avoid this class of bugs entirely?

The full three-resource plan is fed to `deploy --plan` via an ignored plan.json;
the committed golden keeps just resources.postgres_roles.role, which is the
entry whose new_state must carry role_id and parent.
Co-authored-by: Isaac
@denik
deniktemporarily deployed to test-trigger-is June 22, 2026 10:39 — with GitHub Actions Inactive
@denik
deniktemporarily deployed to test-trigger-is June 22, 2026 10:39 — with GitHub Actions Inactive
@denik
denik enabled auto-merge June 22, 2026 10:51
@denik
denik added this pull request to the merge queueJun 22, 2026
Merged via the queue into main with commit e7db777Jun 22, 2026
23 checks passed
@denik
denik deleted the denik/serialized-plan-test branch June 22, 2026 11:40
@denik

Copy link
Copy Markdown
ContributorAuthor

Great find!

Any ideas how we can avoid this class of bugs entirely?

@pietern please take a look at #5681

deco-sdk-taggingBot added a commit that referenced this pull request Jun 24, 2026
## Release v1.5.0
### CLI
* `workspace export-dir` no longer aborts when a workspace object's name is not a legal local filename (e.g. a notebook named `New Notebook 2026-05-04 13:54:24` whose `:` is illegal on Windows). Such files are now exported under a sanitized name with a warning and the export completes ([#5171](#5171)).
* `ssh connect` now opens an interactive `bash` login shell by default instead of the compute image's default `/bin/sh`, falling back gracefully when `bash` is unavailable. Passing an explicit remote command (`-- <cmd>`) is unaffected ([#5687](#5687)).
* `ssh connect` interactive sessions now start in the user's workspace home folder (`/Workspace/Users/<email>`) instead of the OS home directory, falling back to the OS home when that folder is unavailable ([#5688](#5688)).
### Bundles
* Add documentation for the common bundle resource fields `permissions`, `lifecycle`, and `grants` in the JSON schema, so they surface in editor completions and the docs.
* `bundle run` now prints the modern job run URL (`/jobs/<id>/runs/<id>`) so that non-admin users permitted to view the run are taken to the run instead of the workspace homepage.
* References to a registered model's `registered_model_id` now resolve under the direct engine, matching Terraform behavior ([#5621](#5621)).
* Fix missing field descriptions in the bundle JSON schema for fields whose upstream API docs arrived after the field was first annotated (e.g. `vector_search_endpoints.*.target_qps`); stale placeholder markers no longer hide them ([#5588](#5588)).
* Fix `bundle deploy --plan` dropping a `postgres_role`'s `role_id`, which caused the role to be recreated on the next deploy ([#5672](#5672)).
* direct: Fix spurious cluster recreate when `apply_policy_default_values: true` is set ([#5693](#5693)).
* direct: New 'deployment migrate' implementation that parses terraform state instead of fetching resources state from the backend ([#5399](#5399)).
### Dependency updates
* Bump `github.com/databricks/databricks-sdk-go` from v0.141.0 to v0.147.0 ([#5636](#5636)).
* Bump Terraform provider from v1.117.0 to v1.118.0 ([#5637](#5637)).
Chambras pushed a commit to Chambras/newDatabrickscli that referenced this pull request Jul 6, 2026
…atabricks#5681)
Direct-engine resources persist their StateType to the state file and
emit RemoteType in the plan's `remote_state` field, both via
`encoding/json`. A wrapper that embeds an SDK type with its own
`MarshalJSON` but omits its own silently drops the wrapper's extra
fields. Nothing tested this.
The same bug class was fixed manually in databricks#5672 (`role_id`); these tests
turn it into a guard across every resource.
Adds round-trip tests and fixes two resources that had this bug:
- `VectorSearchEndpointRemote` dropped `endpoint_uuid`
- `MlflowModelRemote` dropped `model_id`
Both fields carry the numeric ID the permissions API needs.
Tests:
- `TestRoundtripFixtureStateType` — StateType round-trip on a realistic
per-resource fixture
- `TestRoundtripAllFieldsStateType` / `TestRoundtripAllFieldsRemoteType`
— synthetic all-fields round-trip, so a dropped field is always
observable regardless of fixture coverage
- plus a concrete-value RemoteType check in the existing `testCRUD`
InputType is intentionally not covered in this PR - many issues found
but it is never serialized via `encoding/json` (the bundle config layer
uses dyn).
This pull request and its description were written by Isaac.
Sign up for freeto 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

@denik@eng-dev-ecosystem-bot@pietern