Skip to content

Commit 13a312f

Browse files
authored
add database user reconciler logic (#3034)
1 parent 21d61f1 commit 13a312f

File tree

6 files changed

+172
-35
lines changed

6 files changed

+172
-35
lines changed

config/openapi2crd.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,7 @@ spec:
211211
schema: 'CloudDatabaseUser'
212212
filters:
213213
readWriteOnly: true
214+
skipProperties:
215+
- $.groupId
214216
sensitiveProperties:
215217
- $.password

internal/controller/registry.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import (
4848
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/featureflags"
4949
akov2generatedcluster "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/generated/controller/cluster"
5050
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/generated/controller/connectionsecret"
51+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/generated/controller/databaseuser"
5152
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/generated/controller/flexcluster"
5253
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/generated/controller/group"
5354
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer"
@@ -161,10 +162,16 @@ func (r *Registry) registerControllers(c cluster.Cluster, ap atlas.Provider) err
161162
return fmt.Errorf("error creating flex cluster reconciler: %w", err)
162163
}
163164

165+
databaseUserReconciler, err := databaseuser.NewDatabaseUserReconciler(c, ap, r.logger, r.globalSecretRef, r.deletionProtection, true, r.defaultPredicates())
166+
if err != nil {
167+
return fmt.Errorf("error creating database user reconciler: %w", err)
168+
}
169+
164170
reconcilers = append(reconcilers,
165171
newCtrlStateReconciler(groupReconciler, r.maxConcurrentReconciles),
166172
newCtrlStateReconciler(clusterController, r.maxConcurrentReconciles),
167173
newCtrlStateReconciler(flexController, r.maxConcurrentReconciles),
174+
newCtrlStateReconciler(databaseUserReconciler, r.maxConcurrentReconciles),
168175
)
169176
}
170177

internal/generated/controller/databaseuser/handler_v20250312.go

Lines changed: 160 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ package databaseuser
1616

