Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
### Breaking Changes

* Remove stale resources/datasources/documentation related to Clean Room services.

### New Features and Improvements

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

### Bug Fixes

Expand Down
120 changes: 94 additions & 26 deletions access/resource_permission_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package access

import (
"context"
"errors"
"fmt"
"net/http"
"strconv"

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

func (a PermissionAssignmentAPI) CreateOrUpdate(principalId int64, r Permissions) error {
path := fmt.Sprintf("/preview/permissionassignments/principals/%d", principalId)
return a.client.Put(a.context, path, r)
func (a PermissionAssignmentAPI) CreateOrUpdate(assignment permissionAssignmentEntity) (principalInfo, error) {
if assignment.PrincipalId != 0 {
var resp permissionAssignmentResponseItem
path := fmt.Sprintf("/api/2.0/preview/permissionassignments/principals/%d", assignment.PrincipalId)
err := a.client.Do(a.context, http.MethodPut, path, nil, nil,
Permissions{Permissions: assignment.Permissions}, &resp)
if err == nil && resp.Error != "" {
err = errors.New(resp.Error)
}
return resp.Principal, err
} else {
var principal permissionAssignmentResponse
request := permissionAssignmentRequest{
PermissionAssignments: []permissionAssignmentRequestItem{
{
principalInfo: principalInfo{
ServicePrincipalName: assignment.ServicePrincipalName,
UserName: assignment.UserName,
GroupName: assignment.GroupName,
},
Permissions: assignment.Permissions,
},
},
}
err := a.client.Post(a.context, "/preview/permissionassignments", request, &principal)
if err != nil {
return principalInfo{}, err
}
if len(principal.PermissionAssignments) == 0 {
return principalInfo{}, fmt.Errorf("no permission assignment found")
}
if principal.PermissionAssignments[0].Error != "" {
return principalInfo{}, errors.New(principal.PermissionAssignments[0].Error)
}
return principal.PermissionAssignments[0].Principal, nil
}
}

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

type Principal struct {
DisplayName string `json:"display_name"`
PrincipalID int64 `json:"principal_id"`
type principalInfo struct {
DisplayName string `json:"display_name,omitempty"`
PrincipalID int64 `json:"principal_id,omitempty"`
ServicePrincipalName string `json:"service_principal_name,omitempty"`
UserName string `json:"user_name,omitempty"`
GroupName string `json:"group_name,omitempty"`
}

type PermissionAssignment struct {
type permissionAssignmentRequestItem struct {
principalInfo
Permissions []string `json:"permissions"`
Principal Principal
}

type PermissionAssignmentList struct {
PermissionAssignments []PermissionAssignment `json:"permission_assignments"`
type permissionAssignmentRequest struct {
PermissionAssignments []permissionAssignmentRequestItem `json:"permission_assignments"`
}

func (l PermissionAssignmentList) ForPrincipal(principalId int64) (res Permissions, err error) {
type permissionAssignmentResponseItem struct {
Permissions []string `json:"permissions"`
Principal principalInfo
Error string `json:"error,omitempty"`
}

type permissionAssignmentResponse struct {
PermissionAssignments []permissionAssignmentResponseItem `json:"permission_assignments"`
}

func (l permissionAssignmentResponse) ForPrincipal(principalId int64) (res permissionAssignmentEntity, err error) {
for _, v := range l.PermissionAssignments {
if v.Principal.PrincipalID != principalId {
continue
}
return Permissions{v.Permissions}, nil
return permissionAssignmentEntity{
PrincipalId: v.Principal.PrincipalID,
ServicePrincipalName: v.Principal.ServicePrincipalName,
UserName: v.Principal.UserName,
GroupName: v.Principal.GroupName,
Permissions: v.Permissions,
DisplayName: v.Principal.DisplayName,
}, nil
}
return res, &apierr.APIError{
ErrorCode: "NOT_FOUND",
Expand All @@ -63,48 +116,63 @@ func (l PermissionAssignmentList) ForPrincipal(principalId int64) (res Permissio
}
}

func (a PermissionAssignmentAPI) List() (list PermissionAssignmentList, err error) {
func (a PermissionAssignmentAPI) List() (list permissionAssignmentResponse, err error) {
err = a.client.Get(a.context, "/preview/permissionassignments", nil, &list)
return
}

type permissionAssignmentEntity struct {
PrincipalId int64 `json:"principal_id,omitempty" tf:"computed,force_new"`
ServicePrincipalName string `json:"service_principal_name,omitempty" tf:"computed,force_new"`
UserName string `json:"user_name,omitempty" tf:"computed,force_new"`
GroupName string `json:"group_name,omitempty" tf:"computed,force_new"`
Permissions []string `json:"permissions" tf:"slice_as_set"`
DisplayName string `json:"display_name" tf:"computed"`
}

// ResourcePermissionAssignment performs of users to a workspace
// from a workspace context, though it requires additional set
// data resource for "workspace account scim", whicl will be added later.
func ResourcePermissionAssignment() common.Resource {
type entity struct {
PrincipalId int64 `json:"principal_id"`
Permissions []string `json:"permissions" tf:"slice_as_set"`
}
s := common.StructToSchema(entity{}, common.NoCustomize)
s := common.StructToSchema(permissionAssignmentEntity{}, func(s map[string]*schema.Schema) map[string]*schema.Schema {
fields := []string{"principal_id", "service_principal_name", "user_name", "group_name"}
for _, field := range fields {
s[field].ExactlyOneOf = fields
}
common.CustomizeSchemaPath(s, "display_name").SetReadOnly()
return s
})
return common.Resource{
Schema: s,
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
var assignment entity
var assignment permissionAssignmentEntity
common.DataToStructPointer(d, s, &assignment)
api := NewPermissionAssignmentAPI(ctx, c)
err := api.CreateOrUpdate(assignment.PrincipalId, Permissions{assignment.Permissions})
principal, err := api.CreateOrUpdate(assignment)
if err != nil {
return err
}
d.SetId(fmt.Sprintf("%d", assignment.PrincipalId))
d.SetId(strconv.FormatInt(principal.PrincipalID, 10))
return nil
},
Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
list, err := NewPermissionAssignmentAPI(ctx, c).List()
if err != nil {
return err
}
data := entity{
PrincipalId: common.MustInt64(d.Id()),
}
permissions, err := list.ForPrincipal(data.PrincipalId)
data, err := list.ForPrincipal(common.MustInt64(d.Id()))
if err != nil {
return err
}
data.Permissions = permissions.Permissions
return common.StructToData(data, s, d)
},
Update: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
var assignment permissionAssignmentEntity
common.DataToStructPointer(d, s, &assignment)
api := NewPermissionAssignmentAPI(ctx, c)
_, err := api.CreateOrUpdate(assignment)
return err
},
Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
return NewPermissionAssignmentAPI(ctx, c).Remove(d.Id())
},
Expand Down
Loading
Loading