Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
### Exporter

* Added support for `databricks_data_quality_monitor` resource ([#5193](https://github.com/databricks/terraform-provider-databricks/pull/5193)).
* Added support for `databricks_budget_policy` resource ([#5217](https://github.com/databricks/terraform-provider-databricks/pull/5217)).
* 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 Databricks Apps resources ([#5208](https://github.com/databricks/terraform-provider-databricks/pull/5208)).
Expand Down
3 changes: 2 additions & 1 deletion docs/guides/experimental-exporter.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Services could be specified in combination with predefined aliases (`all` - for
* `access` - **listing** [databricks_permissions](../resources/permissions.md), [databricks_instance_profile](../resources/instance_profile.md), [databricks_ip_access_list](../resources/ip_access_list.md), and [databricks_access_control_rule_set](../resources/access_control_rule_set.md). *Please note that for `databricks_permissions` we list only `authorization = "tokens"`, the permissions for other objects (notebooks, ...) will be emitted when corresponding objects are processed!*
* `alerts` - **listing** [databricks_alert](../resources/alert.md) and [databricks_alert_v2](../resources/alert_v2.md).
* `apps` - **listing** [databricks_app](../resources/app.md) and [databricks_apps_settings_custom_template](../resources/apps_settings_custom_template.md).
* `billing` - **listing** [databricks_budget](../resources/budget.md).
* `billing` - **listing** [databricks_budget](../resources/budget.md) and [databricks_budget_policy](../resources/budget_policy.md).
* `compute` - **listing** [databricks_cluster](../resources/cluster.md).
* `dashboards` - **listing** [databricks_dashboard](../resources/dashboard.md).
* `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.*
Expand Down Expand Up @@ -246,6 +246,7 @@ Exporter aims to generate HCL code for most of the resources within the Databric
| [databricks_apps_settings_custom_template](../resources/apps_settings_custom_template.md) | Yes | No | Yes | No |
| [databricks_artifact_allowlist](../resources/artifact_allowlist.md) | Yes | No | Yes | No |
| [databricks_budget](../resources/budget.md) | Yes | Yes | No | Yes |
| [databricks_budget_policy](../resources/budget_policy.md) | Yes | Yes | No | Yes |
| [databricks_catalog](../resources/catalog.md) | Yes | Yes | Yes | No |
| [databricks_cluster](../resources/cluster.md) | Yes | No | Yes | No |
| [databricks_cluster_policy](../resources/cluster_policy.md) | Yes | No | Yes | No |
Expand Down
14 changes: 14 additions & 0 deletions exporter/exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/databricks/databricks-sdk-go/apierr"
"github.com/databricks/databricks-sdk-go/service/apps"
"github.com/databricks/databricks-sdk-go/service/billing"
sdk_uc "github.com/databricks/databricks-sdk-go/service/catalog"
sdk_compute "github.com/databricks/databricks-sdk-go/service/compute"
sdk_dashboards "github.com/databricks/databricks-sdk-go/service/dashboards"
Expand Down Expand Up @@ -380,6 +381,16 @@ var emptyDataQualityMonitors = qa.HTTPFixture{
ReuseRequest: true,
}

var emptyBudgetPolicies = qa.HTTPFixture{
Method: "GET",
Resource: "/api/2.0/accounts/[^/]+/budget/policies?",
Response: billing.ListBudgetPoliciesResponse{
Policies: []billing.BudgetPolicy{},
NextPageToken: "",
},
ReuseRequest: true,
}

var emptyIpAccessLIst = qa.HTTPFixture{
Method: http.MethodGet,
Resource: "/api/2.0/ip-access-lists",
Expand Down Expand Up @@ -549,6 +560,7 @@ func TestImportingUsersGroupsSecretScopes(t *testing.T) {
noCurrentMetastoreAttached,
emptyApps,
emptyAppsSettingsCustomTemplates,
emptyBudgetPolicies,
emptyLakeviewList,
emptyMetastoreList,
meAdminFixture,
Expand Down Expand Up @@ -822,6 +834,7 @@ func TestImportingNoResourcesError(t *testing.T) {
},
emptyApps,
emptyAppsSettingsCustomTemplates,
emptyBudgetPolicies,
emptyDataQualityMonitors,
emptyUsersList,
emptySpnsList,
Expand Down Expand Up @@ -3416,6 +3429,7 @@ func TestAppExport(t *testing.T) {
meAdminFixture,
noCurrentMetastoreAttached,
emptyAppsSettingsCustomTemplates,
emptyBudgetPolicies,
{
Method: "GET",
Resource: "/api/2.0/apps?",
Expand Down
8 changes: 8 additions & 0 deletions exporter/impl_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ func importApp(ic *importContext, r *resource) error {
}
}

// Budget Policy
if app.BudgetPolicyId != "" {
ic.Emit(&resource{
Resource: "databricks_budget_policy",
ID: app.BudgetPolicyId,
})
}

// Emit permissions
ic.emitPermissionsIfNotIgnored(r, fmt.Sprintf("/apps/%s", app.Name), "app_"+r.Name)
return nil
Expand Down
114 changes: 114 additions & 0 deletions exporter/impl_billing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package exporter

import (
"log"
"strconv"

"github.com/databricks/databricks-sdk-go/service/billing"
"github.com/databricks/terraform-provider-databricks/common"
)

func listBudgetPolicies(ic *importContext) error {
if ic.accountClient == nil {
return nil
}
policies, err := ic.accountClient.BudgetPolicy.ListAll(ic.Context, billing.ListBudgetPoliciesRequest{})
if err != nil {
return err
}
for _, policy := range policies {
if policy.PolicyId == "" {
continue
}
if !ic.MatchesName(policy.PolicyName) {
continue
}
ic.Emit(&resource{
Resource: "databricks_budget_policy",
ID: policy.PolicyId,
})
}
return nil
}

func importBudgetPolicy(ic *importContext, r *resource) error {
// Get binding_workspace_ids directly from DataWrapper
if r.DataWrapper == nil {
log.Printf("[WARN] DataWrapper is nil for budget policy %s", r.ID)
return nil
}

accountID := ic.Client.Config.AccountID
// Emit access control rule set for the budget policy
ic.Emit(&resource{
Resource: "databricks_access_control_rule_set",
ID: "accounts/" + accountID + "/budgetPolicies/" + r.ID + "/ruleSets/default",
})

bindingWorkspaceIdsRaw := r.DataWrapper.Get("binding_workspace_ids")
if bindingWorkspaceIdsRaw != nil {
// Convert to slice of int64
var bindingWorkspaceIds []int64
if workspaceIdsList, ok := bindingWorkspaceIdsRaw.([]int64); ok {
bindingWorkspaceIds = workspaceIdsList
}
// Emit workspace resources for each binding_workspace_id
if !ic.Client.Config.IsAzure() {
for _, workspaceId := range bindingWorkspaceIds {
ic.Emit(&resource{
Resource: "databricks_mws_workspaces",
ID: accountID + "/" + strconv.FormatInt(workspaceId, 10),
})
}
}
}

return nil
}

func listBudgets(ic *importContext) error {
updatedSinceMs := ic.getUpdatedSinceMs()
budgets, err := ic.accountClient.Budgets.ListAll(ic.Context, billing.ListBudgetConfigurationsRequest{})
if err != nil {
return err
}
for _, budget := range budgets {
if ic.incremental && budget.CreateTime < updatedSinceMs {
log.Printf("[DEBUG] skipping budget '%s' that was updated at %d (last active=%d)",
budget.DisplayName, budget.UpdateTime, updatedSinceMs)
continue
}
ic.Emit(&resource{
Resource: "databricks_budget",
ID: ic.accountClient.Config.AccountID + "|" + budget.BudgetConfigurationId,
Name: budget.DisplayName,
})
}
return nil
}

func importBudget(ic *importContext, r *resource) error {
var budget billing.BudgetConfiguration
s := ic.Resources["databricks_budget"].Schema
common.DataToStructPointer(r.Data, s, &budget)
if budget.Filter != nil && budget.Filter.WorkspaceId != nil && !ic.accountClient.Config.IsAzure() {
for _, workspaceId := range budget.Filter.WorkspaceId.Values {
ic.Emit(&resource{
Resource: "databricks_mws_workspaces",
ID: ic.accountClient.Config.AccountID + "/" + strconv.FormatInt(workspaceId, 10),
})
}
}
for _, alert := range budget.AlertConfigurations {
for _, action := range alert.ActionConfigurations {
if action.ActionType == billing.ActionConfigurationTypeEmailNotification {
ic.Emit(&resource{
Resource: "databricks_user",
Attribute: "user_name",
Value: action.Target,
})
}
}
}
return nil
}
64 changes: 17 additions & 47 deletions exporter/importables.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"strings"

"github.com/databricks/databricks-sdk-go/apierr"
"github.com/databricks/databricks-sdk-go/service/billing"
"github.com/databricks/databricks-sdk-go/service/compute"
"github.com/databricks/databricks-sdk-go/service/iam"
"github.com/databricks/databricks-sdk-go/service/ml"
Expand Down Expand Up @@ -1348,7 +1347,7 @@ var resourcesMap map[string]importable = map[string]importable{
{Path: "resources.secret.key", Resource: "databricks_secret", Match: "key",
IsValidApproximation: createIsMatchingScopeAndKey("scope", "key")},
{Path: "resources.uc_securable.securable_full_name", Resource: "databricks_volume"},
// {Path: "budget_policy_id", Resource: "databricks_budget"},
{Path: "budget_policy_id", Resource: "databricks_budget_policy", Match: "policy_id"},
},
},
"databricks_pipeline": {
Expand Down Expand Up @@ -2050,6 +2049,8 @@ var resourcesMap map[string]importable = map[string]importable{
Regexp: regexp.MustCompile("^accounts/[^/]+/servicePrincipals/([^/]+)/ruleSets/default$")},
{Path: "name", Resource: "databricks_group", MatchType: MatchRegexp,
Regexp: regexp.MustCompile("^accounts/[^/]+/groups/([^/]+)/ruleSets/default$")},
{Path: "name", Resource: "databricks_budget_policy", Match: "policy_id", MatchType: MatchRegexp,
Regexp: regexp.MustCompile(`^accounts/[^/]+/budgetPolicies/([^/]+)/ruleSets/default$`)},
},
Ignore: func(ic *importContext, r *resource) bool {
// We're ignoring ACLs without grant rules because we don't know about that at time of emitting from groups/service principals
Expand Down Expand Up @@ -3297,54 +3298,23 @@ var resourcesMap map[string]importable = map[string]importable{
{Path: "credentials_id", Resource: "databricks_mws_credentials", Match: "credentials_id"},
},
},
"databricks_budget_policy": {
AccountLevel: true,
PluginFramework: true,
Service: "billing",
Name: func(ic *importContext, d *schema.ResourceData) string { return d.Id() },
List: listBudgetPolicies,
Import: importBudgetPolicy,
Ignore: generateIgnoreObjectWithEmptyAttributeValue("databricks_budget_policy", "policy_id"),
Depends: []reference{
{Path: "binding_workspace_ids", Resource: "databricks_mws_workspaces", Match: "workspace_id"},
},
},
"databricks_budget": {
AccountLevel: true,
Service: "billing",
List: func(ic *importContext) error {
updatedSinceMs := ic.getUpdatedSinceMs()
budgets, err := ic.accountClient.Budgets.ListAll(ic.Context, billing.ListBudgetConfigurationsRequest{})
if err != nil {
return err
}
for _, budget := range budgets {
if ic.incremental && budget.CreateTime < updatedSinceMs {
log.Printf("[DEBUG] skipping budget '%s' that was updated at %d (last active=%d)",
budget.DisplayName, budget.UpdateTime, updatedSinceMs)
continue
}
ic.Emit(&resource{
Resource: "databricks_budget",
ID: ic.accountClient.Config.AccountID + "|" + budget.BudgetConfigurationId,
Name: budget.DisplayName,
})
}
return nil
},
Import: func(ic *importContext, r *resource) error {
var budget billing.BudgetConfiguration
s := ic.Resources["databricks_budget"].Schema
common.DataToStructPointer(r.Data, s, &budget)
if budget.Filter != nil && budget.Filter.WorkspaceId != nil && !ic.accountClient.Config.IsAzure() {
for _, workspaceId := range budget.Filter.WorkspaceId.Values {
ic.Emit(&resource{
Resource: "databricks_mws_workspaces",
ID: ic.accountClient.Config.AccountID + "/" + strconv.FormatInt(workspaceId, 10),
})
}
}
for _, alert := range budget.AlertConfigurations {
for _, action := range alert.ActionConfigurations {
if action.ActionType == billing.ActionConfigurationTypeEmailNotification {
ic.Emit(&resource{
Resource: "databricks_user",
Attribute: "user_name",
Value: action.Target,
})
}
}
}
return nil
},
List: listBudgets,
Import: importBudget,
Depends: []reference{
{Path: "filter.workspace_id.values", Resource: "databricks_mws_workspaces", Match: "workspace_id"},
{Path: "alert_configurations.action_configurations.target", Resource: "databricks_user", Match: "user_name"},
Expand Down
Loading