From b22ce9dd341fed04ed6d6f8acc7cf900d38dd7f0 Mon Sep 17 00:00:00 2001 From: antonlisovenko Date: Mon, 25 Jan 2021 17:33:58 +0000 Subject: [PATCH 1/5] fix linter --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 379b0e82f0..b2729887fd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ # MongoDB Atlas Operator Welcome to MongoDB Atlas Operator - the Operator allowing to manage Atlas Clusters from Kubernetes. +> Current Status: *pre-alpha*. We are currently working on the initial set of features that will give users the opportunity to +provision Atlas projects, clusters and database users using Kubernetes Specifications and bind connection information +into the applications deployed to Kubernetes. + +## Installation / Upgrade + +### Using Kubernetes config files + +``` +kubectl apply -f +``` + +## Create Atlas Cluster + +In order to work with Atlas you'll need to provide the authentication information that would allow the Atlas Operator +communicate with Atlas API. You need to create the secret first: + +``` +kubectl create secret generic my-atlas-key \ + --from-literal="orgId=" \ + --from-literal="publicApiKey=" \ + --from-literal="privateApiKey=" \ + -n +``` + From c242a121400bbafd7c1b49de77917344a3434367 Mon Sep 17 00:00:00 2001 From: antonlisovenko Date: Tue, 9 Feb 2021 17:19:18 +0000 Subject: [PATCH 2/5] CLOUDP-80516: global connection secret --- config/manager/manager.yaml | 9 ++++ main.go | 19 +++++++ pkg/controller/atlas/connection.go | 8 +-- .../atlascluster/atlascluster_controller.go | 5 +- .../atlasproject/atlasproject_controller.go | 8 +-- test/int/integration_suite_test.go | 3 ++ test/int/project_test.go | 49 +++++++++++++++---- 7 files changed, 79 insertions(+), 22 deletions(-) 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 dffea30086..0d4398670a 100644 --- a/pkg/controller/atlascluster/atlascluster_controller.go +++ b/pkg/controller/atlascluster/atlascluster_controller.go @@ -45,6 +45,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 @@ -69,7 +70,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) @@ -132,7 +133,7 @@ func (r *AtlasClusterReconciler) Delete(obj runtime.Object) 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 fac7f7c0e8..ac65079319 100644 --- a/pkg/controller/atlasproject/atlasproject_controller.go +++ b/pkg/controller/atlasproject/atlasproject_controller.go @@ -23,7 +23,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" mdbv1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1" @@ -41,6 +40,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 @@ -63,14 +63,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) diff --git a/test/int/integration_suite_test.go b/test/int/integration_suite_test.go index 11e08080f0..439f3fc648 100644 --- a/test/int/integration_suite_test.go +++ b/test/int/integration_suite_test.go @@ -46,6 +46,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 ) @@ -172,6 +173,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()) @@ -179,6 +181,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 50f627c60d..6ffce5f0be 100644 --- a/test/int/project_test.go +++ b/test/int/project_test.go @@ -33,13 +33,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()) }) @@ -194,18 +188,53 @@ var _ = Describe("AtlasProject", func() { }) }) + FDescribe("Using the global Connection Secret", func() { + It("Should Succeed", func() { + globalConnectionSecret := buildConnectionSecret("atlas-operator-api-key") + Expect(k8sClient.Create(context.Background(), &globalConnectionSecret)).ToNot(HaveOccurred()) + + createdProject = testAtlasProject(namespace.Name, "test-project", namespace.Name, "") + + Expect(k8sClient.Create(context.Background(), createdProject)).ToNot(HaveOccurred()) + + 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)) + }) + }) + }) +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 } From 24cbafbd03d0178db2fe08d59f6cda8363009f69 Mon Sep 17 00:00:00 2001 From: antonlisovenko Date: Tue, 9 Feb 2021 17:19:35 +0000 Subject: [PATCH 3/5] wip --- test/int/project_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/int/project_test.go b/test/int/project_test.go index 6ffce5f0be..945d149ba0 100644 --- a/test/int/project_test.go +++ b/test/int/project_test.go @@ -188,7 +188,7 @@ var _ = Describe("AtlasProject", func() { }) }) - FDescribe("Using the global Connection Secret", func() { + Describe("Using the global Connection Secret", func() { It("Should Succeed", func() { globalConnectionSecret := buildConnectionSecret("atlas-operator-api-key") Expect(k8sClient.Create(context.Background(), &globalConnectionSecret)).ToNot(HaveOccurred()) From e95c6be8f89e3b6768f3946d62678e323f2a4c7a Mon Sep 17 00:00:00 2001 From: antonlisovenko Date: Tue, 9 Feb 2021 18:05:28 +0000 Subject: [PATCH 4/5] more tests --- test/int/project_test.go | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/test/int/project_test.go b/test/int/project_test.go index d0f9260698..696a5053c2 100644 --- a/test/int/project_test.go +++ b/test/int/project_test.go @@ -80,7 +80,6 @@ var _ = Describe("AtlasProject", func() { Eventually(testutil.WaitFor(k8sClient, createdProject, expectedCondition), 10, interval).Should(BeTrue()) - Expect(createdProject.Status.ObservedGeneration).To(Equal(createdProject.Generation)) expectedConditionsMatchers := testutil.MatchConditions( status.FalseCondition(status.ProjectReadyType), status.FalseCondition(status.ReadyType), @@ -254,11 +253,12 @@ 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)).ToNot(HaveOccurred()) + 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)).ToNot(HaveOccurred()) + Expect(k8sClient.Create(context.Background(), createdProject)).To(Succeed()) Eventually(testutil.WaitFor(k8sClient, createdProject, status.TrueCondition(status.ReadyType)), 20, interval).Should(BeTrue()) @@ -271,6 +271,32 @@ var _ = Describe("AtlasProject", func() { Expect(createdProject.Status.Conditions).To(ConsistOf(expectedConditionsMatchers)) Expect(createdProject.Status.ObservedGeneration).To(Equal(createdProject.Generation)) }) + FIt("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()) + }) + + }) }) }) From 1b5cd12003010e99f7dfa539d5b44c553a7f0a22 Mon Sep 17 00:00:00 2001 From: antonlisovenko Date: Tue, 9 Feb 2021 18:20:40 +0000 Subject: [PATCH 5/5] remove F --- test/int/project_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/int/project_test.go b/test/int/project_test.go index 696a5053c2..38f0ea5205 100644 --- a/test/int/project_test.go +++ b/test/int/project_test.go @@ -271,7 +271,7 @@ var _ = Describe("AtlasProject", func() { Expect(createdProject.Status.Conditions).To(ConsistOf(expectedConditionsMatchers)) Expect(createdProject.Status.ObservedGeneration).To(Equal(createdProject.Generation)) }) - FIt("Should Fail if the global Secret doesn't exist", func() { + 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, "")