@@ -3,6 +3,7 @@ package catalog
33import (
44 "context"
55 "fmt"
6+ "sort"
67 "strings"
78
89 "github.com/databrickslabs/terraform-provider-databricks/common"
@@ -14,6 +15,17 @@ type PermissionsAPI struct {
1415 context context.Context
1516}
1617
18+ // permissionsDiff is the inner structure of updatePermissions RPC
19+ type permissionsDiff struct {
20+ Changes []permissionsChange `json:"changes"`
21+ }
22+
23+ type permissionsChange struct {
24+ Principal string `json:"principal"`
25+ Add []string `json:"add,omitempty"`
26+ Remove []string `json:"remove,omitempty"`
27+ }
28+
1729// PrivilegeAssignment reflects on `grant` block
1830type PrivilegeAssignment struct {
1931 Principal string `json:"principal"`
@@ -26,34 +38,59 @@ type PermissionsList struct {
2638 Assignments []PrivilegeAssignment `json:"privilege_assignments" tf:"slice_set,alias:grant"`
2739}
2840
29- func (pl PermissionsList ) toCreate () (diff PermissionsDiff ) {
41+ // diff returns permissionsDiff of this permissions list with `diff` privileges removed
42+ func (pl PermissionsList ) diff (existing PermissionsList ) (diff permissionsDiff ) {
43+ // diffs change sets
44+ configured := map [string ]* schema.Set {}
3045 for _ , v := range pl .Assignments {
31- diff .Changes = append (diff .Changes , PermissionsChange {
32- Principal : v .Principal ,
33- Add : v .Privileges ,
46+ configured [v .Principal ] = newStringSet (v .Privileges )
47+ }
48+ // existing permissions that needs removal
49+ remote := map [string ]* schema.Set {}
50+ for _ , v := range existing .Assignments {
51+ remote [v .Principal ] = newStringSet (v .Privileges )
52+ }
53+ // STEP 1: detect overlaps
54+ for principal , confPrivs := range configured {
55+ remotePrivs , ok := remote [principal ]
56+ if ! ok {
57+ remotePrivs = newStringSet ([]string {})
58+ }
59+ add := setToStrings (confPrivs .Difference (remotePrivs ))
60+ remove := setToStrings (remotePrivs .Difference (confPrivs ))
61+ if len (add ) == 0 && len (remove ) == 0 {
62+ continue
63+ }
64+ diff .Changes = append (diff .Changes , permissionsChange {
65+ Principal : principal ,
66+ Add : add ,
67+ Remove : remove ,
3468 })
3569 }
36- return
37- }
38-
39- func (pl PermissionsList ) toDelete () (diff PermissionsDiff ) {
40- for _ , v := range pl .Assignments {
41- diff .Changes = append (diff .Changes , PermissionsChange {
42- Principal : v .Principal ,
43- Remove : v .Privileges ,
70+ // STEP 2: non overlap - simply remove
71+ for principal , remove := range remote {
72+ _ , ok := configured [principal ]
73+ if ok { // already handled in STEP 1
74+ continue
75+ }
76+ diff .Changes = append (diff .Changes , permissionsChange {
77+ Principal : principal ,
78+ Remove : setToStrings (remove ),
4479 })
4580 }
46- return
47- }
48-
49- type PermissionsDiff struct {
50- Changes [] PermissionsChange `json:"changes"`
81+ // so that we can deterministic tests
82+ sort . Slice ( diff . Changes , func ( i , j int ) bool {
83+ return diff . Changes [ i ]. Principal < diff . Changes [ j ]. Principal
84+ })
85+ return diff
5186}
5287
53- type PermissionsChange struct {
54- Principal string `json:"principal"`
55- Add []string `json:"add,omitempty"`
56- Remove []string `json:"remove,omitempty"`
88+ func newStringSet (in []string ) * schema.Set {
89+ var out []interface {}
90+ for _ , v := range in {
91+ out = append (out , v )
92+ }
93+ return schema .NewSet (schema .HashString , out )
5794}
5895
5996func NewPermissionsAPI (ctx context.Context , m interface {}) PermissionsAPI {
@@ -65,10 +102,19 @@ func (a PermissionsAPI) getPermissions(securable, name string) (list Permissions
65102 return
66103}
67104
68- func (a PermissionsAPI ) updatePermissions (securable , name string , diff PermissionsDiff ) error {
105+ func (a PermissionsAPI ) updatePermissions (securable , name string , diff permissionsDiff ) error {
69106 return a .client .Patch (a .context , fmt .Sprintf ("/unity-catalog/permissions/%s/%s" , securable , name ), diff )
70107}
71108
109+ // replacePermissions merges removal diff of existing permissions on the platform
110+ func (a PermissionsAPI ) replacePermissions (securable , name string , list PermissionsList ) error {
111+ existing , err := a .getPermissions (securable , name )
112+ if err != nil {
113+ return err
114+ }
115+ return a .updatePermissions (securable , name , list .diff (existing ))
116+ }
117+
72118type securableMapping map [string ]map [string ]bool
73119
74120// reuse ResourceDiff and ResourceData
@@ -150,61 +196,6 @@ func setToStrings(set *schema.Set) (ss []string) {
150196 return
151197}
152198
153- func principalAndPrivsFromRaw (v interface {}) (string , * schema.Set ) {
154- item := v .(map [string ]interface {})
155- principal := item ["principal" ].(string )
156- privileges := item ["privileges" ].(* schema.Set )
157- return principal , privileges
158- }
159-
160- func permissionDiffFromRaw (old , new interface {}) PermissionsDiff {
161- o := old .(* schema.Set )
162- n := new .(* schema.Set )
163- prev := map [string ]* schema.Set {}
164- for _ , v := range o .List () {
165- principal , privileges := principalAndPrivsFromRaw (v )
166- prev [principal ] = privileges
167- }
168- diff := PermissionsDiff {Changes : []PermissionsChange {}}
169- mix := map [string ]bool {}
170- for _ , v := range n .List () {
171- principal , newPrivs := principalAndPrivsFromRaw (v )
172- oldPrivs , exist := prev [principal ]
173- if ! exist {
174- // add new principal privileges
175- diff .Changes = append (diff .Changes , PermissionsChange {
176- Principal : principal ,
177- Add : setToStrings (newPrivs ),
178- })
179- continue
180- }
181- // add or remove principal privileges
182- change := PermissionsChange {
183- Principal : principal ,
184- Remove : setToStrings (oldPrivs .Difference (newPrivs )),
185- Add : setToStrings (newPrivs .Difference (oldPrivs )),
186- }
187- if len (change .Add ) == 0 && len (change .Remove ) == 0 {
188- continue
189- }
190- diff .Changes = append (diff .Changes , change )
191- mix [principal ] = true
192- }
193- for _ , v := range o .Difference (n ).List () {
194- // remove old principal privs
195- principal , oldPrivs := principalAndPrivsFromRaw (v )
196- if mix [principal ] {
197- // skip if mixed removes/adds were detected
198- continue
199- }
200- diff .Changes = append (diff .Changes , PermissionsChange {
201- Principal : principal ,
202- Remove : setToStrings (oldPrivs ),
203- })
204- }
205- return diff
206- }
207-
208199func ResourceGrants () * schema.Resource {
209200 s := common .StructToSchema (PermissionsList {},
210201 func (s map [string ]* schema.Schema ) map [string ]* schema.Schema {
@@ -237,7 +228,7 @@ func ResourceGrants() *schema.Resource {
237228 var grants PermissionsList
238229 common .DataToStructPointer (d , s , & grants )
239230 securable , name := mapping .kv (d )
240- err := NewPermissionsAPI (ctx , c ).updatePermissions (securable , name , grants . toCreate () )
231+ err := NewPermissionsAPI (ctx , c ).replacePermissions (securable , name , grants )
241232 if err != nil {
242233 return err
243234 }
@@ -253,18 +244,23 @@ func ResourceGrants() *schema.Resource {
253244 if err != nil {
254245 return err
255246 }
247+ if len (grants .Assignments ) == 0 {
248+ return common .NotFound ("got empty permissions list" )
249+ }
256250 return common .StructToData (grants , s , d )
257251 },
258252 Update : func (ctx context.Context , d * schema.ResourceData , c * common.DatabricksClient ) error {
259253 securable , name := mapping .kv (d )
260- diff := permissionDiffFromRaw (d .GetChange ("grant" ))
261- return NewPermissionsAPI (ctx , c ).updatePermissions (securable , name , diff )
262- },
263- Delete : func (ctx context.Context , d * schema.ResourceData , c * common.DatabricksClient ) error {
264254 var grants PermissionsList
265255 common .DataToStructPointer (d , s , & grants )
266- securable , name := mapping .kv (d )
267- return NewPermissionsAPI (ctx , c ).updatePermissions (securable , name , grants .toDelete ())
256+ return NewPermissionsAPI (ctx , c ).replacePermissions (securable , name , grants )
257+ },
258+ Delete : func (ctx context.Context , d * schema.ResourceData , c * common.DatabricksClient ) error {
259+ split := strings .SplitN (d .Id (), "/" , 2 )
260+ if len (split ) != 2 {
261+ return fmt .Errorf ("ID must be two elements split by `/`: %s" , d .Id ())
262+ }
263+ return NewPermissionsAPI (ctx , c ).replacePermissions (split [0 ], split [1 ], PermissionsList {})
268264 },
269265 }.ToResource ()
270266}
0 commit comments