Skip to content

Commit aa5cea7

Browse files
committed
Controller changes
1 parent f6e6476 commit aa5cea7

12 files changed

+404
-22
lines changed

api/v1/mdb/mongodb_types.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,12 @@ type SharedConnectionSpec struct {
739739
type Security struct {
740740
TLSConfig *TLSConfig `json:"tls,omitempty"`
741741
Authentication *Authentication `json:"authentication,omitempty"`
742-
Roles []MongoDbRole `json:"roles,omitempty"`
742+
743+
// +optional
744+
Roles []MongoDBRole `json:"roles,omitempty"`
745+
746+
// +optional
747+
RoleRefs []MongoDBRoleRef `json:"roleRefs,omitempty"`
743748

744749
// +optional
745750
CertificatesSecretsPrefix string `json:"certsSecretPrefix"`
@@ -963,7 +968,16 @@ type InheritedRole struct {
963968
Role string `json:"role"`
964969
}
965970

966-
type MongoDbRole struct {
971+
type MongoDBRoleRef struct {
972+
// +kubebuilder:validation:Required
973+
Name string `json:"name"`
974+
975+
// +kubebuilder:validation:Enum=ClusterMongoDBRole
976+
// +kubebuilder:validation:Required
977+
Kind string `json:"kind"`
978+
}
979+
980+
type MongoDBRole struct {
967981
Role string `json:"role"`
968982
AuthenticationRestrictions []AuthenticationRestriction `json:"authenticationRestrictions,omitempty"`
969983
Db string `json:"db"`
@@ -1505,7 +1519,10 @@ func EnsureSecurity(sec *Security) *Security {
15051519
sec.TLSConfig = &TLSConfig{}
15061520
}
15071521
if sec.Roles == nil {
1508-
sec.Roles = make([]MongoDbRole, 0)
1522+
sec.Roles = make([]MongoDBRole, 0)
1523+
}
1524+
if sec.RoleRefs == nil {
1525+
sec.RoleRefs = make([]MongoDBRoleRef, 0)
15091526
}
15101527
return sec
15111528
}

controllers/om/deployment.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -638,12 +638,24 @@ func (d Deployment) SetRoles(roles []mdbv1.MongoDBRole) {
638638
d["roles"] = roles
639639
}
640640

641-
func (d Deployment) GetRoles() []mdbv1.MongoDbRole {
642-
val, ok := d["roles"].([]mdbv1.MongoDbRole)
643-
if !ok {
644-
return []mdbv1.MongoDbRole{}
641+
func (d Deployment) GetRoles() []mdbv1.MongoDBRole {
642+
roles := d["roles"]
643+
result := make([]mdbv1.MongoDBRole, 0)
644+
645+
if roles == nil {
646+
return result
647+
}
648+
649+
rolesBytes, err := json.Marshal(roles)
650+
if err != nil {
651+
return nil
645652
}
646-
return val
653+
654+
if err := json.Unmarshal(rolesBytes, &result); err != nil {
655+
return nil
656+
}
657+
658+
return result
647659
}
648660

649661
// GetAgentVersion returns the current version of all Agents in the deployment. It's empty until the
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package operator
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
"time"
9+
10+
"go.uber.org/zap"
11+
"golang.org/x/xerrors"
12+
"k8s.io/apimachinery/pkg/fields"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
14+
"sigs.k8s.io/controller-runtime/pkg/controller"
15+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
16+
"sigs.k8s.io/controller-runtime/pkg/handler"
17+
"sigs.k8s.io/controller-runtime/pkg/manager"
18+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
19+
"sigs.k8s.io/controller-runtime/pkg/source"
20+
21+
ctrl "sigs.k8s.io/controller-runtime"
22+
23+
mdbv1 "github.com/mongodb/mongodb-kubernetes/api/v1/mdb"
24+
mdbmultiv1 "github.com/mongodb/mongodb-kubernetes/api/v1/mdbmulti"
25+
rolev1 "github.com/mongodb/mongodb-kubernetes/api/v1/role"
26+
"github.com/mongodb/mongodb-kubernetes/controllers/operator/watch"
27+
"github.com/mongodb/mongodb-kubernetes/controllers/operator/workflow"
28+
"github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/pkg/kube/annotations"
29+
"github.com/mongodb/mongodb-kubernetes/pkg/util"
30+
"github.com/mongodb/mongodb-kubernetes/pkg/util/env"
31+
)
32+
33+
const (
34+
ClusterMongoDBRoleIndexForMdb = "mongodb.spec.security.roleRefs"
35+
ClusterMongoDBRoleIndexForMdbMulti = "mdbmulticluster.spec.security.roleRefs"
36+
)
37+
38+
// ClusterMongoDBRoleReconciler reconciles a ClusterMongoDBRole object
39+
type ClusterMongoDBRoleReconciler struct {
40+
*ReconcileCommonController
41+
}
42+
43+
// ClusterMongoDBRole Resource
44+
// +kubebuilder:rbac:groups=mongodb.com,resources={clustermongodbroles,clustermongodbroles/status,clustermongodbroles/finalizers},verbs=*
45+
46+
func (r *ClusterMongoDBRoleReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
47+
log := zap.S().With("ClusterMongoDBRole", request.NamespacedName)
48+
log.Info("-> ClusterMongoDBRole.Reconcile")
49+
50+
role, err := r.getRole(ctx, request, log)
51+
if err != nil {
52+
log.Warnf("error getting custom role %s", err)
53+
return reconcile.Result{RequeueAfter: time.Second * util.RetryTimeSec}, nil
54+
}
55+
56+
log.Infow("ClusterMongoDBRole.Spec", "spec", role.Spec)
57+
58+
if err := role.ProcessValidationsOnReconcile(nil); err != nil {
59+
return r.updateStatus(ctx, role, workflow.Invalid("%s", err.Error()), log)
60+
}
61+
62+
if !role.DeletionTimestamp.IsZero() {
63+
log.Info("ClusterMongoDBRole is being deleted")
64+
65+
if controllerutil.ContainsFinalizer(role, util.RoleFinalizer) {
66+
return r.Delete(ctx, role, log)
67+
}
68+
}
69+
70+
if err := r.ensureFinalizer(ctx, role, log); err != nil {
71+
return r.updateStatus(ctx, role, workflow.Failed(xerrors.Errorf("Failed to add finalizer: %w", err)), log)
72+
}
73+
74+
annotationsToAdd, err := getAnnotationsForCustomRoleResource(role)
75+
if err != nil {
76+
return r.updateStatus(ctx, role, workflow.Failed(err), log)
77+
}
78+
79+
if err := annotations.SetAnnotations(ctx, role, annotationsToAdd, r.client); err != nil {
80+
return r.updateStatus(ctx, role, workflow.Failed(err), log)
81+
}
82+
83+
log.Infof("Finished reconciliation for ClusterMongoDBRole!")
84+
return r.updateStatus(ctx, role, workflow.OK(), log)
85+
}
86+
87+
func AddClusterMongoDBRoleController(ctx context.Context, mgr manager.Manager) error {
88+
reconciler := newClusterMongoDBRoleReconciler(ctx, mgr.GetClient())
89+
c, err := controller.New(util.ClusterMongoDbRoleController, mgr, controller.Options{Reconciler: reconciler, MaxConcurrentReconciles: env.ReadIntOrDefault(util.MaxConcurrentReconcilesEnv, 1)}) // nolint:forbidigo
90+
if err != nil {
91+
return err
92+
}
93+
94+
// Watch for changes to ClusterMongoDBRole resources
95+
// We don't need a Delete handler as there is nothing to do after removing the finalizer
96+
// To not get into an infinite reconcile loop, we ignore the delete event since the cleanup was already performed
97+
err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &rolev1.ClusterMongoDBRole{}, &handler.EnqueueRequestForObject{}, watch.PredicatesForClusterRole()))
98+
if err != nil {
99+
return err
100+
}
101+
102+
if err = mgr.GetFieldIndexer().IndexField(ctx, &mdbv1.MongoDB{}, ClusterMongoDBRoleIndexForMdb, findRolesForMongoDB); err != nil {
103+
return err
104+
}
105+
106+
if err = mgr.GetFieldIndexer().IndexField(ctx, &mdbmultiv1.MongoDBMultiCluster{}, ClusterMongoDBRoleIndexForMdbMulti, findRolesForMongoDBMultiCluster); err != nil {
107+
return err
108+
}
109+
110+
zap.S().Infof("Registered controller %s", util.ClusterMongoDbRoleController)
111+
return nil
112+
}
113+
114+
func newClusterMongoDBRoleReconciler(ctx context.Context, kubeClient client.Client) *ClusterMongoDBRoleReconciler {
115+
return &ClusterMongoDBRoleReconciler{
116+
ReconcileCommonController: NewReconcileCommonController(ctx, kubeClient),
117+
}
118+
}
119+
120+
func (r *ClusterMongoDBRoleReconciler) getRole(ctx context.Context, request reconcile.Request, log *zap.SugaredLogger) (*rolev1.ClusterMongoDBRole, error) {
121+
role := &rolev1.ClusterMongoDBRole{}
122+
if _, err := r.GetResource(ctx, request, role, log); err != nil {
123+
return nil, err
124+
}
125+
126+
return role, nil
127+
}
128+
129+
func (r *ClusterMongoDBRoleReconciler) Delete(ctx context.Context, role *rolev1.ClusterMongoDBRole, log *zap.SugaredLogger) (reconcile.Result, error) {
130+
log.Info("Attempting to remove ClusterMongoDBRole")
131+
132+
err := r.ensureNoReferences(ctx, role)
133+
if err != nil {
134+
return r.updateStatus(ctx, role, workflow.Failed(xerrors.Errorf("Failed to remove role: %w", err)), log)
135+
}
136+
137+
if finalizerRemoved := controllerutil.RemoveFinalizer(role, util.RoleFinalizer); !finalizerRemoved {
138+
return r.updateStatus(ctx, role, workflow.Failed(xerrors.Errorf("Failed to remove finalizer")), log)
139+
}
140+
141+
if err := r.client.Update(ctx, role); err != nil {
142+
return r.updateStatus(ctx, role, workflow.Failed(xerrors.Errorf("Failed to update the role with the removed finalizer: %w", err)), log)
143+
}
144+
145+
log.Info("ClusterMongoDBRole has been removed!")
146+
return r.updateStatus(ctx, role, workflow.OK(), log)
147+
}
148+
149+
func (r *ClusterMongoDBRoleReconciler) ensureFinalizer(ctx context.Context, role *rolev1.ClusterMongoDBRole, log *zap.SugaredLogger) error {
150+
log.Info("Adding finalizer to the ClusterMongoDBRole resource")
151+
152+
if finalizerAdded := controllerutil.AddFinalizer(role, util.RoleFinalizer); finalizerAdded {
153+
if err := r.client.Update(ctx, role); err != nil {
154+
return err
155+
}
156+
}
157+
158+
return nil
159+
}
160+
161+
func (r *ClusterMongoDBRoleReconciler) ensureNoReferences(ctx context.Context, role *rolev1.ClusterMongoDBRole) error {
162+
mdbList := &mdbv1.MongoDBList{}
163+
err := r.client.List(ctx, mdbList, &client.ListOptions{
164+
FieldSelector: fields.OneTermEqualSelector(ClusterMongoDBRoleIndexForMdb, role.Name),
165+
})
166+
if err != nil {
167+
return err
168+
}
169+
170+
multiList := &mdbmultiv1.MongoDBMultiClusterList{}
171+
err = r.client.List(ctx, multiList, &client.ListOptions{
172+
FieldSelector: fields.OneTermEqualSelector(ClusterMongoDBRoleIndexForMdbMulti, role.Name),
173+
})
174+
if err != nil {
175+
return err
176+
}
177+
178+
if len(mdbList.Items) > 0 || len(multiList.Items) > 0 {
179+
resources := make([]string, 0)
180+
for _, mdb := range mdbList.Items {
181+
resources = append(resources, fmt.Sprintf("%s/%s", mdb.Namespace, mdb.Name))
182+
}
183+
for _, mdbmc := range multiList.Items {
184+
resources = append(resources, fmt.Sprintf("%s/%s", mdbmc.Namespace, mdbmc.Name))
185+
}
186+
return xerrors.Errorf("These resources are still referencing this role: %s", strings.Join(resources, ", "))
187+
}
188+
189+
return nil
190+
}
191+
192+
func getAnnotationsForCustomRoleResource(role *rolev1.ClusterMongoDBRole) (map[string]string, error) {
193+
finalAnnotations := make(map[string]string)
194+
specBytes, err := json.Marshal(role.Spec)
195+
if err != nil {
196+
return nil, err
197+
}
198+
finalAnnotations[util.LastAchievedSpec] = string(specBytes)
199+
return finalAnnotations, nil
200+
}
201+
202+
func findRolesForMongoDB(rawObj client.Object) []string {
203+
mdb := rawObj.(*mdbv1.MongoDB)
204+
roles := make([]string, 0)
205+
for _, roleRef := range mdb.Spec.Security.RoleRefs {
206+
if roleRef.Kind == util.ClusterMongoDBRoleKind {
207+
roles = append(roles, roleRef.Name)
208+
}
209+
}
210+
return roles
211+
}
212+
213+
func findRolesForMongoDBMultiCluster(rawObj client.Object) []string {
214+
mdb := rawObj.(*mdbmultiv1.MongoDBMultiCluster)
215+
roles := make([]string, 0)
216+
for _, roleRef := range mdb.Spec.Security.RoleRefs {
217+
if roleRef.Kind == util.ClusterMongoDBRoleKind {
218+
roles = append(roles, roleRef.Name)
219+
}
220+
}
221+
return roles
222+
}

controllers/operator/common_controller.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
v1 "github.com/mongodb/mongodb-kubernetes/api/v1"
2525
mdbv1 "github.com/mongodb/mongodb-kubernetes/api/v1/mdb"
26+
rolev1 "github.com/mongodb/mongodb-kubernetes/api/v1/role"
2627
"github.com/mongodb/mongodb-kubernetes/api/v1/status"
2728
"github.com/mongodb/mongodb-kubernetes/controllers/om"
2829
"github.com/mongodb/mongodb-kubernetes/controllers/om/backup"
@@ -97,7 +98,22 @@ func NewReconcileCommonController(ctx context.Context, client client.Client) *Re
9798
}
9899
}
99100

