Skip to content

Commit 6088aa8

Browse files
authored
[Feature] Added resource databricks_custom_app_integration (#4124)
## Changes Add resource `databricks_custom_app_integration` for OAuth custom app integration ## Tests <!-- How is this tested? Please see the checklist below and also describe any other relevant tests --> - [x] `make test` run locally - [x] relevant change in `docs/` folder - [x] covered with integration tests in `internal/acceptance` - [x] relevant acceptance tests are passing - [x] using Go SDK
1 parent 4bebb0d commit 6088aa8

File tree

5 files changed

+349
-0
lines changed

5 files changed

+349
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package apps
2+
3+
import (
4+
"context"
5+
6+
"github.com/databricks/databricks-sdk-go/service/oauth2"
7+
"github.com/databricks/terraform-provider-databricks/common"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
10+
)
11+
12+
type CustomAppIntegration struct {
13+
oauth2.GetCustomAppIntegrationOutput
14+
// OAuth client-secret generated by the Databricks. If this is a
15+
// confidential OAuth app client-secret will be generated.
16+
ClientSecret string `json:"client_secret,omitempty"`
17+
}
18+
19+
func ResourceCustomAppIntegration() common.Resource {
20+
s := common.StructToSchema(CustomAppIntegration{}, func(m map[string]*schema.Schema) map[string]*schema.Schema {
21+
for _, p := range []string{"client_id", "create_time", "created_by", "creator_username", "integration_id"} {
22+
common.CustomizeSchemaPath(m, p).SetComputed()
23+
}
24+
for _, p := range []string{"confidential", "name", "scopes"} {
25+
common.CustomizeSchemaPath(m, p).SetForceNew()
26+
}
27+
common.CustomizeSchemaPath(m, "client_secret").SetSensitive().SetComputed()
28+
common.CustomizeSchemaPath(m, "token_access_policy", "access_token_ttl_in_minutes").SetValidateFunc(validation.IntBetween(5, 1440))
29+
common.CustomizeSchemaPath(m, "token_access_policy", "refresh_token_ttl_in_minutes").SetValidateFunc(validation.IntBetween(5, 129600))
30+
return m
31+
})
32+
return common.Resource{
33+
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
34+
var create oauth2.CreateCustomAppIntegration
35+
common.DataToStructPointer(d, s, &create)
36+
acc, err := c.AccountClient()
37+
if err != nil {
38+
return err
39+
}
40+
integration, err := acc.CustomAppIntegration.Create(ctx, create)
41+
if err != nil {
42+
return err
43+
}
44+
d.Set("integration_id", integration.IntegrationId)
45+
d.Set("client_id", integration.ClientId)
46+
d.Set("client_secret", integration.ClientSecret)
47+
d.SetId(integration.IntegrationId)
48+
return nil
49+
},
50+
Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
51+
acc, err := c.AccountClient()
52+
if err != nil {
53+
return err
54+
}
55+
integration, err := acc.CustomAppIntegration.GetByIntegrationId(ctx, d.Id())
56+
if err != nil {
57+
return err
58+
}
59+
return common.StructToData(integration, s, d)
60+
},
61+
Update: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
62+
var update oauth2.UpdateCustomAppIntegration
63+
update.IntegrationId = d.Id()
64+
common.DataToStructPointer(d, s, &update)
65+
acc, err := c.AccountClient()
66+
if err != nil {
67+
return err
68+
}
69+
return acc.CustomAppIntegration.Update(ctx, update)
70+
},
71+
Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
72+
acc, err := c.AccountClient()
73+
if err != nil {
74+
return err
75+
}
76+
return acc.CustomAppIntegration.DeleteByIntegrationId(ctx, d.Id())
77+
},
78+
Schema: s,
79+
}
80+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package apps
2+
3+
import (
4+
"testing"
5+
6+
"github.com/databricks/databricks-sdk-go/experimental/mocks"
7+
"github.com/databricks/databricks-sdk-go/service/oauth2"
8+
"github.com/stretchr/testify/mock"
9+
10+
"github.com/databricks/terraform-provider-databricks/qa"
11+
)
12+
13+
func TestResourceCustomAppIntegrationCreate(t *testing.T) {
14+
qa.ResourceFixture{
15+
MockAccountClientFunc: func(a *mocks.MockAccountClient) {
16+
api := a.GetMockCustomAppIntegrationAPI().EXPECT()
17+
api.Create(mock.Anything, oauth2.CreateCustomAppIntegration{
18+
Name: "custom_integration_name",
19+
RedirectUrls: []string{
20+
"https://example.com",
21+
},
22+
Scopes: []string{
23+
"all",
24+
},
25+
TokenAccessPolicy: &oauth2.TokenAccessPolicy{
26+
AccessTokenTtlInMinutes: 60,
27+
RefreshTokenTtlInMinutes: 30,
28+
},
29+
}).Return(&oauth2.CreateCustomAppIntegrationOutput{
30+
ClientId: "client_id",
31+
ClientSecret: "client_secret",
32+
IntegrationId: "integration_id",
33+
}, nil)
34+
api.GetByIntegrationId(mock.Anything, "integration_id").Return(
35+
&oauth2.GetCustomAppIntegrationOutput{
36+
Name: "custom_integration_name",
37+
RedirectUrls: []string{
38+
"https://example.com",
39+
},
40+
Scopes: []string{
41+
"all",
42+
},
43+
TokenAccessPolicy: &oauth2.TokenAccessPolicy{
44+
AccessTokenTtlInMinutes: 60,
45+
RefreshTokenTtlInMinutes: 30,
46+
},
47+
ClientId: "client_id",
48+
IntegrationId: "integration_id",
49+
}, nil,
50+
)
51+
},
52+
Create: true,
53+
AccountID: "account_id",
54+
HCL: `
55+
name = "custom_integration_name"
56+
redirect_urls = ["https://example.com"]
57+
scopes = ["all"]
58+
token_access_policy {
59+
access_token_ttl_in_minutes = 60
60+
refresh_token_ttl_in_minutes = 30
61+
}`,
62+
Resource: ResourceCustomAppIntegration(),
63+
}.ApplyAndExpectData(t, map[string]any{
64+
"name": "custom_integration_name",
65+
"integration_id": "integration_id",
66+
"client_id": "client_id",
67+
"client_secret": "client_secret",
68+
})
69+
}
70+
71+
func TestResourceCustomAppIntegrationRead(t *testing.T) {
72+
qa.ResourceFixture{
73+
MockAccountClientFunc: func(a *mocks.MockAccountClient) {
74+
a.GetMockCustomAppIntegrationAPI().EXPECT().GetByIntegrationId(mock.Anything, "integration_id").Return(
75+
&oauth2.GetCustomAppIntegrationOutput{
76+
Name: "custom_integration_name",
77+
RedirectUrls: []string{
78+
"https://example.com",
79+
},
80+
Scopes: []string{
81+
"all",
82+
},
83+
TokenAccessPolicy: &oauth2.TokenAccessPolicy{
84+
AccessTokenTtlInMinutes: 60,
85+
RefreshTokenTtlInMinutes: 30,
86+
},
87+
ClientId: "client_id",
88+
IntegrationId: "integration_id",
89+
}, nil,
90+
)
91+
},
92+
Resource: ResourceCustomAppIntegration(),
93+
Read: true,
94+
New: true,
95+
AccountID: "account_id",
96+
ID: "integration_id",
97+
}.ApplyAndExpectData(t, map[string]any{
98+
"name": "custom_integration_name",
99+
"integration_id": "integration_id",
100+
"client_id": "client_id",
101+
})
102+
}
103+
104+
func TestResourceCustomAppIntegrationUpdate(t *testing.T) {
105+
qa.ResourceFixture{
106+
MockAccountClientFunc: func(a *mocks.MockAccountClient) {
107+
api := a.GetMockCustomAppIntegrationAPI().EXPECT()
108+
api.Update(mock.Anything, oauth2.UpdateCustomAppIntegration{
109+
IntegrationId: "integration_id",
110+
RedirectUrls: []string{
111+
"https://example.com",
112+
},
113+
TokenAccessPolicy: &oauth2.TokenAccessPolicy{
114+
AccessTokenTtlInMinutes: 30,
115+
RefreshTokenTtlInMinutes: 30,
116+
},
117+
}).Return(nil)
118+
api.GetByIntegrationId(mock.Anything, "integration_id").Return(
119+
&oauth2.GetCustomAppIntegrationOutput{
120+
Name: "custom_integration_name",
121+
RedirectUrls: []string{
122+
"https://example.com",
123+
},
124+
Scopes: []string{
125+
"all",
126+
},
127+
TokenAccessPolicy: &oauth2.TokenAccessPolicy{
128+
AccessTokenTtlInMinutes: 30,
129+
RefreshTokenTtlInMinutes: 30,
130+
},
131+
ClientId: "client_id",
132+
IntegrationId: "integration_id",
133+
}, nil,
134+
)
135+
},
136+
Resource: ResourceCustomAppIntegration(),
137+
Update: true,
138+
HCL: `
139+
name = "custom_integration_name"
140+
redirect_urls = ["https://example.com"]
141+
scopes = ["all"]
142+
token_access_policy {
143+
access_token_ttl_in_minutes = 30
144+
refresh_token_ttl_in_minutes = 30
145+
}`,
146+
InstanceState: map[string]string{
147+
"name": "custom_integration_name",
148+
"integration_id": "integration_id",
149+
"client_id": "client_id",
150+
"scopes.#": "1",
151+
"scopes.0": "all",
152+
"redirect_urls.#": "1",
153+
"redirect_urls.0": "https://example.com",
154+
"token_access_policy.access_token_ttl_in_minutes": "30",
155+
"token_access_policy.refresh_token_ttl_in_minutes": "30",
156+
},
157+
AccountID: "account_id",
158+
ID: "integration_id",
159+
}.ApplyAndExpectData(t, map[string]any{
160+
"name": "custom_integration_name",
161+
"token_access_policy.0.access_token_ttl_in_minutes": 30,
162+
})
163+
}
164+
165+
func TestResourceCustomAppIntegrationDelete(t *testing.T) {
166+
qa.ResourceFixture{
167+
MockAccountClientFunc: func(a *mocks.MockAccountClient) {
168+
a.GetMockCustomAppIntegrationAPI().EXPECT().DeleteByIntegrationId(mock.Anything, "integration_id").Return(nil)
169+
},
170+
Resource: ResourceCustomAppIntegration(),
171+
AccountID: "account_id",
172+
Delete: true,
173+
ID: "integration_id",
174+
}.ApplyAndExpectData(t, nil)
175+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
subcategory: "Apps"
3+
---
4+
# databricks_custom_app_integration Resource
5+
6+
-> Initialize provider with `alias = "account"`, and `host` pointing to the account URL, like, `host = "https://accounts.cloud.databricks.com"`. Use `provider = databricks.account` for all account-level resources.
7+
8+
This resource allows you to enable [custom OAuth applications](https://docs.databricks.com/en/integrations/enable-disable-oauth.html#enable-custom-oauth-applications-using-the-databricks-ui).
9+
10+
## Example Usage
11+
12+
```hcl
13+
resource "databricks_custom_app_integration" "this" {
14+
name = "custom_integration_name"
15+
redirect_urls = ["https://example.com"]
16+
scopes = ["all-apis"]
17+
token_access_policy {
18+
access_token_ttl_in_minutes = %s
19+
refresh_token_ttl_in_minutes = 30
20+
}
21+
}
22+
```
23+
24+
## Argument Reference
25+
26+
The following arguments are available:
27+
28+
* `name` - (Required) Name of the custom OAuth app. Change requires a new resource.
29+
* `confidential` - Indicates whether an OAuth client secret is required to authenticate this client. Default to `false`. Change requires a new resource.
30+
* `redirect_urls` - List of OAuth redirect urls.
31+
* `scopes` - OAuth scopes granted to the application. Supported scopes: `all-apis`, `sql`, `offline_access`, `openid`, `profile`, `email`.
32+
33+
### token_access_policy Configuration Block (Optional)
34+
35+
* `access_token_ttl_in_minutes` - access token time to live (TTL) in minutes.
36+
* `refresh_token_ttl_in_minutes` - refresh token TTL in minutes. The TTL of refresh token cannot be lower than TTL of access token.
37+
38+
## Attribute Reference
39+
40+
In addition to all arguments above, the following attributes are exported:
41+
42+
* `integration_id` - Unique integration id for the custom OAuth app.
43+
* `client_id` - OAuth client-id generated by Databricks
44+
* `client_secret` - OAuth client-secret generated by the Databricks if this is a confidential OAuth app.
45+
46+
## Import
47+
48+
This resource can be imported by its integration ID.
49+
50+
```sh
51+
terraform import databricks_custom_app_integration.this '<integration_id>'
52+
```
53+
54+
## Related Resources
55+
56+
The following resources are used in the context:
57+
58+
* [databricks_mws_workspaces](mws_workspaces.md) to set up Databricks workspaces.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package acceptance
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
var (
9+
customAppIntegrationTemplate = `resource "databricks_custom_app_integration" "this" {
10+
name = "custom_integration_name"
11+
redirect_urls = ["https://example.com"]
12+
scopes = ["all-apis"]
13+
token_access_policy {
14+
access_token_ttl_in_minutes = %s
15+
refresh_token_ttl_in_minutes = 30
16+
}
17+
}`
18+
)
19+
20+
func TestMwsAccCustomAppIntegrationCreate(t *testing.T) {
21+
loadAccountEnv(t)
22+
AccountLevel(t, Step{
23+
Template: fmt.Sprintf(customAppIntegrationTemplate, "30"),
24+
})
25+
}
26+
27+
func TestMwsAccCustomAppIntegrationUpdate(t *testing.T) {
28+
loadAccountEnv(t)
29+
AccountLevel(t, Step{
30+
Template: fmt.Sprintf(customAppIntegrationTemplate, "30"),
31+
}, Step{
32+
Template: fmt.Sprintf(customAppIntegrationTemplate, "15"),
33+
})
34+
}

internal/providers/sdkv2/sdkv2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/databricks/databricks-sdk-go/useragent"
2424

2525
"github.com/databricks/terraform-provider-databricks/access"
26+
"github.com/databricks/terraform-provider-databricks/apps"
2627
"github.com/databricks/terraform-provider-databricks/aws"
2728
"github.com/databricks/terraform-provider-databricks/catalog"
2829
"github.com/databricks/terraform-provider-databricks/clusters"
@@ -137,6 +138,7 @@ func DatabricksProvider() *schema.Provider {
137138
"databricks_budget": finops.ResourceBudget().ToResource(),
138139
"databricks_catalog": catalog.ResourceCatalog().ToResource(),
139140
"databricks_catalog_workspace_binding": catalog.ResourceCatalogWorkspaceBinding().ToResource(),
141+
"databricks_custom_app_integration": apps.ResourceCustomAppIntegration().ToResource(),
140142
"databricks_connection": catalog.ResourceConnection().ToResource(),
141143
"databricks_cluster": clusters.ResourceCluster().ToResource(),
142144
"databricks_cluster_policy": policies.ResourceClusterPolicy().ToResource(),

0 commit comments

Comments
 (0)