Skip to content

Commit 150732d

Browse files
author
Anton
authored
CLOUDP-82591: support removing the database users (#151)
1 parent 2de8000 commit 150732d

File tree

3 files changed

+123
-3
lines changed

3 files changed

+123
-3
lines changed

pkg/controller/atlascluster/atlascluster_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func (r *AtlasClusterReconciler) Delete(e event.DeleteEvent) error {
165165
return fmt.Errorf("cannot delete Atlas cluster: %w", err)
166166
}
167167

168-
log.Infow("Started Atlas cluster deletion process", "projectID", project.Status.ID, "clusterName", cluster.Name)
168+
log.Infow("Started Atlas cluster deletion process", "projectID", project.Status.ID, "clusterName", cluster.Spec.Name)
169169

170170
return nil
171171
}

pkg/controller/atlasdatabaseuser/atlasdatabaseuser_controller.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package atlasdatabaseuser
1818

1919
import (
2020
"context"
21+
"errors"
22+
"fmt"
2123

2224
"go.uber.org/zap"
2325
"k8s.io/apimachinery/pkg/runtime"
@@ -30,10 +32,12 @@ import (
3032
mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
3133
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/status"
3234
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/atlas"
35+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/connectionsecret"
3336
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/customresource"
3437
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/statushandler"
3538
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/watch"
3639
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/workflow"
40+
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/kube"
3741
)
3842

3943
// AtlasDatabaseUserReconciler reconciles an AtlasDatabaseUser object
@@ -123,5 +127,57 @@ func (r *AtlasDatabaseUserReconciler) SetupWithManager(mgr ctrl.Manager) error {
123127
}
124128

125129
func (r AtlasDatabaseUserReconciler) Delete(e event.DeleteEvent) error {
130+
dbUser, ok := e.Object.(*mdbv1.AtlasDatabaseUser)
131+
if !ok {
132+
r.Log.Errorf("Ignoring malformed Delete() call (expected type %T, got %T)", &mdbv1.AtlasDatabaseUser{}, e.Object)
133+
return nil
134+
}
135+
136+
log := r.Log.With("atlasdatabaseuser", kube.ObjectKeyFromObject(dbUser))
137+
138+
log.Infow("-> Starting AtlasDatabaseUser deletion", "spec", dbUser.Spec)
139+
140+
project := &mdbv1.AtlasProject{}
141+
if result := r.readProjectResource(dbUser, project); !result.IsOk() {
142+
return errors.New("cannot read project resource")
143+
}
144+
145+
connection, err := atlas.ReadConnection(log, r.Client, r.OperatorPod, project.ConnectionSecretObjectKey())
146+
if err != nil {
147+
return err
148+
}
149+
150+
atlasClient, err := atlas.Client(r.AtlasDomain, connection, log)
151+
if err != nil {
152+
return fmt.Errorf("cannot build Atlas client: %w", err)
153+
}
154+
155+
userName := dbUser.Spec.Username
156+
_, err = atlasClient.DatabaseUsers.Delete(context.Background(), dbUser.Spec.DatabaseName, project.ID(), userName)
157+
if err != nil {
158+
return fmt.Errorf("cannot delete Database User in Atlas: %w", err)
159+
}
160+
161+
log.Infow("Started DatabaseUser deletion process in Atlas", "projectID", project.ID(), "userName", userName)
162+
163+
secrets, err := connectionsecret.ListByUserName(r.Client, dbUser.Namespace, project.ID(), userName)
164+
if err != nil {
165+
return fmt.Errorf("failed to find connection secrets for the user: %w", err)
166+
}
167+
168+
for _, secret := range secrets {
169+
// Solves the "Implicit memory aliasing in for loop" linter error
170+
s := secret.DeepCopy()
171+
err = r.Client.Delete(context.Background(), s)
172+
if err != nil {
173+
log.Errorf("Failed to remove connection Secret: %v", err)
174+
} else {
175+
log.Debugw("Removed connection Secret", "secret", kube.ObjectKeyFromObject(s))
176+
}
177+
}
178+
if len(secrets) > 0 {
179+
log.Infof("Removed %d connection secrets", len(secrets))
180+
}
181+
126182
return nil
127183
}

test/int/dbuser_test.go

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package int
33
import (
44
"context"
55
"fmt"
6+
"net/http"
67
"net/url"
78
"strings"
89
"time"
@@ -13,6 +14,7 @@ import (
1314
"go.mongodb.org/mongo-driver/bson"
1415
"go.mongodb.org/mongo-driver/mongo/options"
1516
corev1 "k8s.io/api/core/v1"
17+
apiErrors "k8s.io/apimachinery/pkg/api/errors"
1618
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1719
"sigs.k8s.io/controller-runtime/pkg/client"
1820

@@ -61,7 +63,7 @@ var _ = Describe("AtlasDatabaseUser", func() {
6163
WithIPAccessList(project.NewIPAccessList().WithIP("0.0.0.0/0"))
6264
if DevMode {
6365
// While developing tests we need to reuse the same project
64-
createdProject.Spec.Name = "dev-test-atlas-project"
66+
createdProject.Spec.Name = "dev-test atlas-project"
6567
}
6668

6769
Expect(k8sClient.Create(context.Background(), createdProject)).To(Succeed())
@@ -128,6 +130,10 @@ var _ = Describe("AtlasDatabaseUser", func() {
128130
})
129131
}
130132

133+
connSecretname := func(suffix string) string {
134+
return kube.NormalizeIdentifier(createdProject.Spec.Name) + suffix
135+
}
136+
131137
Describe("Create/Update the db user", func() {
132138
It("Should be created successfully", func() {
133139
createdDBUser = mdbv1.DefaultDBUser(namespace.Name, "test-db-user", createdProject.Name).WithPasswordSecret(UserPasswordSecret)
@@ -147,6 +153,12 @@ var _ = Describe("AtlasDatabaseUser", func() {
147153
validateSecret(k8sClient, *createdProject, *createdClusterGCP, *createdDBUser)
148154
validateSecret(k8sClient, *createdProject, *createdClusterAWS, *createdDBUser)
149155
checkNumberOfConnectionSecrets(k8sClient, *createdProject, 2)
156+
157+
expectedSecretsInStatus := map[string]string{
158+
"test-cluster-aws": connSecretname("-test-cluster-aws-test-db-user"),
159+
"test-cluster-gcp": connSecretname("-test-cluster-gcp-test-db-user"),
160+
}
161+
Expect(createdDBUser.Status.ConnectionSecrets).To(Equal(expectedSecretsInStatus))
150162
})
151163
By("Checking connectivity to Clusters", func() {
152164
// The user created lacks read/write roles
@@ -174,6 +186,12 @@ var _ = Describe("AtlasDatabaseUser", func() {
174186
validateSecret(k8sClient, *createdProject, *createdClusterGCP, *createdDBUser)
175187
validateSecret(k8sClient, *createdProject, *createdClusterAWS, *createdDBUser)
176188
checkNumberOfConnectionSecrets(k8sClient, *createdProject, 2)
189+
190+
expectedSecretsInStatus := map[string]string{
191+
"test-cluster-aws": connSecretname("-test-cluster-aws-test-db-user"),
192+
"test-cluster-gcp": connSecretname("-test-cluster-gcp-test-db-user"),
193+
}
194+
Expect(createdDBUser.Status.ConnectionSecrets).To(Equal(expectedSecretsInStatus))
177195
})
178196

179197
By("Checking write permissions for Clusters", func() {
@@ -201,6 +219,8 @@ var _ = Describe("AtlasDatabaseUser", func() {
201219
validateSecret(k8sClient, *createdProject, *createdClusterAWS, *createdDBUser)
202220
validateSecret(k8sClient, *createdProject, *createdClusterGCP, *secondDBUser)
203221
checkNumberOfConnectionSecrets(k8sClient, *createdProject, 3)
222+
expectedSecretsInStatus := map[string]string{"test-cluster-gcp": connSecretname("-test-cluster-gcp-second-db-user")}
223+
Expect(secondDBUser.Status.ConnectionSecrets).To(Equal(expectedSecretsInStatus))
204224
})
205225

206226
By("Checking write permissions for Clusters", func() {
@@ -216,7 +236,24 @@ var _ = Describe("AtlasDatabaseUser", func() {
216236
Expect(err).To(HaveOccurred())
217237
Expect(err.Error()).To(MatchRegexp("not authorized"))
218238
})
239+
By("Removing Second user", func() {
240+
Expect(k8sClient.Delete(context.Background(), secondDBUser)).To(Succeed())
241+
Eventually(checkAtlasDatabaseUserRemoved(createdProject.Status.ID, *secondDBUser), 50, interval).Should(BeTrue())
242+
243+
secretNames := []string{connSecretname("-test-cluster-gcp-second-db-user")}
244+
Eventually(checkSecretsDontExist(namespace.Name, secretNames), 50, interval).Should(BeTrue())
245+
})
219246
})
247+
By("Removing First user", func() {
248+
Expect(k8sClient.Delete(context.Background(), createdDBUser)).To(Succeed())
249+
Eventually(checkAtlasDatabaseUserRemoved(createdProject.Status.ID, *createdDBUser), 50, interval).Should(BeTrue())
250+
251+
secretNames := []string{connSecretname("-test-cluster-aws-test-db-user"), connSecretname("-test-cluster-gcp-test-db-user")}
252+
Eventually(checkSecretsDontExist(namespace.Name, secretNames), 50, interval).Should(BeTrue())
253+
254+
checkNumberOfConnectionSecrets(k8sClient, *createdProject, 0)
255+
})
256+
220257
})
221258
})
222259
})
@@ -344,7 +381,7 @@ func validateSecret(k8sClient client.Client, project mdbv1.AtlasProject, cluster
344381

345382
func checkNumberOfConnectionSecrets(k8sClient client.Client, project mdbv1.AtlasProject, length int) {
346383
secretList := corev1.SecretList{}
347-
Expect(k8sClient.List(context.Background(), &secretList)).To(Succeed())
384+
Expect(k8sClient.List(context.Background(), &secretList, client.InNamespace(namespace.Name))).To(Succeed())
348385

349386
names := make([]string, 0)
350387
for _, item := range secretList.Items {
@@ -360,3 +397,30 @@ func buildConnectionURL(connURL, userName, password string) string {
360397
Expect(err).NotTo(HaveOccurred())
361398
return u
362399
}
400+
401+
func checkAtlasDatabaseUserRemoved(projectID string, user mdbv1.AtlasDatabaseUser) func() bool {
402+
return func() bool {
403+
_, r, err := atlasClient.DatabaseUsers.Get(context.Background(), user.Spec.DatabaseName, projectID, user.Spec.Username)
404+
if err != nil {
405+
if r != nil && r.StatusCode == http.StatusNotFound {
406+
return true
407+
}
408+
}
409+
410+
return false
411+
}
412+
}
413+
414+
func checkSecretsDontExist(namespace string, secretNames []string) func() bool {
415+
return func() bool {
416+
nonExisting := 0
417+
for _, name := range secretNames {
418+
s := corev1.Secret{}
419+
err := k8sClient.Get(context.Background(), kube.ObjectKey(namespace, name), &s)
420+
if err != nil && apiErrors.IsNotFound(err) {
421+
nonExisting++
422+
}
423+
}
424+
return nonExisting == len(secretNames)
425+
}
426+
}

0 commit comments

Comments
 (0)