Skip to content

Commit d3e1bb4

Browse files
committed
[Exporter] Added support for databricks_budget_policy resource
1 parent aacbf20 commit d3e1bb4

File tree

6 files changed

+155
-48
lines changed

6 files changed

+155
-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.*
@@ -247,6 +247,7 @@ Exporter aims to generate HCL code for most of the resources within the Databric
247247
| [databricks_apps_settings_custom_template](../resources/apps_settings_custom_template.md) | Yes | No | Yes | No |
248248
| [databricks_artifact_allowlist](../resources/artifact_allowlist.md) | Yes | No | Yes | No |
249249
| [databricks_budget](../resources/budget.md) | Yes | Yes | No | Yes |
250+
| [databricks_budget_policy](../resources/budget_policy.md) | Yes | Yes | No | Yes |
250251
| [databricks_catalog](../resources/catalog.md) | Yes | Yes | Yes | No |
251252
| [databricks_cluster](../resources/cluster.md) | Yes | No | Yes | No |
252253
| [databricks_cluster_policy](../resources/cluster_policy.md) | Yes | No | Yes | No |

exporter/exporter_test.go

Lines changed: 13 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"
@@ -389,6 +390,16 @@ var emptyDatabaseInstances = qa.HTTPFixture{
389390
ReuseRequest: true,
390391
}
391392

