diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 51dfa77ad0..995319f229 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -40,4 +40,13 @@ spec: requests: cpu: 100m memory: 20Mi + env: + - name: OPERATOR_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace terminationGracePeriodSeconds: 10 diff --git a/main.go b/main.go index b59dd03fe6..8846a40799 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ package main import ( "flag" + "log" "os" "github.com/go-logr/zapr" @@ -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" @@ -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) @@ -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) @@ -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} +} diff --git a/pkg/controller/atlas/connection.go b/pkg/controller/atlas/connection.go index 0d203041c6..3a96aba07d 100644 --- a/pkg/controller/atlas/connection.go +++ b/pkg/controller/atlas/connection.go @@ -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 ( @@ -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) { diff --git a/pkg/controller/atlascluster/atlascluster_controller.go b/pkg/controller/atlascluster/atlascluster_controller.go index 3503005b14..695f6bf8c2 100644 --- a/pkg/controller/atlascluster/atlascluster_controller.go +++ b/pkg/controller/atlascluster/atlascluster_controller.go @@ -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 @@ -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) @@ -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") } diff --git a/pkg/controller/atlasproject/atlasproject_controller.go b/pkg/controller/atlasproject/atlasproject_controller.go index 23d58f7178..e88fce9725 100644 --- a/pkg/controller/atlasproject/atlasproject_controller.go +++ b/pkg/controller/atlasproject/atlasproject_controller.go @@ -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" @@ -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 @@ -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) @@ -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") } diff --git a/test/int/integration_suite_test.go b/test/int/integration_suite_test.go index c7aee2510b..8252dd11fd 100644 --- a/test/int/integration_suite_test.go +++ b/test/int/integration_suite_test.go @@ -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 ) @@ -182,6 +183,7 @@ 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()) @@ -189,6 +191,7 @@ func prepareControllers() { 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()) diff --git a/test/int/project_test.go b/test/int/project_test.go index 9254a5e7a3..ae9f89addf 100644 --- a/test/int/project_test.go +++ b/test/int/project_test.go @@ -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()) }) @@ -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), @@ -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 {