100-
func ensureRoles(roles []mdbv1.MongoDbRole, conn om.Connection, log *zap.SugaredLogger) workflow.Status {
101+
func (r *ReconcileCommonController) ensureRoles(ctx context.Context, localRoles []mdbv1.MongoDBRole, roleRefs []mdbv1.MongoDBRoleRef, conn om.Connection, mongodbResourceNsName types.NamespacedName, log *zap.SugaredLogger) workflow.Status {
102+
if len(localRoles) > 0 && len(roleRefs) > 0 {
103+
return workflow.Failed(xerrors.Errorf("At most one one of roles or roleRefs can be non-empty."))
104+
}
105+
106+
var roles []mdbv1.MongoDBRole
107+
if len(roleRefs) > 0 {
108+
var err error
109+
roles, err = r.getRoleRefs(ctx, roleRefs, mongodbResourceNsName)
110+
if err != nil {
111+
return workflow.Failed(err)
112+
}
113+
} else {
114+
roles = localRoles
115+
}
116+
101117
d, err := conn.ReadDeployment()
102118
if err != nil {
103119
return workflow.Failed(err)
@@ -127,6 +143,34 @@ func ensureRoles(roles []mdbv1.MongoDbRole, conn om.Connection, log *zap.Sugared
127143
return workflow.OK()
128144
}
129145

146+
func (r *ReconcileCommonController) getRoleRefs(ctx context.Context, roleRefs []mdbv1.MongoDBRoleRef, mongodbResourceNsName types.NamespacedName) ([]mdbv1.MongoDBRole, error) {
147+
roles := make([]mdbv1.MongoDBRole, len(roleRefs))
148+
149+
for idx, ref := range roleRefs {
150+
var role mdbv1.MongoDBRole
151+
switch ref.Kind {
152+
153+
case util.ClusterMongoDBRoleKind:
154+
r.resourceWatcher.AddWatchedResourceIfNotAdded(ref.Name, "", watch.ClusterMongoDBRole, mongodbResourceNsName)
155+
156+
customRole := &rolev1.ClusterMongoDBRole{}
157+
err := r.client.Get(ctx, types.NamespacedName{Name: ref.Name}, customRole)
158+
if err != nil {
159+
return nil, xerrors.Errorf("Failed to retrieve ClusterMongoDBRole '%s': %w", ref.Name, err)
160+
}
161+
162+
role = customRole.Spec.MongoDBRole
163+
164+
default:
165+
return nil, xerrors.Errorf("Invalid value %s for roleRef.kind. It must be %s.", ref.Kind, util.ClusterMongoDBRoleKind)
166+
}
167+
168+
roles[idx] = role
169+
}
170+
171+
return roles, nil
172+
}
173+
130174
// updateStatus updates the status for the CR using patch operation. Note, that the resource status is mutated and
131175
// it's important to pass resource by pointer to all methods which invoke current 'updateStatus'.
132176
func (r *ReconcileCommonController) updateStatus(ctx context.Context, reconciledResource v1.CustomResourceReadWriter, st workflow.Status, log *zap.SugaredLogger, statusOptions ...status.Option) (reconcile.Result, error) {

controllers/operator/mongodbmultireplicaset_controller.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/mongodb/mongodb-kubernetes/api/v1/mdb"
3434
mdbmultiv1 "github.com/mongodb/mongodb-kubernetes/api/v1/mdbmulti"
3535
omv1 "github.com/mongodb/mongodb-kubernetes/api/v1/om"
36+
rolev1 "github.com/mongodb/mongodb-kubernetes/api/v1/role"
3637
mdbstatus "github.com/mongodb/mongodb-kubernetes/api/v1/status"
3738
"github.com/mongodb/mongodb-kubernetes/controllers/om"
3839
"github.com/mongodb/mongodb-kubernetes/controllers/om/host"
@@ -360,7 +361,7 @@ func (r *ReconcileMongoDbMultiReplicaSet) reconcileMemberResources(ctx context.C
360361
}
361362
}
362363
// Ensure custom roles are created in OM
363-
if status := ensureRoles(mrs.GetSecurity().Roles, conn, log); !status.IsOK() {
364+
if status := r.ensureRoles(ctx, mrs.GetSecurity().Roles, mrs.GetSecurity().RoleRefs, conn, kube.ObjectKeyFromApiObject(mrs), log); !status.IsOK() {
364365
return status
365366
}
366367

@@ -1113,6 +1114,12 @@ func AddMultiReplicaSetController(ctx context.Context, mgr manager.Manager, imag
11131114
return err
11141115
}
11151116

1117+
err = c.Watch(source.Kind[client.Object](mgr.GetCache(), &rolev1.ClusterMongoDBRole{},
1118+
&watch.ResourcesHandler{ResourceType: watch.ClusterMongoDBRole, ResourceWatcher: reconciler.resourceWatcher}))
1119+
if err != nil {
1120+
return err
1121+
}
1122+
11161123
// register watcher across member clusters
11171124
for k, v := range memberClustersMap {
11181125
err := c.Watch(source.Kind[client.Object](v.GetCache(), &appsv1.StatefulSet{}, &khandler.EnqueueRequestForOwnerMultiCluster{}, watch.PredicatesForMultiStatefulSet()))

0 commit comments

Comments
 (0)