From 487db7e3c779617d4cfd4db05d239e0c2bcc56d8 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 20 Aug 2026 12:39:49 +0200 Subject: [PATCH 1/3] Split dresources resources.yml into one file per resource resources.yml and resources.generated.yml become .yaml and .generated.yaml, omitted when they have no rules. The resource type comes from the file name, so each file holds the rules directly with no enclosing keys. The per-resource files use .yaml so that //go:embed *.yaml picks up exactly these, and not the apitypes*.yml inputs of the generator. --- .../direct/dresources/alerts.generated.yaml | 21 ++++ bundle/direct/dresources/apps.generated.yaml | 45 +++++++ bundle/direct/dresources/apps.yaml | 16 +++ bundle/direct/dresources/catalogs.yaml | 15 +++ bundle/direct/dresources/clusters.yaml | 47 +++++++ .../dresources/dashboards.generated.yaml | 19 +++ bundle/direct/dresources/dashboards.yaml | 12 ++ .../database_catalogs.generated.yaml | 10 ++ .../direct/dresources/database_catalogs.yaml | 11 ++ .../database_instances.generated.yaml | 62 ++++++++++ .../direct/dresources/database_instances.yaml | 3 + .../dresources/experiments.generated.yaml | 11 ++ bundle/direct/dresources/experiments.yaml | 15 +++ .../external_locations.generated.yaml | 9 ++ .../direct/dresources/external_locations.yaml | 10 ++ .../dresources/genie_spaces.generated.yaml | 7 ++ bundle/direct/dresources/genie_spaces.yaml | 5 + bundle/direct/dresources/instance_pools.yaml | 24 ++++ bundle/direct/dresources/job_runs.yaml | 42 +++++++ bundle/direct/dresources/jobs.yaml | 115 ++++++++++++++++++ .../model_serving_endpoints.generated.yaml | 10 ++ .../dresources/model_serving_endpoints.yaml | 82 +++++++++++++ bundle/direct/dresources/models.yaml | 18 +++ .../dresources/pipelines.generated.yaml | 7 ++ bundle/direct/dresources/pipelines.yaml | 73 +++++++++++ .../postgres_branches.generated.yaml | 27 ++++ .../direct/dresources/postgres_branches.yaml | 10 ++ .../postgres_catalogs.generated.yaml | 15 +++ .../direct/dresources/postgres_catalogs.yaml | 10 ++ .../postgres_databases.generated.yaml | 9 ++ .../direct/dresources/postgres_databases.yaml | 10 ++ .../postgres_endpoints.generated.yaml | 25 ++++ .../direct/dresources/postgres_endpoints.yaml | 10 ++ .../postgres_projects.generated.yaml | 25 ++++ .../direct/dresources/postgres_projects.yaml | 4 + .../dresources/postgres_roles.generated.yaml | 15 +++ bundle/direct/dresources/postgres_roles.yaml | 21 ++++ .../postgres_synced_tables.generated.yaml | 29 +++++ .../dresources/postgres_synced_tables.yaml | 12 ++ .../direct/dresources/quality_monitors.yaml | 6 + .../registered_models.generated.yaml | 7 ++ .../direct/dresources/registered_models.yaml | 37 ++++++ bundle/direct/dresources/schemas.yaml | 17 +++ .../dresources/secret_scopes.permissions.yaml | 6 + bundle/direct/dresources/secret_scopes.yaml | 14 +++ .../direct/dresources/secrets.generated.yaml | 34 ++++++ bundle/direct/dresources/secrets.yaml | 3 + bundle/direct/dresources/sql_warehouses.yaml | 37 ++++++ .../synced_database_tables.generated.yaml | 26 ++++ .../dresources/synced_database_tables.yaml | 11 ++ .../dresources/vector_search_endpoints.yaml | 12 ++ .../dresources/vector_search_indexes.yaml | 31 +++++ bundle/direct/dresources/volumes.yaml | 21 ++++ 53 files changed, 1173 insertions(+) create mode 100644 bundle/direct/dresources/alerts.generated.yaml create mode 100644 bundle/direct/dresources/apps.generated.yaml create mode 100644 bundle/direct/dresources/apps.yaml create mode 100644 bundle/direct/dresources/catalogs.yaml create mode 100644 bundle/direct/dresources/clusters.yaml create mode 100644 bundle/direct/dresources/dashboards.generated.yaml create mode 100644 bundle/direct/dresources/dashboards.yaml create mode 100644 bundle/direct/dresources/database_catalogs.generated.yaml create mode 100644 bundle/direct/dresources/database_catalogs.yaml create mode 100644 bundle/direct/dresources/database_instances.generated.yaml create mode 100644 bundle/direct/dresources/database_instances.yaml create mode 100644 bundle/direct/dresources/experiments.generated.yaml create mode 100644 bundle/direct/dresources/experiments.yaml create mode 100644 bundle/direct/dresources/external_locations.generated.yaml create mode 100644 bundle/direct/dresources/external_locations.yaml create mode 100644 bundle/direct/dresources/genie_spaces.generated.yaml create mode 100644 bundle/direct/dresources/genie_spaces.yaml create mode 100644 bundle/direct/dresources/instance_pools.yaml create mode 100644 bundle/direct/dresources/job_runs.yaml create mode 100644 bundle/direct/dresources/jobs.yaml create mode 100644 bundle/direct/dresources/model_serving_endpoints.generated.yaml create mode 100644 bundle/direct/dresources/model_serving_endpoints.yaml create mode 100644 bundle/direct/dresources/models.yaml create mode 100644 bundle/direct/dresources/pipelines.generated.yaml create mode 100644 bundle/direct/dresources/pipelines.yaml create mode 100644 bundle/direct/dresources/postgres_branches.generated.yaml create mode 100644 bundle/direct/dresources/postgres_branches.yaml create mode 100644 bundle/direct/dresources/postgres_catalogs.generated.yaml create mode 100644 bundle/direct/dresources/postgres_catalogs.yaml create mode 100644 bundle/direct/dresources/postgres_databases.generated.yaml create mode 100644 bundle/direct/dresources/postgres_databases.yaml create mode 100644 bundle/direct/dresources/postgres_endpoints.generated.yaml create mode 100644 bundle/direct/dresources/postgres_endpoints.yaml create mode 100644 bundle/direct/dresources/postgres_projects.generated.yaml create mode 100644 bundle/direct/dresources/postgres_projects.yaml create mode 100644 bundle/direct/dresources/postgres_roles.generated.yaml create mode 100644 bundle/direct/dresources/postgres_roles.yaml create mode 100644 bundle/direct/dresources/postgres_synced_tables.generated.yaml create mode 100644 bundle/direct/dresources/postgres_synced_tables.yaml create mode 100644 bundle/direct/dresources/quality_monitors.yaml create mode 100644 bundle/direct/dresources/registered_models.generated.yaml create mode 100644 bundle/direct/dresources/registered_models.yaml create mode 100644 bundle/direct/dresources/schemas.yaml create mode 100644 bundle/direct/dresources/secret_scopes.permissions.yaml create mode 100644 bundle/direct/dresources/secret_scopes.yaml create mode 100644 bundle/direct/dresources/secrets.generated.yaml create mode 100644 bundle/direct/dresources/secrets.yaml create mode 100644 bundle/direct/dresources/sql_warehouses.yaml create mode 100644 bundle/direct/dresources/synced_database_tables.generated.yaml create mode 100644 bundle/direct/dresources/synced_database_tables.yaml create mode 100644 bundle/direct/dresources/vector_search_endpoints.yaml create mode 100644 bundle/direct/dresources/vector_search_indexes.yaml create mode 100644 bundle/direct/dresources/volumes.yaml diff --git a/bundle/direct/dresources/alerts.generated.yaml b/bundle/direct/dresources/alerts.generated.yaml new file mode 100644 index 0000000000..abf522359e --- /dev/null +++ b/bundle/direct/dresources/alerts.generated.yaml @@ -0,0 +1,21 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: create_time + reason: spec:output_only + - field: effective_run_as + reason: spec:output_only + - field: evaluation.last_evaluated_at + reason: spec:output_only + - field: evaluation.state + reason: spec:output_only + - field: id + reason: spec:output_only + - field: lifecycle_state + reason: spec:output_only + - field: owner_user_name + reason: spec:output_only + - field: update_time + reason: spec:output_only diff --git a/bundle/direct/dresources/apps.generated.yaml b/bundle/direct/dresources/apps.generated.yaml new file mode 100644 index 0000000000..6b75e5c618 --- /dev/null +++ b/bundle/direct/dresources/apps.generated.yaml @@ -0,0 +1,45 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: active_deployment + reason: spec:output_only + - field: app_status + reason: spec:output_only + - field: compute_status + reason: spec:output_only + - field: create_time + reason: spec:output_only + - field: creator + reason: spec:output_only + - field: default_source_code_path + reason: spec:output_only + - field: effective_budget_policy_id + reason: spec:output_only + - field: effective_usage_policy_id + reason: spec:output_only + - field: effective_user_api_scopes + reason: spec:output_only + - field: id + reason: spec:output_only + - field: oauth2_app_client_id + reason: spec:output_only + - field: oauth2_app_integration_id + reason: spec:output_only + - field: pending_deployment + reason: spec:output_only + - field: service_principal_client_id + reason: spec:output_only + - field: service_principal_id + reason: spec:output_only + - field: service_principal_name + reason: spec:output_only + - field: thumbnail_url + reason: spec:output_only + - field: update_time + reason: spec:output_only + - field: updater + reason: spec:output_only + - field: url + reason: spec:output_only diff --git a/bundle/direct/dresources/apps.yaml b/bundle/direct/dresources/apps.yaml new file mode 100644 index 0000000000..81291fd49c --- /dev/null +++ b/bundle/direct/dresources/apps.yaml @@ -0,0 +1,16 @@ +provided_id_fields: + - field: name + reason: id_field +backend_defaults: + # Backend sets it "MEDIUM" when not specified in the config + - field: compute_size + # lifecycle.started is derived from remote compute status in RemapState, so the + # remote side always has a value. When the user omits lifecycle from config, + # both old and new are nil and backend_defaults correctly skips the remote value. + # When the user explicitly sets lifecycle.started, old/new are non-nil and normal + # drift detection applies (e.g. detecting out-of-band stop). + - field: lifecycle + - field: lifecycle.started +ignore_remote_changes: + - field: space # This field is not yet supported by Update APIs but exposed in the API spec. TODO: fix when update APIs supports it. + reason: managed diff --git a/bundle/direct/dresources/catalogs.yaml b/bundle/direct/dresources/catalogs.yaml new file mode 100644 index 0000000000..ed3fde5c1d --- /dev/null +++ b/bundle/direct/dresources/catalogs.yaml @@ -0,0 +1,15 @@ +recreate_on_changes: + - field: storage_root + reason: immutable + - field: connection_name + reason: immutable + - field: provider_name + reason: immutable + - field: share_name + reason: immutable +updatable_id_fields: + - field: name + reason: id_changes +backend_defaults: + # UC auto-populates unity.catalog.managed..defaults.* keys after create. + - field: properties['unity.catalog.managed.*.defaults.*'] diff --git a/bundle/direct/dresources/clusters.yaml b/bundle/direct/dresources/clusters.yaml new file mode 100644 index 0000000000..9e55d86f78 --- /dev/null +++ b/bundle/direct/dresources/clusters.yaml @@ -0,0 +1,47 @@ +ignore_remote_changes: + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L361-L363 + # s.SchemaPath("aws_attributes").SetSuppressDiff() + # s.SchemaPath("azure_attributes").SetSuppressDiff() + # s.SchemaPath("gcp_attributes").SetSuppressDiff() + - field: aws_attributes + reason: managed + - field: azure_attributes + reason: managed + - field: gcp_attributes + reason: managed +backend_defaults: + # lifecycle.started is derived from remote cluster state in RemapState, so the + # remote side always has a value. When the user omits lifecycle from config, + # both old and new are nil and backend_defaults correctly skips the remote value. + # When the user explicitly sets lifecycle.started, old/new are non-nil and normal + # drift detection applies (e.g. detecting out-of-band terminate). + - field: lifecycle + - field: lifecycle.started + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L331 + # s.SchemaPath("enable_elastic_disk").SetComputed() + - field: enable_elastic_disk + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L332 + # s.SchemaPath("enable_local_disk_encryption").SetComputed() + - field: enable_local_disk_encryption + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L333 + # s.SchemaPath("node_type_id").SetComputed() + - field: node_type_id + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L334 + # s.SchemaPath("driver_node_type_id").SetComputed() + - field: driver_node_type_id + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L335 + # s.SchemaPath("driver_instance_pool_id").SetComputed() + - field: driver_instance_pool_id + + # Terraform currently does not do this, but it is a field with backend default. + # See https://github.com/databricks/cli/issues/4418 + - field: single_user_name + +# We have custom handler for this in cluster.go +# https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L109-L118 +# DataSecurityModeDiffSuppressFunc: suppress when old != "" && new == "" +#- field: data_security_mode diff --git a/bundle/direct/dresources/dashboards.generated.yaml b/bundle/direct/dresources/dashboards.generated.yaml new file mode 100644 index 0000000000..78bba33a1e --- /dev/null +++ b/bundle/direct/dresources/dashboards.generated.yaml @@ -0,0 +1,19 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +recreate_on_changes: + - field: parent_path + reason: spec:immutable + +ignore_remote_changes: + - field: create_time + reason: spec:output_only + - field: dashboard_id + reason: spec:output_only + - field: lifecycle_state + reason: spec:output_only + - field: path + reason: spec:output_only + - field: update_time + reason: spec:output_only diff --git a/bundle/direct/dresources/dashboards.yaml b/bundle/direct/dresources/dashboards.yaml new file mode 100644 index 0000000000..8760a2de8c --- /dev/null +++ b/bundle/direct/dresources/dashboards.yaml @@ -0,0 +1,12 @@ +ignore_remote_changes: + # "serialized_dashboard" locally and remotely will have different contents + # We only need to rely on etag here, and can skip this field for diff computation. + - field: serialized_dashboard + reason: etag_based + + # "dataset_catalog" and "dataset_schema" are write-only fields that are not returned by the server. + # They will always differ between local config (which has values) and remote state (which has empty strings). + - field: dataset_catalog + reason: input_only + - field: dataset_schema + reason: input_only diff --git a/bundle/direct/dresources/database_catalogs.generated.yaml b/bundle/direct/dresources/database_catalogs.generated.yaml new file mode 100644 index 0000000000..67b2aed240 --- /dev/null +++ b/bundle/direct/dresources/database_catalogs.generated.yaml @@ -0,0 +1,10 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: create_database_if_not_exists + reason: spec:input_only + + - field: uid + reason: spec:output_only diff --git a/bundle/direct/dresources/database_catalogs.yaml b/bundle/direct/dresources/database_catalogs.yaml new file mode 100644 index 0000000000..f130634412 --- /dev/null +++ b/bundle/direct/dresources/database_catalogs.yaml @@ -0,0 +1,11 @@ +provided_id_fields: + - field: name + reason: id_field +# The Database API has no UpdateDatabaseCatalog endpoint (the generated SDK +# method is a stub that returns 501 NOT_IMPLEMENTED), so this resource +# implements no DoUpdate and every settable field must recreate. The +# complementary ignore_remote_changes block in database_catalogs.generated.yaml +# handles the read side (create_database_if_not_exists is input-only, uid is +# output-only) so no-op deploys stay idempotent. +recreate_on_changes: + - reason: immutable diff --git a/bundle/direct/dresources/database_instances.generated.yaml b/bundle/direct/dresources/database_instances.generated.yaml new file mode 100644 index 0000000000..9eb00013f1 --- /dev/null +++ b/bundle/direct/dresources/database_instances.generated.yaml @@ -0,0 +1,62 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +recreate_on_changes: + - field: parent_instance_ref + reason: spec:immutable + +ignore_remote_changes: + - field: custom_tags + reason: spec:input_only + - field: enable_pg_native_login + reason: spec:input_only + - field: enable_readable_secondaries + reason: spec:input_only + - field: node_count + reason: spec:input_only + - field: parent_instance_ref.lsn + reason: spec:input_only + - field: retention_window_in_days + reason: spec:input_only + - field: stopped + reason: spec:input_only + - field: usage_policy_id + reason: spec:input_only + + - field: child_instance_refs + reason: spec:output_only + - field: creation_time + reason: spec:output_only + - field: creator + reason: spec:output_only + - field: effective_capacity + reason: spec:output_only + - field: effective_custom_tags + reason: spec:output_only + - field: effective_enable_pg_native_login + reason: spec:output_only + - field: effective_enable_readable_secondaries + reason: spec:output_only + - field: effective_node_count + reason: spec:output_only + - field: effective_retention_window_in_days + reason: spec:output_only + - field: effective_stopped + reason: spec:output_only + - field: effective_usage_policy_id + reason: spec:output_only + - field: parent_instance_ref.effective_lsn + reason: spec:output_only + - field: parent_instance_ref.uid + reason: spec:output_only + - field: pg_version + reason: spec:output_only + - field: read_only_dns + reason: spec:output_only + - field: read_write_dns + reason: spec:output_only + - field: state + reason: spec:output_only + - field: uid + reason: spec:output_only diff --git a/bundle/direct/dresources/database_instances.yaml b/bundle/direct/dresources/database_instances.yaml new file mode 100644 index 0000000000..b6ab8dc9e8 --- /dev/null +++ b/bundle/direct/dresources/database_instances.yaml @@ -0,0 +1,3 @@ +provided_id_fields: + - field: name + reason: id_field diff --git a/bundle/direct/dresources/experiments.generated.yaml b/bundle/direct/dresources/experiments.generated.yaml new file mode 100644 index 0000000000..2ca8095f66 --- /dev/null +++ b/bundle/direct/dresources/experiments.generated.yaml @@ -0,0 +1,11 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +recreate_on_changes: + - field: trace_location + reason: spec:immutable + +ignore_remote_changes: + - field: trace_location.uc_trace_location.effective_table_prefix + reason: spec:output_only diff --git a/bundle/direct/dresources/experiments.yaml b/bundle/direct/dresources/experiments.yaml new file mode 100644 index 0000000000..200bf0ffbf --- /dev/null +++ b/bundle/direct/dresources/experiments.yaml @@ -0,0 +1,15 @@ +# TF implementation: https://github.com/databricks/terraform-provider-databricks/blob/6c106e8e7052bb2726148d66309fd460ed444236/mlflow/resource_mlflow_experiment.go#L22 +recreate_on_changes: + - field: artifact_location + reason: immutable +backend_defaults: + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/mlflow/resource_mlflow_experiment.go#L34 + # SetForceNew().SetSuppressDiff(): backend generates artifact_location when not set by user + - field: artifact_location +ignore_remote_changes: + # Tags updates are not supported by TF. This mirrors that behaviour. + - field: tags + reason: terraform_compat +ignore_local_changes: + - field: tags + reason: terraform_compat diff --git a/bundle/direct/dresources/external_locations.generated.yaml b/bundle/direct/dresources/external_locations.generated.yaml new file mode 100644 index 0000000000..bb45055e26 --- /dev/null +++ b/bundle/direct/dresources/external_locations.generated.yaml @@ -0,0 +1,9 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: effective_enable_file_events + reason: spec:output_only + - field: effective_file_event_queue + reason: spec:output_only diff --git a/bundle/direct/dresources/external_locations.yaml b/bundle/direct/dresources/external_locations.yaml new file mode 100644 index 0000000000..55612ef68a --- /dev/null +++ b/bundle/direct/dresources/external_locations.yaml @@ -0,0 +1,10 @@ +recreate_on_changes: + - field: credential_name + reason: immutable + - field: encryption_details + reason: immutable + - field: file_event_queue + reason: immutable +updatable_id_fields: + - field: name + reason: id_changes diff --git a/bundle/direct/dresources/genie_spaces.generated.yaml b/bundle/direct/dresources/genie_spaces.generated.yaml new file mode 100644 index 0000000000..c5eba1a628 --- /dev/null +++ b/bundle/direct/dresources/genie_spaces.generated.yaml @@ -0,0 +1,7 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: etag + reason: spec:output_only diff --git a/bundle/direct/dresources/genie_spaces.yaml b/bundle/direct/dresources/genie_spaces.yaml new file mode 100644 index 0000000000..383a88a636 --- /dev/null +++ b/bundle/direct/dresources/genie_spaces.yaml @@ -0,0 +1,5 @@ +ignore_remote_changes: + # serialized_space locally (structured YAML) and remotely (JSON string) will differ + # textually, so we cannot meaningfully compare them for drift. + - field: serialized_space + reason: etag_based diff --git a/bundle/direct/dresources/instance_pools.yaml b/bundle/direct/dresources/instance_pools.yaml new file mode 100644 index 0000000000..7c3148b2dc --- /dev/null +++ b/bundle/direct/dresources/instance_pools.yaml @@ -0,0 +1,24 @@ +# Field behaviors follow the TF provider tags cross-referenced with the edit API (compute.EditInstancePool): +# https://github.com/databricks/terraform-provider-databricks/blob/main/pools/resource_instance_pool.go +ignore_remote_changes: + # Backend fills cloud defaults the user omits; treated as managed like clusters.yaml. + - field: aws_attributes + reason: managed + - field: azure_attributes + reason: managed + - field: gcp_attributes + reason: managed +recreate_on_changes: + # force_new and not accepted by /instance-pools/edit. + - field: disk_spec + - field: node_type_flexibility + - field: preloaded_spark_versions + - field: preloaded_docker_images +backend_defaults: + # Defaults to true server-side. + - field: enable_elastic_disk + # Backend applies a default of 60 minutes when the field is omitted. + - field: idle_instance_autotermination_minutes + values: [60] + # GCP seeds max_capacity (1000) when omitted; unconstrained as the default is cloud-dependent. + - field: max_capacity diff --git a/bundle/direct/dresources/job_runs.yaml b/bundle/direct/dresources/job_runs.yaml new file mode 100644 index 0000000000..9b1f13a39d --- /dev/null +++ b/bundle/direct/dresources/job_runs.yaml @@ -0,0 +1,42 @@ +# Every jobs.RunNow field is listed, so nothing the request carries is drift. +# TestJobRunIgnoresEveryRequestField keeps the list in step with the SDK. +# result_state is not in the list: comparing it against the remote is what +# makes a run that has not succeeded visible to the plan. +ignore_remote_changes: + - field: job_id + reason: immutable + # GetRun reports job_parameters resolved against the job's defaults, and + # nests the rest under overriding_parameters. + - field: dbt_commands + reason: effective_vs_requested + - field: jar_params + reason: effective_vs_requested + - field: job_parameters + reason: effective_vs_requested + - field: notebook_params + reason: effective_vs_requested + - field: pipeline_params + reason: effective_vs_requested + - field: python_named_params + reason: effective_vs_requested + - field: python_params + reason: effective_vs_requested + - field: spark_submit_params + reason: effective_vs_requested + - field: sql_params + reason: effective_vs_requested + # Fields the run-now request carries that GetRun never reports back. + - field: idempotency_token + reason: not_returned_by_api + - field: only + reason: not_returned_by_api + - field: performance_target + reason: not_returned_by_api + - field: queue + reason: not_returned_by_api +# A run is immutable and fire-once, so any change recreates it. Omitting +# `field` matches every field (root; see TestFieldRuleOmittedIsRoot). +# `field: ""` would instead match nothing. The one exception is a run that is +# still going, which ResourceJobRun.OverrideChangeDesc downgrades to an update. +recreate_on_changes: + - reason: immutable diff --git a/bundle/direct/dresources/jobs.yaml b/bundle/direct/dresources/jobs.yaml new file mode 100644 index 0000000000..dcb6a9e4bc --- /dev/null +++ b/bundle/direct/dresources/jobs.yaml @@ -0,0 +1,115 @@ +# version_id is set to the current DMS deployment version on every deploy, so +# it changes constantly. Ignoring it as a local and remote change keeps that +# churn from driving an update or showing as drift on its own; when the job is +# updated for any other reason, DoUpdate sends the full config via Reset, so +# the current version_id is still recorded. deployment_id is intentionally +# left out: it is stable across versions, so a change to it is worth showing. +ignore_local_changes: + - field: deployment.version_id + reason: managed by the deployment metadata service + +ignore_remote_changes: + - field: deployment.version_id + reason: managed by the deployment metadata service + + # Same as clusters.{aws,azure,gcp}_attributes — see clusters/resource_cluster.go#L361-L363 + # s.SchemaPath("aws_attributes").SetSuppressDiff() + # s.SchemaPath("azure_attributes").SetSuppressDiff() + # s.SchemaPath("gcp_attributes").SetSuppressDiff() + - field: tasks[*].new_cluster.aws_attributes + reason: managed + - field: tasks[*].new_cluster.azure_attributes + reason: managed + - field: tasks[*].new_cluster.gcp_attributes + reason: managed + - field: tasks[*].for_each_task.task.new_cluster.aws_attributes + reason: managed + - field: tasks[*].for_each_task.task.new_cluster.azure_attributes + reason: managed + - field: tasks[*].for_each_task.task.new_cluster.gcp_attributes + reason: managed + - field: job_clusters[*].new_cluster.aws_attributes + reason: managed + - field: job_clusters[*].new_cluster.azure_attributes + reason: managed + - field: job_clusters[*].new_cluster.gcp_attributes + reason: managed + + # The Jobs API accepts apply_policy_default_values but does not return it + # in GET responses, so the remote is always nil even when the user sets it. + # Ignore remote-side nil so old==new (no user change) doesn't trigger drift. + - field: tasks[*].new_cluster.apply_policy_default_values + reason: not_returned_by_api + - field: tasks[*].for_each_task.task.new_cluster.apply_policy_default_values + reason: not_returned_by_api + - field: job_clusters[*].new_cluster.apply_policy_default_values + reason: not_returned_by_api + +backend_defaults: + # Same as clusters.enable_elastic_disk — see clusters/resource_cluster.go#L331 + # s.SchemaPath("enable_elastic_disk").SetComputed() + - field: tasks[*].new_cluster.enable_elastic_disk + - field: tasks[*].for_each_task.task.new_cluster.enable_elastic_disk + - field: job_clusters[*].new_cluster.enable_elastic_disk + + # Same as clusters.enable_local_disk_encryption — see clusters/resource_cluster.go#L332 + # s.SchemaPath("enable_local_disk_encryption").SetComputed() + - field: tasks[*].new_cluster.enable_local_disk_encryption + - field: tasks[*].for_each_task.task.new_cluster.enable_local_disk_encryption + - field: job_clusters[*].new_cluster.enable_local_disk_encryption + + # Same as clusters.node_type_id — see clusters/resource_cluster.go#L333 + # s.SchemaPath("node_type_id").SetComputed() + - field: tasks[*].new_cluster.node_type_id + - field: tasks[*].for_each_task.task.new_cluster.node_type_id + - field: job_clusters[*].new_cluster.node_type_id + + # Same as clusters.driver_node_type_id — see clusters/resource_cluster.go#L334 + # s.SchemaPath("driver_node_type_id").SetComputed() + - field: tasks[*].new_cluster.driver_node_type_id + - field: tasks[*].for_each_task.task.new_cluster.driver_node_type_id + - field: job_clusters[*].new_cluster.driver_node_type_id + + # Same as clusters.driver_instance_pool_id — see clusters/resource_cluster.go#L335 + # s.SchemaPath("driver_instance_pool_id").SetComputed() + - field: tasks[*].new_cluster.driver_instance_pool_id + - field: tasks[*].for_each_task.task.new_cluster.driver_instance_pool_id + - field: job_clusters[*].new_cluster.driver_instance_pool_id + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L531 + # s.SchemaPath("format").SetComputed() + - field: format + values: ["MULTI_TASK", "SINGLE_TASK"] + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L639 + # s.SchemaPath("task", "run_if").SetSuppressDiffWithDefault(jobs.RunIfAllSuccess) + - field: "tasks[*].run_if" + values: ["ALL_SUCCESS"] + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L640 + # s.SchemaPath("task", "for_each_task", "task", "run_if").SetSuppressDiffWithDefault(jobs.RunIfAllSuccess) + - field: "tasks[*].for_each_task.task.run_if" + values: ["ALL_SUCCESS"] + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L527 + # s.SchemaPath("run_as").SetComputed() + - field: run_as + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L521-L524 + # s.SchemaPath("task", "notebook_task", "source").SetSuppressDiff() + # s.SchemaPath("task", "spark_python_task", "source").SetSuppressDiff() + # s.SchemaPath("task", "sql_task", "file", "source").SetSuppressDiff() + # s.SchemaPath("task", "dbt_task", "source").SetSuppressDiff() + - field: tasks[*].notebook_task.source + - field: tasks[*].for_each_task.task.notebook_task.source + - field: tasks[*].spark_python_task.source + - field: tasks[*].for_each_task.task.spark_python_task.source + - field: tasks[*].sql_task.file.source + - field: tasks[*].for_each_task.task.sql_task.file.source + - field: tasks[*].dbt_task.source + - field: tasks[*].for_each_task.task.dbt_task.source + + # Same as clusters.data_security_mode: backend sets this when not specified + - field: tasks[*].new_cluster.data_security_mode + - field: tasks[*].for_each_task.task.new_cluster.data_security_mode + - field: job_clusters[*].new_cluster.data_security_mode diff --git a/bundle/direct/dresources/model_serving_endpoints.generated.yaml b/bundle/direct/dresources/model_serving_endpoints.generated.yaml new file mode 100644 index 0000000000..e0a1c501ec --- /dev/null +++ b/bundle/direct/dresources/model_serving_endpoints.generated.yaml @@ -0,0 +1,10 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: telemetry_config.table_names + reason: spec:input_only + + - field: telemetry_config.inference_table_config.name + reason: spec:output_only diff --git a/bundle/direct/dresources/model_serving_endpoints.yaml b/bundle/direct/dresources/model_serving_endpoints.yaml new file mode 100644 index 0000000000..20c4a6abcb --- /dev/null +++ b/bundle/direct/dresources/model_serving_endpoints.yaml @@ -0,0 +1,82 @@ +# TF implementation: https://github.com/databricks/terraform-provider-databricks/blob/6c106e8e7052bb2726148d66309fd460ed444236/mlflow/resource_mlflow_experiment.go#L22 +provided_id_fields: + - field: name + reason: id_field +recreate_on_changes: + # description is immutable, can't be updated via API + - field: description + reason: immutable + - field: config.auto_capture_config.catalog_name + reason: immutable + - field: config.auto_capture_config.schema_name + reason: immutable + - field: config.auto_capture_config.table_name_prefix + reason: immutable + - field: route_optimized + reason: immutable +ignore_remote_changes: + # budget_policy_id is in ServingEndpointDetailed but GET never populates it + # (the API returns effective_budget_policy_id instead), so the remote always + # reports empty. This is not a backend default, so suppress remote changes. + - field: budget_policy_id + reason: no_update_api + # Accepted on write but not returned by GET. + - field: config.served_entities[*].burst_scaling_enabled + reason: input_only + # Write-only secrets: the backend stores them and returns the reference field, not the plaintext. + - field: config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext + reason: input_only + - field: config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext + reason: input_only + - field: config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext + reason: input_only + - field: config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext + reason: input_only + - field: config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext + reason: input_only + - field: config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext + reason: input_only + - field: config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext + reason: input_only + - field: config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext + reason: input_only + - field: config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext + reason: input_only + - field: config.served_entities[*].external_model.openai_config.openai_api_key_plaintext + reason: input_only + - field: config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext + reason: input_only + - field: config.served_entities[*].external_model.palm_config.palm_api_key_plaintext + reason: input_only +ignore_local_changes: + - field: budget_policy_id + reason: no_update_api + - field: rate_limits + reason: not_implemented +backend_defaults: + # Remote-only telemetry is reported as a change at this parent path. + - field: telemetry_config + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L383 + # common.CustomizeSchemaPath(m, "config", "served_entities", "name").SetComputed() + - field: config.served_entities[*].name + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L384 + # common.CustomizeSchemaPath(m, "config", "served_entities", "workload_type").SetComputed() + - field: config.served_entities[*].workload_type + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L372 + # common.CustomizeSchemaPath(m, "config", "auto_capture_config", "enabled").SetComputed() + - field: config.auto_capture_config.enabled + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L395-L396 + # route_optimized is ForceNew; backend returns false when not set by user. + - field: route_optimized + values: [false] + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L370 + # common.CustomizeSchemaPath(m, "config", "traffic_config").SetComputed() + # The backend defaults traffic_config (100% to the served entity) when the user + # does not specify one. Suppress only that default: if the user sets traffic_config + # and the remote diverges, normal drift detection still applies. + - field: config.traffic_config diff --git a/bundle/direct/dresources/models.yaml b/bundle/direct/dresources/models.yaml new file mode 100644 index 0000000000..15032fac68 --- /dev/null +++ b/bundle/direct/dresources/models.yaml @@ -0,0 +1,18 @@ +provided_id_fields: + # Recreate matches current behavior of Terraform. It is possible to rename without recreate + # but that would require dynamic select of the method during update since + # the ml.RenameModel needs to be called instead of ml.UpdateModel. + # We might reasonably choose to never fix this because this is a legacy resource. + - field: name + reason: terraform_compat +# Allowing updates for tags requires dynamic selection of the method since +# tags can only be updated by calling ml.SetModelTag or ml.DeleteModelTag methods. +# Skip annotation matches the current behavior of Terraform where tags changes are showed +# in plan but are just ignored / not applied. Since this is a legacy resource we might +# reasonably choose to not fix it here as well. +ignore_remote_changes: + - field: tags + reason: terraform_compat +ignore_local_changes: + - field: tags + reason: terraform_compat diff --git a/bundle/direct/dresources/pipelines.generated.yaml b/bundle/direct/dresources/pipelines.generated.yaml new file mode 100644 index 0000000000..08d6864249 --- /dev/null +++ b/bundle/direct/dresources/pipelines.generated.yaml @@ -0,0 +1,7 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: ingestion_definition.source_type + reason: spec:output_only diff --git a/bundle/direct/dresources/pipelines.yaml b/bundle/direct/dresources/pipelines.yaml new file mode 100644 index 0000000000..6d4ce17bb4 --- /dev/null +++ b/bundle/direct/dresources/pipelines.yaml @@ -0,0 +1,73 @@ +recreate_on_changes: + - field: storage + reason: immutable + - field: ingestion_definition.connection_name + reason: immutable + - field: ingestion_definition.ingestion_gateway_id + reason: immutable + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L204 + - field: gateway_definition.connection_id + reason: immutable + - field: gateway_definition.connection_name + reason: immutable + - field: gateway_definition.gateway_storage_catalog + reason: immutable + - field: gateway_definition.gateway_storage_schema + reason: immutable + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L209 + - field: ingestion_definition.ingest_from_uc_foreign_catalog + reason: immutable + +# See jobs.yaml: version_id is set on every deploy, so it is ignored as a +# local/remote change. deployment_id is left out so a change to it still shows. +ignore_remote_changes: + - field: deployment.version_id + reason: managed by the deployment metadata service + # "id" is handled in a special way before any fields changed + # However, it is also part of RemotePipeline via CreatePipeline. + # Thus it shows up as a remote change since we don't set on the object. + - field: id + reason: "!drop" + # QQQ should this be here? When run_as is explicitly set, the GET response echoes it back + # as a structured run_as.user_name (verified on e2-dogfood with a real user), so it may not + # be truly input-only. The explicit-set case could not be confirmed on aws-cli, azure-cli, + # or gcp-cli: those envs authenticate as a service principal that lacks servicePrincipal.user + # on itself, so it can't self-bind run_as. In the default (unset) case on all three clouds, + # GET returns only the flat run_as_user_name and no structured run_as. + - field: run_as + reason: input_only + # Carried by CreatePipeline/EditPipeline but never returned by GET, so remote + # always reads back false and a config value of true never converges. + - field: allow_duplicate_names + reason: input_only + +ignore_local_changes: + - field: deployment.version_id + reason: managed by the deployment metadata service + # "id" is output-only, providing it in config would be a mistake + - field: id + reason: "!drop" + +backend_defaults: + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L238 + # s.SchemaPath("storage").SetCustomSuppressDiff(suppressStorageDiff) + # Backend generates storage path like dbfs:/pipelines/ when not set by user. + - field: storage + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L218 + # s.SchemaPath("cluster", "node_type_id").SetComputed() + - field: clusters[*].node_type_id + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L219 + # s.SchemaPath("cluster", "driver_node_type_id").SetComputed() + - field: clusters[*].driver_node_type_id + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L220 + # s.SchemaPath("cluster", "enable_local_disk_encryption").SetComputed() + - field: clusters[*].enable_local_disk_encryption + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L229-L230 + # s.SchemaPath("event_log", "catalog").SetComputed() + # s.SchemaPath("event_log", "schema").SetComputed() + - field: event_log.catalog + - field: event_log.schema diff --git a/bundle/direct/dresources/postgres_branches.generated.yaml b/bundle/direct/dresources/postgres_branches.generated.yaml new file mode 100644 index 0000000000..600a646d39 --- /dev/null +++ b/bundle/direct/dresources/postgres_branches.generated.yaml @@ -0,0 +1,27 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +recreate_on_changes: + - field: source_branch + reason: spec:immutable + - field: source_branch_lsn + reason: spec:immutable + - field: source_branch_time + reason: spec:immutable + +ignore_remote_changes: + - field: expire_time + reason: spec:input_only + - field: is_protected + reason: spec:input_only + - field: no_expiry + reason: spec:input_only + - field: source_branch + reason: spec:input_only + - field: source_branch_lsn + reason: spec:input_only + - field: source_branch_time + reason: spec:input_only + - field: ttl + reason: spec:input_only diff --git a/bundle/direct/dresources/postgres_branches.yaml b/bundle/direct/dresources/postgres_branches.yaml new file mode 100644 index 0000000000..bbd344f4fd --- /dev/null +++ b/bundle/direct/dresources/postgres_branches.yaml @@ -0,0 +1,10 @@ +provided_id_fields: + # parent and branch_id are immutable (part of hierarchical name, not in API spec) + - field: parent + reason: id_field + - field: branch_id + reason: id_field +ignore_local_changes: + # replace_existing only takes effect on create; toggling it later is a no-op. + - field: replace_existing + reason: "input_only; cannot be updated after create" diff --git a/bundle/direct/dresources/postgres_catalogs.generated.yaml b/bundle/direct/dresources/postgres_catalogs.generated.yaml new file mode 100644 index 0000000000..9814af4d97 --- /dev/null +++ b/bundle/direct/dresources/postgres_catalogs.generated.yaml @@ -0,0 +1,15 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +recreate_on_changes: + - field: postgres_database + reason: spec:immutable + +ignore_remote_changes: + - field: branch + reason: spec:input_only + - field: create_database_if_missing + reason: spec:input_only + - field: postgres_database + reason: spec:input_only diff --git a/bundle/direct/dresources/postgres_catalogs.yaml b/bundle/direct/dresources/postgres_catalogs.yaml new file mode 100644 index 0000000000..c4f6945071 --- /dev/null +++ b/bundle/direct/dresources/postgres_catalogs.yaml @@ -0,0 +1,10 @@ +provided_id_fields: + # catalog_id is part of the hierarchical name and immutable. + - field: catalog_id + reason: id_field +recreate_on_changes: + # The Postgres SDK has no UpdateCatalog endpoint, so any local change + # requires delete+create. The OpenAPI spec only marks postgres_database + # as IMMUTABLE (handled by autogen); branch and create_database_if_missing + # need explicit entries here. + - reason: immutable diff --git a/bundle/direct/dresources/postgres_databases.generated.yaml b/bundle/direct/dresources/postgres_databases.generated.yaml new file mode 100644 index 0000000000..0d90d027f3 --- /dev/null +++ b/bundle/direct/dresources/postgres_databases.generated.yaml @@ -0,0 +1,9 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: postgres_database + reason: spec:input_only + - field: role + reason: spec:input_only diff --git a/bundle/direct/dresources/postgres_databases.yaml b/bundle/direct/dresources/postgres_databases.yaml new file mode 100644 index 0000000000..504f096217 --- /dev/null +++ b/bundle/direct/dresources/postgres_databases.yaml @@ -0,0 +1,10 @@ +provided_id_fields: + # parent and database_id are immutable (part of hierarchical name, not in API spec) + - field: parent + reason: id_field + - field: database_id + reason: id_field +ignore_local_changes: + # replace_existing only takes effect on create; toggling it later is a no-op. + - field: replace_existing + reason: "input_only; cannot be updated after create" diff --git a/bundle/direct/dresources/postgres_endpoints.generated.yaml b/bundle/direct/dresources/postgres_endpoints.generated.yaml new file mode 100644 index 0000000000..83d8e05337 --- /dev/null +++ b/bundle/direct/dresources/postgres_endpoints.generated.yaml @@ -0,0 +1,25 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +recreate_on_changes: + - field: endpoint_type + reason: spec:immutable + +ignore_remote_changes: + - field: autoscaling_limit_max_cu + reason: spec:input_only + - field: autoscaling_limit_min_cu + reason: spec:input_only + - field: disabled + reason: spec:input_only + - field: endpoint_type + reason: spec:input_only + - field: group + reason: spec:input_only + - field: no_suspension + reason: spec:input_only + - field: settings + reason: spec:input_only + - field: suspend_timeout_duration + reason: spec:input_only diff --git a/bundle/direct/dresources/postgres_endpoints.yaml b/bundle/direct/dresources/postgres_endpoints.yaml new file mode 100644 index 0000000000..f5f09c323f --- /dev/null +++ b/bundle/direct/dresources/postgres_endpoints.yaml @@ -0,0 +1,10 @@ +provided_id_fields: + # parent and endpoint_id are immutable (part of hierarchical name, not in API spec) + - field: parent + reason: id_field + - field: endpoint_id + reason: id_field +ignore_local_changes: + # replace_existing only takes effect on create; toggling it later is a no-op. + - field: replace_existing + reason: "input_only; cannot be updated after create" diff --git a/bundle/direct/dresources/postgres_projects.generated.yaml b/bundle/direct/dresources/postgres_projects.generated.yaml new file mode 100644 index 0000000000..ddabfda23f --- /dev/null +++ b/bundle/direct/dresources/postgres_projects.generated.yaml @@ -0,0 +1,25 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +recreate_on_changes: + - field: pg_version + reason: spec:immutable + +ignore_remote_changes: + - field: budget_policy_id + reason: spec:input_only + - field: custom_tags + reason: spec:input_only + - field: default_branch + reason: spec:input_only + - field: default_endpoint_settings + reason: spec:input_only + - field: display_name + reason: spec:input_only + - field: enable_pg_native_login + reason: spec:input_only + - field: history_retention_duration + reason: spec:input_only + - field: pg_version + reason: spec:input_only diff --git a/bundle/direct/dresources/postgres_projects.yaml b/bundle/direct/dresources/postgres_projects.yaml new file mode 100644 index 0000000000..4ac5657a4b --- /dev/null +++ b/bundle/direct/dresources/postgres_projects.yaml @@ -0,0 +1,4 @@ +provided_id_fields: + # project_id is immutable (part of hierarchical name, not in API spec) + - field: project_id + reason: id_field diff --git a/bundle/direct/dresources/postgres_roles.generated.yaml b/bundle/direct/dresources/postgres_roles.generated.yaml new file mode 100644 index 0000000000..1cf4c28fe4 --- /dev/null +++ b/bundle/direct/dresources/postgres_roles.generated.yaml @@ -0,0 +1,15 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: attributes + reason: spec:input_only + - field: auth_method + reason: spec:input_only + - field: identity_type + reason: spec:input_only + - field: membership_roles + reason: spec:input_only + - field: postgres_role + reason: spec:input_only diff --git a/bundle/direct/dresources/postgres_roles.yaml b/bundle/direct/dresources/postgres_roles.yaml new file mode 100644 index 0000000000..4a5a471541 --- /dev/null +++ b/bundle/direct/dresources/postgres_roles.yaml @@ -0,0 +1,21 @@ +ignore_local_changes: + # replace_existing only takes effect on create; toggling it later is a no-op. + - field: replace_existing + reason: "input_only; cannot be updated after create" +recreate_on_changes: + # parent and role_id are immutable (together they form the hierarchical name). + - field: parent + reason: immutable + - field: role_id + reason: immutable + # The PATCH update_mask only accepts spec.attributes and spec.membership_roles; + # the backend rejects spec.postgres_role, spec.auth_method, and spec.identity_type + # with 400 INVALID_PARAMETER_VALUE "Unknown field path in update_mask". These spec + # fields are not marked immutable in the OpenAPI definition yet, so the generator + # doesn't catch them — declare the constraint manually until upstream is fixed. + - field: postgres_role + reason: immutable + - field: auth_method + reason: immutable + - field: identity_type + reason: immutable diff --git a/bundle/direct/dresources/postgres_synced_tables.generated.yaml b/bundle/direct/dresources/postgres_synced_tables.generated.yaml new file mode 100644 index 0000000000..d658cbf418 --- /dev/null +++ b/bundle/direct/dresources/postgres_synced_tables.generated.yaml @@ -0,0 +1,29 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: accelerated_sync + reason: spec:input_only + - field: branch + reason: spec:input_only + - field: create_database_objects_if_missing + reason: spec:input_only + - field: existing_pipeline_id + reason: spec:input_only + - field: extra_columns + reason: spec:input_only + - field: new_pipeline_spec + reason: spec:input_only + - field: postgres_database + reason: spec:input_only + - field: primary_key_columns + reason: spec:input_only + - field: scheduling_policy + reason: spec:input_only + - field: source_table_full_name + reason: spec:input_only + - field: timeseries_key + reason: spec:input_only + - field: type_overrides + reason: spec:input_only diff --git a/bundle/direct/dresources/postgres_synced_tables.yaml b/bundle/direct/dresources/postgres_synced_tables.yaml new file mode 100644 index 0000000000..dcf6e92c61 --- /dev/null +++ b/bundle/direct/dresources/postgres_synced_tables.yaml @@ -0,0 +1,12 @@ +# The Postgres API has no UpdateSyncedTable endpoint, so every settable +# field is recreate-only on the intent side (local YAML edit -> delete + +# create). The complementary ignore_remote_changes block for this resource +# lives in postgres_synced_tables.generated.yaml and handles the read side: it +# suppresses drift for the same fields because the GET API does not echo back +# the spec. Together they make no-op deploys idempotent while a real config +# edit still triggers a recreate. Same pattern as secret_scopes. +provided_id_fields: + - field: synced_table_id + reason: id_field +recreate_on_changes: + - reason: immutable diff --git a/bundle/direct/dresources/quality_monitors.yaml b/bundle/direct/dresources/quality_monitors.yaml new file mode 100644 index 0000000000..98f5b05901 --- /dev/null +++ b/bundle/direct/dresources/quality_monitors.yaml @@ -0,0 +1,6 @@ +provided_id_fields: + - field: table_name + reason: id_field +recreate_on_changes: + - field: assets_dir + reason: immutable diff --git a/bundle/direct/dresources/registered_models.generated.yaml b/bundle/direct/dresources/registered_models.generated.yaml new file mode 100644 index 0000000000..b23f27c3aa --- /dev/null +++ b/bundle/direct/dresources/registered_models.generated.yaml @@ -0,0 +1,7 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: browse_only + reason: spec:output_only diff --git a/bundle/direct/dresources/registered_models.yaml b/bundle/direct/dresources/registered_models.yaml new file mode 100644 index 0000000000..f5800e34df --- /dev/null +++ b/bundle/direct/dresources/registered_models.yaml @@ -0,0 +1,37 @@ +ignore_remote_changes: + # Output-only timestamp/user fields populated by the backend on read. + # The user never sets them, so remote-only differences are ignored here + # rather than zeroed in RemapState. + - field: created_at + reason: output_only + - field: created_by + reason: output_only + - field: updated_at + reason: output_only + - field: updated_by + reason: output_only + # Aliases are managed on model versions through a separate API, and DoRead + # passes IncludeAliases=false, so GET never echoes them back. Without this a + # config that sets aliases reports a perpetual update (remote stays empty). + - field: aliases + reason: input_only +provided_id_fields: + # The name can technically be updated without recreate. We recreate for now though + # to match TF implementation. + - field: name + reason: terraform_compat + - field: catalog_name + reason: id_field + - field: schema_name + reason: id_field +recreate_on_changes: + - field: storage_location + reason: immutable +backend_defaults: + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/catalog/resource_registered_model.go#L28 + # m["storage_location"].Computed = true + - field: storage_location + # owner, full_name, metastore_id are Computed in TF (backend-set output fields). + - field: owner + - field: full_name + - field: metastore_id diff --git a/bundle/direct/dresources/schemas.yaml b/bundle/direct/dresources/schemas.yaml new file mode 100644 index 0000000000..8ef27e71d1 --- /dev/null +++ b/bundle/direct/dresources/schemas.yaml @@ -0,0 +1,17 @@ +provided_id_fields: + # UC lowercases identifier names; remote returns "myschema" for config "MySchema". + - field: name + reason: id_field + - field: catalog_name + reason: id_field +recreate_on_changes: + - field: storage_root + reason: immutable +normalize_slash: + - field: storage_root + reason: uc_strips_trailing_slash +backend_defaults: + # UC auto-populates unity.catalog.managed..defaults.* keys after create. + # Without this, every subsequent plan produces an Update whose payload is empty, + # and UC rejects it with "UpdateSchema Nothing to update". + - field: properties['unity.catalog.managed.*.defaults.*'] diff --git a/bundle/direct/dresources/secret_scopes.permissions.yaml b/bundle/direct/dresources/secret_scopes.permissions.yaml new file mode 100644 index 0000000000..61c734eb8c --- /dev/null +++ b/bundle/direct/dresources/secret_scopes.permissions.yaml @@ -0,0 +1,6 @@ +# Permissions for secret scopes use ResourceSecretScopeAcls. +updatable_id_fields: + # When scope name changes, we need UpdateWithID trigger. This is necessary so that subsequent + # DoRead operations use the correct ID and we do not end up with a persistent drift. + - field: scope_name + reason: id_changes diff --git a/bundle/direct/dresources/secret_scopes.yaml b/bundle/direct/dresources/secret_scopes.yaml new file mode 100644 index 0000000000..ffde4b45f7 --- /dev/null +++ b/bundle/direct/dresources/secret_scopes.yaml @@ -0,0 +1,14 @@ +backend_defaults: + # The Secrets API defaults scope_backend_type to DATABRICKS when not specified. + - field: scope_backend_type + values: ["DATABRICKS"] +provided_id_fields: + - field: scope + reason: id_field +recreate_on_changes: + - field: scope_backend_type + reason: immutable + - field: backend_azure_keyvault + reason: immutable + - field: initial_manage_principal + reason: immutable diff --git a/bundle/direct/dresources/secrets.generated.yaml b/bundle/direct/dresources/secrets.generated.yaml new file mode 100644 index 0000000000..4a658391c4 --- /dev/null +++ b/bundle/direct/dresources/secrets.generated.yaml @@ -0,0 +1,34 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +recreate_on_changes: + - field: catalog_name + reason: spec:immutable + - field: name + reason: spec:immutable + - field: schema_name + reason: spec:immutable + +ignore_remote_changes: + - field: owner + reason: spec:input_only + - field: value + reason: spec:input_only + + - field: create_time + reason: spec:output_only + - field: created_by + reason: spec:output_only + - field: effective_owner + reason: spec:output_only + - field: effective_value + reason: spec:output_only + - field: full_name + reason: spec:output_only + - field: metastore_id + reason: spec:output_only + - field: update_time + reason: spec:output_only + - field: updated_by + reason: spec:output_only diff --git a/bundle/direct/dresources/secrets.yaml b/bundle/direct/dresources/secrets.yaml new file mode 100644 index 0000000000..22ca3d7f49 --- /dev/null +++ b/bundle/direct/dresources/secrets.yaml @@ -0,0 +1,3 @@ +sensitive_fields: + - field: value + - field: effective_value diff --git a/bundle/direct/dresources/sql_warehouses.yaml b/bundle/direct/dresources/sql_warehouses.yaml new file mode 100644 index 0000000000..582126d2d8 --- /dev/null +++ b/bundle/direct/dresources/sql_warehouses.yaml @@ -0,0 +1,37 @@ +ignore_remote_changes: + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L62 + # common.CustomizeSchemaPath(m, "channel").SetSuppressDiff() + - field: channel + reason: managed + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L82 + # common.CustomizeSchemaPath(m, "tags").SetSuppressDiff() + - field: tags + reason: managed + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L85-L87 + # common.CustomizeSchemaPath(m, "warehouse_type").SetSuppressDiff() + - field: warehouse_type + reason: managed + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L75 + # common.CustomizeSchemaPath(m, "min_num_clusters").SetSuppressDiff() + - field: min_num_clusters + reason: managed + + # creator_name is readonly, can't be updated via API + - field: creator_name + reason: output_only + +backend_defaults: + # lifecycle.started is derived from remote warehouse state in RemapState, so the + # remote side always has a value. When the user omits lifecycle from config, + # both old and new are nil and backend_defaults correctly skips the remote value. + # When the user explicitly sets lifecycle.started, old/new are non-nil and normal + # drift detection applies (e.g. detecting out-of-band stop). + - field: lifecycle + - field: lifecycle.started + + # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L69 + # m["enable_serverless_compute"].Computed = true + - field: enable_serverless_compute diff --git a/bundle/direct/dresources/synced_database_tables.generated.yaml b/bundle/direct/dresources/synced_database_tables.generated.yaml new file mode 100644 index 0000000000..a439d8cf32 --- /dev/null +++ b/bundle/direct/dresources/synced_database_tables.generated.yaml @@ -0,0 +1,26 @@ +# Generated, do not edit. API field behaviors from OpenAPI schema. +# +# For manual rules and the description of each category, see README.md. + +ignore_remote_changes: + - field: database_instance_name + reason: spec:input_only + - field: logical_database_name + reason: spec:input_only + - field: spec.accelerated_sync + reason: spec:input_only + - field: spec.create_database_objects_if_missing + reason: spec:input_only + - field: spec.existing_pipeline_id + reason: spec:input_only + - field: spec.new_pipeline_spec + reason: spec:input_only + + - field: data_synchronization_status + reason: spec:output_only + - field: effective_database_instance_name + reason: spec:output_only + - field: effective_logical_database_name + reason: spec:output_only + - field: unity_catalog_provisioning_state + reason: spec:output_only diff --git a/bundle/direct/dresources/synced_database_tables.yaml b/bundle/direct/dresources/synced_database_tables.yaml new file mode 100644 index 0000000000..ed66613899 --- /dev/null +++ b/bundle/direct/dresources/synced_database_tables.yaml @@ -0,0 +1,11 @@ +provided_id_fields: + - field: name + reason: id_field +# The Database API has no UpdateSyncedDatabaseTable endpoint (the generated +# SDK method is a stub that returns 501 NOT_IMPLEMENTED), so this resource +# implements no DoUpdate and every settable field must recreate. The +# complementary ignore_remote_changes block in synced_database_tables.generated.yaml +# handles the read side (input-only and output-only fields) so no-op deploys +# stay idempotent. Same pattern as postgres_synced_tables. +recreate_on_changes: + - reason: immutable diff --git a/bundle/direct/dresources/vector_search_endpoints.yaml b/bundle/direct/dresources/vector_search_endpoints.yaml new file mode 100644 index 0000000000..34fe753bd2 --- /dev/null +++ b/bundle/direct/dresources/vector_search_endpoints.yaml @@ -0,0 +1,12 @@ +provided_id_fields: + # The endpoint API has no rename; the endpoint is fetched by name. + - field: name + reason: id_field +recreate_on_changes: + - field: endpoint_type + reason: immutable +ignore_remote_changes: + # The API returns effective_budget_policy_id which may include inherited workspace policies, + # not the user-set budget_policy_id. Ignore until the API exposes the user-set value directly. + - field: budget_policy_id + reason: effective_vs_requested diff --git a/bundle/direct/dresources/vector_search_indexes.yaml b/bundle/direct/dresources/vector_search_indexes.yaml new file mode 100644 index 0000000000..e57c9d6f12 --- /dev/null +++ b/bundle/direct/dresources/vector_search_indexes.yaml @@ -0,0 +1,31 @@ +provided_id_fields: + - field: name + reason: id_field +recreate_on_changes: + # The index API has no rename or update path, so every config change + # has to go through delete + create. + - field: endpoint_name + reason: immutable + - field: index_type + reason: immutable + - field: index_subtype + reason: immutable + - field: primary_key + reason: immutable + - field: delta_sync_index_spec + reason: immutable + - field: direct_access_index_spec + reason: immutable +ignore_remote_changes: + # The backend rewrites schema_json on create: user-facing type names + # ("integer", "long", "short", "byte") are stored in Unity Catalog as + # Spark type names ("int", "bigint", "smallint", "tinyint") and the + # columns come back in sorted key order, so GET never echoes the user's + # literal input. Without this rule the rewrite reads as a change to the + # immutable direct_access_index_spec and plans a destructive recreate + # that drops all upserted vectors. + - field: direct_access_index_spec.schema_json + reason: normalized_by_backend +backend_defaults: + # The Vector Search API assigns index_subtype when the config omits it + - field: index_subtype diff --git a/bundle/direct/dresources/volumes.yaml b/bundle/direct/dresources/volumes.yaml new file mode 100644 index 0000000000..2968d978a8 --- /dev/null +++ b/bundle/direct/dresources/volumes.yaml @@ -0,0 +1,21 @@ +provided_id_fields: + - field: catalog_name + reason: id_field + - field: schema_name + reason: id_field +recreate_on_changes: + - field: storage_location + reason: immutable + - field: volume_type + reason: immutable +updatable_id_fields: + - field: name + reason: id_changes +normalize_slash: + # UC strips trailing slashes on create; matches the Terraform provider's suppressLocationDiff. + # https://github.com/databricks/terraform-provider-databricks/blob/v1.65.1/catalog/resource_volume.go#L25 + - field: storage_location + reason: uc_strips_trailing_slash +backend_defaults: + # storage_location is Computed; backend generates it for managed volumes. + - field: storage_location From 58985b74c9450bbe5c4da412350fd30fc4aaf153 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 20 Aug 2026 12:40:34 +0200 Subject: [PATCH 2/3] Load the per-resource files and drop the combined ones Embed *.yaml and key each resource type by its file name, remove resources.yml and resources.generated.yml, and make the generator write one file per resource. Comments and test messages now name the file they mean. --- .agents/rules/auto-generated-files.md | 2 + .agents/skills/bump-sdk/SKILL.md | 4 +- Taskfile.yml | 4 +- .../empty_string_dropped/gen_empty_config.py | 15 +- bundle/configsync/defaults.go | 2 +- bundle/direct/bundle_plan_test.go | 4 +- bundle/direct/dresources/.gitattributes | 1 + bundle/direct/dresources/README.md | 22 +- bundle/direct/dresources/apps.yaml | 4 + bundle/direct/dresources/config.go | 74 +- bundle/direct/dresources/config_test.go | 42 +- bundle/direct/dresources/dashboard.go | 2 +- bundle/direct/dresources/job_run_test.go | 2 +- bundle/direct/dresources/postgres_branch.go | 2 +- bundle/direct/dresources/postgres_catalog.go | 2 +- bundle/direct/dresources/postgres_database.go | 2 +- bundle/direct/dresources/postgres_endpoint.go | 2 +- bundle/direct/dresources/postgres_project.go | 2 +- bundle/direct/dresources/postgres_role.go | 2 +- .../dresources/postgres_synced_table.go | 2 +- bundle/direct/dresources/registered_model.go | 2 +- .../direct/dresources/resources.generated.yml | 424 --------- bundle/direct/dresources/resources.yml | 852 ------------------ .../direct/dresources/vector_search_index.go | 2 +- .../dresources/vector_search_index_test.go | 2 +- bundle/direct/tools/generate_resources.py | 84 +- 26 files changed, 183 insertions(+), 1375 deletions(-) delete mode 100644 bundle/direct/dresources/resources.generated.yml delete mode 100644 bundle/direct/dresources/resources.yml diff --git a/.agents/rules/auto-generated-files.md b/.agents/rules/auto-generated-files.md index d79fec6475..cdd2ae97ba 100644 --- a/.agents/rules/auto-generated-files.md +++ b/.agents/rules/auto-generated-files.md @@ -16,6 +16,7 @@ globs: - "internal/genkit/tagging.py" - "internal/mocks/**/*.go" - "bundle/direct/dresources/*.generated.yml" + - "bundle/direct/dresources/*.generated.yaml" - "bundle/internal/validation/generated/*.go" - "bundle/schema/jsonschema.json" - "python/databricks/bundles/version.py" @@ -37,6 +38,7 @@ paths: - "internal/genkit/tagging.py" - "internal/mocks/**/*.go" - "bundle/direct/dresources/*.generated.yml" + - "bundle/direct/dresources/*.generated.yaml" - "bundle/internal/validation/generated/*.go" - "bundle/schema/jsonschema.json" - "python/databricks/bundles/version.py" diff --git a/.agents/skills/bump-sdk/SKILL.md b/.agents/skills/bump-sdk/SKILL.md index fb8a1f39b2..e4537f15a2 100644 --- a/.agents/skills/bump-sdk/SKILL.md +++ b/.agents/skills/bump-sdk/SKILL.md @@ -9,7 +9,7 @@ allowed-tools: Read, Edit, Write, Bash, Glob, Grep, WebFetch, AskUserQuestion The SDK version lives in `go.mod` (`github.com/databricks/databricks-sdk-go`) and the pinned spec SHA lives in `.codegen/_openapi_sha`. These two move as a pair; everything else in this skill is regenerated from them or is fallout you fix by hand. -Do not hand-edit generated files (`.codegen/cli.json`, `cmd/workspace/*`, `cmd/account/*`, `bundle/schema/jsonschema.json`, `bundle/internal/validation/generated/*`, `bundle/direct/dresources/resources.generated.yml`, `bundle/terraform_dabs_map/generated.go`, `python/databricks/bundles/**`); regenerate them. +Do not hand-edit generated files (`.codegen/cli.json`, `cmd/workspace/*`, `cmd/account/*`, `bundle/schema/jsonschema.json`, `bundle/internal/validation/generated/*`, `bundle/direct/dresources/*.generated.yaml`, `bundle/terraform_dabs_map/generated.go`, `python/databricks/bundles/**`); regenerate them. The Python tasks (`pydabs-*`, and the `pydabs-codegen` step inside `generate-check`) all run through `uv`. If one fails because `uv` is missing or because the host's `python3` is too old (e.g. 3.9), install `uv` (`curl -LsSf https://astral.sh/uv/install.sh | sh`) rather than touching the system Python: `uv run` provisions the interpreter each package pins (`>=3.10`, and `==3.13.*` under `python/codegen/`) and downloads it if needed. Do not chase the system Python version. @@ -42,7 +42,7 @@ Run `go build ./...` and fix compile breakages before touching acceptance golden Read the SDK's `CHANGELOG.md` at the target version (in the module cache) to enumerate breaking changes before chasing compile errors. A removed struct field that the CLI used (e.g. `jobs.AiRuntimeTask.CodeSourcePath`) should have its usage temporarily disabled with a comment noting it returns in a later SDK bump, not deleted outright. A new struct field triggers an `exhaustruct` lint failure in `bundle/direct/dresources/*`. Run `./task lint` and (in case of issues) wire the field through `PrepareState` and `RemapState` when it exists on both the input and remote types. -A field the new spec now annotates as output-only may already be emitted into `resources.generated.yml`, making the manual entry in `resources.yml` redundant; `TestResourcesYMLNoRedundantRules` catches this, so remove the manual entry. +A field the new spec now annotates as output-only may already be emitted into `.generated.yaml`, making the manual entry in `.yaml` redundant; `TestResourcesYMLNoRedundantRules` catches this, so remove the manual entry. **6. Refresh goldens, then VERIFY.** diff --git a/Taskfile.yml b/Taskfile.yml index 8ed24ad0f6..3fa8432521 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1032,9 +1032,9 @@ tasks: - bundle/direct/dresources/apitypes.yml - acceptance/bundle/refschema/out.fields.txt generates: - - bundle/direct/dresources/resources.generated.yml + - bundle/direct/dresources/*.generated.yaml cmds: - - "sh -c 'uv run --script bundle/direct/tools/generate_resources.py .codegen/cli.json bundle/direct/dresources/apitypes.generated.yml bundle/direct/dresources/apitypes.yml acceptance/bundle/refschema/out.fields.txt > bundle/direct/dresources/resources.generated.yml'" + - "uv run --script bundle/direct/tools/generate_resources.py .codegen/cli.json bundle/direct/dresources/apitypes.generated.yml bundle/direct/dresources/apitypes.yml acceptance/bundle/refschema/out.fields.txt bundle/direct/dresources" # pydabs-* tasks are defined in python/Taskfile.yml (included above). diff --git a/acceptance/bundle/empty_string_dropped/gen_empty_config.py b/acceptance/bundle/empty_string_dropped/gen_empty_config.py index 73d360d1e8..6fbbb2f90d 100644 --- a/acceptance/bundle/empty_string_dropped/gen_empty_config.py +++ b/acceptance/bundle/empty_string_dropped/gen_empty_config.py @@ -11,8 +11,8 @@ A field is settable/eligible when out.fields.txt types it as `string` (this skips enums, which are named types) with flag ALL or INPUT, and it is not -output_only (resources.generated.yml), a bundle-framework field, or a known -terraform-erroring field. +output_only (.generated.yaml), a bundle-framework field, or a +known terraform-erroring field. Run from the repo root; writes databricks.yml in the test directory: acceptance/bundle/empty_string_dropped/gen_empty_config.py @@ -24,7 +24,8 @@ import yaml FIELDS = Path("acceptance/bundle/refschema/out.fields.txt") -GENERATED = Path("bundle/direct/dresources/resources.generated.yml") +DRESOURCES = Path("bundle/direct/dresources") +GENERATED_SUFFIX = ".generated.yaml" TESTDIR = Path("acceptance/bundle/empty_string_dropped") BASE = TESTDIR / "base.yml" @@ -79,14 +80,14 @@ def string_leaf_parents(): def output_only_fields(): """Map resource type -> {output_only field paths} (user cannot set).""" - gen = yaml.safe_load(GENERATED.read_text()) or {} result = {} - for rtype, spec in (gen.get("resources") or {}).items(): + for path in sorted(DRESOURCES.glob("*" + GENERATED_SUFFIX)): + spec = yaml.safe_load(path.read_text()) or {} fields = set() - for entry in (spec or {}).get("ignore_remote_changes") or []: + for entry in spec.get("ignore_remote_changes") or []: if str(entry.get("reason", "")).startswith("spec:output_only"): fields.add(entry["field"]) - result[rtype] = fields + result[path.name.removesuffix(GENERATED_SUFFIX)] = fields return result diff --git a/bundle/configsync/defaults.go b/bundle/configsync/defaults.go index 4dff5c143d..f638bfdd14 100644 --- a/bundle/configsync/defaults.go +++ b/bundle/configsync/defaults.go @@ -63,7 +63,7 @@ var serverSideDefaults = map[string]any{ // custom_tags and cluster_log_conf are commonly injected by cluster policies // when the user omits them, so they exist only remotely. Syncing them back leaks // one environment's policy values into (often shared) config and breaks deploys in - // other environments. TODO: move to backend_defaults in resources.yml once + // other environments. TODO: move to backend_defaults in jobs.yaml once // configsync filtering is migrated to the direct engine lifecycle metadata. "resources.jobs.*.tasks[*].new_cluster.custom_tags": backendDefault, "resources.jobs.*.tasks[*].new_cluster.cluster_log_conf": backendDefault, diff --git a/bundle/direct/bundle_plan_test.go b/bundle/direct/bundle_plan_test.go index 288618f339..b28715913d 100644 --- a/bundle/direct/bundle_plan_test.go +++ b/bundle/direct/bundle_plan_test.go @@ -78,8 +78,8 @@ resources: } func TestShouldSkipBackendDefault_ManagedPropertiesOnly(t *testing.T) { - // Rules mirror the schemas backend_defaults in resources.yml, but the test is - // deliberately self-contained so that edits to resources.yml don't break it. + // Rules mirror the backend_defaults in schemas.yaml, but the test is + // deliberately self-contained so that edits to schemas.yaml don't break it. // The real wiring is covered by acceptance/bundle/resources/schemas/drift. managedDefaults, err := structpath.ParsePattern("properties['unity.catalog.managed.*.defaults.*']") require.NoError(t, err) diff --git a/bundle/direct/dresources/.gitattributes b/bundle/direct/dresources/.gitattributes index 48aea31654..a6cbdd802e 100644 --- a/bundle/direct/dresources/.gitattributes +++ b/bundle/direct/dresources/.gitattributes @@ -1 +1,2 @@ *.generated.yml linguist-generated=true +*.generated.yaml linguist-generated=true diff --git a/bundle/direct/dresources/README.md b/bundle/direct/dresources/README.md index e93d5abcc1..7c770e642c 100644 --- a/bundle/direct/dresources/README.md +++ b/bundle/direct/dresources/README.md @@ -9,22 +9,28 @@ An exception could be made if default error message lacks the necessary context. - The arguments point to actual struct that will be persisted in state, any changes to it will affect what is stored in state. Usually there is no need to change it, but if there is, there should always be detailed explanation. - Each Create/Update/Delete method should correspond to one API call. We persist state right after, so there is minimum chance of having orphaned resources. - - We should calculate the update type during plan phase. This means it should be configured via resources.yml as much as possible, falling back to OverrideChangeDesc(). The DoUpdate() implementation should be as predictable as possible based on the plan. In particular, avoid reading remote state in DoUpdate() to decide what kind of update to dod. + - We should calculate the update type during plan phase. This means it should be configured via the resource's YAML file as much as possible, falling back to OverrideChangeDesc(). The DoUpdate() implementation should be as predictable as possible based on the plan. In particular, avoid reading remote state in DoUpdate() to decide what kind of update to dod. - Create/Update/Delete methods should not need to do read requests. They can read state passed to them via \*PlanEntry but that should be reserved for exceptional cases. Most resources should have 1-1 mapping to single SDK/API call. - - For update with complex logic, ensure that DoUpdate() never results in no-op. If certain fields could not be updated, they should be excluded at plan level in resources.yml. + - For update with complex logic, ensure that DoUpdate() never results in no-op. If certain fields could not be updated, they should be excluded at plan level in `.yaml`. -## Field classification in resources.yml +## Field classification in `.yaml` -Each field with special plan/deploy behavior must be declared in `resources.yml`. Choose the right category: +Each field with special plan/deploy behavior must be declared in the YAML file of its resource type. Both files of a resource type are optional and are omitted when they would be empty: + + - `.yaml` (e.g. `jobs.yaml`) — hand-written rules. + - `.generated.yaml` — API field behaviors from the OpenAPI schema. Generated by `./task generate-direct-resources`, do not edit. + +Each entry has a `field` (the field path; omitting it means the root, which matches every field) and a `reason` (`immutable`, `input_only`, `output_only`, or descriptive text). Choose the right category: - **`backend_defaults`**: The backend may fill in a value when the user doesn't specify one. Suppresses the diff when the user's config is nil/empty but remote has a value. Optionally restrict to specific allowed remote values via `values:`. Use for fields the API fills in as defaults (e.g., `format`, `run_if`, `node_type_id`). Link to TF provider suppression comment in the same format as existing entries. - - **`ignore_remote_changes`**: Ignore changes the remote makes to this field. Use for fields the backend manages (e.g., cloud-provider attributes like `aws_attributes`, `gcp_attributes`) or fields not returned by the update endpoint. Do not zero out such fields in `RemapState` to hide them from diff computation: carry the real remote value through and declare the field here instead, since zeroing discards information and duplicates the suppression logic. For `output_only` fields this rule is often already produced by `resources.generated.yml` from the OpenAPI annotation. Reason codes: + - **`ignore_remote_changes`**: Ignore changes the remote makes to this field. Use for fields the backend manages (e.g., cloud-provider attributes like `aws_attributes`, `gcp_attributes`) or fields not returned by the update endpoint. Do not zero out such fields in `RemapState` to hide them from diff computation: carry the real remote value through and declare the field here instead, since zeroing discards information and duplicates the suppression logic. For `output_only` fields this rule is often already produced by `.generated.yaml` from the OpenAPI annotation. Reason codes: - `output_only` — the field is computed by the backend; the user never sets it - `input_only` — accepted on create/update but not returned by GET (e.g., write-only tokens, flags) - `managed` — managed by the cloud provider or platform, not by the user config - - **`ignore_local_changes`**: Ignore changes the user makes to this field. Use for fields that cannot be updated via API — either they are immutable after creation or require a separate API that is not yet implemented. Must have a comment in resources.yml explaining why. + - **`ignore_local_changes`**: Ignore changes the user makes to this field. Use for fields that cannot be updated via API — either they are immutable after creation or require a separate API that is not yet implemented. Must have a comment explaining why. - **`recreate_on_changes`**: Changing this field requires delete + create. Use for truly immutable fields (name, type, location). The reason should reference API docs or TF provider. - - **`updatable_id_fields`**: Changing this field changes the resource's ID. Requires `DoUpdateWithID` to be implemented. + - **`provided_id_fields`**: Fields composing the name-based ID the resource is fetched by. A local change triggers delete + create. A remote-only difference is skipped: a successful get-by-ID means a differing remote value can only be backend normalization, since a real out-of-band rename would 404 (handled as resource-gone). + - **`updatable_id_fields`**: Changing this field changes the resource's ID. Requires `DoUpdateWithID` to be implemented. A remote-only difference is skipped as with `provided_id_fields`. ## Update mask @@ -96,7 +102,7 @@ in normal drift detection and is no longer subject to the `missing_in_remote` su ## OverrideChangeDesc -Use `OverrideChangeDesc` only as a last resort when `resources.yml` settings cannot express the needed logic. Skipping an action with `change.Action = deployplan.Skip` in `OverrideChangeDesc` creates a silent no-op: the plan shows no change even if the user's config differs from remote. Document the skip reason clearly in both the comment and `change.Reason`. +Use `OverrideChangeDesc` only as a last resort when the `.yaml` settings cannot express the needed logic. Skipping an action with `change.Action = deployplan.Skip` in `OverrideChangeDesc` creates a silent no-op: the plan shows no change even if the user's config differs from remote. Document the skip reason clearly in both the comment and `change.Reason`. ## Nice to have - Add link to corresponding API documentation before each method. diff --git a/bundle/direct/dresources/apps.yaml b/bundle/direct/dresources/apps.yaml index 81291fd49c..a6725316be 100644 --- a/bundle/direct/dresources/apps.yaml +++ b/bundle/direct/dresources/apps.yaml @@ -4,6 +4,10 @@ provided_id_fields: backend_defaults: # Backend sets it "MEDIUM" when not specified in the config - field: compute_size + # Backend enables token forwarding and reports true even when the config omits it, + # so an omitted field would otherwise plan an update on every deploy. An explicit + # value in the config still diffs normally. + - field: forward_user_access_token # lifecycle.started is derived from remote compute status in RemapState, so the # remote side always has a value. When the user omits lifecycle from config, # both old and new are nil and backend_defaults correctly skips the remote value. diff --git a/bundle/direct/dresources/config.go b/bundle/direct/dresources/config.go index 33a00e0a2f..d486735357 100644 --- a/bundle/direct/dresources/config.go +++ b/bundle/direct/dresources/config.go @@ -1,8 +1,11 @@ package dresources import ( - _ "embed" + "embed" "encoding/json" + "fmt" + "io/fs" + "strings" "sync" "github.com/databricks/cli/libs/structs/structpath" @@ -84,16 +87,25 @@ type ResourceLifecycleConfig struct { SensitiveFields []FieldRule `yaml:"sensitive_fields,omitempty"` } -// Config is the root configuration structure for resource lifecycle behavior. +// Config holds the lifecycle behavior of every resource type, keyed by resource type. type Config struct { - Resources map[string]ResourceLifecycleConfig `yaml:"resources"` + Resources map[string]ResourceLifecycleConfig } -//go:embed resources.yml -var resourcesYAML []byte +// One file per resource type: .yaml holds the hand-written rules and +// .generated.yaml the ones derived from the OpenAPI spec. A resource +// type without rules has no file. The apitypes*.yml files are inputs to the +// generator, not lifecycle config, hence the .yaml/.yml split in extensions. +// +//go:embed *.yaml +var configFS embed.FS -//go:embed resources.generated.yml -var resourcesGeneratedYAML []byte +const ( + yamlSuffix = ".yaml" + + // generatedSuffix marks a generated file once yamlSuffix is trimmed. + generatedSuffix = ".generated" +) var empty = ResourceLifecycleConfig{ IgnoreRemoteChanges: nil, @@ -106,28 +118,50 @@ var empty = ResourceLifecycleConfig{ SensitiveFields: nil, } -func mustParseConfig(data []byte) func() *Config { - return sync.OnceValue(func() *Config { - c := &Config{Resources: nil} - if err := yaml.Unmarshal(data, c); err != nil { +// loadConfigs parses every embedded YAML file into the hand-written or the generated +// config, keyed by the resource type the file is named after. +var loadConfigs = sync.OnceValues(func() (*Config, *Config) { + handWritten := &Config{Resources: map[string]ResourceLifecycleConfig{}} + generated := &Config{Resources: map[string]ResourceLifecycleConfig{}} + + names, err := fs.Glob(configFS, "*"+yamlSuffix) + if err != nil { + panic(err) + } + + for _, name := range names { + dst, resourceType := handWritten, strings.TrimSuffix(name, yamlSuffix) + if trimmed, ok := strings.CutSuffix(resourceType, generatedSuffix); ok { + dst, resourceType = generated, trimmed + } + + data, err := configFS.ReadFile(name) + if err != nil { panic(err) } - return c - }) -} -var loadConfig = mustParseConfig(resourcesYAML) + var rc ResourceLifecycleConfig + if err := yaml.Unmarshal(data, &rc); err != nil { + panic(fmt.Errorf("%s: %w", name, err)) + } + + dst.Resources[resourceType] = rc + } -var loadGeneratedConfig = mustParseConfig(resourcesGeneratedYAML) + return handWritten, generated +}) -// MustLoadConfig returns the parsed resources.yml configuration. +// MustLoadConfig returns the configuration parsed from the .yaml files. func MustLoadConfig() *Config { - return loadConfig() + handWritten, _ := loadConfigs() + return handWritten } -// MustLoadGeneratedConfig returns the parsed resources.generated.yml configuration. +// MustLoadGeneratedConfig returns the configuration parsed from the +// .generated.yaml files. func MustLoadGeneratedConfig() *Config { - return loadGeneratedConfig() + _, generated := loadConfigs() + return generated } // GetResourceConfig returns the lifecycle config for a given resource type. diff --git a/bundle/direct/dresources/config_test.go b/bundle/direct/dresources/config_test.go index 33345306b0..0484a3e5f1 100644 --- a/bundle/direct/dresources/config_test.go +++ b/bundle/direct/dresources/config_test.go @@ -1,6 +1,8 @@ package dresources import ( + "io/fs" + "strings" "testing" "github.com/databricks/cli/libs/structs/structaccess" @@ -15,6 +17,26 @@ func TestMustLoadConfig(t *testing.T) { assert.NotEmpty(t, cfg.Resources) } +// TestConfigFiles guards the file naming the loader derives resource types from: a +// misspelled name would silently drop every rule in the file. A file holding no rules +// is a mistake too — an empty resource has no file at all. +func TestConfigFiles(t *testing.T) { + names, err := fs.Glob(configFS, "*"+yamlSuffix) + require.NoError(t, err) + require.NotEmpty(t, names) + + for _, name := range names { + resourceType := strings.TrimSuffix(strings.TrimSuffix(name, yamlSuffix), generatedSuffix) + assert.Containsf(t, SupportedResources, resourceType, "%s: %q is not a supported resource type", name, resourceType) + } + + for _, cfg := range []*Config{MustLoadConfig(), MustLoadGeneratedConfig()} { + for resourceType, rc := range cfg.Resources { + assert.NotEqualf(t, empty, rc, "%s: declares no rules; delete the file", resourceType) + } + } +} + func TestGetResourceConfig(t *testing.T) { assert.NotEmpty(t, GetResourceConfig("volumes").RecreateOnChanges) assert.Empty(t, GetResourceConfig("nonexistent").RecreateOnChanges) @@ -54,10 +76,10 @@ func categoryRules(c ResourceLifecycleConfig) []struct { } } -// TestResourcesYMLNoRedundantRules guards against two redundancy classes in -// resources.yml: duplicate field entries within the same category of a -// resource, and entries that the autogenerated resources.generated.yml already -// produces from the OpenAPI schema. +// TestResourcesYMLNoRedundantRules guards against two redundancy classes in the +// hand-written .yaml files: duplicate field entries within the same +// category of a resource, and entries that the autogenerated +// .generated.yaml already produces from the OpenAPI schema. func TestResourcesYMLNoRedundantRules(t *testing.T) { handWritten := MustLoadConfig() generated := MustLoadGeneratedConfig() @@ -78,11 +100,11 @@ func TestResourcesYMLNoRedundantRules(t *testing.T) { for _, r := range c.rules { field := r.Field.String() if seen[field] { - t.Errorf("bundle/direct/dresources/resources.yml: %s.%s lists %q twice; remove the duplicate entry", resourceType, c.name, field) + t.Errorf("bundle/direct/dresources/%s.yaml: %s lists %q twice; remove the duplicate entry", resourceType, c.name, field) } seen[field] = true if genFields[c.name][field] { - t.Errorf("bundle/direct/dresources/resources.yml: %s.%s entry %q is already produced by resources.generated.yml; remove it from resources.yml", resourceType, c.name, field) + t.Errorf("bundle/direct/dresources/%[1]s.yaml: %[2]s entry %[3]q is already produced by %[1]s.generated.yaml; remove it", resourceType, c.name, field) } } } @@ -90,7 +112,7 @@ func TestResourcesYMLNoRedundantRules(t *testing.T) { } // TestResourcesYMLNoRedundantMissingInRemote guards that ignore_remote_changes entries -// in resources.yml do not duplicate the automatic missing-in-remote suppression. A field +// in .yaml do not duplicate the automatic missing-in-remote suppression. A field // absent from RemoteType is already skipped automatically (reason: missing_in_remote) when // there is no local change, so a manual ignore_remote_changes entry for it is dead weight. // @@ -104,14 +126,14 @@ func TestResourcesYMLNoRedundantMissingInRemote(t *testing.T) { for resourceType, rc := range cfg.Resources { adapter, err := NewAdapter(SupportedResources[resourceType], resourceType, nil) if err != nil { - t.Errorf("resources.yml: %s: failed to create adapter: %v", resourceType, err) + t.Errorf("%s.yaml: failed to create adapter: %v", resourceType, err) continue } for _, r := range rc.IgnoreRemoteChanges { inState := structaccess.ValidatePattern(adapter.StateType(), r.Field) == nil inRemote := structaccess.ValidatePattern(adapter.RemoteType(), r.Field) == nil if inState && !inRemote { - t.Errorf("resources.yml: %s.ignore_remote_changes entry %q is automatically handled (field absent from RemoteType); remove it", resourceType, r.Field) + t.Errorf("%s.yaml: ignore_remote_changes entry %q is automatically handled (field absent from RemoteType); remove it", resourceType, r.Field) } } } @@ -140,7 +162,7 @@ func TestResourcesYMLActionCategoriesExclusive(t *testing.T) { for _, r := range c.rules { field := r.Field.String() if prev, ok := firstCat[field]; ok { - t.Errorf("bundle/direct/dresources/resources.yml: %s lists %q in both %s and %s; a field's action belongs to exactly one category", resourceType, field, prev, c.name) + t.Errorf("bundle/direct/dresources/%s.yaml: %q is listed in both %s and %s; a field's action belongs to exactly one category", resourceType, field, prev, c.name) } else { firstCat[field] = c.name } diff --git a/bundle/direct/dresources/dashboard.go b/bundle/direct/dresources/dashboard.go index aaf1feea61..316524a127 100644 --- a/bundle/direct/dresources/dashboard.go +++ b/bundle/direct/dresources/dashboard.go @@ -107,7 +107,7 @@ func (r *ResourceDashboard) RemapState(state *DashboardState) *DashboardState { ForceSendFields: forceSendFields, // Output only fields. Remote changes to these are ignored via - // ignore_remote_changes in resources.yml rather than zeroed here. + // ignore_remote_changes in dashboards.yaml rather than zeroed here. CreateTime: state.CreateTime, DashboardId: state.DashboardId, LifecycleState: state.LifecycleState, diff --git a/bundle/direct/dresources/job_run_test.go b/bundle/direct/dresources/job_run_test.go index 27c1b93fac..dc1cfef28d 100644 --- a/bundle/direct/dresources/job_run_test.go +++ b/bundle/direct/dresources/job_run_test.go @@ -431,7 +431,7 @@ func TestJobRunRemapStateCarriesTheOutcome(t *testing.T) { } } -// resources.yml ignores remote drift on everything the RunNow request carries, +// job_runs.yaml ignores remote drift on everything the RunNow request carries, // since GetRun does not echo it back faithfully, and leaves result_state alone. func TestJobRunIgnoresEveryRequestField(t *testing.T) { adapters, err := InitAll(nil) diff --git a/bundle/direct/dresources/postgres_branch.go b/bundle/direct/dresources/postgres_branch.go index b033b42a8f..70ee13241b 100644 --- a/bundle/direct/dresources/postgres_branch.go +++ b/bundle/direct/dresources/postgres_branch.go @@ -79,7 +79,7 @@ func (*ResourcePostgresBranch) RemapState(remote *PostgresBranchRemote) *Postgre // makePostgresBranchRemote converts the SDK Branch into the embedded remote shape. // GET does not echo spec today (only status is returned); the embedded spec fields -// stay at their zero values, and resources.yml suppresses phantom drift via +// stay at their zero values, and postgres_branches.yaml suppresses phantom drift via // ignore_remote_changes with reason spec:input_only. func makePostgresBranchRemote(branch *postgres.Branch) *PostgresBranchRemote { var spec postgres.BranchSpec diff --git a/bundle/direct/dresources/postgres_catalog.go b/bundle/direct/dresources/postgres_catalog.go index 2a35b9e706..924d1c2a37 100644 --- a/bundle/direct/dresources/postgres_catalog.go +++ b/bundle/direct/dresources/postgres_catalog.go @@ -62,7 +62,7 @@ func (*ResourcePostgresCatalog) RemapState(remote *PostgresCatalogRemote) *Postg // makePostgresCatalogRemote converts the SDK Catalog into the embedded remote shape. // GET does not echo spec today (only status is returned); the embedded spec fields -// stay at their zero values, and resources.yml suppresses phantom drift via +// stay at their zero values, and postgres_catalogs.yaml suppresses phantom drift via // ignore_remote_changes with reason spec:input_only. // // The user-facing catalog id only appears as the trailing component of diff --git a/bundle/direct/dresources/postgres_database.go b/bundle/direct/dresources/postgres_database.go index adfd7326ee..76aad0ba68 100644 --- a/bundle/direct/dresources/postgres_database.go +++ b/bundle/direct/dresources/postgres_database.go @@ -71,7 +71,7 @@ func (*ResourcePostgresDatabase) RemapState(remote *PostgresDatabaseRemote) *Pos // makePostgresDatabaseRemote converts the SDK Database into the embedded remote // shape. GET does not echo spec today (only status is returned); the embedded -// spec fields stay at their zero values, and resources.yml suppresses phantom +// spec fields stay at their zero values, and postgres_databases.yaml suppresses phantom // drift via ignore_remote_changes with reason spec:input_only. func makePostgresDatabaseRemote(database *postgres.Database) *PostgresDatabaseRemote { var spec postgres.DatabaseDatabaseSpec diff --git a/bundle/direct/dresources/postgres_endpoint.go b/bundle/direct/dresources/postgres_endpoint.go index c370ff120b..420d967d87 100644 --- a/bundle/direct/dresources/postgres_endpoint.go +++ b/bundle/direct/dresources/postgres_endpoint.go @@ -80,7 +80,7 @@ func (*ResourcePostgresEndpoint) RemapState(remote *PostgresEndpointRemote) *Pos // makePostgresEndpointRemote converts the SDK Endpoint into the embedded remote shape. // GET does not echo spec today (only status is returned); the embedded spec fields -// stay at their zero values, and resources.yml suppresses phantom drift via +// stay at their zero values, and postgres_endpoints.yaml suppresses phantom drift via // ignore_remote_changes with reason spec:input_only. func makePostgresEndpointRemote(endpoint *postgres.Endpoint) *PostgresEndpointRemote { var spec postgres.EndpointSpec diff --git a/bundle/direct/dresources/postgres_project.go b/bundle/direct/dresources/postgres_project.go index ae9fa51ccb..96b40eedc5 100644 --- a/bundle/direct/dresources/postgres_project.go +++ b/bundle/direct/dresources/postgres_project.go @@ -73,7 +73,7 @@ func (*ResourcePostgresProject) RemapState(remote *PostgresProjectRemote) *Postg // makePostgresProjectRemote converts the SDK Project into the embedded remote shape. // GET does not echo spec today (only status is returned); the embedded spec fields -// stay at their zero values, and resources.yml suppresses phantom drift via +// stay at their zero values, and postgres_projects.yaml suppresses phantom drift via // ignore_remote_changes with reason spec:input_only. func makePostgresProjectRemote(project *postgres.Project) *PostgresProjectRemote { var spec postgres.ProjectSpec diff --git a/bundle/direct/dresources/postgres_role.go b/bundle/direct/dresources/postgres_role.go index 99f847dc56..d79ae5f902 100644 --- a/bundle/direct/dresources/postgres_role.go +++ b/bundle/direct/dresources/postgres_role.go @@ -96,7 +96,7 @@ func (*ResourcePostgresRole) RemapState(remote *PostgresRoleRemote) *PostgresRol // makePostgresRoleRemote converts the SDK Role into the embedded remote shape. // GET does not echo spec today (only status is returned); the embedded spec fields -// stay at their zero values, and resources.yml suppresses phantom drift via +// stay at their zero values, and postgres_roles.yaml suppresses phantom drift via // ignore_remote_changes with reason spec:input_only. func makePostgresRoleRemote(role *postgres.Role) *PostgresRoleRemote { var spec postgres.RoleRoleSpec diff --git a/bundle/direct/dresources/postgres_synced_table.go b/bundle/direct/dresources/postgres_synced_table.go index 0f07c33293..6ec5e0633c 100644 --- a/bundle/direct/dresources/postgres_synced_table.go +++ b/bundle/direct/dresources/postgres_synced_table.go @@ -62,7 +62,7 @@ func (*ResourcePostgresSyncedTable) RemapState(remote *PostgresSyncedTableRemote // makePostgresSyncedTableRemote converts the SDK SyncedTable into the embedded // remote shape. GET does not echo spec today (only status is returned); the -// embedded spec fields stay at their zero values, and resources.yml suppresses +// embedded spec fields stay at their zero values, and postgres_synced_tables.yaml suppresses // phantom drift via ignore_remote_changes with reason spec:input_only. // // The synced-table API doesn't expose the user-facing id as a named field. It diff --git a/bundle/direct/dresources/registered_model.go b/bundle/direct/dresources/registered_model.go index b72038a79f..b84d322743 100644 --- a/bundle/direct/dresources/registered_model.go +++ b/bundle/direct/dresources/registered_model.go @@ -39,7 +39,7 @@ func (*ResourceRegisteredModel) RemapState(model *catalog.RegisteredModelInfo) * Owner: model.Owner, // Output only fields. Remote changes to these are ignored via - // ignore_remote_changes in resources.yml rather than zeroed here. + // ignore_remote_changes in registered_models.yaml rather than zeroed here. CreatedAt: model.CreatedAt, CreatedBy: model.CreatedBy, UpdatedAt: model.UpdatedAt, diff --git a/bundle/direct/dresources/resources.generated.yml b/bundle/direct/dresources/resources.generated.yml deleted file mode 100644 index 9d0fc0f3b4..0000000000 --- a/bundle/direct/dresources/resources.generated.yml +++ /dev/null @@ -1,424 +0,0 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual edits and schema description, see resources.yml. - -resources: - - alerts: - - ignore_remote_changes: - - field: create_time - reason: spec:output_only - - field: effective_run_as - reason: spec:output_only - - field: evaluation.last_evaluated_at - reason: spec:output_only - - field: evaluation.state - reason: spec:output_only - - field: id - reason: spec:output_only - - field: lifecycle_state - reason: spec:output_only - - field: owner_user_name - reason: spec:output_only - - field: update_time - reason: spec:output_only - - apps: - - ignore_remote_changes: - - field: active_deployment - reason: spec:output_only - - field: app_status - reason: spec:output_only - - field: compute_status - reason: spec:output_only - - field: create_time - reason: spec:output_only - - field: creator - reason: spec:output_only - - field: default_source_code_path - reason: spec:output_only - - field: effective_budget_policy_id - reason: spec:output_only - - field: effective_usage_policy_id - reason: spec:output_only - - field: effective_user_api_scopes - reason: spec:output_only - - field: id - reason: spec:output_only - - field: oauth2_app_client_id - reason: spec:output_only - - field: oauth2_app_integration_id - reason: spec:output_only - - field: pending_deployment - reason: spec:output_only - - field: service_principal_client_id - reason: spec:output_only - - field: service_principal_id - reason: spec:output_only - - field: service_principal_name - reason: spec:output_only - - field: thumbnail_url - reason: spec:output_only - - field: update_time - reason: spec:output_only - - field: updater - reason: spec:output_only - - field: url - reason: spec:output_only - - # catalogs: no api field behaviors - - # clusters: no api field behaviors - - dashboards: - - recreate_on_changes: - - field: parent_path - reason: spec:immutable - - ignore_remote_changes: - - field: create_time - reason: spec:output_only - - field: dashboard_id - reason: spec:output_only - - field: lifecycle_state - reason: spec:output_only - - field: path - reason: spec:output_only - - field: update_time - reason: spec:output_only - - database_catalogs: - - ignore_remote_changes: - - field: create_database_if_not_exists - reason: spec:input_only - - - field: uid - reason: spec:output_only - - database_instances: - - recreate_on_changes: - - field: parent_instance_ref - reason: spec:immutable - - ignore_remote_changes: - - field: custom_tags - reason: spec:input_only - - field: enable_pg_native_login - reason: spec:input_only - - field: enable_readable_secondaries - reason: spec:input_only - - field: node_count - reason: spec:input_only - - field: parent_instance_ref.lsn - reason: spec:input_only - - field: retention_window_in_days - reason: spec:input_only - - field: stopped - reason: spec:input_only - - field: usage_policy_id - reason: spec:input_only - - - field: child_instance_refs - reason: spec:output_only - - field: creation_time - reason: spec:output_only - - field: creator - reason: spec:output_only - - field: effective_capacity - reason: spec:output_only - - field: effective_custom_tags - reason: spec:output_only - - field: effective_enable_pg_native_login - reason: spec:output_only - - field: effective_enable_readable_secondaries - reason: spec:output_only - - field: effective_node_count - reason: spec:output_only - - field: effective_retention_window_in_days - reason: spec:output_only - - field: effective_stopped - reason: spec:output_only - - field: effective_usage_policy_id - reason: spec:output_only - - field: parent_instance_ref.effective_lsn - reason: spec:output_only - - field: parent_instance_ref.uid - reason: spec:output_only - - field: pg_version - reason: spec:output_only - - field: read_only_dns - reason: spec:output_only - - field: read_write_dns - reason: spec:output_only - - field: state - reason: spec:output_only - - field: uid - reason: spec:output_only - - experiments: - - recreate_on_changes: - - field: trace_location - reason: spec:immutable - - ignore_remote_changes: - - field: trace_location.uc_trace_location.effective_table_prefix - reason: spec:output_only - - external_locations: - - ignore_remote_changes: - - field: effective_enable_file_events - reason: spec:output_only - - field: effective_file_event_queue - reason: spec:output_only - - genie_spaces: - - ignore_remote_changes: - - field: etag - reason: spec:output_only - - # instance_pools: no api field behaviors - - # job_runs: no api field behaviors - - # jobs: no api field behaviors - - model_serving_endpoints: - - ignore_remote_changes: - - field: telemetry_config.table_names - reason: spec:input_only - - - field: telemetry_config.inference_table_config.name - reason: spec:output_only - - # models: no api field behaviors - - pipelines: - - ignore_remote_changes: - - field: ingestion_definition.source_type - reason: spec:output_only - - postgres_branches: - - recreate_on_changes: - - field: source_branch - reason: spec:immutable - - field: source_branch_lsn - reason: spec:immutable - - field: source_branch_time - reason: spec:immutable - - ignore_remote_changes: - - field: expire_time - reason: spec:input_only - - field: is_protected - reason: spec:input_only - - field: no_expiry - reason: spec:input_only - - field: source_branch - reason: spec:input_only - - field: source_branch_lsn - reason: spec:input_only - - field: source_branch_time - reason: spec:input_only - - field: ttl - reason: spec:input_only - - postgres_catalogs: - - recreate_on_changes: - - field: postgres_database - reason: spec:immutable - - ignore_remote_changes: - - field: branch - reason: spec:input_only - - field: create_database_if_missing - reason: spec:input_only - - field: postgres_database - reason: spec:input_only - - postgres_databases: - - ignore_remote_changes: - - field: postgres_database - reason: spec:input_only - - field: role - reason: spec:input_only - - postgres_endpoints: - - recreate_on_changes: - - field: endpoint_type - reason: spec:immutable - - ignore_remote_changes: - - field: autoscaling_limit_max_cu - reason: spec:input_only - - field: autoscaling_limit_min_cu - reason: spec:input_only - - field: disabled - reason: spec:input_only - - field: endpoint_type - reason: spec:input_only - - field: group - reason: spec:input_only - - field: no_suspension - reason: spec:input_only - - field: settings - reason: spec:input_only - - field: suspend_timeout_duration - reason: spec:input_only - - postgres_projects: - - recreate_on_changes: - - field: pg_version - reason: spec:immutable - - ignore_remote_changes: - - field: budget_policy_id - reason: spec:input_only - - field: custom_tags - reason: spec:input_only - - field: default_branch - reason: spec:input_only - - field: default_endpoint_settings - reason: spec:input_only - - field: display_name - reason: spec:input_only - - field: enable_pg_native_login - reason: spec:input_only - - field: history_retention_duration - reason: spec:input_only - - field: pg_version - reason: spec:input_only - - postgres_roles: - - ignore_remote_changes: - - field: attributes - reason: spec:input_only - - field: auth_method - reason: spec:input_only - - field: identity_type - reason: spec:input_only - - field: membership_roles - reason: spec:input_only - - field: postgres_role - reason: spec:input_only - - postgres_synced_tables: - - ignore_remote_changes: - - field: accelerated_sync - reason: spec:input_only - - field: branch - reason: spec:input_only - - field: create_database_objects_if_missing - reason: spec:input_only - - field: existing_pipeline_id - reason: spec:input_only - - field: extra_columns - reason: spec:input_only - - field: new_pipeline_spec - reason: spec:input_only - - field: postgres_database - reason: spec:input_only - - field: primary_key_columns - reason: spec:input_only - - field: scheduling_policy - reason: spec:input_only - - field: source_table_full_name - reason: spec:input_only - - field: timeseries_key - reason: spec:input_only - - field: type_overrides - reason: spec:input_only - - # quality_monitors: no api field behaviors - - registered_models: - - ignore_remote_changes: - - field: browse_only - reason: spec:output_only - - # schemas: no api field behaviors - - # secret_scopes: no api field behaviors - - secrets: - - recreate_on_changes: - - field: catalog_name - reason: spec:immutable - - field: name - reason: spec:immutable - - field: schema_name - reason: spec:immutable - - ignore_remote_changes: - - field: owner - reason: spec:input_only - - field: value - reason: spec:input_only - - - field: create_time - reason: spec:output_only - - field: created_by - reason: spec:output_only - - field: effective_owner - reason: spec:output_only - - field: effective_value - reason: spec:output_only - - field: full_name - reason: spec:output_only - - field: metastore_id - reason: spec:output_only - - field: update_time - reason: spec:output_only - - field: updated_by - reason: spec:output_only - - # sql_warehouses: no api field behaviors - - synced_database_tables: - - ignore_remote_changes: - - field: database_instance_name - reason: spec:input_only - - field: logical_database_name - reason: spec:input_only - - field: spec.accelerated_sync - reason: spec:input_only - - field: spec.create_database_objects_if_missing - reason: spec:input_only - - field: spec.existing_pipeline_id - reason: spec:input_only - - field: spec.new_pipeline_spec - reason: spec:input_only - - - field: data_synchronization_status - reason: spec:output_only - - field: effective_database_instance_name - reason: spec:output_only - - field: effective_logical_database_name - reason: spec:output_only - - field: unity_catalog_provisioning_state - reason: spec:output_only - - # vector_search_endpoints: no api field behaviors - - # vector_search_indexes: no api field behaviors - - # volumes: no api field behaviors diff --git a/bundle/direct/dresources/resources.yml b/bundle/direct/dresources/resources.yml deleted file mode 100644 index ce0564ddc8..0000000000 --- a/bundle/direct/dresources/resources.yml +++ /dev/null @@ -1,852 +0,0 @@ -# Resource lifecycle configuration for Declarative Automation Bundles. -# This file defines how field changes affect resource operations. -# -# Available options: -# recreate_on_changes: fields that trigger delete + create -# provided_id_fields: fields composing the name-based ID the resource is fetched by. -# Local changes trigger delete + create. Remote-only differences are skipped: -# a successful get-by-ID means a differing remote value can only be backend -# normalization; a real out-of-band rename would 404 (handled as resource-gone). -# updatable_id_fields: like provided_id_fields, but a local change triggers -# UpdateWithID (rename; the ID changes) instead of delete + create. Remote-only -# differences are skipped the same way (only local changes act). -# ignore_remote_changes: fields where remote changes are ignored -# ignore_local_changes: fields where local changes are ignored (can't be updated via API) -# backend_defaults: fields where the backend may set defaults (skipped when old/new are nil but remote is set) -# Optional "values" list constrains which remote values are allowed (as JSON-compatible literals). -# -# Each field entry has: -# field: the field path -# reason: why this field is in this category (immutable, input_only, output_only, or descriptive text) - -resources: - - jobs: - # version_id is set to the current DMS deployment version on every deploy, so - # it changes constantly. Ignoring it as a local and remote change keeps that - # churn from driving an update or showing as drift on its own; when the job is - # updated for any other reason, DoUpdate sends the full config via Reset, so - # the current version_id is still recorded. deployment_id is intentionally - # left out: it is stable across versions, so a change to it is worth showing. - ignore_local_changes: - - field: deployment.version_id - reason: managed by the deployment metadata service - - ignore_remote_changes: - - field: deployment.version_id - reason: managed by the deployment metadata service - - # Same as clusters.{aws,azure,gcp}_attributes — see clusters/resource_cluster.go#L361-L363 - # s.SchemaPath("aws_attributes").SetSuppressDiff() - # s.SchemaPath("azure_attributes").SetSuppressDiff() - # s.SchemaPath("gcp_attributes").SetSuppressDiff() - - field: tasks[*].new_cluster.aws_attributes - reason: managed - - field: tasks[*].new_cluster.azure_attributes - reason: managed - - field: tasks[*].new_cluster.gcp_attributes - reason: managed - - field: tasks[*].for_each_task.task.new_cluster.aws_attributes - reason: managed - - field: tasks[*].for_each_task.task.new_cluster.azure_attributes - reason: managed - - field: tasks[*].for_each_task.task.new_cluster.gcp_attributes - reason: managed - - field: job_clusters[*].new_cluster.aws_attributes - reason: managed - - field: job_clusters[*].new_cluster.azure_attributes - reason: managed - - field: job_clusters[*].new_cluster.gcp_attributes - reason: managed - - # The Jobs API accepts apply_policy_default_values but does not return it - # in GET responses, so the remote is always nil even when the user sets it. - # Ignore remote-side nil so old==new (no user change) doesn't trigger drift. - - field: tasks[*].new_cluster.apply_policy_default_values - reason: not_returned_by_api - - field: tasks[*].for_each_task.task.new_cluster.apply_policy_default_values - reason: not_returned_by_api - - field: job_clusters[*].new_cluster.apply_policy_default_values - reason: not_returned_by_api - - backend_defaults: - # Same as clusters.enable_elastic_disk — see clusters/resource_cluster.go#L331 - # s.SchemaPath("enable_elastic_disk").SetComputed() - - field: tasks[*].new_cluster.enable_elastic_disk - - field: tasks[*].for_each_task.task.new_cluster.enable_elastic_disk - - field: job_clusters[*].new_cluster.enable_elastic_disk - - # Same as clusters.enable_local_disk_encryption — see clusters/resource_cluster.go#L332 - # s.SchemaPath("enable_local_disk_encryption").SetComputed() - - field: tasks[*].new_cluster.enable_local_disk_encryption - - field: tasks[*].for_each_task.task.new_cluster.enable_local_disk_encryption - - field: job_clusters[*].new_cluster.enable_local_disk_encryption - - # Same as clusters.node_type_id — see clusters/resource_cluster.go#L333 - # s.SchemaPath("node_type_id").SetComputed() - - field: tasks[*].new_cluster.node_type_id - - field: tasks[*].for_each_task.task.new_cluster.node_type_id - - field: job_clusters[*].new_cluster.node_type_id - - # Same as clusters.driver_node_type_id — see clusters/resource_cluster.go#L334 - # s.SchemaPath("driver_node_type_id").SetComputed() - - field: tasks[*].new_cluster.driver_node_type_id - - field: tasks[*].for_each_task.task.new_cluster.driver_node_type_id - - field: job_clusters[*].new_cluster.driver_node_type_id - - # Same as clusters.driver_instance_pool_id — see clusters/resource_cluster.go#L335 - # s.SchemaPath("driver_instance_pool_id").SetComputed() - - field: tasks[*].new_cluster.driver_instance_pool_id - - field: tasks[*].for_each_task.task.new_cluster.driver_instance_pool_id - - field: job_clusters[*].new_cluster.driver_instance_pool_id - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L531 - # s.SchemaPath("format").SetComputed() - - field: format - values: ["MULTI_TASK", "SINGLE_TASK"] - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L639 - # s.SchemaPath("task", "run_if").SetSuppressDiffWithDefault(jobs.RunIfAllSuccess) - - field: "tasks[*].run_if" - values: ["ALL_SUCCESS"] - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L640 - # s.SchemaPath("task", "for_each_task", "task", "run_if").SetSuppressDiffWithDefault(jobs.RunIfAllSuccess) - - field: "tasks[*].for_each_task.task.run_if" - values: ["ALL_SUCCESS"] - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L527 - # s.SchemaPath("run_as").SetComputed() - - field: run_as - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/jobs/resource_job.go#L521-L524 - # s.SchemaPath("task", "notebook_task", "source").SetSuppressDiff() - # s.SchemaPath("task", "spark_python_task", "source").SetSuppressDiff() - # s.SchemaPath("task", "sql_task", "file", "source").SetSuppressDiff() - # s.SchemaPath("task", "dbt_task", "source").SetSuppressDiff() - - field: tasks[*].notebook_task.source - - field: tasks[*].for_each_task.task.notebook_task.source - - field: tasks[*].spark_python_task.source - - field: tasks[*].for_each_task.task.spark_python_task.source - - field: tasks[*].sql_task.file.source - - field: tasks[*].for_each_task.task.sql_task.file.source - - field: tasks[*].dbt_task.source - - field: tasks[*].for_each_task.task.dbt_task.source - - # Same as clusters.data_security_mode: backend sets this when not specified - - field: tasks[*].new_cluster.data_security_mode - - field: tasks[*].for_each_task.task.new_cluster.data_security_mode - - field: job_clusters[*].new_cluster.data_security_mode - - job_runs: - # Every jobs.RunNow field is listed, so nothing the request carries is drift. - # TestJobRunIgnoresEveryRequestField keeps the list in step with the SDK. - # result_state is not in the list: comparing it against the remote is what - # makes a run that has not succeeded visible to the plan. - ignore_remote_changes: - - field: job_id - reason: immutable - # GetRun reports job_parameters resolved against the job's defaults, and - # nests the rest under overriding_parameters. - - field: dbt_commands - reason: effective_vs_requested - - field: jar_params - reason: effective_vs_requested - - field: job_parameters - reason: effective_vs_requested - - field: notebook_params - reason: effective_vs_requested - - field: pipeline_params - reason: effective_vs_requested - - field: python_named_params - reason: effective_vs_requested - - field: python_params - reason: effective_vs_requested - - field: spark_submit_params - reason: effective_vs_requested - - field: sql_params - reason: effective_vs_requested - # Fields the run-now request carries that GetRun never reports back. - - field: idempotency_token - reason: not_returned_by_api - - field: only - reason: not_returned_by_api - - field: performance_target - reason: not_returned_by_api - - field: queue - reason: not_returned_by_api - # A run is immutable and fire-once, so any change recreates it. Omitting - # `field` matches every field (root; see TestFieldRuleOmittedIsRoot). - # `field: ""` would instead match nothing. The one exception is a run that is - # still going, which ResourceJobRun.OverrideChangeDesc downgrades to an update. - recreate_on_changes: - - reason: immutable - - pipelines: - recreate_on_changes: - - field: storage - reason: immutable - - field: ingestion_definition.connection_name - reason: immutable - - field: ingestion_definition.ingestion_gateway_id - reason: immutable - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L204 - - field: gateway_definition.connection_id - reason: immutable - - field: gateway_definition.connection_name - reason: immutable - - field: gateway_definition.gateway_storage_catalog - reason: immutable - - field: gateway_definition.gateway_storage_schema - reason: immutable - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L209 - - field: ingestion_definition.ingest_from_uc_foreign_catalog - reason: immutable - - # See jobs above: version_id is set on every deploy, so it is ignored as a - # local/remote change. deployment_id is left out so a change to it still shows. - ignore_remote_changes: - - field: deployment.version_id - reason: managed by the deployment metadata service - # "id" is handled in a special way before any fields changed - # However, it is also part of RemotePipeline via CreatePipeline. - # Thus it shows up as a remote change since we don't set on the object. - - field: id - reason: "!drop" - # QQQ should this be here? When run_as is explicitly set, the GET response echoes it back - # as a structured run_as.user_name (verified on e2-dogfood with a real user), so it may not - # be truly input-only. The explicit-set case could not be confirmed on aws-cli, azure-cli, - # or gcp-cli: those envs authenticate as a service principal that lacks servicePrincipal.user - # on itself, so it can't self-bind run_as. In the default (unset) case on all three clouds, - # GET returns only the flat run_as_user_name and no structured run_as. - - field: run_as - reason: input_only - # Carried by CreatePipeline/EditPipeline but never returned by GET, so remote - # always reads back false and a config value of true never converges. - - field: allow_duplicate_names - reason: input_only - - ignore_local_changes: - - field: deployment.version_id - reason: managed by the deployment metadata service - # "id" is output-only, providing it in config would be a mistake - - field: id - reason: "!drop" - - backend_defaults: - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L238 - # s.SchemaPath("storage").SetCustomSuppressDiff(suppressStorageDiff) - # Backend generates storage path like dbfs:/pipelines/ when not set by user. - - field: storage - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L218 - # s.SchemaPath("cluster", "node_type_id").SetComputed() - - field: clusters[*].node_type_id - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L219 - # s.SchemaPath("cluster", "driver_node_type_id").SetComputed() - - field: clusters[*].driver_node_type_id - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L220 - # s.SchemaPath("cluster", "enable_local_disk_encryption").SetComputed() - - field: clusters[*].enable_local_disk_encryption - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L229-L230 - # s.SchemaPath("event_log", "catalog").SetComputed() - # s.SchemaPath("event_log", "schema").SetComputed() - - field: event_log.catalog - - field: event_log.schema - - models: - provided_id_fields: - # Recreate matches current behavior of Terraform. It is possible to rename without recreate - # but that would require dynamic select of the method during update since - # the ml.RenameModel needs to be called instead of ml.UpdateModel. - # We might reasonably choose to never fix this because this is a legacy resource. - - field: name - reason: terraform_compat - # Allowing updates for tags requires dynamic selection of the method since - # tags can only be updated by calling ml.SetModelTag or ml.DeleteModelTag methods. - # Skip annotation matches the current behavior of Terraform where tags changes are showed - # in plan but are just ignored / not applied. Since this is a legacy resource we might - # reasonably choose to not fix it here as well. - ignore_remote_changes: - - field: tags - reason: terraform_compat - ignore_local_changes: - - field: tags - reason: terraform_compat - - # TF implementation: https://github.com/databricks/terraform-provider-databricks/blob/6c106e8e7052bb2726148d66309fd460ed444236/mlflow/resource_mlflow_experiment.go#L22 - experiments: - recreate_on_changes: - - field: artifact_location - reason: immutable - backend_defaults: - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/mlflow/resource_mlflow_experiment.go#L34 - # SetForceNew().SetSuppressDiff(): backend generates artifact_location when not set by user - - field: artifact_location - ignore_remote_changes: - # Tags updates are not supported by TF. This mirrors that behaviour. - - field: tags - reason: terraform_compat - ignore_local_changes: - - field: tags - reason: terraform_compat - - # TF implementation: https://github.com/databricks/terraform-provider-databricks/blob/6c106e8e7052bb2726148d66309fd460ed444236/mlflow/resource_mlflow_experiment.go#L22 - model_serving_endpoints: - provided_id_fields: - - field: name - reason: id_field - recreate_on_changes: - # description is immutable, can't be updated via API - - field: description - reason: immutable - - field: config.auto_capture_config.catalog_name - reason: immutable - - field: config.auto_capture_config.schema_name - reason: immutable - - field: config.auto_capture_config.table_name_prefix - reason: immutable - - field: route_optimized - reason: immutable - ignore_remote_changes: - # budget_policy_id is in ServingEndpointDetailed but GET never populates it - # (the API returns effective_budget_policy_id instead), so the remote always - # reports empty. This is not a backend default, so suppress remote changes. - - field: budget_policy_id - reason: no_update_api - # Accepted on write but not returned by GET. - - field: config.served_entities[*].burst_scaling_enabled - reason: input_only - # Write-only secrets: the backend stores them and returns the reference field, not the plaintext. - - field: config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext - reason: input_only - - field: config.served_entities[*].external_model.amazon_bedrock_config.aws_access_key_id_plaintext - reason: input_only - - field: config.served_entities[*].external_model.amazon_bedrock_config.aws_secret_access_key_plaintext - reason: input_only - - field: config.served_entities[*].external_model.anthropic_config.anthropic_api_key_plaintext - reason: input_only - - field: config.served_entities[*].external_model.cohere_config.cohere_api_key_plaintext - reason: input_only - - field: config.served_entities[*].external_model.custom_provider_config.api_key_auth.value_plaintext - reason: input_only - - field: config.served_entities[*].external_model.custom_provider_config.bearer_token_auth.token_plaintext - reason: input_only - - field: config.served_entities[*].external_model.databricks_model_serving_config.databricks_api_token_plaintext - reason: input_only - - field: config.served_entities[*].external_model.google_cloud_vertex_ai_config.private_key_plaintext - reason: input_only - - field: config.served_entities[*].external_model.openai_config.openai_api_key_plaintext - reason: input_only - - field: config.served_entities[*].external_model.openai_config.microsoft_entra_client_secret_plaintext - reason: input_only - - field: config.served_entities[*].external_model.palm_config.palm_api_key_plaintext - reason: input_only - ignore_local_changes: - - field: budget_policy_id - reason: no_update_api - - field: rate_limits - reason: not_implemented - backend_defaults: - # Remote-only telemetry is reported as a change at this parent path. - - field: telemetry_config - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L383 - # common.CustomizeSchemaPath(m, "config", "served_entities", "name").SetComputed() - - field: config.served_entities[*].name - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L384 - # common.CustomizeSchemaPath(m, "config", "served_entities", "workload_type").SetComputed() - - field: config.served_entities[*].workload_type - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L372 - # common.CustomizeSchemaPath(m, "config", "auto_capture_config", "enabled").SetComputed() - - field: config.auto_capture_config.enabled - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L395-L396 - # route_optimized is ForceNew; backend returns false when not set by user. - - field: route_optimized - values: [false] - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L370 - # common.CustomizeSchemaPath(m, "config", "traffic_config").SetComputed() - # The backend defaults traffic_config (100% to the served entity) when the user - # does not specify one. Suppress only that default: if the user sets traffic_config - # and the remote diverges, normal drift detection still applies. - - field: config.traffic_config - - registered_models: - ignore_remote_changes: - # Output-only timestamp/user fields populated by the backend on read. - # The user never sets them, so remote-only differences are ignored here - # rather than zeroed in RemapState. - - field: created_at - reason: output_only - - field: created_by - reason: output_only - - field: updated_at - reason: output_only - - field: updated_by - reason: output_only - # Aliases are managed on model versions through a separate API, and DoRead - # passes IncludeAliases=false, so GET never echoes them back. Without this a - # config that sets aliases reports a perpetual update (remote stays empty). - - field: aliases - reason: input_only - provided_id_fields: - # The name can technically be updated without recreate. We recreate for now though - # to match TF implementation. - - field: name - reason: terraform_compat - - field: catalog_name - reason: id_field - - field: schema_name - reason: id_field - recreate_on_changes: - - field: storage_location - reason: immutable - backend_defaults: - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/catalog/resource_registered_model.go#L28 - # m["storage_location"].Computed = true - - field: storage_location - # owner, full_name, metastore_id are Computed in TF (backend-set output fields). - - field: owner - - field: full_name - - field: metastore_id - - quality_monitors: - provided_id_fields: - - field: table_name - reason: id_field - recreate_on_changes: - - field: assets_dir - reason: immutable - - catalogs: - recreate_on_changes: - - field: storage_root - reason: immutable - - field: connection_name - reason: immutable - - field: provider_name - reason: immutable - - field: share_name - reason: immutable - updatable_id_fields: - - field: name - reason: id_changes - backend_defaults: - # UC auto-populates unity.catalog.managed..defaults.* keys after create. - - field: properties['unity.catalog.managed.*.defaults.*'] - - schemas: - provided_id_fields: - # UC lowercases identifier names; remote returns "myschema" for config "MySchema". - - field: name - reason: id_field - - field: catalog_name - reason: id_field - recreate_on_changes: - - field: storage_root - reason: immutable - normalize_slash: - - field: storage_root - reason: uc_strips_trailing_slash - backend_defaults: - # UC auto-populates unity.catalog.managed..defaults.* keys after create. - # Without this, every subsequent plan produces an Update whose payload is empty, - # and UC rejects it with "UpdateSchema Nothing to update". - - field: properties['unity.catalog.managed.*.defaults.*'] - - external_locations: - recreate_on_changes: - - field: credential_name - reason: immutable - - field: encryption_details - reason: immutable - - field: file_event_queue - reason: immutable - updatable_id_fields: - - field: name - reason: id_changes - - volumes: - provided_id_fields: - - field: catalog_name - reason: id_field - - field: schema_name - reason: id_field - recreate_on_changes: - - field: storage_location - reason: immutable - - field: volume_type - reason: immutable - updatable_id_fields: - - field: name - reason: id_changes - normalize_slash: - # UC strips trailing slashes on create; matches the Terraform provider's suppressLocationDiff. - # https://github.com/databricks/terraform-provider-databricks/blob/v1.65.1/catalog/resource_volume.go#L25 - - field: storage_location - reason: uc_strips_trailing_slash - backend_defaults: - # storage_location is Computed; backend generates it for managed volumes. - - field: storage_location - - dashboards: - ignore_remote_changes: - # "serialized_dashboard" locally and remotely will have different contents - # We only need to rely on etag here, and can skip this field for diff computation. - - field: serialized_dashboard - reason: etag_based - - # "dataset_catalog" and "dataset_schema" are write-only fields that are not returned by the server. - # They will always differ between local config (which has values) and remote state (which has empty strings). - - field: dataset_catalog - reason: input_only - - field: dataset_schema - reason: input_only - - genie_spaces: - ignore_remote_changes: - # serialized_space locally (structured YAML) and remotely (JSON string) will differ - # textually, so we cannot meaningfully compare them for drift. - - field: serialized_space - reason: etag_based - - database_instances: - provided_id_fields: - - field: name - reason: id_field - - database_catalogs: - provided_id_fields: - - field: name - reason: id_field - # The Database API has no UpdateDatabaseCatalog endpoint (the generated SDK - # method is a stub that returns 501 NOT_IMPLEMENTED), so this resource - # implements no DoUpdate and every settable field must recreate. The - # complementary ignore_remote_changes block in resources.generated.yml - # handles the read side (create_database_if_not_exists is input-only, uid is - # output-only) so no-op deploys stay idempotent. - recreate_on_changes: - - reason: immutable - - synced_database_tables: - provided_id_fields: - - field: name - reason: id_field - # The Database API has no UpdateSyncedDatabaseTable endpoint (the generated - # SDK method is a stub that returns 501 NOT_IMPLEMENTED), so this resource - # implements no DoUpdate and every settable field must recreate. The - # complementary ignore_remote_changes block in resources.generated.yml - # handles the read side (input-only and output-only fields) so no-op deploys - # stay idempotent. Same pattern as postgres_synced_tables. - recreate_on_changes: - - reason: immutable - - apps: - provided_id_fields: - - field: name - reason: id_field - backend_defaults: - # Backend sets it "MEDIUM" when not specified in the config - - field: compute_size - # Backend enables token forwarding and reports true even when the config omits it, - # so an omitted field would otherwise plan an update on every deploy. An explicit - # value in the config still diffs normally. - - field: forward_user_access_token - # lifecycle.started is derived from remote compute status in RemapState, so the - # remote side always has a value. When the user omits lifecycle from config, - # both old and new are nil and backend_defaults correctly skips the remote value. - # When the user explicitly sets lifecycle.started, old/new are non-nil and normal - # drift detection applies (e.g. detecting out-of-band stop). - - field: lifecycle - - field: lifecycle.started - ignore_remote_changes: - - field: space # This field is not yet supported by Update APIs but exposed in the API spec. TODO: fix when update APIs supports it. - reason: managed - - secret_scopes: - backend_defaults: - # The Secrets API defaults scope_backend_type to DATABRICKS when not specified. - - field: scope_backend_type - values: ["DATABRICKS"] - provided_id_fields: - - field: scope - reason: id_field - recreate_on_changes: - - field: scope_backend_type - reason: immutable - - field: backend_azure_keyvault - reason: immutable - - field: initial_manage_principal - reason: immutable - - # Permissions for secret scopes use ResourceSecretScopeAcls. - secret_scopes.permissions: - updatable_id_fields: - # When scope name changes, we need UpdateWithID trigger. This is necessary so that subsequent - # DoRead operations use the correct ID and we do not end up with a persistent drift. - - field: scope_name - reason: id_changes - - secrets: - sensitive_fields: - - field: value - - field: effective_value - - clusters: - ignore_remote_changes: - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L361-L363 - # s.SchemaPath("aws_attributes").SetSuppressDiff() - # s.SchemaPath("azure_attributes").SetSuppressDiff() - # s.SchemaPath("gcp_attributes").SetSuppressDiff() - - field: aws_attributes - reason: managed - - field: azure_attributes - reason: managed - - field: gcp_attributes - reason: managed - backend_defaults: - # lifecycle.started is derived from remote cluster state in RemapState, so the - # remote side always has a value. When the user omits lifecycle from config, - # both old and new are nil and backend_defaults correctly skips the remote value. - # When the user explicitly sets lifecycle.started, old/new are non-nil and normal - # drift detection applies (e.g. detecting out-of-band terminate). - - field: lifecycle - - field: lifecycle.started - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L331 - # s.SchemaPath("enable_elastic_disk").SetComputed() - - field: enable_elastic_disk - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L332 - # s.SchemaPath("enable_local_disk_encryption").SetComputed() - - field: enable_local_disk_encryption - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L333 - # s.SchemaPath("node_type_id").SetComputed() - - field: node_type_id - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L334 - # s.SchemaPath("driver_node_type_id").SetComputed() - - field: driver_node_type_id - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L335 - # s.SchemaPath("driver_instance_pool_id").SetComputed() - - field: driver_instance_pool_id - - # Terraform currently does not do this, but it is a field with backend default. - # See https://github.com/databricks/cli/issues/4418 - - field: single_user_name - - # We have custom handler for this in cluster.go - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/clusters/resource_cluster.go#L109-L118 - # DataSecurityModeDiffSuppressFunc: suppress when old != "" && new == "" - #- field: data_security_mode - - instance_pools: - # Field behaviors follow the TF provider tags cross-referenced with the edit API (compute.EditInstancePool): - # https://github.com/databricks/terraform-provider-databricks/blob/main/pools/resource_instance_pool.go - ignore_remote_changes: - # Backend fills cloud defaults the user omits; treated as managed like clusters above. - - field: aws_attributes - reason: managed - - field: azure_attributes - reason: managed - - field: gcp_attributes - reason: managed - recreate_on_changes: - # force_new and not accepted by /instance-pools/edit. - - field: disk_spec - - field: node_type_flexibility - - field: preloaded_spark_versions - - field: preloaded_docker_images - backend_defaults: - # Defaults to true server-side. - - field: enable_elastic_disk - # Backend applies a default of 60 minutes when the field is omitted. - - field: idle_instance_autotermination_minutes - values: [60] - # GCP seeds max_capacity (1000) when omitted; unconstrained as the default is cloud-dependent. - - field: max_capacity - - sql_warehouses: - ignore_remote_changes: - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L62 - # common.CustomizeSchemaPath(m, "channel").SetSuppressDiff() - - field: channel - reason: managed - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L82 - # common.CustomizeSchemaPath(m, "tags").SetSuppressDiff() - - field: tags - reason: managed - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L85-L87 - # common.CustomizeSchemaPath(m, "warehouse_type").SetSuppressDiff() - - field: warehouse_type - reason: managed - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L75 - # common.CustomizeSchemaPath(m, "min_num_clusters").SetSuppressDiff() - - field: min_num_clusters - reason: managed - - # creator_name is readonly, can't be updated via API - - field: creator_name - reason: output_only - - backend_defaults: - # lifecycle.started is derived from remote warehouse state in RemapState, so the - # remote side always has a value. When the user omits lifecycle from config, - # both old and new are nil and backend_defaults correctly skips the remote value. - # When the user explicitly sets lifecycle.started, old/new are non-nil and normal - # drift detection applies (e.g. detecting out-of-band stop). - - field: lifecycle - - field: lifecycle.started - - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L69 - # m["enable_serverless_compute"].Computed = true - - field: enable_serverless_compute - - postgres_projects: - provided_id_fields: - # project_id is immutable (part of hierarchical name, not in API spec) - - field: project_id - reason: id_field - - postgres_branches: - provided_id_fields: - # parent and branch_id are immutable (part of hierarchical name, not in API spec) - - field: parent - reason: id_field - - field: branch_id - reason: id_field - ignore_local_changes: - # replace_existing only takes effect on create; toggling it later is a no-op. - - field: replace_existing - reason: "input_only; cannot be updated after create" - - postgres_databases: - provided_id_fields: - # parent and database_id are immutable (part of hierarchical name, not in API spec) - - field: parent - reason: id_field - - field: database_id - reason: id_field - ignore_local_changes: - # replace_existing only takes effect on create; toggling it later is a no-op. - - field: replace_existing - reason: "input_only; cannot be updated after create" - - postgres_endpoints: - provided_id_fields: - # parent and endpoint_id are immutable (part of hierarchical name, not in API spec) - - field: parent - reason: id_field - - field: endpoint_id - reason: id_field - ignore_local_changes: - # replace_existing only takes effect on create; toggling it later is a no-op. - - field: replace_existing - reason: "input_only; cannot be updated after create" - - postgres_catalogs: - provided_id_fields: - # catalog_id is part of the hierarchical name and immutable. - - field: catalog_id - reason: id_field - recreate_on_changes: - # The Postgres SDK has no UpdateCatalog endpoint, so any local change - # requires delete+create. The OpenAPI spec only marks postgres_database - # as IMMUTABLE (handled by autogen); branch and create_database_if_missing - # need explicit entries here. - - reason: immutable - - postgres_synced_tables: - # The Postgres API has no UpdateSyncedTable endpoint, so every settable - # field is recreate-only on the intent side (local YAML edit -> delete + - # create). The complementary ignore_remote_changes block for this resource - # lives in resources.generated.yml and handles the read side: it suppresses - # drift for the same fields because the GET API does not echo back the - # spec. Together they make no-op deploys idempotent while a real config - # edit still triggers a recreate. Same pattern as secret_scopes. - provided_id_fields: - - field: synced_table_id - reason: id_field - recreate_on_changes: - - reason: immutable - - postgres_roles: - ignore_local_changes: - # replace_existing only takes effect on create; toggling it later is a no-op. - - field: replace_existing - reason: "input_only; cannot be updated after create" - recreate_on_changes: - # parent and role_id are immutable (together they form the hierarchical name). - - field: parent - reason: immutable - - field: role_id - reason: immutable - # The PATCH update_mask only accepts spec.attributes and spec.membership_roles; - # the backend rejects spec.postgres_role, spec.auth_method, and spec.identity_type - # with 400 INVALID_PARAMETER_VALUE "Unknown field path in update_mask". These spec - # fields are not marked immutable in the OpenAPI definition yet, so the generator - # doesn't catch them — declare the constraint manually until upstream is fixed. - - field: postgres_role - reason: immutable - - field: auth_method - reason: immutable - - field: identity_type - reason: immutable - - vector_search_endpoints: - provided_id_fields: - # The endpoint API has no rename; the endpoint is fetched by name. - - field: name - reason: id_field - recreate_on_changes: - - field: endpoint_type - reason: immutable - ignore_remote_changes: - # The API returns effective_budget_policy_id which may include inherited workspace policies, - # not the user-set budget_policy_id. Ignore until the API exposes the user-set value directly. - - field: budget_policy_id - reason: effective_vs_requested - - vector_search_indexes: - provided_id_fields: - - field: name - reason: id_field - recreate_on_changes: - # The index API has no rename or update path, so every config change - # has to go through delete + create. - - field: endpoint_name - reason: immutable - - field: index_type - reason: immutable - - field: index_subtype - reason: immutable - - field: primary_key - reason: immutable - - field: delta_sync_index_spec - reason: immutable - - field: direct_access_index_spec - reason: immutable - ignore_remote_changes: - # The backend rewrites schema_json on create: user-facing type names - # ("integer", "long", "short", "byte") are stored in Unity Catalog as - # Spark type names ("int", "bigint", "smallint", "tinyint") and the - # columns come back in sorted key order, so GET never echoes the user's - # literal input. Without this rule the rewrite reads as a change to the - # immutable direct_access_index_spec and plans a destructive recreate - # that drops all upserted vectors. - - field: direct_access_index_spec.schema_json - reason: normalized_by_backend - backend_defaults: - # The Vector Search API assigns index_subtype when the config omits it - - field: index_subtype diff --git a/bundle/direct/dresources/vector_search_index.go b/bundle/direct/dresources/vector_search_index.go index f7bd2f60f9..2c1830642e 100644 --- a/bundle/direct/dresources/vector_search_index.go +++ b/bundle/direct/dresources/vector_search_index.go @@ -182,7 +182,7 @@ func isIndexPendingDeletion(err error) bool { } // No DoUpdate: vector search indexes have no update API. All SDK fields are -// declared in resources.yml under recreate_on_changes or ignore_remote_changes. +// declared in vector_search_indexes.yaml under recreate_on_changes or ignore_remote_changes. // If a future SDK bump adds a new field that isn't classified, the framework // rejects the resulting Update plan at bundle_plan.go (see also the reflection // test in vector_search_index_test.go which catches it earlier at unit-test time). diff --git a/bundle/direct/dresources/vector_search_index_test.go b/bundle/direct/dresources/vector_search_index_test.go index 55fe35c228..ff40b55c2c 100644 --- a/bundle/direct/dresources/vector_search_index_test.go +++ b/bundle/direct/dresources/vector_search_index_test.go @@ -40,7 +40,7 @@ func TestVectorSearchIndexAllSDKFieldsAreClassified(t *testing.T) { } jsonTag = strings.TrimSuffix(jsonTag, ",omitempty") assert.Truef(t, classified[jsonTag], - "field %q is not declared in resources.yml under vector_search_indexes; "+ + "field %q is not declared in vector_search_indexes.yaml; "+ "vector_search_indexes has no update API, so every SDK field must be in "+ "recreate_on_changes, provided_id_fields or ignore_remote_changes", jsonTag, diff --git a/bundle/direct/tools/generate_resources.py b/bundle/direct/tools/generate_resources.py index 081de8b327..3eaeb766ca 100644 --- a/bundle/direct/tools/generate_resources.py +++ b/bundle/direct/tools/generate_resources.py @@ -5,7 +5,10 @@ # ] # /// """ -Generate resources.generated.yml from cli.json field behaviors. +Generate per-resource .generated.yaml files from cli.json field behaviors. + +A resource with no field behaviors gets no file; a stale file left over from a +previous run is removed. """ import argparse @@ -109,7 +112,9 @@ def filter_prefixes(fields): def write_field_group(lines, header, fields): """Write a group of fields with field and reason, grouped by behavior.""" - lines.append(f"\n {header}:") + if lines: + lines.append("") + lines.append(f"{header}:") # Group by behavior by_behavior = {} for field, behavior in fields: @@ -121,51 +126,59 @@ def write_field_group(lines, header, fields): first = False reason = f"spec:{behavior.lower()}" for field in by_behavior[behavior]: - lines.append(f" - field: {field}") - lines.append(f" reason: {reason}") + lines.append(f" - field: {field}") + lines.append(f" reason: {reason}") + +GENERATED_SUFFIX = ".generated.yaml" -def generate(resource_behaviors): - """Generate resources.yml.""" - lines = [ - """# Generated, do not edit. API field behaviors from OpenAPI schema. +HEADER = """# Generated, do not edit. API field behaviors from OpenAPI schema. # -# For manual edits and schema description, see resources.yml. +# For manual rules and the description of each category, see README.md.""" -resources:""" - ] - for resource in sorted(resource_behaviors): - behaviors = resource_behaviors[resource] +def generate(behaviors): + """Render one resource's field behaviors, or "" if it has none.""" + ignore_remote, recreate = [], [] + for field, fb in sorted(behaviors.items()): + if "OUTPUT_ONLY" in fb: + ignore_remote.append((field, "OUTPUT_ONLY")) + elif "INPUT_ONLY" in fb: + ignore_remote.append((field, "INPUT_ONLY")) + if "IMMUTABLE" in fb: + recreate.append((field, "IMMUTABLE")) - ignore_remote, recreate = [], [] - for field, fb in sorted(behaviors.items()): - if "OUTPUT_ONLY" in fb: - ignore_remote.append((field, "OUTPUT_ONLY")) - elif "INPUT_ONLY" in fb: - ignore_remote.append((field, "INPUT_ONLY")) - if "IMMUTABLE" in fb: - recreate.append((field, "IMMUTABLE")) + ignore_remote = filter_prefixes(ignore_remote) + recreate = filter_prefixes(recreate) - ignore_remote = filter_prefixes(ignore_remote) - recreate = filter_prefixes(recreate) + if not ignore_remote and not recreate: + return "" - if not ignore_remote and not recreate: - lines.append(f"\n # {resource}: no api field behaviors") - continue + lines = [] + if recreate: + write_field_group(lines, "recreate_on_changes", recreate) + if ignore_remote: + write_field_group(lines, "ignore_remote_changes", ignore_remote) - lines.append(f"\n {resource}:") + return HEADER + "\n\n" + "\n".join(lines) + "\n" - if recreate: - write_field_group(lines, "recreate_on_changes", recreate) - if ignore_remote: - write_field_group(lines, "ignore_remote_changes", ignore_remote) +def write_files(outdir, resource_behaviors): + """Write .generated.yaml per resource, pruning files that are now empty.""" + written = set() + for resource, behaviors in sorted(resource_behaviors.items()): + content = generate(behaviors) + if not content: + continue + path = outdir / (resource + GENERATED_SUFFIX) + path.write_text(content) + written.add(path) - while lines and lines[-1] == "": - lines.pop() + for path in sorted(outdir.glob("*" + GENERATED_SUFFIX)): + if path not in written: + path.unlink() - return "\n".join(lines) + print(f"wrote {len(written)} files to {outdir}", file=sys.stderr) def main(): @@ -174,6 +187,7 @@ def main(): parser.add_argument("apitypes", type=Path, help="Path to apitypes.generated.yml file") parser.add_argument("apitypes_override", type=Path, help="Path to apitypes.yml override file") parser.add_argument("out_fields", type=Path, help="Path to out.fields.txt file") + parser.add_argument("outdir", type=Path, help="Directory to write .generated.yaml files to") args = parser.parse_args() resource_types = parse_apitypes(args.apitypes, args.apitypes_override) @@ -191,7 +205,7 @@ def main(): print(f" {field}: {all_behaviors[field]}", file=sys.stderr) resource_behaviors[resource] = {f: b for f, b in all_behaviors.items() if f in fields} - print(generate(resource_behaviors)) + write_files(args.outdir, resource_behaviors) if __name__ == "__main__": From 29e146d55c33134517e8117defcd4566f85506f6 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 20 Aug 2026 12:56:50 +0200 Subject: [PATCH 3/3] Shorten the header of the generated files to one line --- bundle/direct/dresources/alerts.generated.yaml | 4 +--- bundle/direct/dresources/apps.generated.yaml | 4 +--- bundle/direct/dresources/dashboards.generated.yaml | 4 +--- bundle/direct/dresources/database_catalogs.generated.yaml | 4 +--- bundle/direct/dresources/database_instances.generated.yaml | 4 +--- bundle/direct/dresources/experiments.generated.yaml | 4 +--- bundle/direct/dresources/external_locations.generated.yaml | 4 +--- bundle/direct/dresources/genie_spaces.generated.yaml | 4 +--- .../direct/dresources/model_serving_endpoints.generated.yaml | 4 +--- bundle/direct/dresources/pipelines.generated.yaml | 4 +--- bundle/direct/dresources/postgres_branches.generated.yaml | 4 +--- bundle/direct/dresources/postgres_catalogs.generated.yaml | 4 +--- bundle/direct/dresources/postgres_databases.generated.yaml | 4 +--- bundle/direct/dresources/postgres_endpoints.generated.yaml | 4 +--- bundle/direct/dresources/postgres_projects.generated.yaml | 4 +--- bundle/direct/dresources/postgres_roles.generated.yaml | 4 +--- .../direct/dresources/postgres_synced_tables.generated.yaml | 4 +--- bundle/direct/dresources/registered_models.generated.yaml | 4 +--- bundle/direct/dresources/secrets.generated.yaml | 4 +--- .../direct/dresources/synced_database_tables.generated.yaml | 4 +--- bundle/direct/tools/generate_resources.py | 4 +--- 21 files changed, 21 insertions(+), 63 deletions(-) diff --git a/bundle/direct/dresources/alerts.generated.yaml b/bundle/direct/dresources/alerts.generated.yaml index abf522359e..8b9e18006b 100644 --- a/bundle/direct/dresources/alerts.generated.yaml +++ b/bundle/direct/dresources/alerts.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: create_time diff --git a/bundle/direct/dresources/apps.generated.yaml b/bundle/direct/dresources/apps.generated.yaml index 6b75e5c618..19cd2817e7 100644 --- a/bundle/direct/dresources/apps.generated.yaml +++ b/bundle/direct/dresources/apps.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: active_deployment diff --git a/bundle/direct/dresources/dashboards.generated.yaml b/bundle/direct/dresources/dashboards.generated.yaml index 78bba33a1e..936b6e7f1c 100644 --- a/bundle/direct/dresources/dashboards.generated.yaml +++ b/bundle/direct/dresources/dashboards.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. recreate_on_changes: - field: parent_path diff --git a/bundle/direct/dresources/database_catalogs.generated.yaml b/bundle/direct/dresources/database_catalogs.generated.yaml index 67b2aed240..5eddcfd2a4 100644 --- a/bundle/direct/dresources/database_catalogs.generated.yaml +++ b/bundle/direct/dresources/database_catalogs.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: create_database_if_not_exists diff --git a/bundle/direct/dresources/database_instances.generated.yaml b/bundle/direct/dresources/database_instances.generated.yaml index 9eb00013f1..9c87464f1e 100644 --- a/bundle/direct/dresources/database_instances.generated.yaml +++ b/bundle/direct/dresources/database_instances.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. recreate_on_changes: - field: parent_instance_ref diff --git a/bundle/direct/dresources/experiments.generated.yaml b/bundle/direct/dresources/experiments.generated.yaml index 2ca8095f66..afd372d58f 100644 --- a/bundle/direct/dresources/experiments.generated.yaml +++ b/bundle/direct/dresources/experiments.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. recreate_on_changes: - field: trace_location diff --git a/bundle/direct/dresources/external_locations.generated.yaml b/bundle/direct/dresources/external_locations.generated.yaml index bb45055e26..007e77c124 100644 --- a/bundle/direct/dresources/external_locations.generated.yaml +++ b/bundle/direct/dresources/external_locations.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: effective_enable_file_events diff --git a/bundle/direct/dresources/genie_spaces.generated.yaml b/bundle/direct/dresources/genie_spaces.generated.yaml index c5eba1a628..d9c3e63803 100644 --- a/bundle/direct/dresources/genie_spaces.generated.yaml +++ b/bundle/direct/dresources/genie_spaces.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: etag diff --git a/bundle/direct/dresources/model_serving_endpoints.generated.yaml b/bundle/direct/dresources/model_serving_endpoints.generated.yaml index e0a1c501ec..16f7827ecd 100644 --- a/bundle/direct/dresources/model_serving_endpoints.generated.yaml +++ b/bundle/direct/dresources/model_serving_endpoints.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: telemetry_config.table_names diff --git a/bundle/direct/dresources/pipelines.generated.yaml b/bundle/direct/dresources/pipelines.generated.yaml index 08d6864249..60639caed5 100644 --- a/bundle/direct/dresources/pipelines.generated.yaml +++ b/bundle/direct/dresources/pipelines.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: ingestion_definition.source_type diff --git a/bundle/direct/dresources/postgres_branches.generated.yaml b/bundle/direct/dresources/postgres_branches.generated.yaml index 600a646d39..dbe737d7f1 100644 --- a/bundle/direct/dresources/postgres_branches.generated.yaml +++ b/bundle/direct/dresources/postgres_branches.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. recreate_on_changes: - field: source_branch diff --git a/bundle/direct/dresources/postgres_catalogs.generated.yaml b/bundle/direct/dresources/postgres_catalogs.generated.yaml index 9814af4d97..63f0569c66 100644 --- a/bundle/direct/dresources/postgres_catalogs.generated.yaml +++ b/bundle/direct/dresources/postgres_catalogs.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. recreate_on_changes: - field: postgres_database diff --git a/bundle/direct/dresources/postgres_databases.generated.yaml b/bundle/direct/dresources/postgres_databases.generated.yaml index 0d90d027f3..9a9eb66418 100644 --- a/bundle/direct/dresources/postgres_databases.generated.yaml +++ b/bundle/direct/dresources/postgres_databases.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: postgres_database diff --git a/bundle/direct/dresources/postgres_endpoints.generated.yaml b/bundle/direct/dresources/postgres_endpoints.generated.yaml index 83d8e05337..2560c7f5ef 100644 --- a/bundle/direct/dresources/postgres_endpoints.generated.yaml +++ b/bundle/direct/dresources/postgres_endpoints.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. recreate_on_changes: - field: endpoint_type diff --git a/bundle/direct/dresources/postgres_projects.generated.yaml b/bundle/direct/dresources/postgres_projects.generated.yaml index ddabfda23f..4899dd2e13 100644 --- a/bundle/direct/dresources/postgres_projects.generated.yaml +++ b/bundle/direct/dresources/postgres_projects.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. recreate_on_changes: - field: pg_version diff --git a/bundle/direct/dresources/postgres_roles.generated.yaml b/bundle/direct/dresources/postgres_roles.generated.yaml index 1cf4c28fe4..d5b3710434 100644 --- a/bundle/direct/dresources/postgres_roles.generated.yaml +++ b/bundle/direct/dresources/postgres_roles.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: attributes diff --git a/bundle/direct/dresources/postgres_synced_tables.generated.yaml b/bundle/direct/dresources/postgres_synced_tables.generated.yaml index d658cbf418..b15292e2fb 100644 --- a/bundle/direct/dresources/postgres_synced_tables.generated.yaml +++ b/bundle/direct/dresources/postgres_synced_tables.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: accelerated_sync diff --git a/bundle/direct/dresources/registered_models.generated.yaml b/bundle/direct/dresources/registered_models.generated.yaml index b23f27c3aa..9e376d4524 100644 --- a/bundle/direct/dresources/registered_models.generated.yaml +++ b/bundle/direct/dresources/registered_models.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: browse_only diff --git a/bundle/direct/dresources/secrets.generated.yaml b/bundle/direct/dresources/secrets.generated.yaml index 4a658391c4..34877e8fb1 100644 --- a/bundle/direct/dresources/secrets.generated.yaml +++ b/bundle/direct/dresources/secrets.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. recreate_on_changes: - field: catalog_name diff --git a/bundle/direct/dresources/synced_database_tables.generated.yaml b/bundle/direct/dresources/synced_database_tables.generated.yaml index a439d8cf32..2958becd44 100644 --- a/bundle/direct/dresources/synced_database_tables.generated.yaml +++ b/bundle/direct/dresources/synced_database_tables.generated.yaml @@ -1,6 +1,4 @@ -# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md. +# Generated, do not edit. ignore_remote_changes: - field: database_instance_name diff --git a/bundle/direct/tools/generate_resources.py b/bundle/direct/tools/generate_resources.py index 3eaeb766ca..cbbf707d8a 100644 --- a/bundle/direct/tools/generate_resources.py +++ b/bundle/direct/tools/generate_resources.py @@ -132,9 +132,7 @@ def write_field_group(lines, header, fields): GENERATED_SUFFIX = ".generated.yaml" -HEADER = """# Generated, do not edit. API field behaviors from OpenAPI schema. -# -# For manual rules and the description of each category, see README.md.""" +HEADER = "# Generated, do not edit." def generate(behaviors):