1717
import (
1818
"context"
19+
"errors"
20+
"fmt"
1921

2022
v20250312sdk "go.mongodb.org/atlas-sdk/v20250312009/admin"
23+
v1 "k8s.io/api/core/v1"
2124
controllerruntime "sigs.k8s.io/controller-runtime"
2225
builder "sigs.k8s.io/controller-runtime/pkg/builder"
2326
client "sigs.k8s.io/controller-runtime/pkg/client"
@@ -49,65 +52,196 @@ func NewHandlerv20250312(kubeClient client.Client, atlasClient *v20250312sdk.API
4952

5053
// HandleInitial handles the initial state for version v20250312
5154
func (h *Handlerv20250312) HandleInitial(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
52-
// TODO: Implement initial state logic
53-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
54-
return result.NextState(state.StateUpdated, "Updated AtlasDatabaseUser.")
55+
deps, err := h.getDependencies(ctx, databaseuser)
56+
if err != nil {
57+
return result.Error(state.StateInitial, fmt.Errorf("Failed to get dependencies: %w", err))
58+
}
59+
60+
body := &v20250312sdk.CloudDatabaseUser{}
61+
params := &v20250312sdk.CreateDatabaseUserApiParams{
62+
CloudDatabaseUser: body,
63+
}
64+
65+
if err := h.translator.ToAPI(params, databaseuser, deps...); err != nil {
66+
return result.Error(state.StateInitial, fmt.Errorf("failed to translate params: %w", err))
67+
}
68+
69+
if err := h.translator.ToAPI(body, databaseuser, deps...); err != nil {
70+
return result.Error(state.StateInitial, fmt.Errorf("failed to translate body: %w", err))
71+
}
72+
73+
_, _, err = h.atlasClient.DatabaseUsersApi.CreateDatabaseUserWithParams(ctx, params).Execute()
74+
if err != nil {
75+
return result.Error(state.StateInitial, fmt.Errorf("failed to create datebaseuser: %w", err))
76+
}
77+
78+
if err := ctrlstate.NewPatcher(databaseuser).UpdateStateTracker(deps...).Patch(ctx, h.kubeClient); err != nil {
79+
return result.Error(state.StateCreated, fmt.Errorf("failed to update state tracker: %w", err))
80+
}
81+
82+
return result.NextState(state.StateCreated, "Created Database User.")
5583
}
5684

5785
// HandleImportRequested handles the importrequested state for version v20250312
5886
func (h *Handlerv20250312) HandleImportRequested(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
59-
// TODO: Implement importrequested state logic
60-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
87+
deps, err := h.getDependencies(ctx, databaseuser)
88+
if err != nil {
89+
return result.Error(state.StateInitial, fmt.Errorf("Failed to get dependencies: %w", err))
90+
}
91+
92+
groupId, ok := databaseuser.GetAnnotations()["mongodb.com/external-group-id"]
93+
if !ok {
94+
return result.Error(state.StateImportRequested, errors.New("missing annotation mongodb.com/external-id"))
95+
}
96+
97+
databaseName, ok := databaseuser.GetAnnotations()["mongodb.com/external-database-name"]
98+
if !ok {
99+
return result.Error(state.StateImportRequested, errors.New("missing annotation mongodb.com/external-database-name"))
100+
}
101+
102+
username, ok := databaseuser.GetAnnotations()["mongodb.com/external-username"]
103+
if !ok {
104+
return result.Error(state.StateImportRequested, errors.New("missing annotation mongodb.com/external-username"))
105+
}
106+
107+
params := &v20250312sdk.GetDatabaseUserApiParams{
108+
GroupId: groupId,
109+
DatabaseName: databaseName,
110+
Username: username,
111+
}
112+
_, _, err = h.atlasClient.DatabaseUsersApi.GetDatabaseUserWithParams(ctx, params).Execute()
113+
if err != nil {
114+
return result.Error(state.StateInitial, fmt.Errorf("failed to create datebaseuser: %w", err))
115+
}
116+
117+
if err := ctrlstate.NewPatcher(databaseuser).UpdateStateTracker(deps...).Patch(ctx, h.kubeClient); err != nil {
118+
return result.Error(state.StateImported, fmt.Errorf("failed to update state tracker: %w", err))
119+
}
120+
61121
return result.NextState(state.StateImported, "Import completed")
62122
}
63123

64124
// HandleImported handles the imported state for version v20250312
65125
func (h *Handlerv20250312) HandleImported(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
66-
// TODO: Implement imported state logic
67-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
68-
return result.NextState(state.StateUpdated, "Ready")
126+
return h.handleIdle(ctx, state.StateImported, databaseuser)
69127
}
70128

71129
// HandleCreating handles the creating state for version v20250312
72130
func (h *Handlerv20250312) HandleCreating(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
73-
// TODO: Implement creating state logic
74-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
75-
return result.NextState(state.StateCreated, "Resource created")
131+
panic("unsupported state")
76132
}
77133

78134
// HandleCreated handles the created state for version v20250312
79135
func (h *Handlerv20250312) HandleCreated(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
80-
// TODO: Implement created state logic
81-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
82-
return result.NextState(state.StateUpdated, "Ready")
136+
return h.handleIdle(ctx, state.StateCreated, databaseuser)
83137
}
84138

85139
// HandleUpdating handles the updating state for version v20250312
86140
func (h *Handlerv20250312) HandleUpdating(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
87-
// TODO: Implement updating state logic
88-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
89-
return result.NextState(state.StateUpdated, "Update completed")
141+
panic("unsupported state")
90142
}
91143

92144
// HandleUpdated handles the updated state for version v20250312
93145
func (h *Handlerv20250312) HandleUpdated(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
94-
// TODO: Implement updated state logic
95-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
96-
return result.NextState(state.StateUpdated, "Ready")
146+
return h.handleIdle(ctx, state.StateUpdated, databaseuser)
97147
}
98148

99149
// HandleDeletionRequested handles the deletionrequested state for version v20250312
100150
func (h *Handlerv20250312) HandleDeletionRequested(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
101-
// TODO: Implement deletionrequested state logic
102-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
103-
return result.NextState(state.StateDeleting, "Deletion started")
151+
deps, err := h.getDependencies(ctx, databaseuser)
152+
if err != nil {
153+
return result.Error(state.StateDeletionRequested, fmt.Errorf("Failed to get dependencies: %w", err))
154+
}
155+
156+
params := &v20250312sdk.DeleteDatabaseUserApiParams{}
157+
if err := h.translator.ToAPI(params, databaseuser, deps...); err != nil {
158+
return result.Error(state.StateDeletionRequested, fmt.Errorf("failed to translate params: %w", err))
159+
}
160+
_, err = h.atlasClient.DatabaseUsersApi.DeleteDatabaseUserWithParams(ctx, params).Execute()
161+
if err != nil {
162+
return result.Error(state.StateDeletionRequested, fmt.Errorf("failed to delete datebaseuser: %w", err))
163+
}
164+
165+
return result.NextState(state.StateDeleted, "User deleted.")
104166
}
105167

106168
// HandleDeleting handles the deleting state for version v20250312
107169
func (h *Handlerv20250312) HandleDeleting(ctx context.Context, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
108-
// TODO: Implement deleting state logic
109-
// TODO: Use h.atlasProvider.SdkClientSet(ctx, h.globalSecretRef, h.log) to get Atlas SDK client
110-
return result.NextState(state.StateDeleted, "Deleted")
170+
panic("unsupported state")
171+
}
172+
173+
func (h *Handlerv20250312) handleIdle(ctx context.Context, currentState state.ResourceState, databaseuser *akov2generated.DatabaseUser) (ctrlstate.Result, error) {
174+
deps, err := h.getDependencies(ctx, databaseuser)
175+
if err != nil {
176+
return result.Error(currentState, fmt.Errorf("Failed to get dependencies: %w", err))
177+
}
178+
179+
update, err := ctrlstate.ShouldUpdate(databaseuser, deps...)
180+
if err != nil {
181+
return result.Error(currentState, reconcile.TerminalError(err))
182+
}
183+
184+
if !update {
185+
return result.NextState(currentState, "Database user up to date. No update required.")
186+
}
187+
188+
body := &v20250312sdk.CloudDatabaseUser{}
189+
params := &v20250312sdk.UpdateDatabaseUserApiParams{
190+
CloudDatabaseUser: body,
191+
}
192+
193+
if err := h.translator.ToAPI(params, databaseuser, deps...); err != nil {
194+
return result.Error(currentState, fmt.Errorf("failed to translate params: %w", err))
195+
}
196+
197+
if err := h.translator.ToAPI(body, databaseuser, deps...); err != nil {
198+
return result.Error(currentState, fmt.Errorf("failed to translate body: %w", err))
199+
}
200+
201+
_, _, err = h.atlasClient.DatabaseUsersApi.UpdateDatabaseUserWithParams(ctx, params).Execute()
202+
if err != nil {
203+
return result.Error(currentState, fmt.Errorf("failed to update datebaseuser: %w", err))
204+
}
205+
206+
if err := ctrlstate.NewPatcher(databaseuser).UpdateStateTracker(deps...).Patch(ctx, h.kubeClient); err != nil {
207+
return result.Error(currentState, fmt.Errorf("failed to update state tracker: %w", err))
208+
}
209+
210+
return result.NextState(state.StateUpdated, "Updated Database User.")
211+
}
212+
213+
func (h *Handlerv20250312) getDependencies(ctx context.Context, databaseuser *akov2generated.DatabaseUser) ([]client.Object, error) {
214+
var deps []client.Object
215+
216+
// Check if passwordSecretRef is present
217+
if databaseuser.Spec.V20250312 != nil && databaseuser.Spec.V20250312.Entry != nil && databaseuser.Spec.V20250312.Entry.PasswordSecretRef != nil {
218+
secret := &v1.Secret{}
219+
err := h.kubeClient.Get(ctx, client.ObjectKey{
220+
Name: databaseuser.Spec.V20250312.Entry.PasswordSecretRef.Name,
221+
Namespace: databaseuser.GetNamespace(),
222+
}, secret)
223+
if err != nil {
224+
return deps, fmt.Errorf("failed to get Secret %s/%s: %w", databaseuser.GetNamespace(), databaseuser.Spec.V20250312.Entry.PasswordSecretRef.Name, err)
225+
}
226+
227+
deps = append(deps, secret)
228+
}
229+
230+
// Check if groupRef is present
231+
if databaseuser.Spec.V20250312 != nil && databaseuser.Spec.V20250312.GroupRef != nil {
232+
group := &akov2generated.Group{}
233+
err := h.kubeClient.Get(ctx, client.ObjectKey{
234+
Name: databaseuser.Spec.V20250312.GroupRef.Name,
235+
Namespace: databaseuser.GetNamespace(),
236+
}, group)
237+
if err != nil {
238+
return deps, fmt.Errorf("failed to get Group %s/%s: %w", databaseuser.GetNamespace(), databaseuser.Spec.V20250312.GroupRef.Name, err)
239+
}
240+
241+
deps = append(deps, group)
242+
}
243+
244+
return deps, nil
111245
}
112246

113247
// For returns the resource and predicates for the controller

internal/generated/crds/crds.yaml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,10 +1962,6 @@ spec:
19621962
description:
19631963
description: Description of this database user.
19641964
type: string
1965-
groupId:
1966-
description: Unique 24-hexadecimal digit string that identifies
1967-
the project.
1968-
type: string
19691965
labels:
19701966
description: List that contains the key-value pairs for tagging
19711967
and categorizing the MongoDB database user. The labels that
@@ -2007,7 +2003,7 @@ spec:
20072003
Alphanumeric string that authenticates this database user against the database specified in `databaseName`. To authenticate with SCRAM-SHA, you must specify this parameter. This parameter doesn't appear in this response.
20082004
properties:
20092005
key:
2010-
default: .data.password
2006+
default: password
20112007
description: Key of the secret data containing the sensitive
20122008
field value, defaults to "password".
20132009
type: string
@@ -2095,7 +2091,6 @@ spec:
20952091
type: string
20962092
required:
20972093
- databaseName
2098-
- groupId
20992094
- username
21002095
type: object
21012096
groupId:

internal/indexer/indexer.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ func RegisterAll(ctx context.Context, c cluster.Cluster, logger *zap.Logger) err
7979
NewAtlasDataFederationByProjectIDIndexer(ctx, c.GetClient(), logger),
8080
indexer.NewFlexClusterByGroupIndexer(logger),
8181
indexer.NewClusterByGroupIndexer(logger),
82+
indexer.NewDatabaseUserBySecretIndexer(logger),
83+
indexer.NewDatabaseUserByGroupIndexer(logger),
8284
)
8385
}
8486
return Register(ctx, c, indexers...)

internal/nextapi/generated/v1/databaseuser.go

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)