Skip to content

Commit 298ca47

Browse files
CLOUDP-293113: Fixed unintended removals of custom roles (#2023)
Fixed unintended removals of custom roles
1 parent 5d3a4db commit 298ca47

File tree

3 files changed

+145
-20
lines changed

3 files changed

+145
-20
lines changed

api/v1/atlascustomrole_types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ func init() {
1717
// +kubebuilder:object:root=true
1818
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
1919
// +kubebuilder:printcolumn:name="Name",type=string,JSONPath=`.spec.role.name`
20-
// +kubebuilder:printcolumn:name="Project ID",type=string,JSONPath=`.spec.projectIDRef.id`
2120
// +kubebuilder:subresource:status
2221
// +groupName:=atlas.mongodb.com
2322
// +kubebuilder:resource:categories=atlas,shortName=acr

internal/controller/atlasproject/custom_roles.go

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,20 @@ func getLastAppliedCustomRoles(atlasProject *akov2.AtlasProject) ([]akov2.Custom
5050
return lastAppliedSpec.CustomRoles, nil
5151
}
5252

53-
func findRolesToDelete(prevSpec, atlasRoles []customroles.CustomRole) map[string]customroles.CustomRole {
53+
func findRolesToDelete(prevSpec, akoRoles, atlasRoles []customroles.CustomRole) map[string]customroles.CustomRole {
5454
result := map[string]customroles.CustomRole{}
55-
for atlasRoleIdx := range atlasRoles {
56-
for specRoleIdx := range prevSpec {
57-
if atlasRoles[atlasRoleIdx].Name == prevSpec[specRoleIdx].Name {
58-
result[prevSpec[specRoleIdx].Name] = prevSpec[specRoleIdx]
59-
continue
60-
}
55+
lastAppliedRolesMap := mapCustomRolesByName(prevSpec)
56+
akoRolesMap := mapCustomRolesByName(akoRoles)
57+
atlasRolesMap := mapCustomRolesByName(atlasRoles)
58+
59+
for atlasName, atlasRole := range atlasRolesMap {
60+
_, inAKO := akoRolesMap[atlasName]
61+
_, inLastApplied := lastAppliedRolesMap[atlasName]
62+
if !inAKO && inLastApplied {
63+
result[atlasName] = atlasRole
6164
}
6265
}
66+
6367
return result
6468
}
6569

@@ -94,7 +98,7 @@ func ensureCustomRoles(workflowCtx *workflow.Context, project *akov2.AtlasProjec
9498
service: customroles.NewCustomRoles(workflowCtx.SdkClient.CustomDatabaseRolesApi),
9599
}
96100

97-
currentCustomRoles, err := r.service.List(r.ctx.Context, r.project.ID())
101+
currentAtlasCustomRoles, err := r.service.List(r.ctx.Context, r.project.ID())
98102
if err != nil {
99103
return workflow.Terminate(workflow.ProjectCustomRolesReady, err.Error())
100104
}
@@ -104,12 +108,12 @@ func ensureCustomRoles(workflowCtx *workflow.Context, project *akov2.AtlasProjec
104108
akoRoles[i] = customroles.NewCustomRole(&project.Spec.CustomRoles[i])
105109
}
106110

107-
ops := calculateChanges(currentCustomRoles, akoRoles)
111+
ops := calculateChanges(currentAtlasCustomRoles, akoRoles)
108112

109113
var deleteStatus map[string]status.CustomRole
110114
if len(lastAppliedCustomRoles) > 0 {
111115
deleteStatus = r.deleteCustomRoles(workflowCtx, project.ID(),
112-
findRolesToDelete(convertToInternalRoles(lastAppliedCustomRoles), currentCustomRoles))
116+
findRolesToDelete(convertToInternalRoles(lastAppliedCustomRoles), akoRoles, currentAtlasCustomRoles))
113117
}
114118
updateStatus := r.updateCustomRoles(workflowCtx, project.ID(), ops.Update)
115119
createStatus := r.createCustomRoles(workflowCtx, project.ID(), ops.Create)
@@ -218,22 +222,13 @@ func mapCustomRolesByName(customRoles []customroles.CustomRole) map[string]custo
218222
type CustomRolesOperations struct {
219223
Create map[string]customroles.CustomRole
220224
Update map[string]customroles.CustomRole
221-
Delete map[string]customroles.CustomRole
222225
}
223226

224227
func calculateChanges(currentCustomRoles []customroles.CustomRole, desiredCustomRoles []customroles.CustomRole) CustomRolesOperations {
225228
currentCustomRolesByName := mapCustomRolesByName(currentCustomRoles)
226-
desiredCustomRolesByName := mapCustomRolesByName(desiredCustomRoles)
227229
ops := CustomRolesOperations{
228230
Create: map[string]customroles.CustomRole{},
229231
Update: map[string]customroles.CustomRole{},
230-
Delete: map[string]customroles.CustomRole{},
231-
}
232-
233-
for _, currentCustomRole := range currentCustomRoles {
234-
if _, ok := desiredCustomRolesByName[currentCustomRole.Name]; !ok {
235-
ops.Delete[currentCustomRole.Name] = currentCustomRole
236-
}
237232
}
238233

239234
for _, desiredCustomRole := range desiredCustomRoles {

internal/controller/atlasproject/custom_roles_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,137 @@ func TestEnsureCustomRoles(t *testing.T) {
214214
}(),
215215
isOK: true,
216216
},
217+
{
218+
name: "Roles in AKO and in last applied config. Delete only those that were deleted from the spec",
219+
projectAnnotations: map[string]string{
220+
customresource.AnnotationLastAppliedConfiguration: func() string {
221+
d, _ := json.Marshal(&akov2.AtlasProjectSpec{
222+
CustomRoles: []akov2.CustomRole{
223+
{
224+
Name: "test-role",
225+
InheritedRoles: []akov2.Role{
226+
{Name: "role", Database: "db"},
227+
},
228+
Actions: []akov2.Action{
229+
{
230+
Name: "action",
231+
Resources: []akov2.Resource{
232+
{
233+
Database: pointer.MakePtr("db"),
234+
Cluster: pointer.MakePtr(true),
235+
Collection: pointer.MakePtr("test-collection"),
236+
},
237+
},
238+
},
239+
},
240+
},
241+
{
242+
Name: "test-role-2",
243+
InheritedRoles: []akov2.Role{
244+
{Name: "role2", Database: "db2"},
245+
},
246+
Actions: []akov2.Action{
247+
{
248+
Name: "action2",
249+
Resources: []akov2.Resource{
250+
{Database: pointer.MakePtr("db2")},
251+
},
252+
},
253+
},
254+
},
255+
},
256+
})
257+
return string(d)
258+
}(),
259+
},
260+
roles: []akov2.CustomRole{
261+
{
262+
Name: "test-role",
263+
InheritedRoles: []akov2.Role{
264+
{Name: "role", Database: "db"},
265+
},
266+
Actions: []akov2.Action{
267+
{
268+
Name: "action",
269+
Resources: []akov2.Resource{
270+
{
271+
Database: pointer.MakePtr("db"),
272+
Cluster: pointer.MakePtr(true),
273+
Collection: pointer.MakePtr("test-collection"),
274+
},
275+
},
276+
},
277+
},
278+
},
279+
},
280+
roleAPI: func() *mockadmin.CustomDatabaseRolesApi {
281+
roleAPI := mockadmin.NewCustomDatabaseRolesApi(t)
282+
roleAPI.EXPECT().ListCustomDatabaseRoles(context.Background(), "").
283+
Return(admin.ListCustomDatabaseRolesApiRequest{ApiService: roleAPI})
284+
roleAPI.EXPECT().ListCustomDatabaseRolesExecute(mock.Anything).
285+
Return(
286+
[]admin.UserCustomDBRole{
287+
{
288+
RoleName: "test-role",
289+
InheritedRoles: &[]admin.DatabaseInheritedRole{
290+
{Role: "role", Db: "db"},
291+
},
292+
Actions: &[]admin.DatabasePrivilegeAction{
293+
{
294+
Action: "action",
295+
Resources: &[]admin.DatabasePermittedNamespaceResource{
296+
{
297+
Db: "db",
298+
Collection: "test-collection",
299+
Cluster: true,
300+
},
301+
},
302+
},
303+
},
304+
},
305+
{
306+
RoleName: "test-role-1",
307+
InheritedRoles: &[]admin.DatabaseInheritedRole{
308+
{Role: "role1", Db: "db1"},
309+
},
310+
Actions: &[]admin.DatabasePrivilegeAction{
311+
{
312+
Action: "action1",
313+
Resources: &[]admin.DatabasePermittedNamespaceResource{
314+
{Db: "db1"},
315+
},
316+
},
317+
},
318+
},
319+
{
320+
RoleName: "test-role-2",
321+
InheritedRoles: &[]admin.DatabaseInheritedRole{
322+
{Role: "role2", Db: "db2"},
323+
},
324+
Actions: &[]admin.DatabasePrivilegeAction{
325+
{
326+
Action: "action2",
327+
Resources: &[]admin.DatabasePermittedNamespaceResource{
328+
{Db: "db2"},
329+
},
330+
},
331+
},
332+
},
333+
},
334+
&http.Response{},
335+
nil,
336+
)
337+
roleAPI.EXPECT().DeleteCustomDatabaseRole(context.Background(), "", "test-role-2").
338+
Return(admin.DeleteCustomDatabaseRoleApiRequest{ApiService: roleAPI})
339+
roleAPI.EXPECT().DeleteCustomDatabaseRoleExecute(mock.Anything).
340+
Return(
341+
&http.Response{},
342+
nil,
343+
)
344+
return roleAPI
345+
}(),
346+
isOK: true,
347+
},
217348
{
218349
name: "Roles not in AKO but are in Atlas (Do not Delete) and NO previous in AKO",
219350
roleAPI: func() *mockadmin.CustomDatabaseRolesApi {

0 commit comments

Comments
 (0)