393+
var emptyBudgetPolicies = qa.HTTPFixture{
394+
Method: "GET",
395+
Resource: "/api/2.0/accounts/[^/]+/budget/policies?",
396+
Response: billing.ListBudgetPoliciesResponse{
397+
Policies: []billing.BudgetPolicy{},
398+
NextPageToken: "",
399+
},
400+
ReuseRequest: true,
401+
}
402+
392403
var emptyIpAccessLIst = qa.HTTPFixture{
393404
Method: http.MethodGet,
394405
Resource: "/api/2.0/ip-access-lists",
@@ -558,6 +569,7 @@ func TestImportingUsersGroupsSecretScopes(t *testing.T) {
558569
noCurrentMetastoreAttached,
559570
emptyApps,
560571
emptyAppsSettingsCustomTemplates,
572+
emptyBudgetPolicies,
561573
emptyLakeviewList,
562574
emptyMetastoreList,
563575
meAdminFixture,
@@ -832,6 +844,7 @@ func TestImportingNoResourcesError(t *testing.T) {
832844
},
833845
emptyApps,
834846
emptyAppsSettingsCustomTemplates,
847+
emptyBudgetPolicies,
835848
emptyDataQualityMonitors,
836849
emptyDatabaseInstances,
837850
emptyUsersList,

exporter/impl_apps.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ func importApp(ic *importContext, r *resource) error {
105105
}
106106
}
107107

108+
// Budget Policy
109+
if app.BudgetPolicyId != "" {
110+
ic.Emit(&resource{
111+
Resource: "databricks_budget_policy",
112+
ID: app.BudgetPolicyId,
113+
})
114+
}
115+
108116
// Emit permissions
109117
ic.emitPermissionsIfNotIgnored(r, fmt.Sprintf("/apps/%s", app.Name), "app_"+r.Name)
110118
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"
@@ -1351,7 +1350,7 @@ var resourcesMap map[string]importable = map[string]importable{
13511350
IsValidApproximation: createIsMatchingScopeAndKey("scope", "key")},
13521351
{Path: "resources.uc_securable.securable_full_name", Resource: "databricks_volume"},
13531352
{Path: "resources.database.instance_name", Resource: "databricks_database_instance", Match: "name"},
1354-
// {Path: "budget_policy_id", Resource: "databricks_budget"},
1353+
{Path: "budget_policy_id", Resource: "databricks_budget_policy", Match: "policy_id"},
13551354
},
13561355
},
13571356
"databricks_pipeline": {
@@ -2065,6 +2064,8 @@ var resourcesMap map[string]importable = map[string]importable{
20652064
Regexp: regexp.MustCompile("^accounts/[^/]+/servicePrincipals/([^/]+)/ruleSets/default$")},
20662065
{Path: "name", Resource: "databricks_group", MatchType: MatchRegexp,
20672066
Regexp: regexp.MustCompile("^accounts/[^/]+/groups/([^/]+)/ruleSets/default$")},
2067+
{Path: "name", Resource: "databricks_budget_policy", Match: "policy_id", MatchType: MatchRegexp,
2068+
Regexp: regexp.MustCompile(`^accounts/[^/]+/budgetPolicies/([^/]+)/ruleSets/default$`)},
20682069
},
20692070
Ignore: func(ic *importContext, r *resource) bool {
20702071
// We're ignoring ACLs without grant rules because we don't know about that at time of emitting from groups/service principals
@@ -3312,54 +3313,23 @@ var resourcesMap map[string]importable = map[string]importable{
33123313
{Path: "credentials_id", Resource: "databricks_mws_credentials", Match: "credentials_id"},
33133314
},
33143315
},
3316+
"databricks_budget_policy": {
3317+
AccountLevel: true,
3318+
PluginFramework: true,
3319+
Service: "billing",
3320+
Name: func(ic *importContext, d *schema.ResourceData) string { return d.Id() },
3321+
List: listBudgetPolicies,
3322+
Import: importBudgetPolicy,
3323+
Ignore: generateIgnoreObjectWithEmptyAttributeValue("databricks_budget_policy", "policy_id"),
3324+
Depends: []reference{
3325+
{Path: "binding_workspace_ids", Resource: "databricks_mws_workspaces", Match: "workspace_id"},
3326+
},
3327+
},
33153328
"databricks_budget": {
33163329
AccountLevel: true,
33173330
Service: "billing",
3318-
List: func(ic *importContext) error {
3319-
updatedSinceMs := ic.getUpdatedSinceMs()
3320-
budgets, err := ic.accountClient.Budgets.ListAll(ic.Context, billing.ListBudgetConfigurationsRequest{})
3321-
if err != nil {
3322-
return err
3323-
}
3324-
for _, budget := range budgets {
3325-
if ic.incremental && budget.CreateTime < updatedSinceMs {
3326-
log.Printf("[DEBUG] skipping budget '%s' that was updated at %d (last active=%d)",
3327-
budget.DisplayName, budget.UpdateTime, updatedSinceMs)
3328-
continue
3329-
}
3330-
ic.Emit(&resource{
3331-
Resource: "databricks_budget",
3332-
ID: ic.accountClient.Config.AccountID + "|" + budget.BudgetConfigurationId,
3333-
Name: budget.DisplayName,
3334-
})
3335-
}
3336-
return nil
3337-
},
3338-
Import: func(ic *importContext, r *resource) error {
3339-
var budget billing.BudgetConfiguration
3340-
s := ic.Resources["databricks_budget"].Schema
3341-
common.DataToStructPointer(r.Data, s, &budget)
3342-
if budget.Filter != nil && budget.Filter.WorkspaceId != nil && !ic.accountClient.Config.IsAzure() {
3343-
for _, workspaceId := range budget.Filter.WorkspaceId.Values {
3344-
ic.Emit(&resource{
3345-
Resource: "databricks_mws_workspaces",
3346-
ID: ic.accountClient.Config.AccountID + "/" + strconv.FormatInt(workspaceId, 10),
3347-
})
3348-
}
3349-
}
3350-
for _, alert := range budget.AlertConfigurations {
3351-
for _, action := range alert.ActionConfigurations {
3352-
if action.ActionType == billing.ActionConfigurationTypeEmailNotification {
3353-
ic.Emit(&resource{
3354-
Resource: "databricks_user",
3355-
Attribute: "user_name",
3356-
Value: action.Target,
3357-
})
3358-
}
3359-
}
3360-
}
3361-
return nil
3362-
},
3331+
List: listBudgets,
3332+
Import: importBudget,
33633333
Depends: []reference{
33643334
{Path: "filter.workspace_id.values", Resource: "databricks_mws_workspaces", Match: "workspace_id"},
33653335
{Path: "alert_configurations.action_configurations.target", Resource: "databricks_user", Match: "user_name"},

0 commit comments

Comments
 (0)