diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 5bec8e8e02..bf7cbbc547 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -25,6 +25,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)). * Added support for Database Instance resource (aka Lakebase) ([#5212](https://github.com/databricks/terraform-provider-databricks/pull/5212)). diff --git a/docs/guides/experimental-exporter.md b/docs/guides/experimental-exporter.md index fa82ab2464..51135d0bc5 100644 --- a/docs/guides/experimental-exporter.md +++ b/docs/guides/experimental-exporter.md @@ -213,6 +213,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. @@ -315,6 +316,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 a3ae5bf5ad..878885f2ba 100644 --- a/exporter/exporter_test.go +++ b/exporter/exporter_test.go @@ -27,6 +27,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, @@ -566,6 +574,7 @@ func TestImportingUsersGroupsSecretScopes(t *testing.T) { emptyDataQualityMonitors, emptyDatabaseInstances, emptyConnections, + emptyTagPolicies, emptyRecipients, emptyGitCredentials, emptyWorkspace, @@ -846,6 +855,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 2b628668ed..396f8b70aa 100644 --- a/exporter/importables.go +++ b/exporter/importables.go @@ -3365,4 +3365,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 + }, }