Skip to content

Commit b72863a

Browse files
authored
Added databricks_mws_permission_assignment resource (#1491)
These resources are invoked in the account context. Provider must have `account_id` attribute configured. Example usage in account context, adding account-level group to a workspace: ```hcl provider "databricks" { // <other properties> account_id = "<databricks account id>" } resource "databricks_group" "data_eng" { display_name = "Data Engineering" } resource "databricks_mws_permission_assignment" "add_admin_group" { workspace_id = databricks_mws_workspaces.this.workspace_id principal_id = databricks_group.data_eng.id permissions = ["ADMIN"] } ```
1 parent e9bf1f7 commit b72863a

12 files changed

+593
-13
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
| [databricks_mws_customer_managed_keys](docs/resources/mws_customer_managed_keys.md)
4747
| [databricks_mws_log_delivery](docs/resources/mws_log_delivery.md)
4848
| [databricks_mws_networks](docs/resources/mws_networks.md)
49+
| [databricks_mws_permission_assignment](docs/resources/mws_permission_assignment.md)
4950
| [databricks_mws_private_access_settings](docs/resources/mws_private_access_settings.md)
5051
| [databricks_mws_storage_configurations](docs/resources/mws_storage_configurations.md)
5152
| [databricks_mws_vpc_endpoint](docs/resources/mws_vpc_endpoint.md)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package access
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
8+
"github.com/databricks/terraform-provider-databricks/common"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
)
11+
12+
func NewPermissionAssignmentAPI(ctx context.Context, m any) PermissionAssignmentAPI {
13+
return PermissionAssignmentAPI{m.(*common.DatabricksClient), ctx}
14+
}
15+
16+
type PermissionAssignmentAPI struct {
17+
client *common.DatabricksClient
18+
context context.Context
19+
}
20+
21+
type Permissions struct {
22+
Permissions []string `json:"permissions"`
23+
}
24+
25+
func (a PermissionAssignmentAPI) CreateOrUpdate(principalId int64, r Permissions) error {
26+
path := fmt.Sprintf("/preview/permissionassignments/principals/%d", principalId)
27+
return a.client.Put(a.context, path, r)
28+
}
29+
30+
func (a PermissionAssignmentAPI) Remove(principalId string) error {
31+
path := fmt.Sprintf("/preview/permissionassignments/principals/%s", principalId)
32+
return a.client.Delete(a.context, path, nil)
33+
}
34+
35+
type Principal struct {
36+
DisplayName string `json:"display_name"`
37+
PrincipalID int64 `json:"principal_id"`
38+
ServicePrincipalName string `json:"service_principal_name,omitempty"`
39+
UserName string `json:"user_name,omitempty"`
40+
GroupName string `json:"group_name,omitempty"`
41+
}
42+
43+
type PermissionAssignment struct {
44+
Permissions []string `json:"permissions"`
45+
Principal Principal
46+
}
47+
48+
type PermissionAssignmentList struct {
49+
PermissionAssignments []PermissionAssignment `json:"permission_assignments"`
50+
}
51+
52+
func (l PermissionAssignmentList) ForPrincipal(principalId int64) (res Permissions, err error) {
53+
for _, v := range l.PermissionAssignments {
54+
if v.Principal.PrincipalID != principalId {
55+
continue
56+
}
57+
return Permissions{v.Permissions}, nil
58+
}
59+
return res, common.NotFound(fmt.Sprintf("%d not found", principalId))
60+
}
61+
62+
func (a PermissionAssignmentAPI) List() (list PermissionAssignmentList, err error) {
63+
err = a.client.Get(a.context, "/preview/permissionassignments", nil, &list)
64+
return
65+
}
66+
67+
func mustInt64(s string) int64 {
68+
n, err := strconv.ParseInt(s, 10, 0)
69+
if err != nil {
70+
panic(err)
71+
}
72+
return n
73+
}
74+
75+
// ResourcePermissionAssignment performs of users to a workspace
76+
// from a workspace context, though it requires additional set
77+
// data resource for "workspace account scim", whicl will be added later.
78+
func ResourcePermissionAssignment() *schema.Resource {
79+
type entity struct {
80+
PrincipalId int64 `json:"principal_id"`
81+
Permissions []string `json:"permissions" tf:"slice_as_set"`
82+
}
83+
s := common.StructToSchema(entity{},
84+
func(m map[string]*schema.Schema) map[string]*schema.Schema {
85+
return m
86+
})
87+
return common.Resource{
88+
Schema: s,
89+
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
90+
var assignment entity
91+
common.DataToStructPointer(d, s, &assignment)
92+
api := NewPermissionAssignmentAPI(ctx, c)
93+
err := api.CreateOrUpdate(assignment.PrincipalId, Permissions{assignment.Permissions})
94+
if err != nil {
95+
return err
96+
}
97+
d.SetId(fmt.Sprintf("%d", assignment.PrincipalId))
98+
return nil
99+
},
100+
Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
101+
list, err := NewPermissionAssignmentAPI(ctx, c).List()
102+
if err != nil {
103+
return err
104+
}
105+
permissions, err := list.ForPrincipal(mustInt64(d.Id()))
106+
if err != nil {
107+
return err
108+
}
109+
return common.StructToData(permissions, s, d)
110+
},
111+
Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
112+
return NewPermissionAssignmentAPI(ctx, c).Remove(d.Id())
113+
},
114+
}.ToResource()
115+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package access
2+
3+
import (
4+
"testing"
5+
6+
"github.com/databricks/terraform-provider-databricks/qa"
7+
)
8+
9+
func TestPermissionAssignmentCreate(t *testing.T) {
10+
qa.ResourceFixture{
11+
Fixtures: []qa.HTTPFixture{
12+
{
13+
Method: "PUT",
14+
Resource: "/api/2.0/preview/permissionassignments/principals/345",
15+
ExpectedRequest: Permissions{
16+
Permissions: []string{"USER"},
17+
},
18+
},
19+
{
20+
Method: "GET",
21+
Resource: "/api/2.0/preview/permissionassignments",
22+
Response: PermissionAssignmentList{
23+
PermissionAssignments: []PermissionAssignment{
24+
{
25+
Permissions: []string{"USER"},
26+
Principal: Principal{
27+
PrincipalID: 345,
28+
},
29+
},
30+
},
31+
},
32+
},
33+
},
34+
Resource: ResourcePermissionAssignment(),
35+
Create: true,
36+
AccountID: "abc",
37+
HCL: `
38+
principal_id = 345
39+
permissions = ["USER"]
40+
`,
41+
}.ApplyNoError(t)
42+
}
43+
44+
func TestPermissionAssignmentReadNotFound(t *testing.T) {
45+
qa.ResourceFixture{
46+
Fixtures: []qa.HTTPFixture{
47+
{
48+
Method: "GET",
49+
Resource: "/api/2.0/preview/permissionassignments",
50+
Response: PermissionAssignmentList{
51+
PermissionAssignments: []PermissionAssignment{
52+
{
53+
Permissions: []string{"USER"},
54+
Principal: Principal{
55+
PrincipalID: 345,
56+
},
57+
},
58+
},
59+
},
60+
},
61+
},
62+
Resource: ResourcePermissionAssignment(),
63+
Read: true,
64+
Removed: true,
65+
AccountID: "abc",
66+
ID: "123",
67+
}.ApplyNoError(t)
68+
}
69+
70+
func TestPermissionAssignmentFuzz(t *testing.T) {
71+
qa.ResourceCornerCases(t, ResourcePermissionAssignment())
72+
}

