diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 2328b3cc24..620dd5a453 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -20,6 +20,7 @@ * Added support for `databricks_data_quality_monitor` resource ([#5193](https://github.com/databricks/terraform-provider-databricks/pull/5193)). * Fix typo in the name of environment variable ([#5158](https://github.com/databricks/terraform-provider-databricks/pull/5158)). * Export permission assignments on workspace level ([#5169](https://github.com/databricks/terraform-provider-databricks/pull/5169)). +* Added support for UC Tag policies ([#5213](https://github.com/databricks/terraform-provider-databricks/pull/5213)). * Added support for Databricks Apps resources ([#5208](https://github.com/databricks/terraform-provider-databricks/pull/5208)). ### Internal Changes diff --git a/docs/guides/experimental-exporter.md b/docs/guides/experimental-exporter.md index 7cd8cce8e0..920e1ae937 100644 --- a/docs/guides/experimental-exporter.md +++ b/docs/guides/experimental-exporter.md @@ -212,6 +212,7 @@ Services could be specified in combination with predefined aliases (`all` - for * `uc-shares` - **listing** [databricks_share](../resources/share.md) and [databricks_recipient](../resources/recipient.md) * `uc-storage-credentials` - **listing** exports [databricks_storage_credential](../resources/storage_credential.md) resources on workspace or account level. * `uc-system-schemas` - **listing** exports [databricks_system_schema](../resources/system_schema.md) resources for the UC metastore of the current workspace. +* `uc-tags` - **listing** exports [databricks_tag_policy](../resources/tag_policy.md) resources. * `uc-tables` - **listing** (*we can't list directly, only via dependencies to top-level object*) [databricks_sql_table](../resources/sql_table.md) resource. * `uc-volumes` - **listing** (*we can't list directly, only via dependencies to top-level object*) [databricks_volume](../resources/volume.md) * `users` - **listing** [databricks_user](../resources/user.md) and [databricks_service_principal](../resources/service_principal.md) are written to their own files, simply because of their number. If Identity Federation is enabled on the workspace (when UC Metastore is attached), then users and service principals are exposed as data sources because they are defined on an account level. See the note above on how to perform migration between workspaces with Identity Federation enabled. @@ -313,6 +314,7 @@ Exporter aims to generate HCL code for most of the resources within the Databric | [databricks_sql_widget](../resources/sql_widget.md) | Yes | Yes | Yes | No | | [databricks_storage_credential](../resources/storage_credential.md) | Yes | Yes | Yes | No | | [databricks_system_schema](../resources/system_schema.md) | Yes | No | Yes | No | +| [databricks_tag_policy](../resources/tag_policy.md) | Yes | No | Yes | No | | [databricks_token](../resources/token.md) | Not Applicable | No | Yes | No | | [databricks_user](../resources/user.md) | Yes | No | Yes | Yes | | [databricks_user_instance_profile](../resources/user_instance_profile.md) | No | No | No | No | diff --git a/exporter/exporter_test.go b/exporter/exporter_test.go index 214775cb0e..f14785f658 100644 --- a/exporter/exporter_test.go +++ b/exporter/exporter_test.go @@ -26,6 +26,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/settings" "github.com/databricks/databricks-sdk-go/service/sharing" sdk_sql "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/databricks/databricks-sdk-go/service/tags" sdk_vs "github.com/databricks/databricks-sdk-go/service/vectorsearch" sdk_workspace "github.com/databricks/databricks-sdk-go/service/workspace" @@ -311,6 +312,13 @@ var emptyConnections = qa.HTTPFixture{ Response: sdk_uc.ListConnectionsResponse{}, } +var emptyTagPolicies = qa.HTTPFixture{ + Method: "GET", + Resource: "/api/2.1/tag-policies?", + Response: tags.ListTagPoliciesResponse{}, + ReuseRequest: true, +} + var emptyRepos = qa.HTTPFixture{ Method: "GET", ReuseRequest: true, @@ -556,6 +564,7 @@ func TestImportingUsersGroupsSecretScopes(t *testing.T) { emptyShares, emptyDataQualityMonitors, emptyConnections, + emptyTagPolicies, emptyRecipients, emptyGitCredentials, emptyWorkspace, @@ -835,6 +844,7 @@ func TestImportingNoResourcesError(t *testing.T) { emptyUcCredentials, emptyShares, emptyConnections, + emptyTagPolicies, emptyRecipients, emptyModelServing, emptyMlflowWebhooks, diff --git a/exporter/impl_uc.go b/exporter/impl_uc.go index beff413aa1..dc33729baa 100644 --- a/exporter/impl_uc.go +++ b/exporter/impl_uc.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/databricks-sdk-go/service/dataquality" + "github.com/databricks/databricks-sdk-go/service/tags" tf_uc "github.com/databricks/terraform-provider-databricks/catalog" "github.com/databricks/terraform-provider-databricks/common" data_quality_monitor "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/products/data_quality_monitor" @@ -674,6 +675,29 @@ func listArtifactAllowLists(ic *importContext) error { return nil } +func listTagPolicies(ic *importContext) error { + tagPolicies, err := ic.workspaceClient.TagPolicies.ListTagPoliciesAll(ic.Context, tags.ListTagPoliciesRequest{}) + if err != nil { + return err + } + i := 0 + for _, tagPolicy := range tagPolicies { + i++ + if !ic.MatchesName(tagPolicy.TagKey) { + continue + } + ic.Emit(&resource{ + Resource: "databricks_tag_policy", + ID: tagPolicy.TagKey, + }) + if i%50 == 0 { + log.Printf("[INFO] Imported %d Tag Policies", i) + } + } + log.Printf("[INFO] Listed %d Tag Policies", i) + return nil +} + func importSqlTable(ic *importContext, r *resource) error { tableFullName := r.ID ic.emitUCGrantsWithOwner("table/"+tableFullName, r) diff --git a/exporter/impl_uc_test.go b/exporter/impl_uc_test.go index 1db9a4b445..393cbd91ce 100644 --- a/exporter/impl_uc_test.go +++ b/exporter/impl_uc_test.go @@ -1,8 +1,14 @@ package exporter import ( + "context" + "fmt" + "os" "testing" + "github.com/databricks/databricks-sdk-go/service/tags" + "github.com/databricks/terraform-provider-databricks/common" + "github.com/databricks/terraform-provider-databricks/qa" "github.com/stretchr/testify/assert" ) @@ -26,3 +32,85 @@ func TestEmitUserSpOrGroup(t *testing.T) { assert.Contains(t, ic.testEmits, "databricks_group[] (display_name: users @ test.com)") } + +func TestTagPolicyExport(t *testing.T) { + qa.HTTPFixturesApply(t, []qa.HTTPFixture{ + meAdminFixture, + noCurrentMetastoreAttached, + { + Method: "GET", + Resource: "/api/2.1/tag-policies?", + Response: tags.ListTagPoliciesResponse{ + TagPolicies: []tags.TagPolicy{ + { + TagKey: "environment", + Description: "Environment tag policy", + Values: []tags.Value{ + {Name: "dev"}, + {Name: "staging"}, + {Name: "production"}, + }, + }, + { + TagKey: "team", + Description: "Team tag policy", + Values: []tags.Value{ + {Name: "engineering"}, + {Name: "data"}, + }, + }, + }, + }, + }, + { + Method: "GET", + Resource: "/api/2.1/tag-policies/environment?", + ReuseRequest: true, + Response: tags.TagPolicy{ + TagKey: "environment", + Description: "Environment tag policy", + Values: []tags.Value{ + {Name: "dev"}, + {Name: "staging"}, + {Name: "production"}, + }, + }, + }, + { + Method: "GET", + Resource: "/api/2.1/tag-policies/team?", + ReuseRequest: true, + Response: tags.TagPolicy{ + TagKey: "team", + Description: "Team tag policy", + Values: []tags.Value{ + {Name: "engineering"}, + {Name: "data"}, + }, + }, + }, + }, func(ctx context.Context, client *common.DatabricksClient) { + tmpDir := fmt.Sprintf("/tmp/tf-%s", qa.RandomName()) + defer os.RemoveAll(tmpDir) + + ic := newImportContext(client) + ic.noFormat = true + ic.Directory = tmpDir + ic.enableListing("uc-tags") + ic.enableServices("uc-tags") + + err := ic.Run() + assert.NoError(t, err) + + content, err := os.ReadFile(tmpDir + "/uc-tags.tf") + assert.NoError(t, err) + contentStr := normalizeWhitespace(string(content)) + assert.Contains(t, contentStr, `resource "databricks_tag_policy" "environment"`) + assert.Contains(t, contentStr, `resource "databricks_tag_policy" "team"`) + assert.Contains(t, contentStr, `tag_key = "environment"`) + assert.Contains(t, contentStr, `description = "Environment tag policy"`) + assert.Contains(t, contentStr, `name = "dev"`) + assert.Contains(t, contentStr, `name = "staging"`) + assert.Contains(t, contentStr, `name = "production"`) + }) +} diff --git a/exporter/importables.go b/exporter/importables.go index 5d65794ee3..0c706794e3 100644 --- a/exporter/importables.go +++ b/exporter/importables.go @@ -3350,4 +3350,12 @@ var resourcesMap map[string]importable = map[string]importable{ {Path: "alert_configurations.action_configurations.target", Resource: "databricks_user", Match: "user_name"}, }, }, + "databricks_tag_policy": { + WorkspaceLevel: true, + PluginFramework: true, + Service: "uc-tags", + List: listTagPolicies, + // TODO: add import function that will emit access control rule set for the tag policy + // This requires knowing the account ID, so will be added later + }, }