Skip to content

Commit b948d9f

Browse files
alexott840
andauthored
[Exporter] Added support for databricks_data_quality_monitor resource (#5193)
## Changes <!-- Summary of your changes that are easy to understand --> Also: - added unit tests for DQM and AlertsV2 - fixed a missing piece of resource read (calling the ImportState) - Moved TF Exporter instructions into a separate AGENTS.md file ## Tests <!-- How is this tested? Please see the checklist below and also describe any other relevant tests --> - [x] `make test` run locally - [x] relevant change in `docs/` folder - [ ] covered with integration tests in `internal/acceptance` - [x] using Go SDK - [x] using TF Plugin Framework - [x] has entry in `NEXT_CHANGELOG.md` file --------- Co-authored-by: Cedric <[email protected]>
1 parent 6e7f2e1 commit b948d9f

File tree

8 files changed

+389
-35
lines changed

8 files changed

+389
-35
lines changed

AGENTS.md

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -95,44 +95,11 @@ Resources are being migrated from SDKv2 to Plugin Framework. When migrating:
9595
- `TestMwsAcc*` - Account-level tests across all clouds
9696
- `TestUcAcc*` - Unity Catalog tests across all clouds
9797

98-
### Exporter Architecture
98+
### Terraform Exporter
9999

100100
The exporter (`exporter/` directory) generates Terraform configuration (`.tf` files) and import scripts from existing Databricks resources.
101101

102-
#### Unified HCL Code Generation
103-
104-
The exporter uses a **unified code generation approach** for both SDKv2 and Plugin Framework resources:
105-
106-
**Entry Point**: `unifiedDataToHcl()` in `exporter/codegen.go`
107-
- Works with both SDKv2 and Plugin Framework resources through abstraction layers
108-
- Uses `ResourceDataWrapper` and `SchemaWrapper` interfaces for unified data access
109-
- Extracts ~70% of common logic into shared helper functions
110-
111-
**Architecture**:
112-
```
113-
unifiedDataToHcl()
114-
├── extractFieldsForGeneration() ← Shared: field iteration, omission, variable refs, skip logic
115-
├── generateSdkv2Field() ← SDKv2-specific: generates blocks
116-
├── generatePluginFrameworkField() ← Plugin Framework-specific: generates attributes
117-
└── generateDependsOnAttribute() ← Shared: depends_on generation
118-
```
119-
120-
**Resource Definition Callbacks** (`importable` struct in `exporter/model.go`):
121-
- `ShouldOmitFieldUnified` - Unified callback for field omission (works with both frameworks)
122-
- `ShouldGenerateFieldUnified` - Unified callback for field generation (works with both frameworks)
123-
- `ShouldOmitField` - Legacy SDKv2-only callback (deprecated, use Unified version)
124-
- `ShouldGenerateField` - Legacy SDKv2-only callback (deprecated, use Unified version)
125-
126-
**Key Differences**:
127-
- SDKv2 generates nested structures as **blocks**: `evaluation { ... }`
128-
- Plugin Framework generates nested structures as **attributes**: `evaluation = { ... }`
129-
130-
**When Adding Exporter Support**:
131-
1. Define resource in `exporter/importables.go` with `PluginFramework: true` for Plugin Framework resources
132-
2. Implement `List` function to discover resources
133-
3. Implement `Import` function to emit dependencies (use `convertPluginFrameworkToGoSdk` helper for Plugin Framework)
134-
4. Define `Depends` relationships for cross-resource references
135-
5. Use unified callbacks (`ShouldOmitFieldUnified`, `ShouldGenerateFieldUnified`) for custom field logic
102+
When working on exporter code, read `exporter/AGENTS.md` for additional instructions.
136103

137104
### Dual-Provider Resource Patterns
138105

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
### Exporter
1717

18+
* Added support for `databricks_data_quality_monitor` resource ([#5193](https://github.com/databricks/terraform-provider-databricks/pull/5193)).
1819
* Fix typo in the name of environment variable ([#5158](https://github.com/databricks/terraform-provider-databricks/pull/5158)).
1920
* Export permission assignments on workspace level ([#5169](https://github.com/databricks/terraform-provider-databricks/pull/5169)).
2021

docs/guides/experimental-exporter.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ Services could be specified in combination with predefined aliases (`all` - for
179179
* `dashboards` - **listing** [databricks_dashboard](../resources/dashboard.md).
180180
* `directories` - **listing** [databricks_directory](../resources/directory.md). *Please note that directories aren't listed when running in the incremental mode! Only directories with updated notebooks will be emitted.*
181181
* `dlt` - **listing** [databricks_pipeline](../resources/pipeline.md).
182+
* `dq` - **listing** [databricks_data_quality_monitor](../resources/data_quality_monitor.md)
182183
* `groups` - **listing** [databricks_group](../data-sources/group.md) with [membership](../resources/group_member.md) and [data access](../resources/group_instance_profile.md). If Identity Federation is enabled on the workspace (when UC Metastore is attached), then account-level groups are exposed as data sources because they are defined on account level, and only workspace-level groups are exposed as resources. See the note above on how to perform migration between workspaces with Identity Federation enabled.
183184
* `idfed` - **listing** [databricks_mws_permission_assignment](../resources/mws_permission_assignment.md) (account-level) and [databricks_permission_assignment](../resources/permission_assignment.md) (workspace-level). When listing is done on account level, you can filter assignment only to specific workspace IDs as specified by `-match`, `-matchRegex`, and `-excludeRegex` options. I.e., to export assignments only for two workspaces, use `-matchRegex '^1688808130562317|5493220389262917$'`.
184185
* `jobs` - **listing** [databricks_job](../resources/job.md). Usually, there are more automated workflows than interactive clusters, so they get their own file in this tool's output. *Please note that workflows deployed and maintained via [Databricks Asset Bundles](https://docs.databricks.com/en/dev-tools/bundles/index.html) aren't exported!*
@@ -248,6 +249,7 @@ Exporter aims to generate HCL code for most of the resources within the Databric
248249
| [databricks_connection](../resources/connection.md) | Yes | Yes | Yes | No |
249250
| [databricks_credential](../resources/credential.md) | Yes | Yes | Yes | No |
250251
| [databricks_dashboard](../resources/dashboard.md) | Yes | No | Yes | No |
252+
| [databricks_data_quality_monitor](../resources/data_quality_monitor.md) | Yes | Yes | Yes | No |
251253
| [databricks_dbfs_file](../resources/dbfs_file.md) | Yes | No | Yes | No |
252254
| [databricks_external_location](../resources/external_location.md) | Yes | Yes | Yes | No |
253255
| [databricks_file](../resources/file.md) | Yes | No | Yes | No |

exporter/AGENTS.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Terraform Exporter
2+
3+
The exporter (`exporter/` directory) generates Terraform configuration (`.tf` files) and import scripts from existing Databricks resources.
4+
5+
## How it works under the hood
6+
7+
Let's look at how it works:
8+
9+
- When you start the Exporter, it allows you to select interactively which resources you want to export. You can also specify the resource types by using `-listing` and `-services` options that accept a comma-separated list of values (if nothing is specified, all resources are exported). The first option is used to list objects of specific resource classes, and the second option is used to handle transitive dependencies, such as permissions, notebooks referred to in jobs, etc.
10+
- Next, the Exporter looks to determine what objects it can represent as Terraform. It first goes through all supported resource types and, for each of them, lists available objects and imports these objects' current state into corresponding [Terraform objects](https://developer.hashicorp.com/terraform/language/expressions/type-constraints#object). If necessary, the exporter also handles dependencies between resources, for example, importing permissions, triggering imports of the notebook(s) referred to in the job definition, etc. It also downloads external resources, such as libraries, init scripts, notebooks, etc., and stores them as files on the disk. external resources - libraries, init scripts, notebooks, …
11+
- Finally, the Exporter generates the Terraform code and writes it to individual files corresponding to service types.
12+
13+
The logic of importing the individual resource type is isolated from the generation of the HCL (HashiCorp configuration language) code that is used by Terraform. Each resource type is defined as an instance of the `importable` structure that is related to a specific Databricks Terraform resource. This structure consists of the following fields (usually we need to define only what is strictly required for each of the resource types - for most of the resources these are: `Service`, `List`, `Name`, `Import`, `WorkspaceLevel`/`AccountLevel`, and `Depends`):
14+
15+
- `Service` defines to which service this resource belongs (`compute`, `notebooks`, `access`, etc.). These values could be used with the `-services` command-line option to select only specific service types to export.
16+
- `Name` is a function that generates a Terraform resource name for a specific instance of the resource.
17+
- `List` is a function responsible for listing the objects of a given resource type. This function just emits the object ID, and the actual import happens later.
18+
- `Search` is a function that is used to find the actual ID of the object referred to by some attribute. For example, if the user is referred to by its display name.
19+
- `Import` is a function that may emit additional objects after the object's state is read (read happens automatically). Most often it's used to emit references to permissions, or, for example, to generate files on the disk for init scripts, etc.
20+
- `Body` is a function that could be used to customize the HCL structure that will be put in the file. But it's rarely required, only for a few complex cases, such as `dataricks_mount,` where we need to generate nested blocks based on the storage URL.
21+
- `Ignore` is a function that checks if the current object needs to be ignored or not.
22+
- `ShouldOmitField` is a function that checks if the specific field of the resource should be omitted from the generated HCL code. Primarily, it’s used for ignoring computed attributes or attributes with default values. For example, it’s used to omit the automatically generated `application_id` attribute for the `databricks_service_principal` resource on AWS & GCP.
23+
- `Depends` defines how to extract references to other objects, guaranteeing the referential integrity of the generated Terraform code, as explained below.
24+
- `ApiVersion` is an optional REST API version that should be used for the given resource. For example, we use REST API version 2.1 for the `databricks_job` resource. This attribute will not be necessary when we fully switch to using the Go SDK everywhere.
25+
- `AccountLevel` defines if the specific service is an account-level resource.
26+
- `WorkspaceLevel` defines if the specific service is a workspace-level resource.
27+
- `PluginFramework` defines if the specific service is written using Terraform Plugin Framework.
28+
29+
## Adding a new resource to Exporter
30+
31+
When adding a new resource to Terraform Exporter we need to perform next steps:
32+
33+
1. Define a new `importable` instance in the `importables.go`.
34+
2. Specify if it's account-level or workspace-level resource, or both.
35+
3. Specify a service to which resource belongs to. Either use one of the existing, if it fits, or define a new one (ask user for confirmation).
36+
4. Implement the `List` function that will be discover and emit instances of the specific resource.
37+
5. (Optional) Implement the `Name` function that will extract TF resource name from an instance of a specific resource.
38+
6. (Recommended) Implement the `Import` function that is responsible for emitting of dependencies for this resource - permissions/grants, etc.
39+
7. (Optional) Implement the `ShouldOmitField` if some fields should be conditionally omitted.
40+
8. (Recommended) Add `Depends` that describes relationships between fields of the current resource and its dependencies.
41+
9. (Recommended) Add unit test that will validate the generated code, similar to `TestImportingLakeviewDashboards` or `TestNotificationDestinationExport` tests in `exporter_test.go` file. For resources that use Go SDK, use `MockWorkspaceClientFunc` from Databricks Go SDK instead of `HTTPFixture`.
42+
10. Update support matrix in `docs/guides/experimental-exporter.md` to indicate support for the new resource. Keep list of supported resources sorted.
43+
11. If new service name was introduced, add it to the corresponding section of the documentation. Keep it sorted alphabetically.
44+
45+
Recommendations:
46+
47+
- Use Go SDK as much as possible in `List` implementation and other places.
48+
- Existing resources may emit newly implemented resource, i.e., `databricks_sql_table` should emit `databricks_quality_monitor` when it's added to Exporter.
49+
- In some cases, references to dependencies could be ambiguous, i.e., there could be tables or schemas with the same name in different catalogs/schema. In this case we may need to add `IsValidApproximation` implementation.
50+
- When there is a need to access a one or a few attributes in the Terraform resource data/state, use `.Get`, but if there is a need to access fields in nested structures, or have access to multiple fields, convert resource data/state into a Go SDK struct and use it.
51+
- Refer [Databricks REST API documentation](https://docs.databricks.com/api/llms.txt) to understand a payload used in specific API.
52+
53+
**When Adding Exporter Support for resource implemented with Terraform plugin framework**:
54+
55+
1. Define resource in `exporter/importables.go` with `PluginFramework: true` for Plugin Framework resources
56+
2. Use `convertPluginFrameworkToGoSdk` helper for Plugin Framework
57+
3. Use unified callbacks (`ShouldOmitFieldUnified`, `ShouldGenerateFieldUnified`) for custom field logic
58+
59+
60+
## Unified HCL Code Generation
61+
62+
The exporter uses a **unified code generation approach** for both SDKv2 and Plugin Framework resources:
63+
64+
**Entry Point**: `unifiedDataToHcl()` in `exporter/codegen.go`
65+
- Works with both SDKv2 and Plugin Framework resources through abstraction layers
66+
- Uses `ResourceDataWrapper` and `SchemaWrapper` interfaces for unified data access
67+
- Extracts ~70% of common logic into shared helper functions
68+
69+
**Architecture**:
70+
```
71+
unifiedDataToHcl()
72+
├── extractFieldsForGeneration() ← Shared: field iteration, omission, variable refs, skip logic
73+
├── generateSdkv2Field() ← SDKv2-specific: generates blocks
74+
├── generatePluginFrameworkField() ← Plugin Framework-specific: generates attributes
75+
└── generateDependsOnAttribute() ← Shared: depends_on generation
76+
```
77+
78+
**Resource Definition Callbacks** (`importable` struct in `exporter/model.go`):
79+
- `ShouldOmitFieldUnified` - Unified callback for field omission (works with both frameworks)
80+
- `ShouldGenerateFieldUnified` - Unified callback for field generation (works with both frameworks)
81+
- `ShouldOmitField` - Legacy SDKv2-only callback (deprecated, use Unified version)
82+
- `ShouldGenerateField` - Legacy SDKv2-only callback (deprecated, use Unified version)
83+
84+
**Key Differences**:
85+
- SDKv2 generates nested structures as **blocks**: `evaluation { ... }`
86+
- Plugin Framework generates nested structures as **attributes**: `evaluation = { ... }`

exporter/context.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,26 @@ func (ic *importContext) readPluginFrameworkResource(r *resource, ir importable)
10041004
// Set the ID in the state
10051005
state.SetAttribute(ic.Context, path.Root("id"), types.StringValue(r.ID))
10061006

1007+
// Call ImportState if the resource supports it (for composite IDs, etc.)
1008+
if importableResource, ok := pfResource.(frameworkresource.ResourceWithImportState); ok {
1009+
importReq := frameworkresource.ImportStateRequest{
1010+
ID: r.ID,
1011+
}
1012+
importResp := frameworkresource.ImportStateResponse{
1013+
State: state,
1014+
}
1015+
1016+
importableResource.ImportState(ic.Context, importReq, &importResp)
1017+
1018+
if importResp.Diagnostics.HasError() {
1019+
log.Printf("[ERROR] Error importing state for %s#%s: %v", r.Resource, r.ID, importResp.Diagnostics)
1020+
return nil
1021+
}
1022+
1023+
// Use the state from ImportState for the Read call
1024+
state = importResp.State
1025+
}
1026+
10071027
var finalState *tfsdk.State
10081028

10091029
// Call Plugin Framework Read with retry logic

0 commit comments

Comments
 (0)