catalog/resource_metastore_assignment.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ func ResourceMetastoreAssignment() *schema.Resource {
5151
func(m map[string]*schema.Schema) map[string]*schema.Schema {
5252
return m
5353
})
54-
pi := common.NewPairID("workspace_id", "metastore_id")
54+
pi := common.NewPairID("workspace_id", "metastore_id").Schema(
55+
func(m map[string]*schema.Schema) map[string]*schema.Schema {
56+
return s
57+
})
5558
return common.Resource{
5659
Schema: s,
5760
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {

common/pair.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,23 @@ func (p *Pair) Unpack(d *schema.ResourceData) (string, string, error) {
5656
d.SetId("")
5757
return "", "", fmt.Errorf("%s cannot be empty", p.right)
5858
}
59-
d.Set(p.left, parts[0])
60-
if p.schema[p.right].Type == schema.TypeInt {
61-
i64, err := strconv.ParseInt(parts[1], 10, 64)
62-
if err != nil {
63-
return parts[0], parts[1], err
64-
}
65-
d.Set(p.right, i64)
66-
} else {
67-
d.Set(p.right, parts[1])
59+
err := p.setField(d, p.left, parts[0])
60+
if err != nil {
61+
return parts[0], parts[1], err
6862
}
69-
return parts[0], parts[1], nil
63+
err = p.setField(d, p.right, parts[1])
64+
return parts[0], parts[1], err
65+
}
66+
67+
func (p *Pair) setField(d *schema.ResourceData, col, val string) error {
68+
if p.schema[col].Type != schema.TypeInt {
69+
return d.Set(col, val)
70+
}
71+
i64, err := strconv.ParseInt(val, 10, 64)
72+
if err != nil {
73+
return err
74+
}
75+
return d.Set(col, i64)
7076
}
7177

7278
// Pack data attributes to ID
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
subcategory: "Unity Catalog"
3+
---
4+
# databricks_mws_permission_assignment Resource
5+
6+
These resources are invoked in the account context. Provider must have `account_id` attribute configured.
7+
8+
## Example Usage
9+
10+
In account context, adding account-level group to a workspace:
11+
12+
```hcl
13+
provider "databricks" {
14+
// <other properties>
15+
account_id = "<databricks account id>"
16+
}
17+
18+
resource "databricks_group" "data_eng" {
19+
display_name = "Data Engineering"
20+
}
21+
22+
resource "databricks_mws_permission_assignment" "add_admin_group" {
23+
workspace_id = databricks_mws_workspaces.this.workspace_id
24+
principal_id = databricks_group.data_eng.id
25+
permissions = ["ADMIN"]
26+
}
27+
```
28+
29+
In account context, adding account-level user to a workspace:
30+
31+
```hcl
32+
provider "databricks" {
33+
// <other properties>
34+
account_id = "<databricks account id>"
35+
}
36+
37+
resource "databricks_user" "me" {
38+
user_name = "[email protected]"
39+
}
40+
41+
resource "databricks_mws_permission_assignment" "add_user" {
42+
workspace_id = databricks_mws_workspaces.this.workspace_id
43+
principal_id = databricks_user.me.id
44+
permissions = ["USER"]
45+
}
46+
```
47+
48+
In account context, adding account-level service principal to a workspace:
49+
50+
```hcl
51+
provider "databricks" {
52+
// <other properties>
53+
account_id = "<databricks account id>"
54+
}
55+
56+
resource "databricks_service_principal" "sp" {
57+
display_name = "Automation-only SP"
58+
}
59+
60+
resource "databricks_mws_permission_assignment" "add_admin_spn" {
61+
workspace_id = databricks_mws_workspaces.this.workspace_id
62+
principal_id = databricks_service_principal.sp.id
63+
permissions = ["ADMIN"]
64+
}
65+
```

internal/acceptance/acceptance.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ func Test(t *testing.T, steps []Step, otherVars ...map[string]string) {
4949
awsAttrs = "aws_attributes {}"
5050
}
5151
instancePoolID := ""
52-
if cloudEnv != "MWS" && cloudEnv != "gcp-accounts" {
52+
if cloudEnv != "MWS" && cloudEnv != "gcp-accounts" && !strings.HasPrefix(cloudEnv, "unity-catalog-") {
53+
// TODO: replace this with data resource
5354
instancePoolID = compute.CommonInstancePoolID()
5455
}
5556
vars := map[string]string{
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package acceptance
2+
3+
import (
4+
"testing"
5+
6+
"github.com/databricks/terraform-provider-databricks/internal/acceptance"
7+
"github.com/databricks/terraform-provider-databricks/qa"
8+
)
9+
10+
func TestAccAssignGroupToWorkspace(t *testing.T) {
11+
qa.RequireCloudEnv(t, "unity-catalog-account")
12+
acceptance.Test(t, []acceptance.Step{
13+
{
14+
Template: `
15+
resource "databricks_group" "this" {
16+
display_name = "TF {var.RANDOM}"
17+
}
18+
resource "databricks_mws_permission_assignment" "this" {
19+
workspace_id = {env.TEST_UC_WORKSPACE_ID}
20+
principal_id = databricks_group.this.id
21+
permissions = ["USER"]
22+
}`,
23+
},
24+
{
25+
Template: `
26+
resource "databricks_group" "this" {
27+
display_name = "TF {var.RANDOM}"
28+
}
29+
resource "databricks_mws_permission_assignment" "this" {
30+
workspace_id = {env.TEST_UC_WORKSPACE_ID}
31+
principal_id = databricks_group.this.id
32+
permissions = ["ADMIN"]
33+
}`,
34+
},
35+
{
36+
Template: `
37+
resource "databricks_group" "this" {
38+
display_name = "TF {var.RANDOM}"
39+
}
40+
resource "databricks_mws_permission_assignment" "this" {
41+
workspace_id = {env.TEST_UC_WORKSPACE_ID}
42+
principal_id = databricks_group.this.id
43+
permissions = ["USER"]
44+
}`,
45+
},
46+
})
47+
}
48+
49+
func TestAccAssignSpnToWorkspace(t *testing.T) {
50+
qa.RequireCloudEnv(t, "unity-catalog-account")
51+
acceptance.Test(t, []acceptance.Step{
52+
{
53+
Template: `
54+
resource "databricks_service_principal" "this" {
55+
display_name = "TF {var.RANDOM}"
56+
}
57+
resource "databricks_mws_permission_assignment" "this" {
58+
workspace_id = {env.TEST_UC_WORKSPACE_ID}
59+
principal_id = databricks_service_principal.this.id
60+
permissions = ["USER"]
61+
}`,
62+
},
63+
})
64+
}

0 commit comments

Comments
 (0)