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
9 changes: 9 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,13 @@ spec:
requests:
cpu: 100m
memory: 20Mi
env:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the "downward API" in Kubernetes - that allows to pass some Kuberentes information (pod name, namespace, annotations etc) to the container

- name: OPERATOR_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: OPERATOR_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
terminationGracePeriodSeconds: 10
19 changes: 19 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
"flag"
"log"
"os"

"github.com/go-logr/zapr"
Expand All @@ -27,6 +28,7 @@ import (
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrzap "sigs.k8s.io/controller-runtime/pkg/log/zap"

mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
Expand Down Expand Up @@ -71,11 +73,14 @@ func main() {
os.Exit(1)
}

operatorPod := operatorPodObjectKey()

if err = (&atlascluster.AtlasClusterReconciler{
Client: mgr.GetClient(),
Log: logger.Named("controllers").Named("AtlasCluster").Sugar(),
Scheme: mgr.GetScheme(),
AtlasDomain: config.AtlasDomain,
OperatorPod: operatorPod,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "AtlasCluster")
os.Exit(1)
Expand All @@ -87,6 +92,7 @@ func main() {
Scheme: mgr.GetScheme(),
AtlasDomain: config.AtlasDomain,
ResourceWatcher: watch.NewResourceWatcher(),
OperatorPod: operatorPod,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "AtlasProject")
os.Exit(1)
Expand Down Expand Up @@ -118,3 +124,16 @@ func parseConfiguration() Config {
flag.Parse()
return config
}

func operatorPodObjectKey() client.ObjectKey {
operatorName := os.Getenv("OPERATOR_NAME")
if operatorName == "" {
log.Fatal(`"OPERATOR_NAME" environment variable must be set!`)
}
operatorNamespace := os.Getenv("OPERATOR_NAMESPACE")
if operatorNamespace == "" {
log.Fatal(`"OPERATOR_NAMESPACE" environment variable must be set!`)
}

return client.ObjectKey{Namespace: operatorNamespace, Name: operatorName}
}
8 changes: 4 additions & 4 deletions pkg/controller/atlas/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/workflow"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/kube"
)

const (
Expand All @@ -26,16 +27,15 @@ type Connection struct {

// ReadConnection reads Atlas API connection parameters from AtlasProject Secret or from the default Operator one if the
// former is not specified
func ReadConnection(log *zap.SugaredLogger, kubeClient client.Client, operatorName string, projectOverrideSecretRef *client.ObjectKey) (Connection, workflow.Result) {
func ReadConnection(log *zap.SugaredLogger, kubeClient client.Client, operatorPodObjectKey client.ObjectKey, projectOverrideSecretRef *client.ObjectKey) (Connection, workflow.Result) {
if projectOverrideSecretRef != nil {
// TODO is it possible that part of connection (like orgID is still in the Operator level secret and needs to get merged?)
log.Infof("Reading Atlas API credentials from the AtlasProject Secret %s", projectOverrideSecretRef)
return readAtlasConnectionFromSecret(kubeClient, *projectOverrideSecretRef)
}

// TODO check the default "Operator level" Secret
// return readAtlasConnectionFromSecret(operatorName + "-connection")
return Connection{}, workflow.Terminate(workflow.AtlasCredentialsNotProvided, "the API keys are not configured")
log.Debug("AtlasProject connection Secret is not specified - using the Operator one")
return readAtlasConnectionFromSecret(kubeClient, kube.ObjectKey(operatorPodObjectKey.Namespace, operatorPodObjectKey.Name+"-api-key"))
}

func readAtlasConnectionFromSecret(kubeClient client.Client, secretRef client.ObjectKey) (Connection, workflow.Result) {
Expand Down
5 changes: 3 additions & 2 deletions pkg/controller/atlascluster/atlascluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type AtlasClusterReconciler struct {
Log *zap.SugaredLogger
Scheme *runtime.Scheme
AtlasDomain string
OperatorPod client.ObjectKey
}

// +kubebuilder:rbac:groups=atlas.mongodb.com,resources=atlasclusters,verbs=get;list;watch;create;update;patch;delete
Expand All @@ -70,7 +71,7 @@ func (r *AtlasClusterReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error
return result.ReconcileResult(), nil
}

connection, result := atlas.ReadConnection(log, r.Client, "TODO!", project.ConnectionSecretObjectKey())
connection, result := atlas.ReadConnection(log, r.Client, r.OperatorPod, project.ConnectionSecretObjectKey())
if !result.IsOk() {
// merge result into ctx
ctx.SetConditionFromResult(status.ClusterReadyType, result)
Expand Down Expand Up @@ -136,7 +137,7 @@ func (r *AtlasClusterReconciler) Delete(e event.DeleteEvent) error {
return errors.New("cannot read project resource")
}

connection, result := atlas.ReadConnection(log, r.Client, "TODO!", project.ConnectionSecretObjectKey())
connection, result := atlas.ReadConnection(log, r.Client, r.OperatorPod, project.ConnectionSecretObjectKey())
if !result.IsOk() {
return errors.New("cannot read Atlas connection")
}
Expand Down
10 changes: 3 additions & 7 deletions pkg/controller/atlasproject/atlasproject_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"

mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1"
Expand All @@ -47,6 +46,7 @@ type AtlasProjectReconciler struct {
Log *zap.SugaredLogger
Scheme *runtime.Scheme
AtlasDomain string
OperatorPod client.ObjectKey
}

// +kubebuilder:rbac:groups=atlas.mongodb.com,resources=atlasprojects,verbs=get;list;watch;create;update;patch;delete
Expand All @@ -69,14 +69,10 @@ func (r *AtlasProjectReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error

log.Infow("-> Starting AtlasProject reconciliation", "spec", project.Spec)

if project.Spec.ConnectionSecret == nil {
log.Error("So far the Connection Secret in AtlasProject is mandatory!")
return reconcile.Result{}, nil
}
// This update will make sure the status is always updated in case of any errors or successful result
defer statushandler.Update(ctx, r.Client, project)

connection, result := atlas.ReadConnection(log, r.Client, "TODO!", project.ConnectionSecretObjectKey())
connection, result := atlas.ReadConnection(log, r.Client, r.OperatorPod, project.ConnectionSecretObjectKey())
if !result.IsOk() {
// merge result into ctx
ctx.SetConditionFromResult(status.ProjectReadyType, result)
Expand Down Expand Up @@ -113,7 +109,7 @@ func (r *AtlasProjectReconciler) Delete(e event.DeleteEvent) error {

log.Infow("-> Starting AtlasProject deletion", "spec", project.Spec)

connection, result := atlas.ReadConnection(log, r.Client, "TODO!", project.ConnectionSecretObjectKey())
connection, result := atlas.ReadConnection(log, r.Client, r.OperatorPod, project.ConnectionSecretObjectKey())
if !result.IsOk() {
return errors.New("cannot read Atlas connection")
}
Expand Down
3 changes: 3 additions & 0 deletions test/int/integration_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/atlasproject"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/controller/watch"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/httputil"
"github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/kube"
// +kubebuilder:scaffold:imports
)

Expand Down Expand Up @@ -182,13 +183,15 @@ func prepareControllers() {
Log: logger.Named("controllers").Named("AtlasProject").Sugar(),
AtlasDomain: "https://cloud-qa.mongodb.com",
ResourceWatcher: watch.NewResourceWatcher(),
OperatorPod: kube.ObjectKey(namespace.Name, "atlas-operator"),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())

err = (&atlascluster.AtlasClusterReconciler{
Client: k8sManager.GetClient(),
Log: logger.Named("controllers").Named("AtlasCluster").Sugar(),
AtlasDomain: "https://cloud-qa.mongodb.com",
OperatorPod: kube.ObjectKey(namespace.Name, "atlas-operator"),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())

Expand Down
78 changes: 67 additions & 11 deletions test/int/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,7 @@ var _ = Describe("AtlasProject", func() {

createdProject = &mdbv1.AtlasProject{}

connectionSecret = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "my-atlas-key",
Namespace: namespace.Name,
},
StringData: map[string]string{"orgId": connection.OrgID, "publicApiKey": connection.PublicKey, "privateApiKey": connection.PrivateKey},
}
connectionSecret = buildConnectionSecret("my-atlas-key")
By(fmt.Sprintf("Creating the Secret %s", kube.ObjectKeyFromObject(&connectionSecret)))
Expect(k8sClient.Create(context.Background(), &connectionSecret)).ToNot(HaveOccurred())
})
Expand Down Expand Up @@ -111,7 +105,6 @@ var _ = Describe("AtlasProject", func() {
Eventually(testutil.WaitFor(k8sClient, createdProject, expectedCondition),
20, interval).Should(BeTrue())

Expect(createdProject.Status.ObservedGeneration).To(Equal(createdProject.Generation))
expectedConditionsMatchers := testutil.MatchConditions(
status.FalseCondition(status.ProjectReadyType),
status.FalseCondition(status.ReadyType),
Expand Down Expand Up @@ -339,20 +332,83 @@ var _ = Describe("AtlasProject", func() {
})
})
})

Describe("Using the global Connection Secret", func() {
It("Should Succeed", func() {
globalConnectionSecret := buildConnectionSecret("atlas-operator-api-key")
Expect(k8sClient.Create(context.Background(), &globalConnectionSecret)).To(Succeed())

// We don't specify the connection Secret per project - the global one must be used
createdProject = testAtlasProject(namespace.Name, "test-project", namespace.Name, "")

Expect(k8sClient.Create(context.Background(), createdProject)).To(Succeed())

Eventually(testutil.WaitFor(k8sClient, createdProject, status.TrueCondition(status.ReadyType)),
20, interval).Should(BeTrue())

expectedConditionsMatchers := testutil.MatchConditions(
status.TrueCondition(status.ProjectReadyType),
status.TrueCondition(status.IPAccessListReadyType),
status.TrueCondition(status.ReadyType),
)
Expect(createdProject.Status.Conditions).To(ConsistOf(expectedConditionsMatchers))
Expect(createdProject.Status.ObservedGeneration).To(Equal(createdProject.Generation))
})
It("Should Fail if the global Secret doesn't exist", func() {
By("Creating without a global Secret", func() {
createdProject = testAtlasProject(namespace.Name, "test-project", namespace.Name, "")

Expect(k8sClient.Create(context.Background(), createdProject)).ToNot(HaveOccurred())

Eventually(testutil.WaitFor(k8sClient, createdProject, status.FalseCondition(status.ReadyType)),
20, interval).Should(BeTrue())

expectedConditionsMatchers := testutil.MatchConditions(
status.FalseCondition(status.ProjectReadyType).WithReason(string(workflow.AtlasCredentialsNotProvided)),
status.FalseCondition(status.ReadyType),
)
Expect(createdProject.Status.Conditions).To(ConsistOf(expectedConditionsMatchers))
Expect(createdProject.ID()).To(BeEmpty())
Expect(createdProject.Status.ObservedGeneration).To(Equal(createdProject.Generation))
})
By("Creating a global Secret - should get fixed", func() {
globalConnectionSecret := buildConnectionSecret("atlas-operator-api-key")
Expect(k8sClient.Create(context.Background(), &globalConnectionSecret)).To(Succeed())

Eventually(testutil.WaitFor(k8sClient, createdProject, status.TrueCondition(status.ReadyType)),
20, interval).Should(BeTrue())
})

})
})

})

func buildConnectionSecret(name string) corev1.Secret {
return corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace.Name,
},
StringData: map[string]string{"orgId": connection.OrgID, "publicApiKey": connection.PublicKey, "privateApiKey": connection.PrivateKey},
}
}

// TODO builders
func testAtlasProject(namespace, name, atlasName, connectionSecretName string) *mdbv1.AtlasProject {
return &mdbv1.AtlasProject{
project := mdbv1.AtlasProject{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: mdbv1.AtlasProjectSpec{
Name: atlasName,
ConnectionSecret: &mdbv1.ResourceRef{Name: connectionSecretName},
Name: atlasName,
},
}
if connectionSecretName != "" {
project.Spec.ConnectionSecret = &mdbv1.ResourceRef{Name: connectionSecretName}
}
return &project
}

func removeAtlasProject(projectID string) func() bool {
Expand Down