Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/crd/bases/atlas.mongodb.com_atlasdatabaseusers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ spec:
- type
type: object
type: array
connectionSecrets:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot to run make manifests in the previous PR

additionalProperties:
type: string
type: object
observedGeneration:
description: ObservedGeneration indicates the generation of the resource
specification that the Atlas Operator is aware of. The Atlas Operator
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/atlascluster/atlascluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (r *AtlasClusterReconciler) Delete(e event.DeleteEvent) error {
return fmt.Errorf("cannot delete Atlas cluster: %w", err)
}

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

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package atlasdatabaseuser

import (
"context"
"errors"
"fmt"

"go.uber.org/zap"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -30,10 +32,12 @@ import (
mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/status"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/atlas"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/connectionsecret"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/customresource"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/statushandler"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/watch"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/workflow"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/kube"
)

// AtlasDatabaseUserReconciler reconciles an AtlasDatabaseUser object
Expand Down Expand Up @@ -121,5 +125,57 @@ func (r *AtlasDatabaseUserReconciler) SetupWithManager(mgr ctrl.Manager) error {
}

func (r AtlasDatabaseUserReconciler) Delete(e event.DeleteEvent) error {
dbUser, ok := e.Object.(*mdbv1.AtlasDatabaseUser)
if !ok {
r.Log.Errorf("Ignoring malformed Delete() call (expected type %T, got %T)", &mdbv1.AtlasDatabaseUser{}, e.Object)
return nil
}

log := r.Log.With("atlasdatabaseuser", kube.ObjectKeyFromObject(dbUser))

log.Infow("-> Starting AtlasDatabaseUser deletion", "spec", dbUser.Spec)

project := &mdbv1.AtlasProject{}
if result := r.readProjectResource(dbUser, project); !result.IsOk() {
return errors.New("cannot read project resource")
}

connection, err := atlas.ReadConnection(log, r.Client, r.OperatorPod, project.ConnectionSecretObjectKey())
if err != nil {
return err
}

atlasClient, err := atlas.Client(r.AtlasDomain, connection, log)
if err != nil {
return fmt.Errorf("cannot build Atlas client: %w", err)
}

userName := dbUser.Spec.Username
_, err = atlasClient.DatabaseUsers.Delete(context.Background(), dbUser.Spec.DatabaseName, project.ID(), userName)
if err != nil {
return fmt.Errorf("cannot delete Database User in Atlas: %w", err)
}

log.Infow("Started DatabaseUser deletion process in Atlas", "projectID", project.ID(), "userName", userName)

secrets, err := connectionsecret.ListByUserName(r.Client, dbUser.Namespace, project.ID(), userName)
if err != nil {
return fmt.Errorf("failed to find connection secrets for the user: %w", err)
}

for _, secret := range secrets {
// Solves the "Implicit memory aliasing in for loop" linter error
s := secret.DeepCopy()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could use secrets[i]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, will use in the future!

err = r.Client.Delete(context.Background(), s)
if err != nil {
log.Errorf("Failed to remove connection Secret: %v", err)
} else {
log.Debugw("Removed connection Secret", "secret", kube.ObjectKeyFromObject(s))
}
}
if len(secrets) > 0 {
log.Infof("Removed %d connection secrets", len(secrets))
}

return nil
}
68 changes: 66 additions & 2 deletions test/int/dbuser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package int
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
Expand All @@ -13,6 +14,7 @@ import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
corev1 "k8s.io/api/core/v1"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

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

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

connSecretname := func(suffix string) string {
return kube.NormalizeIdentifier(createdProject.Spec.Name) + suffix
}

Describe("Create/Update the db user", func() {
It("Should be created successfully", func() {
createdDBUser = mdbv1.DefaultDBUser(namespace.Name, "test-db-user", createdProject.Name).WithPasswordSecret(UserPasswordSecret)
Expand All @@ -147,6 +153,12 @@ var _ = Describe("AtlasDatabaseUser", func() {
validateSecret(k8sClient, *createdProject, *createdClusterGCP, *createdDBUser)
validateSecret(k8sClient, *createdProject, *createdClusterAWS, *createdDBUser)
checkNumberOfConnectionSecrets(k8sClient, *createdProject, 2)

expectedSecretsInStatus := map[string]string{
"test-cluster-aws": connSecretname("-test-cluster-aws-test-db-user"),
"test-cluster-gcp": connSecretname("-test-cluster-gcp-test-db-user"),
}
Expect(createdDBUser.Status.ConnectionSecrets).To(Equal(expectedSecretsInStatus))
})
By("Checking connectivity to Clusters", func() {
// The user created lacks read/write roles
Expand Down Expand Up @@ -174,6 +186,12 @@ var _ = Describe("AtlasDatabaseUser", func() {
validateSecret(k8sClient, *createdProject, *createdClusterGCP, *createdDBUser)
validateSecret(k8sClient, *createdProject, *createdClusterAWS, *createdDBUser)
checkNumberOfConnectionSecrets(k8sClient, *createdProject, 2)

expectedSecretsInStatus := map[string]string{
"test-cluster-aws": connSecretname("-test-cluster-aws-test-db-user"),
"test-cluster-gcp": connSecretname("-test-cluster-gcp-test-db-user"),
}
Expect(createdDBUser.Status.ConnectionSecrets).To(Equal(expectedSecretsInStatus))
})

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

By("Checking write permissions for Clusters", func() {
Expand All @@ -216,7 +236,24 @@ var _ = Describe("AtlasDatabaseUser", func() {
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(MatchRegexp("not authorized"))
})
By("Removing Second user", func() {
Expect(k8sClient.Delete(context.Background(), secondDBUser)).To(Succeed())
Eventually(checkAtlasDatabaseUserRemoved(createdProject.Status.ID, *secondDBUser), 50, interval).Should(BeTrue())

secretNames := []string{connSecretname("-test-cluster-gcp-second-db-user")}
Eventually(checkSecretsDontExist(namespace.Name, secretNames), 50, interval).Should(BeTrue())
})
})
By("Removing First user", func() {
Expect(k8sClient.Delete(context.Background(), createdDBUser)).To(Succeed())
Eventually(checkAtlasDatabaseUserRemoved(createdProject.Status.ID, *createdDBUser), 50, interval).Should(BeTrue())

secretNames := []string{connSecretname("-test-cluster-aws-test-db-user"), connSecretname("-test-cluster-gcp-test-db-user")}
Eventually(checkSecretsDontExist(namespace.Name, secretNames), 50, interval).Should(BeTrue())

checkNumberOfConnectionSecrets(k8sClient, *createdProject, 0)
})

})
})
})
Expand Down Expand Up @@ -344,7 +381,7 @@ func validateSecret(k8sClient client.Client, project mdbv1.AtlasProject, cluster

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

names := make([]string, 0)
for _, item := range secretList.Items {
Expand All @@ -360,3 +397,30 @@ func buildConnectionURL(connURL, userName, password string) string {
Expect(err).NotTo(HaveOccurred())
return u
}

func checkAtlasDatabaseUserRemoved(projectID string, user mdbv1.AtlasDatabaseUser) func() bool {
return func() bool {
_, r, err := atlasClient.DatabaseUsers.Get(context.Background(), user.Spec.DatabaseName, projectID, user.Spec.Username)
if err != nil {
if r != nil && r.StatusCode == http.StatusNotFound {
return true
}
}

return false
}
}

func checkSecretsDontExist(namespace string, secretNames []string) func() bool {
return func() bool {
nonExisting := 0
for _, name := range secretNames {
s := corev1.Secret{}
err := k8sClient.Get(context.Background(), kube.ObjectKey(namespace, name), &s)
if err != nil && apiErrors.IsNotFound(err) {
nonExisting++
}
}
return nonExisting == len(secretNames)
}
}