Skip to content

Commit 62fed93

Browse files
committed
[Exporter] Added support for databricks_budget_policy resource
1 parent 1075e39 commit 62fed93

File tree

6 files changed

+156
-48
lines changed

6 files changed

+156
-48
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
### Exporter
1919

2020
* Added support for `databricks_data_quality_monitor` resource ([#5193](https://github.com/databricks/terraform-provider-databricks/pull/5193)).
21+
* Added support for `databricks_budget_policy` resource ([#5217](https://github.com/databricks/terraform-provider-databricks/pull/5217)).
2122
* Fix typo in the name of environment variable ([#5158](https://github.com/databricks/terraform-provider-databricks/pull/5158)).
2223
* Export permission assignments on workspace level ([#5169](https://github.com/databricks/terraform-provider-databricks/pull/5169)).
2324
* Added support for Databricks Apps resources ([#5208](https://github.com/databricks/terraform-provider-databricks/pull/5208)).

docs/guides/experimental-exporter.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ Services could be specified in combination with predefined aliases (`all` - for
175175
* `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!*
176176
* `alerts` - **listing** [databricks_alert](../resources/alert.md) and [databricks_alert_v2](../resources/alert_v2.md).
177177
* `apps` - **listing** [databricks_app](../resources/app.md) and [databricks_apps_settings_custom_template](../resources/apps_settings_custom_template.md).
178-
* `billing` - **listing** [databricks_budget](../resources/budget.md).
178+
* `billing` - **listing** [databricks_budget](../resources/budget.md) and [databricks_budget_policy](../resources/budget_policy.md).
179179
* `compute` - **listing** [databricks_cluster](../resources/cluster.md).
180180
* `dashboards` - **listing** [databricks_dashboard](../resources/dashboard.md).
181181
* `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.*
@@ -246,6 +246,7 @@ Exporter aims to generate HCL code for most of the resources within the Databric
246246
| [databricks_apps_settings_custom_template](../resources/apps_settings_custom_template.md) | Yes | No | Yes | No |
247247
| [databricks_artifact_allowlist](../resources/artifact_allowlist.md) | Yes | No | Yes | No |
248248
| [databricks_budget](../resources/budget.md) | Yes | Yes | No | Yes |
249+
| [databricks_budget_policy](../resources/budget_policy.md) | Yes | Yes | No | Yes |
249250
| [databricks_catalog](../resources/catalog.md) | Yes | Yes | Yes | No |
250251
| [databricks_cluster](../resources/cluster.md) | Yes | No | Yes | No |
251252
| [databricks_cluster_policy](../resources/cluster_policy.md) | Yes | No | Yes | No |

exporter/exporter_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/databricks/databricks-sdk-go/apierr"
1616
"github.com/databricks/databricks-sdk-go/service/apps"
17+
"github.com/databricks/databricks-sdk-go/service/billing"
1718
sdk_uc "github.com/databricks/databricks-sdk-go/service/catalog"
1819
sdk_compute "github.com/databricks/databricks-sdk-go/service/compute"
1920
sdk_dashboards "github.com/databricks/databricks-sdk-go/service/dashboards"
@@ -380,6 +381,16 @@ var emptyDataQualityMonitors = qa.HTTPFixture{
380381
ReuseRequest: true,
381382
}
382383

384+
var emptyBudgetPolicies = qa.HTTPFixture{
385+
Method: "GET",
386+
Resource: "/api/2.0/accounts/[^/]+/budget/policies?",
387+
Response: billing.ListBudgetPoliciesResponse{
388+
Policies: []billing.BudgetPolicy{},
389+
NextPageToken: "",
390+
},
391+
ReuseRequest: true,
392+
}
393+
383394
var emptyIpAccessLIst = qa.HTTPFixture{
384395
Method: http.MethodGet,
385396
Resource: "/api/2.0/ip-access-lists",
@@ -549,6 +560,7 @@ func TestImportingUsersGroupsSecretScopes(t *testing.T) {
549560
noCurrentMetastoreAttached,
550561
emptyApps,
551562
emptyAppsSettingsCustomTemplates,
563+
emptyBudgetPolicies,
552564
emptyLakeviewList,
553565
emptyMetastoreList,
554566
meAdminFixture,
@@ -822,6 +834,7 @@ func TestImportingNoResourcesError(t *testing.T) {
822834
},
823835
emptyApps,
824836
emptyAppsSettingsCustomTemplates,
837+
emptyBudgetPolicies,
825838
emptyDataQualityMonitors,
826839
emptyUsersList,
827840
emptySpnsList,
@@ -3416,6 +3429,7 @@ func TestAppExport(t *testing.T) {
34163429
meAdminFixture,
34173430
noCurrentMetastoreAttached,
34183431
emptyAppsSettingsCustomTemplates,
3432+
emptyBudgetPolicies,
34193433
{
34203434
Method: "GET",
34213435
Resource: "/api/2.0/apps?",

exporter/impl_apps.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ func importApp(ic *importContext, r *resource) error {
9898
}
9999
}
100100

101+
// Budget Policy
102+
if app.BudgetPolicyId != "" {
103+
ic.Emit(&resource{
104+
Resource: "databricks_budget_policy",
105+
ID: app.BudgetPolicyId,
106+
})
107+
}
108+
101109
// Emit permissions
102110
ic.emitPermissionsIfNotIgnored(r, fmt.Sprintf("/apps/%s", app.Name), "app_"+r.Name)
103111
return nil

exporter/impl_billing.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package exporter
2+
3+
import (
4+
"log"
5+
"strconv"
6+
7+
"github.com/databricks/databricks-sdk-go/service/billing"
8+
"github.com/databricks/terraform-provider-databricks/common"
9+
)
10+
11+
func listBudgetPolicies(ic *importContext) error {
12+
if ic.accountClient == nil {
13+
return nil
14+
}
15+
policies, err := ic.accountClient.BudgetPolicy.ListAll(ic.Context, billing.ListBudgetPoliciesRequest{})
16+
if err != nil {
17+
return err
18+
}
19+
for _, policy := range policies {
20+
if policy.PolicyId == "" {
21+
continue
22+
}
23+
if !ic.MatchesName(policy.PolicyName) {
24+
continue
25+
}
26+
ic.Emit(&resource{
27+
Resource: "databricks_budget_policy",
28+
ID: policy.PolicyId,
29+
})
30+
}
31+
return nil
32+
}
33+
34+
func importBudgetPolicy(ic *importContext, r *resource) error {
35+
// Get binding_workspace_ids directly from DataWrapper
36+
if r.DataWrapper == nil {
37+
log.Printf("[WARN] DataWrapper is nil for budget policy %s", r.ID)
38+
return nil
39+
}
40+
41+
accountID := ic.Client.Config.AccountID
42+
// Emit access control rule set for the budget policy
43+
ic.Emit(&resource{
44+
Resource: "databricks_access_control_rule_set",
45+
ID: "accounts/" + accountID + "/budgetPolicies/" + r.ID + "/ruleSets/default",
46+
})
47+
48+
bindingWorkspaceIdsRaw := r.DataWrapper.Get("binding_workspace_ids")
49+
if bindingWorkspaceIdsRaw != nil {
50+
// Convert to slice of int64
51+
var bindingWorkspaceIds []int64
52+
if workspaceIdsList, ok := bindingWorkspaceIdsRaw.([]int64); ok {
53+
bindingWorkspaceIds = workspaceIdsList
54+
}
55+
// Emit workspace resources for each binding_workspace_id
56+
if !ic.Client.Config.IsAzure() {
57+
for _, workspaceId := range bindingWorkspaceIds {
58+
ic.Emit(&resource{
59+
Resource: "databricks_mws_workspaces",
60+
ID: accountID + "/" + strconv.FormatInt(workspaceId, 10),
61+
})
62+
}
63+
}
64+
}
65+
66+
return nil
67+
}
68+
69+
func listBudgets(ic *importContext) error {
70+
updatedSinceMs := ic.getUpdatedSinceMs()
71+
budgets, err := ic.accountClient.Budgets.ListAll(ic.Context, billing.ListBudgetConfigurationsRequest{})
72+
if err != nil {
73+
return err
74+
}
75+
for _, budget := range budgets {
76+
if ic.incremental && budget.CreateTime < updatedSinceMs {
77+
log.Printf("[DEBUG] skipping budget '%s' that was updated at %d (last active=%d)",
78+
budget.DisplayName, budget.UpdateTime, updatedSinceMs)
79+
continue
80+
}
81+
ic.Emit(&resource{
82+
Resource: "databricks_budget",
83+
ID: ic.accountClient.Config.AccountID + "|" + budget.BudgetConfigurationId,
84+
Name: budget.DisplayName,
85+
})
86+
}
87+
return nil
88+
}
89+
90+
func importBudget(ic *importContext, r *resource) error {
91+
var budget billing.BudgetConfiguration
92+
s := ic.Resources["databricks_budget"].Schema
93+
common.DataToStructPointer(r.Data, s, &budget)
94+
if budget.Filter != nil && budget.Filter.WorkspaceId != nil && !ic.accountClient.Config.IsAzure() {
95+
for _, workspaceId := range budget.Filter.WorkspaceId.Values {
96+
ic.Emit(&resource{
97+
Resource: "databricks_mws_workspaces",
98+
ID: ic.accountClient.Config.AccountID + "/" + strconv.FormatInt(workspaceId, 10),
99+
})
100+
}
101+
}
102+
for _, alert := range budget.AlertConfigurations {
103+
for _, action := range alert.ActionConfigurations {
104+
if action.ActionType == billing.ActionConfigurationTypeEmailNotification {
105+
ic.Emit(&resource{
106+
Resource: "databricks_user",
107+
Attribute: "user_name",
108+
Value: action.Target,
109+
})
110+
}
111+
}
112+
}
113+
return nil
114+
}

exporter/importables.go

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"strings"
1313

1414
"github.com/databricks/databricks-sdk-go/apierr"
15-
"github.com/databricks/databricks-sdk-go/service/billing"
1615
"github.com/databricks/databricks-sdk-go/service/compute"
1716
"github.com/databricks/databricks-sdk-go/service/iam"
1817
"github.com/databricks/databricks-sdk-go/service/ml"
@@ -1348,7 +1347,7 @@ var resourcesMap map[string]importable = map[string]importable{
13481347
{Path: "resources.secret.key", Resource: "databricks_secret", Match: "key",
13491348
IsValidApproximation: createIsMatchingScopeAndKey("scope", "key")},
13501349
{Path: "resources.uc_securable.securable_full_name", Resource: "databricks_volume"},
1351-
// {Path: "budget_policy_id", Resource: "databricks_budget"},
1350+
{Path: "budget_policy_id", Resource: "databricks_budget_policy", Match: "policy_id"},
13521351
},
13531352
},
13541353
"databricks_pipeline": {
@@ -2050,6 +2049,8 @@ var resourcesMap map[string]importable = map[string]importable{
20502049
Regexp: regexp.MustCompile("^accounts/[^/]+/servicePrincipals/([^/]+)/ruleSets/default$")},
20512050
{Path: "name", Resource: "databricks_group", MatchType: MatchRegexp,
20522051
Regexp: regexp.MustCompile("^accounts/[^/]+/groups/([^/]+)/ruleSets/default$")},
2052+
{Path: "name", Resource: "databricks_budget_policy", Match: "policy_id", MatchType: MatchRegexp,
2053+
Regexp: regexp.MustCompile(`^accounts/[^/]+/budgetPolicies/([^/]+)/ruleSets/default$`)},
20532054
},
20542055
Ignore: func(ic *importContext, r *resource) bool {
20552056
// We're ignoring ACLs without grant rules because we don't know about that at time of emitting from groups/service principals
@@ -3297,54 +3298,23 @@ var resourcesMap map[string]importable = map[string]importable{
32973298
{Path: "credentials_id", Resource: "databricks_mws_credentials", Match: "credentials_id"},
32983299
},
32993300
},
3301+
"databricks_budget_policy": {
3302+
AccountLevel: true,
3303+
PluginFramework: true,
3304+
Service: "billing",
3305+
Name: func(ic *importContext, d *schema.ResourceData) string { return d.Id() },
3306+
List: listBudgetPolicies,
3307+
Import: importBudgetPolicy,
3308+
Ignore: generateIgnoreObjectWithEmptyAttributeValue("databricks_budget_policy", "policy_id"),
3309+
Depends: []reference{
3310+
{Path: "binding_workspace_ids", Resource: "databricks_mws_workspaces", Match: "workspace_id"},
3311+
},
3312+
},
33003313
"databricks_budget": {
33013314
AccountLevel: true,
33023315
Service: "billing",
3303-
List: func(ic *importContext) error {
3304-
updatedSinceMs := ic.getUpdatedSinceMs()
3305-
budgets, err := ic.accountClient.Budgets.ListAll(ic.Context, billing.ListBudgetConfigurationsRequest{})
3306-
if err != nil {
3307-
return err
3308-
}
3309-
for _, budget := range budgets {
3310-
if ic.incremental && budget.CreateTime < updatedSinceMs {
3311-
log.Printf("[DEBUG] skipping budget '%s' that was updated at %d (last active=%d)",
3312-
budget.DisplayName, budget.UpdateTime, updatedSinceMs)
3313-
continue
3314-
}
3315-
ic.Emit(&resource{
3316-
Resource: "databricks_budget",
3317-
ID: ic.accountClient.Config.AccountID + "|" + budget.BudgetConfigurationId,
3318-
Name: budget.DisplayName,
3319-
})
3320-
}
3321-
return nil
3322-
},
3323-
Import: func(ic *importContext, r *resource) error {
3324-
var budget billing.BudgetConfiguration
3325-
s := ic.Resources["databricks_budget"].Schema
3326-
common.DataToStructPointer(r.Data, s, &budget)
3327-
if budget.Filter != nil && budget.Filter.WorkspaceId != nil && !ic.accountClient.Config.IsAzure() {
3328-
for _, workspaceId := range budget.Filter.WorkspaceId.Values {
3329-
ic.Emit(&resource{
3330-
Resource: "databricks_mws_workspaces",
3331-
ID: ic.accountClient.Config.AccountID + "/" + strconv.FormatInt(workspaceId, 10),
3332-
})
3333-
}
3334-
}
3335-
for _, alert := range budget.AlertConfigurations {
3336-
for _, action := range alert.ActionConfigurations {
3337-
if action.ActionType == billing.ActionConfigurationTypeEmailNotification {
3338-
ic.Emit(&resource{
3339-
Resource: "databricks_user",
3340-
Attribute: "user_name",
3341-
Value: action.Target,
3342-
})
3343-
}
3344-
}
3345-
}
3346-
return nil
3347-
},
3316+
List: listBudgets,
3317+
Import: importBudget,
33483318
Depends: []reference{
33493319
{Path: "filter.workspace_id.values", Resource: "databricks_mws_workspaces", Match: "workspace_id"},
33503320
{Path: "alert_configurations.action_configurations.target", Resource: "databricks_user", Match: "user_name"},

0 commit comments

Comments
 (0)