Skip to content

Add Cluster Policy support to bundle resources - #6225

Open
Sankalp-Mittal wants to merge 29 commits into
mainfrom
sankalp-mittal/cluster-policy-resource
Open

Add Cluster Policy support to bundle resources#6225
Sankalp-Mittal wants to merge 29 commits into
mainfrom
sankalp-mittal/cluster-policy-resource

Conversation

@Sankalp-Mittal

@Sankalp-Mittal Sankalp-Mittal commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Changes

Add support for a new cluster_policies resource type in DABs. Cluster policies are admin-defined guardrails that constrain how clusters can be configured (allowed node types, fixed autotermination, required tags, cost limits). This lets users manage them declaratively alongside their other bundle resources. This includes:

  • New resources.ClusterPolicy config type, wrapping compute.CreatePolicy.
  • Direct-engine resource implementation (ResourceClusterPolicy) covering create/read/update/delete plus PrepareState/RemapState.
  • Wiring into resource enumerations: supported resources, direct-engine registration (all.go), dev-mode name prefixing (presets), and the workspace URL pattern.
  • Authoring the policy definition as native inline YAML, in addition to a JSON string or block scalar (see "Key points" below).
  • Regenerated JSON schema, reference schema, and annotations.
  • Testserver handlers for the /api/2.0/policies/clusters/* endpoints.

Cluster policies are only supported in direct deployment mode — they have no Terraform provider mapping here, so they are excluded from Terraform-mode conversion.

Why

Cluster policies were previously only referenceable from bundles via a variable lookup (resolving an existing policy name to its policy_id). Users want to define and own the policies themselves as code, instead of provisioning them out-of-band through the UI.

Example

The definition can be an inline JSON string:

resources:
  cluster_policies:
    team_policy:
      name: my_cluster_policy
      definition: '{"spark_version":{"type":"fixed","value":"13.3.x-scala2.12"}}'

…or native YAML, which is normalized to the same JSON string at deploy time:

resources:
  cluster_policies:
    team_policy:
      name: my_cluster_policy
      definition:
        spark_version:
          type: fixed
          value: 13.3.x-scala2.12

Key points to note

Update uses Edit, and it is a full replace. The SDK's update call is ClusterPolicies.Edit (not Update); DoUpdate sends the whole desired policy along with the existing policy_id.

Output-only fields are excluded from state. policy_id, created_at_timestamp, creator_user_name, and is_default are returned on read but never written back, so RemapState copies only the fields shared with CreatePolicy.

Dev-mode name prefixing. The policy name is a user-facing display name (unique, 1–100 chars), not the API identity (policy_id), so it is prefixed in development mode to avoid collisions between developers without changing identity.

Inline-YAML definition replicates the Genie serialized_space pattern. A top-level Definition any field shadows the embedded compute.CreatePolicy.Definition string, and a per-resource dyn-layer mutator (ConfigureClusterPolicyDefinition, registered right after ConfigureGenieSpaceSerializedSpace()) normalizes it before deploy: a string passes through unchanged, an inline map/sequence is marshaled to a compact JSON string (json.Marshal, sorted keys), and any other kind is rejected with a diagnostic. PrepareState then copies the normalized string into state; the state type stays compute.CreatePolicy, so there is no state-compatibility break.

The conversion is done at the config layer on purpose: authoring structured content inline in YAML decodes small integers as Go int, while the state JSON round-trip decodes them as float64, so structdiff reports false drift on every plan. Normalizing to a canonical JSON string makes config-side and state-side hold an identical string — the same reason Genie does it. The dashboard serialized_dashboard mutator is deliberately not the model here: it only reads a file_path into an opaque string and does no YAML→JSON conversion.

Tests

  • Acceptance group acceptance/bundle/resources/cluster_policies/:
    • basic/ — single-policy validate/summary/deploy plus create/edit/delete, pinned to the direct engine.
    • job_ref/ — a job task referencing the policy via ${resources.cluster_policies.pol.id}; asserts the direct engine orders policy create before job create (and job delete before policy delete on destroy) and resolves the policy id into the job body.
    • definition_multiline/ — a block-scalar JSON definition preserved as a \n-escaped string end to end.
    • definition_yaml/ — a native YAML mapping that serializes to the same compact JSON string the API receives as basic/.
    • direct-only/ — deploying in terraform mode fails with the actionable "only supported with direct deployment mode" error.
    • missing_ref/ — a job referencing an undeclared cluster_policies.* resource fails to plan with a dependency-resolution error.
    • cross_bundle_id/ — one bundle creates a policy; a separate bundle consumes it by hardcoding the generated policy_id (captured from the producer's state) in a job.
    • cross_bundle_lookup/ — a separate bundle resolves the policy created by the producer via a variable lookup: {cluster_policy: <name>} and uses the resolved id in a job.
  • Unit tests for ConfigureClusterPolicyDefinition (string passthrough, map/sequence → JSON, invalid-kind diagnostic, absent field) and for ResourceClusterPolicy.PrepareState.
  • Made the testserver's GET /api/2.0/policies/clusters/list stateful (returning created policies, seeded with the two legacy names the variable-lookup tests rely on) so the lookup-by-name flow is exercisable.
  • Added cluster_policies coverage to shared suites: bundle permissions, presets/target-mode prefixing, run-as, state-load round-tripping, and the Terraform lifecycle exclusion list.

This pull request and its description were written by Isaac.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 1ce85e0

Run: 31706870130

Env 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 5 3 296 1434 6:28
💚​ aws windows 5 3 298 1432 5:26
💚​ azure linux 5 3 295 1434 8:01
💚​ azure windows 5 3 297 1432 6:04
💚​ gcp linux 2 4 296 1434 8:24
💚​ gcp windows 2 4 298 1432 7:40
8 interesting tests: 5 RECOVERED, 3 SKIP
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 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ 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
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R
Top 8 slowest tests (at least 2 minutes):
duration env testname
3:39 gcp windows TestAccept/bundle/invariant/delete_idempotent/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=cluster_policy.yml.tmpl/READPLAN=
3:22 aws windows TestAccept
3:06 gcp windows TestAccept
3:06 azure windows TestAccept
3:05 gcp linux TestAccept
3:02 azure linux TestAccept
2:58 aws linux TestAccept
2:24 azure linux TestAccept/bundle/invariant/delete_idempotent/DATABRICKS_BUNDLE_ENGINE=direct/INPUT_CONFIG=cluster_policy.yml.tmpl/READPLAN=1

"vector_search_indexes",
"volumes",
},
require.Equal(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This change is due to the linter

@Sankalp-Mittal
Sankalp-Mittal force-pushed the sankalp-mittal/cluster-policy-resource branch 2 times, most recently from c67e925 to 01b7a51 Compare August 13, 2026 12:19
The cluster_policies resource was added without regenerating derived files,
failing validate-generated and the refschema acceptance test.

Co-authored-by: Isaac
Adding cluster_policies with a workspace URL pattern extended the list of
openable resource types, but the workspace_open command tests hardcoded the
old list. Add cluster_policies to the expected completion, help text, and
unknown-type error assertions.

Co-authored-by: Isaac
The workspace open command's supported-resource-type list now includes
cluster_policies; regenerate the golden output.

Co-authored-by: Isaac
TestInvariantConfigsCoverage requires every resource type to have an
invariant config. Add a cluster_policy config and register it in the
invariant matrix. Exclude it from the migrate suite (terraform-seeded; the
resource is direct-only) and continue_293 (unsupported on the old CLI).
Regenerate the affected out.test.toml snapshots.

Co-authored-by: Isaac
Restructure the single cluster_policies acceptance test into a group:
- move the existing test unchanged into basic/
- add job_ref/: a job task referencing the policy via
  ${resources.cluster_policies.pol.id}, asserting the direct engine
  orders policy create before job create (and job delete before policy
  delete on destroy) and resolves the policy id into the job body
- add definition_multiline/: a block-scalar JSON definition, asserting
  it is preserved as a newline-escaped string end to end

No production code change.

Co-authored-by: Isaac
The cluster policy `definition` was a plain JSON string. Add a top-level
`Definition any` field that shadows the embedded compute.CreatePolicy
string so the definition can also be written as native YAML.

ConfigureClusterPolicyDefinition normalizes an inline map/sequence to a
JSON string at the dyn layer (same approach as genie serialized_space),
avoiding int/float structdiff drift; PrepareState copies the normalized
string into state. A string definition passes through unchanged.

Co-authored-by: Isaac
- unit test for ConfigureClusterPolicyDefinition (string passthrough,
  map/sequence -> JSON string, invalid-kind diagnostic, absent field)
- unit test for ResourceClusterPolicy.PrepareState copying the
  normalized string into state
- acceptance test definition_yaml/: a native YAML mapping serializes to
  the same compact JSON string the API receives as the basic test

Co-authored-by: Isaac
The inline-YAML definition feature added an 'any'-typed definition input
field but did not regenerate out.fields.txt, failing validate-generated.

Co-authored-by: Isaac
Add a direct-only acceptance test asserting that deploying a
cluster_policies resource with DATABRICKS_BUNDLE_ENGINE=terraform fails
with an actionable error, mirroring secrets/direct-only.

Co-authored-by: Isaac
Add an acceptance test where a job new_cluster references an undeclared
cluster_policies resource; bundle plan fails with a config-time
dependency-resolution error.

Co-authored-by: Isaac
One bundle creates a cluster policy; a second, separate bundle consumes
it by hardcoding the generated policy_id (captured from the first
bundle's state) in a job's new_cluster. Asserts the consumer job's
create request carries the producer's policy id.

Co-authored-by: Isaac
Make the testserver's /api/2.0/policies/clusters/list stateful so it
returns policies created via the create handler, seeding the two legacy
names the variable-lookup tests rely on. Add an acceptance test where a
consumer bundle resolves a policy created by a separate producer bundle
through a variable lookup {cluster_policy: <name>}, then uses the
resolved id in a job.

Co-authored-by: Isaac
@Sankalp-Mittal
Sankalp-Mittal force-pushed the sankalp-mittal/cluster-policy-resource branch from 08272eb to a93ad20 Compare August 13, 2026 13:40
Instead of inline JSON everywhere, spread the three authoring forms
across the non-targeted tests for incidental coverage: job_ref and
cross_bundle_lookup use a multiline JSON block scalar, direct-only uses
native YAML, cross_bundle_id keeps inline JSON. basic and the two
targeted definition tests are unchanged.

Co-authored-by: Isaac
@Sankalp-Mittal
Sankalp-Mittal marked this pull request as ready for review August 13, 2026 13:58
@github-actions

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/bundle/ - needs approval

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

/bundle/ - needs approval

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

General files (require maintainer)

7 files changed
Based on git history:

  • @janniklasrose -- recent work in bundle/config/mutator/resourcemutator/, bundle/direct/dresources/, libs/testserver/

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

case dyn.KindInvalid, dyn.KindNil, dyn.KindString:
// KindInvalid means definition is absent; leave it for backend validation.
return v, nil
case dyn.KindMap, dyn.KindSequence:

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.

why do we allow KindSequence? yes, it's valid JSON but it's not valid policy language (or is it?)

@@ -0,0 +1,2 @@
Error: invalid dependency "${resources.cluster_policies.missing.id}", no such node "resources.cluster_policies.missing"

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.

this test case is unnecessary for cluster policy specifically

@@ -0,0 +1,4 @@
title "Deploy should fail in terraform mode"
trace $CLI bundle deploy 2>&1 | contains.py \
"Cluster Policy resources are only supported with direct deployment mode" \

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.

no need for a resource-specific test for this - we already encode resources not supported in TF elsewhere

cluster_policies:
pol:
name: my_policy
definition:

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.

this test would be more powerful if you also added a non-string field as part of the definition to make sure yml -> json mapping is done correctly


# catalogs: no api field behaviors

# cluster_policies: no api field behaviors

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.

would be great to add hashed_in_state behaviour for the policy field after #6105 lands & cover with acceptance test for a large policy doc

>>> [CLI] bundle validate -o json
{
"pol": {
"definition": "{\n \"spark_version\": {\n \"type\": \"fixed\",\n \"value\": \"13.3.x-scala2.12\"\n }\n}",

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.

I'd be interested to see if the backend normalises/formats this json

@@ -0,0 +1,17 @@
title "Bundle A creates the cluster policy"

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.

not sure about the value of the cross_bundle_ tests - this is not really resource specific and standard DABs behaviuor

@@ -0,0 +1,6 @@
Cloud = false

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.

please enable cloud for some of the tests once you have run them and verified they pass


trace $CLI bundle summary

title "Update the cluster policy name"

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.

can you add a new test case (not in basic/) for an out-of-band edit to the policy?

Comment on lines +211 to +213
// Reads (dynamic): resources.cluster_policies.*.definition
// Updates (dynamic): resources.cluster_policies.*.definition (inline YAML -> JSON string)
ConfigureClusterPolicyDefinition(),

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.

Seems there's definition and policy_family_definition_overrides, both are JSON policies

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