Skip to content

Commit f07faae

Browse files
alexottmgyucht
andauthored
[Feature] Perform workspace-level permission assignment by user_name, group_name, or service_principal_name (#5068)
## Changes <!-- Summary of your changes that are easy to understand --> Use a previously not available API to perform permission assignment by user name, group name or SP application ID. This solves a long-standing problem with the assignment of principals by workspace administrators who don't have access to account-level APIs. We got ok from Identity team for using this endpoint Resolves #3412 ## 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 - [ ] covered with integration tests in `internal/acceptance` - [x] tested manually - [ ] using Go SDK - [ ] using TF Plugin Framework - [x] has entry in `NEXT_CHANGELOG.md` file --------- Co-authored-by: Miles Yucht <[email protected]>
1 parent 06a7422 commit f07faae

File tree

4 files changed

+430
-37
lines changed

4 files changed

+430
-37
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### New Features and Improvements
1010

1111
* Add `arm` option to `databricks_node_type` instead of `graviton` ([#5028](https://github.com/databricks/terraform-provider-databricks/pull/5028))
12+
* Perform workspace-level permission assignment by `user_name`, `group_name`, or `service_principal_name` ([#5068](https://github.com/databricks/terraform-provider-databricks/pull/5068)).
1213

1314
### Bug Fixes
1415

access/resource_permission_assignment.go

Lines changed: 94 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package access
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
"net/http"
8+
"strconv"
69

710
"github.com/databricks/databricks-sdk-go/apierr"
811
"github.com/databricks/terraform-provider-databricks/common"
@@ -22,39 +25,89 @@ type Permissions struct {
2225
Permissions []string `json:"permissions"`
2326
}
2427

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+
func (a PermissionAssignmentAPI) CreateOrUpdate(assignment permissionAssignmentEntity) (principalInfo, error) {
29+
if assignment.PrincipalId != 0 {
30+
var resp permissionAssignmentResponseItem
31+
path := fmt.Sprintf("/api/2.0/preview/permissionassignments/principals/%d", assignment.PrincipalId)
32+
err := a.client.Do(a.context, http.MethodPut, path, nil, nil,
33+
Permissions{Permissions: assignment.Permissions}, &resp)
34+
if err == nil && resp.Error != "" {
35+
err = errors.New(resp.Error)
36+
}
37+
return resp.Principal, err
38+
} else {
39+
var principal permissionAssignmentResponse
40+
request := permissionAssignmentRequest{
41+
PermissionAssignments: []permissionAssignmentRequestItem{
42+
{
43+
principalInfo: principalInfo{
44+
ServicePrincipalName: assignment.ServicePrincipalName,
45+
UserName: assignment.UserName,
46+
GroupName: assignment.GroupName,
47+
},
48+
Permissions: assignment.Permissions,
49+
},
50+
},
51+
}
52+
err := a.client.Post(a.context, "/preview/permissionassignments", request, &principal)
53+
if err != nil {
54+
return principalInfo{}, err
55+
}
56+
if len(principal.PermissionAssignments) == 0 {
57+
return principalInfo{}, fmt.Errorf("no permission assignment found")
58+
}
59+
if principal.PermissionAssignments[0].Error != "" {
60+
return principalInfo{}, errors.New(principal.PermissionAssignments[0].Error)
61+
}
62+
return principal.PermissionAssignments[0].Principal, nil
63+
}
2864
}
2965

3066
func (a PermissionAssignmentAPI) Remove(principalId string) error {
3167
path := fmt.Sprintf("/preview/permissionassignments/principals/%s", principalId)
3268
return a.client.Delete(a.context, path, nil)
3369
}
3470

35-
type Principal struct {
36-
DisplayName string `json:"display_name"`
37-
PrincipalID int64 `json:"principal_id"`
71+
type principalInfo struct {
72+
DisplayName string `json:"display_name,omitempty"`
73+
PrincipalID int64 `json:"principal_id,omitempty"`
3874
ServicePrincipalName string `json:"service_principal_name,omitempty"`
3975
UserName string `json:"user_name,omitempty"`
4076
GroupName string `json:"group_name,omitempty"`
4177
}
4278

43-
type PermissionAssignment struct {
79+
type permissionAssignmentRequestItem struct {
80+
principalInfo
4481
Permissions []string `json:"permissions"`
45-
Principal Principal
4682
}
4783

48-
type PermissionAssignmentList struct {
49-
PermissionAssignments []PermissionAssignment `json:"permission_assignments"`
84+
type permissionAssignmentRequest struct {
85+
PermissionAssignments []permissionAssignmentRequestItem `json:"permission_assignments"`
5086
}
5187

52-
func (l PermissionAssignmentList) ForPrincipal(principalId int64) (res Permissions, err error) {
88+
type permissionAssignmentResponseItem struct {
89+
Permissions []string `json:"permissions"`
90+
Principal principalInfo
91+
Error string `json:"error,omitempty"`
92+
}
93+
94+
type permissionAssignmentResponse struct {
95+
PermissionAssignments []permissionAssignmentResponseItem `json:"permission_assignments"`
96+
}
97+
98+
func (l permissionAssignmentResponse) ForPrincipal(principalId int64) (res permissionAssignmentEntity, err error) {
5399
for _, v := range l.PermissionAssignments {
54100
if v.Principal.PrincipalID != principalId {
55101
continue
56102
}
57-
return Permissions{v.Permissions}, nil
103+
return permissionAssignmentEntity{
104+
PrincipalId: v.Principal.PrincipalID,
105+
ServicePrincipalName: v.Principal.ServicePrincipalName,
106+
UserName: v.Principal.UserName,
107+
GroupName: v.Principal.GroupName,
108+
Permissions: v.Permissions,
109+
DisplayName: v.Principal.DisplayName,
110+
}, nil
58111
}
59112
return res, &apierr.APIError{
60113
ErrorCode: "NOT_FOUND",
@@ -63,48 +116,63 @@ func (l PermissionAssignmentList) ForPrincipal(principalId int64) (res Permissio
63116
}
64117
}
65118

66-
func (a PermissionAssignmentAPI) List() (list PermissionAssignmentList, err error) {
119+
func (a PermissionAssignmentAPI) List() (list permissionAssignmentResponse, err error) {
67120
err = a.client.Get(a.context, "/preview/permissionassignments", nil, &list)
68121
return
69122
}
70123

124+
type permissionAssignmentEntity struct {
125+
PrincipalId int64 `json:"principal_id,omitempty" tf:"computed,force_new"`
126+
ServicePrincipalName string `json:"service_principal_name,omitempty" tf:"computed,force_new"`
127+
UserName string `json:"user_name,omitempty" tf:"computed,force_new"`
128+
GroupName string `json:"group_name,omitempty" tf:"computed,force_new"`
129+
Permissions []string `json:"permissions" tf:"slice_as_set"`
130+
DisplayName string `json:"display_name" tf:"computed"`
131+
}
132+
71133
// ResourcePermissionAssignment performs of users to a workspace
72134
// from a workspace context, though it requires additional set
73135
// data resource for "workspace account scim", whicl will be added later.
74136
func ResourcePermissionAssignment() common.Resource {
75-
type entity struct {
76-
PrincipalId int64 `json:"principal_id"`
77-
Permissions []string `json:"permissions" tf:"slice_as_set"`
78-
}
79-
s := common.StructToSchema(entity{}, common.NoCustomize)
137+
s := common.StructToSchema(permissionAssignmentEntity{}, func(s map[string]*schema.Schema) map[string]*schema.Schema {
138+
fields := []string{"principal_id", "service_principal_name", "user_name", "group_name"}
139+
for _, field := range fields {
140+
s[field].ExactlyOneOf = fields
141+
}
142+
common.CustomizeSchemaPath(s, "display_name").SetReadOnly()
143+
return s
144+
})
80145
return common.Resource{
81146
Schema: s,
82147
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
83-
var assignment entity
148+
var assignment permissionAssignmentEntity
84149
common.DataToStructPointer(d, s, &assignment)
85150
api := NewPermissionAssignmentAPI(ctx, c)
86-
err := api.CreateOrUpdate(assignment.PrincipalId, Permissions{assignment.Permissions})
151+
principal, err := api.CreateOrUpdate(assignment)
87152
if err != nil {
88153
return err
89154
}
90-
d.SetId(fmt.Sprintf("%d", assignment.PrincipalId))
155+
d.SetId(strconv.FormatInt(principal.PrincipalID, 10))
91156
return nil
92157
},
93158
Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
94159
list, err := NewPermissionAssignmentAPI(ctx, c).List()
95160
if err != nil {
96161
return err
97162
}
98-
data := entity{
99-
PrincipalId: common.MustInt64(d.Id()),
100-
}
101-
permissions, err := list.ForPrincipal(data.PrincipalId)
163+
data, err := list.ForPrincipal(common.MustInt64(d.Id()))
102164
if err != nil {
103165
return err
104166
}
105-
data.Permissions = permissions.Permissions
106167
return common.StructToData(data, s, d)
107168
},
169+
Update: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
170+
var assignment permissionAssignmentEntity
171+
common.DataToStructPointer(d, s, &assignment)
172+
api := NewPermissionAssignmentAPI(ctx, c)
173+
_, err := api.CreateOrUpdate(assignment)
174+
return err
175+
},
108176
Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
109177
return NewPermissionAssignmentAPI(ctx, c).Remove(d.Id())
110178
},

0 commit comments

Comments
 (0)