@@ -4,36 +4,101 @@ import (
44 "context"
55 "errors"
66 "fmt"
7+ "time"
78
89 "github.com/google/go-cmp/cmp"
910 "github.com/google/go-cmp/cmp/cmpopts"
1011 "go.mongodb.org/atlas/mongodbatlas"
1112 "go.uber.org/zap"
1213 corev1 "k8s.io/api/core/v1"
14+ "sigs.k8s.io/controller-runtime/pkg/client"
1315
1416 mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
1517 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/status"
1618 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/atlas"
1719 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/workflow"
1820 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/compat"
21+ "github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/timeutil"
1922)
2023
2124func (r * AtlasDatabaseUserReconciler ) ensureDatabaseUser (ctx * workflow.Context , project mdbv1.AtlasProject , dbUser mdbv1.AtlasDatabaseUser ) workflow.Result {
22- retryAfterUpdate := workflow .InProgress (workflow .DatabaseUserClustersAppliedChanges , "Clusters are scheduled to handle database users updates" )
23-
2425 apiUser , err := dbUser .ToAtlas (r .Client )
2526 if err != nil {
2627 return workflow .Terminate (workflow .Internal , err .Error ())
2728 }
28- secret := & corev1. Secret {}
29- if err := r .Client . Get ( context . Background (), * dbUser . PasswordSecretObjectKey (), secret ); err != nil {
30- return workflow . Terminate ( workflow . Internal , err . Error ())
29+
30+ if result := checkUserExpired ( ctx . Log , r .Client , project . ID (), dbUser ); ! result . IsOk () {
31+ return result
3132 }
32- currentPasswordResourceVersion := secret .ResourceVersion
3333
3434 if err = validateScopes (ctx , project .ID (), dbUser ); err != nil {
3535 return workflow .Terminate (workflow .DatabaseUserInvalidSpec , err .Error ())
3636 }
37+
38+ if result := performUpdateInAtlas (ctx , r .Client , project , dbUser , apiUser ); ! result .IsOk () {
39+ return result
40+ }
41+
42+ if result := checkClustersHaveReachedGoalState (ctx , project .ID (), dbUser ); ! result .IsOk () {
43+ return result
44+ }
45+
46+ if result := createOrUpdateConnectionSecrets (ctx , r .Client , project , dbUser ); ! result .IsOk () {
47+ return result
48+ }
49+
50+ // We need to remove the old Atlas User right after all the connection secrets are ensured if username has changed.
51+ if result := handleUserNameChange (ctx , project .ID (), dbUser ); ! result .IsOk () {
52+ return result
53+ }
54+
55+ // We mark the status.Username only when everything is finished including connection secrets
56+ ctx .EnsureStatusOption (status .AtlasDatabaseUserNameOption (dbUser .Spec .Username ))
57+
58+ return workflow .OK ()
59+ }
60+
61+ func handleUserNameChange (ctx * workflow.Context , projectID string , dbUser mdbv1.AtlasDatabaseUser ) workflow.Result {
62+ if dbUser .Spec .Username != dbUser .Status .UserName && dbUser .Status .UserName != "" {
63+ ctx .Log .Infow ("'spec.username' has changed - removing the old user from Atlas" , "newUserName" , dbUser .Spec .Username , "oldUserName" , dbUser .Status .UserName )
64+
65+ _ , err := ctx .Client .DatabaseUsers .Delete (context .Background (), dbUser .Spec .DatabaseName , projectID , dbUser .Status .UserName )
66+ if err != nil {
67+ // There may be some rare errors due to the databaseName change or maybe the user has already been removed - this
68+ // is not-critical (the stale connection secret has already been removed) and we shouldn't retry to avoid infinite retries
69+ ctx .Log .Errorf ("Failed to remove user %s from Atlas: %s" , dbUser .Status .UserName , err )
70+ }
71+ }
72+ return workflow .OK ()
73+ }
74+
75+ func checkUserExpired (log * zap.SugaredLogger , k8sClient client.Client , projectID string , dbUser mdbv1.AtlasDatabaseUser ) workflow.Result {
76+ if dbUser .Spec .DeleteAfterDate == "" {
77+ return workflow .OK ()
78+ }
79+
80+ deleteAfter , err := timeutil .ParseISO8601 (dbUser .Spec .DeleteAfterDate )
81+ if err != nil {
82+ return workflow .Terminate (workflow .DatabaseUserInvalidSpec , err .Error ()).WithoutRetry ()
83+ }
84+ if deleteAfter .Before (time .Now ()) {
85+ if err = removeStaleSecretsByUserName (k8sClient , projectID , dbUser .Spec .Username , dbUser , log ); err != nil {
86+ return workflow .Terminate (workflow .Internal , err .Error ())
87+ }
88+ return workflow .Terminate (workflow .DatabaseUserExpired , "The database user is expired and has been removed from Atlas" ).WithoutRetry ()
89+ }
90+ return workflow .OK ()
91+ }
92+
93+ func performUpdateInAtlas (ctx * workflow.Context , k8sClient client.Client , project mdbv1.AtlasProject , dbUser mdbv1.AtlasDatabaseUser , apiUser * mongodbatlas.DatabaseUser ) workflow.Result {
94+ secret := & corev1.Secret {}
95+ if err := k8sClient .Get (context .Background (), * dbUser .PasswordSecretObjectKey (), secret ); err != nil {
96+ return workflow .Terminate (workflow .Internal , err .Error ())
97+ }
98+ currentPasswordResourceVersion := secret .ResourceVersion
99+
100+ retryAfterUpdate := workflow .InProgress (workflow .DatabaseUserClustersAppliedChanges , "Clusters are scheduled to handle database users updates" )
101+
37102 // Try to find the user
38103 u , _ , err := ctx .Client .DatabaseUsers .Get (context .Background (), dbUser .Spec .DatabaseName , project .ID (), dbUser .Spec .Username )
39104 if err != nil {
@@ -66,18 +131,6 @@ func (r *AtlasDatabaseUserReconciler) ensureDatabaseUser(ctx *workflow.Context,
66131 // after the successful update we'll retry reconciliation so that clusters had a chance to start working
67132 return retryAfterUpdate
68133 }
69-
70- if result := checkClustersHaveReachedGoalState (ctx , project .ID (), dbUser ); ! result .IsOk () {
71- return result
72- }
73-
74- if result := createOrUpdateConnectionSecrets (ctx , r .Client , project , dbUser ); ! result .IsOk () {
75- return result
76- }
77-
78- // We mark the status.Username only when everything is finished including connection secrets
79- ctx .EnsureStatusOption (status .AtlasDatabaseUserNameOption (dbUser .Spec .Username ))
80-
81134 return workflow .OK ()
82135}
83136
@@ -180,6 +233,21 @@ func userMatchesSpec(log *zap.SugaredLogger, atlasSpec *mongodbatlas.DatabaseUse
180233 return false , err
181234 }
182235
236+ // performing some normalization of dates
237+ if atlasSpec .DeleteAfterDate != "" {
238+ atlasDeleteDate , err := timeutil .ParseISO8601 (atlasSpec .DeleteAfterDate )
239+ if err != nil {
240+ return false , err
241+ }
242+ atlasSpec .DeleteAfterDate = timeutil .FormatISO8601 (atlasDeleteDate )
243+ }
244+ if operatorSpec .DeleteAfterDate != "" {
245+ operatorDeleteDate , err := timeutil .ParseISO8601 (operatorSpec .DeleteAfterDate )
246+ if err != nil {
247+ return false , err
248+ }
249+ userMerged .DeleteAfterDate = timeutil .FormatISO8601 (operatorDeleteDate )
250+ }
183251 d := cmp .Diff (* atlasSpec , userMerged , cmpopts .EquateEmpty ())
184252 if d != "" {
185253 log .Debugf ("Users differs from spec: %s" , d )
0 commit comments