From a0aa658cddd78f27c6cd5bf8da83e44dee543e33 Mon Sep 17 00:00:00 2001 From: "jose.vazquez" Date: Tue, 17 Dec 2024 11:56:19 +0100 Subject: [PATCH] Use dual ref on deployments Signed-off-by: jose.vazquez --- .../atlas.mongodb.com_atlasdeployments.yaml | 10 +- pkg/api/credentials.go | 19 - pkg/api/v1/atlasdeployment_types.go | 30 +- pkg/api/v1/atlasdeployment_types_test.go | 52 ++- pkg/api/v1/zz_generated.deepcopy.go | 12 +- pkg/api/zz_generated.deepcopy.go | 20 - .../atlasdeployment/advanced_deployment.go | 39 +- .../advanced_deployment_test.go | 12 +- .../atlasdeployment_controller.go | 142 ++---- .../atlasdeployment_controller_test.go | 435 +++--------------- pkg/controller/atlasdeployment/backup.go | 23 +- .../atlasdeployment/customzonemapping.go | 15 +- .../atlasdeployment/customzonemapping_test.go | 5 +- .../atlasdeployment/deployment_test.go | 6 +- .../atlasdeployment/managed_namespaces.go | 12 +- .../managed_namespaces_test.go | 5 +- .../atlasdeployment/serverless_deployment.go | 9 +- .../serverless_deployment_test.go | 36 +- test/e2e/atlas_gov_test.go | 16 +- test/e2e/operator_type_wide_test.go | 4 +- .../e2e/actions/deploy/deploy_operator.go | 2 +- test/helper/e2e/actions/steps.go | 2 +- test/helper/e2e/data/deployments.go | 48 +- test/helper/e2e/model/deployment.go | 2 +- test/int/clusterwide/dbuser_test.go | 2 +- test/int/deployment_independent_test.go | 8 +- 26 files changed, 296 insertions(+), 670 deletions(-) diff --git a/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml b/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml index 01cdcaba06..725d658ff4 100644 --- a/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml +++ b/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml @@ -618,8 +618,9 @@ spec: - name type: object externalProjectRef: - description: ExternalProjectRef holds the Atlas project ID the user - belongs to + description: |- + "externalProjectRef" holds the parent Atlas project ID. + Mutually exclusive with the "projectRef" field properties: id: description: ID is the Atlas project ID @@ -655,8 +656,9 @@ spec: type: integer type: object projectRef: - description: Project is a reference to AtlasProject resource the deployment - belongs to + description: |- + "projectRef" is a reference to the parent AtlasProject resource. + Mutually exclusive with the "externalProjectRef" field properties: name: description: Name is the name of the Kubernetes Resource diff --git a/pkg/api/credentials.go b/pkg/api/credentials.go index 7fe2412b3d..076b0a24ac 100644 --- a/pkg/api/credentials.go +++ b/pkg/api/credentials.go @@ -18,22 +18,3 @@ type ObjectWithCredentials interface { client.Object CredentialsProvider } - -// +k8s:deepcopy-gen=false - -// ResourceWithCredentials is to be implemented by all CRDs using custom local credentials -type ResourceWithCredentials interface { - CredentialsProvider - GetName() string - GetNamespace() string -} - -// LocalCredentialHolder is to be embedded by Specs of CRDs using custom local credentials -type LocalCredentialHolder struct { - // Name of the secret containing Atlas API private and public keys - ConnectionSecret *LocalObjectReference `json:"connectionSecret,omitempty"` -} - -func (ch *LocalCredentialHolder) Credentials() *LocalObjectReference { - return ch.ConnectionSecret -} diff --git a/pkg/api/v1/atlasdeployment_types.go b/pkg/api/v1/atlasdeployment_types.go index 3bce8aff62..f7c8f13a8d 100644 --- a/pkg/api/v1/atlasdeployment_types.go +++ b/pkg/api/v1/atlasdeployment_types.go @@ -50,12 +50,8 @@ const ( // +kubebuilder:validation:XValidation:rule="(has(self.externalProjectRef) && !has(self.projectRef)) || (!has(self.externalProjectRef) && has(self.projectRef))",message="must define only one project reference through externalProjectRef or projectRef" // +kubebuilder:validation:XValidation:rule="(has(self.externalProjectRef) && has(self.connectionSecret)) || !has(self.externalProjectRef)",message="must define a local connection secret when referencing an external project" type AtlasDeploymentSpec struct { - api.LocalCredentialHolder `json:",inline"` - - // Project is a reference to AtlasProject resource the deployment belongs to - Project *common.ResourceRefNamespaced `json:"projectRef,omitempty"` - // ExternalProjectRef holds the Atlas project ID the user belongs to - ExternalProjectRef *ExternalProjectReference `json:"externalProjectRef,omitempty"` + // ProjectReference is the dual external or kubernetes reference with access credentials + ProjectDualReference `json:",inline"` // Configuration for the advanced (v1.5) deployment API https://www.mongodb.com/docs/atlas/reference/api/clusters/ // +optional @@ -505,10 +501,10 @@ type AtlasDeploymentList struct { func (c AtlasDeployment) AtlasProjectObjectKey() client.ObjectKey { ns := c.Namespace - if c.Spec.Project.Namespace != "" { - ns = c.Spec.Project.Namespace + if c.Spec.ProjectRef.Namespace != "" { + ns = c.Spec.ProjectRef.Namespace } - return kube.ObjectKey(ns, c.Spec.Project.Name) + return kube.ObjectKey(ns, c.Spec.ProjectRef.Name) } func (c *AtlasDeployment) GetStatus() api.Status { @@ -527,7 +523,11 @@ func (c *AtlasDeployment) UpdateStatus(conditions []api.Condition, options ...ap } func (c *AtlasDeployment) Credentials() *api.LocalObjectReference { - return c.Spec.Credentials() + return c.Spec.ConnectionSecret +} + +func (c *AtlasDeployment) ProjectDualRef() *ProjectDualReference { + return &c.Spec.ProjectDualReference } // ************************************ Builder methods ************************************************* @@ -633,7 +633,7 @@ func (c *AtlasDeployment) WithAtlasName(name string) *AtlasDeployment { } func (c *AtlasDeployment) WithProjectName(projectName string) *AtlasDeployment { - c.Spec.Project = &common.ResourceRefNamespaced{Name: projectName} + c.Spec.ProjectRef = &common.ResourceRefNamespaced{Name: projectName} return c } @@ -689,14 +689,12 @@ func (c *AtlasDeployment) WithSearchNodes(instanceSize string, count uint8) *Atl } func (c *AtlasDeployment) WithExternaLProject(projectID, credentialsName string) *AtlasDeployment { - c.Spec.Project = nil + c.Spec.ProjectRef = nil c.Spec.ExternalProjectRef = &ExternalProjectReference{ ID: projectID, } - c.Spec.LocalCredentialHolder = api.LocalCredentialHolder{ - ConnectionSecret: &api.LocalObjectReference{ - Name: credentialsName, - }, + c.Spec.ConnectionSecret = &api.LocalObjectReference{ + Name: credentialsName, } return c diff --git a/pkg/api/v1/atlasdeployment_types_test.go b/pkg/api/v1/atlasdeployment_types_test.go index 892261c5fc..de8e4d6d76 100644 --- a/pkg/api/v1/atlasdeployment_types_test.go +++ b/pkg/api/v1/atlasdeployment_types_test.go @@ -18,11 +18,13 @@ func TestDeploymentProjectReference(t *testing.T) { "both project references are set": { object: &AtlasDeployment{ Spec: AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", - }, - ExternalProjectRef: &ExternalProjectReference{ - ID: "my-project-id", + ProjectDualReference: ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, + ExternalProjectRef: &ExternalProjectReference{ + ID: "my-project-id", + }, }, }, }, @@ -34,8 +36,10 @@ func TestDeploymentProjectReference(t *testing.T) { "external project references is set": { object: &AtlasDeployment{ Spec: AtlasDeploymentSpec{ - ExternalProjectRef: &ExternalProjectReference{ - ID: "my-project-id", + ProjectDualReference: ProjectDualReference{ + ExternalProjectRef: &ExternalProjectReference{ + ID: "my-project-id", + }, }, }, }, @@ -46,8 +50,10 @@ func TestDeploymentProjectReference(t *testing.T) { "kubernetes project references is set": { object: &AtlasDeployment{ Spec: AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", + ProjectDualReference: ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, }, }, }, @@ -62,8 +68,10 @@ func TestDeploymentExternalProjectReferenceConnectionSecret(t *testing.T) { "external project references is set without connection secret": { object: &AtlasDeployment{ Spec: AtlasDeploymentSpec{ - ExternalProjectRef: &ExternalProjectReference{ - ID: "my-project-id", + ProjectDualReference: ProjectDualReference{ + ExternalProjectRef: &ExternalProjectReference{ + ID: "my-project-id", + }, }, }, }, @@ -74,10 +82,10 @@ func TestDeploymentExternalProjectReferenceConnectionSecret(t *testing.T) { "external project references is set with connection secret": { object: &AtlasDeployment{ Spec: AtlasDeploymentSpec{ - ExternalProjectRef: &ExternalProjectReference{ - ID: "my-project-id", - }, - LocalCredentialHolder: api.LocalCredentialHolder{ + ProjectDualReference: ProjectDualReference{ + ExternalProjectRef: &ExternalProjectReference{ + ID: "my-project-id", + }, ConnectionSecret: &api.LocalObjectReference{ Name: "my-dbuser-connection-secret", }, @@ -88,8 +96,10 @@ func TestDeploymentExternalProjectReferenceConnectionSecret(t *testing.T) { "kubernetes project references is set without connection secret": { object: &AtlasDeployment{ Spec: AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", + ProjectDualReference: ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, }, }, }, @@ -97,10 +107,10 @@ func TestDeploymentExternalProjectReferenceConnectionSecret(t *testing.T) { "kubernetes project references is set with connection secret": { object: &AtlasDeployment{ Spec: AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", - }, - LocalCredentialHolder: api.LocalCredentialHolder{ + ProjectDualReference: ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, ConnectionSecret: &api.LocalObjectReference{ Name: "my-dbuser-connection-secret", }, diff --git a/pkg/api/v1/zz_generated.deepcopy.go b/pkg/api/v1/zz_generated.deepcopy.go index fc732ab896..19c420a369 100644 --- a/pkg/api/v1/zz_generated.deepcopy.go +++ b/pkg/api/v1/zz_generated.deepcopy.go @@ -879,17 +879,7 @@ func (in *AtlasDeploymentList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AtlasDeploymentSpec) DeepCopyInto(out *AtlasDeploymentSpec) { *out = *in - in.LocalCredentialHolder.DeepCopyInto(&out.LocalCredentialHolder) - if in.Project != nil { - in, out := &in.Project, &out.Project - *out = new(common.ResourceRefNamespaced) - **out = **in - } - if in.ExternalProjectRef != nil { - in, out := &in.ExternalProjectRef, &out.ExternalProjectRef - *out = new(ExternalProjectReference) - **out = **in - } + in.ProjectDualReference.DeepCopyInto(&out.ProjectDualReference) if in.DeploymentSpec != nil { in, out := &in.DeploymentSpec, &out.DeploymentSpec *out = new(AdvancedDeploymentSpec) diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index 3e2216c836..5c709de7ea 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -50,26 +50,6 @@ func (in *Condition) DeepCopy() *Condition { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LocalCredentialHolder) DeepCopyInto(out *LocalCredentialHolder) { - *out = *in - if in.ConnectionSecret != nil { - in, out := &in.ConnectionSecret, &out.ConnectionSecret - *out = new(LocalObjectReference) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalCredentialHolder. -func (in *LocalCredentialHolder) DeepCopy() *LocalCredentialHolder { - if in == nil { - return nil - } - out := new(LocalCredentialHolder) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { *out = *in diff --git a/pkg/controller/atlasdeployment/advanced_deployment.go b/pkg/controller/atlasdeployment/advanced_deployment.go index 92092e1674..5c676b0a8b 100644 --- a/pkg/controller/atlasdeployment/advanced_deployment.go +++ b/pkg/controller/atlasdeployment/advanced_deployment.go @@ -12,6 +12,7 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/stringutil" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/deployment" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/project" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/searchindex" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api" akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1" @@ -24,10 +25,10 @@ import ( const FreeTier = "M0" -func (r *AtlasDeploymentReconciler) handleAdvancedDeployment(ctx *workflow.Context, deploymentInAKO, deploymentInAtlas *deployment.Cluster) (ctrl.Result, error) { +func (r *AtlasDeploymentReconciler) handleAdvancedDeployment(ctx *workflow.Context, projectService project.ProjectService, deploymentService deployment.AtlasDeploymentsService, deploymentInAKO, deploymentInAtlas *deployment.Cluster) (ctrl.Result, error) { if deploymentInAtlas == nil { ctx.Log.Infof("Advanced Deployment %s doesn't exist in Atlas - creating", deploymentInAKO.GetName()) - newDeployment, err := r.deploymentService.CreateDeployment(ctx.Context, deploymentInAKO) + newDeployment, err := deploymentService.CreateDeployment(ctx.Context, deploymentInAKO) if err != nil { return r.terminate(ctx, workflow.DeploymentNotCreatedInAtlas, err) } @@ -38,7 +39,7 @@ func (r *AtlasDeploymentReconciler) handleAdvancedDeployment(ctx *workflow.Conte switch deploymentInAtlas.GetState() { case status.StateIDLE: if changes, occurred := deployment.ComputeChanges(deploymentInAKO, deploymentInAtlas); occurred { - updatedDeployment, err := r.deploymentService.UpdateDeployment(ctx.Context, changes) + updatedDeployment, err := deploymentService.UpdateDeployment(ctx.Context, changes) if err != nil { return r.terminate(ctx, workflow.DeploymentNotUpdatedInAtlas, err) } @@ -46,52 +47,54 @@ func (r *AtlasDeploymentReconciler) handleAdvancedDeployment(ctx *workflow.Conte return r.inProgress(ctx, deploymentInAKO.GetCustomResource(), updatedDeployment, workflow.DeploymentUpdating, "deployment is updating") } - transition := r.ensureBackupScheduleAndPolicy(ctx, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource()) + transition := r.ensureBackupScheduleAndPolicy(ctx, deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource()) if transition != nil { return transition(workflow.Internal) } - transition = r.ensureAdvancedOptions(ctx, deploymentInAKO, deploymentInAtlas) + transition = r.ensureAdvancedOptions(ctx, deploymentService, deploymentInAKO, deploymentInAtlas) if transition != nil { return transition(workflow.DeploymentAdvancedOptionsReady) } - err := r.ensureConnectionSecrets(ctx, deploymentInAKO, deploymentInAtlas.GetConnection()) + err := r.ensureConnectionSecrets(ctx, projectService, deploymentInAKO, deploymentInAtlas.GetConnection()) if err != nil { return r.terminate(ctx, workflow.DeploymentConnectionSecretsNotCreated, err) } if !r.AtlasProvider.IsCloudGov() { searchNodeResult := handleSearchNodes(ctx, deploymentInAKO.GetCustomResource(), deploymentInAKO.GetProjectID()) - if transition = r.transitionFromResult(ctx, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), searchNodeResult); transition != nil { + if transition = r.transitionFromResult(ctx, deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), searchNodeResult); transition != nil { return transition(workflow.Internal) } } searchService := searchindex.NewSearchIndexes(ctx.SdkClientSet.SdkClient20241113001.AtlasSearchApi) result := handleSearchIndexes(ctx, r.Client, searchService, deploymentInAKO.GetCustomResource(), deploymentInAKO.GetProjectID()) - if transition = r.transitionFromResult(ctx, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), result); transition != nil { + if transition = r.transitionFromResult(ctx, deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), result); transition != nil { return transition(workflow.Internal) } result = r.ensureCustomZoneMapping( ctx, + deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource().Spec.DeploymentSpec.CustomZoneMapping, deploymentInAKO.GetName(), ) - if transition = r.transitionFromResult(ctx, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), result); transition != nil { + if transition = r.transitionFromResult(ctx, deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), result); transition != nil { return transition(workflow.Internal) } result = r.ensureManagedNamespaces( ctx, + deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.ClusterType, deploymentInAKO.GetCustomResource().Spec.DeploymentSpec.ManagedNamespaces, deploymentInAKO.GetName(), ) - if transition = r.transitionFromResult(ctx, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), result); transition != nil { + if transition = r.transitionFromResult(ctx, deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), result); transition != nil { return transition(workflow.Internal) } @@ -112,7 +115,7 @@ func (r *AtlasDeploymentReconciler) handleAdvancedDeployment(ctx *workflow.Conte } } -func (r *AtlasDeploymentReconciler) ensureConnectionSecrets(ctx *workflow.Context, deploymentInAKO deployment.Deployment, connection *status.ConnectionStrings) error { +func (r *AtlasDeploymentReconciler) ensureConnectionSecrets(ctx *workflow.Context, projectService project.ProjectService, deploymentInAKO deployment.Deployment, connection *status.ConnectionStrings) error { databaseUsers := &akov2.AtlasDatabaseUserList{} listOpts := &client.ListOptions{ FieldSelector: fields.OneTermEqualSelector(indexer.AtlasDatabaseUserByProject, deploymentInAKO.GetProjectID()), @@ -168,7 +171,7 @@ func (r *AtlasDeploymentReconciler) ensureConnectionSecrets(ctx *workflow.Contex }) } - project, err := r.projectService.GetProject(ctx.Context, deploymentInAKO.GetProjectID()) + project, err := projectService.GetProject(ctx.Context, deploymentInAKO.GetProjectID()) if err != nil { return err } @@ -188,23 +191,23 @@ func (r *AtlasDeploymentReconciler) ensureConnectionSecrets(ctx *workflow.Contex return nil } -func (r *AtlasDeploymentReconciler) ensureAdvancedOptions(ctx *workflow.Context, deploymentInAKO, deploymentInAtlas *deployment.Cluster) transitionFn { +func (r *AtlasDeploymentReconciler) ensureAdvancedOptions(ctx *workflow.Context, deploymentService deployment.AtlasDeploymentsService, deploymentInAKO, deploymentInAtlas *deployment.Cluster) transitionFn { if deploymentInAKO.IsTenant() { return nil } - err := r.deploymentService.ClusterWithProcessArgs(ctx.Context, deploymentInAtlas) + err := deploymentService.ClusterWithProcessArgs(ctx.Context, deploymentInAtlas) if err != nil { - return r.transitionFromLegacy(ctx, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), err) + return r.transitionFromLegacy(ctx, deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), err) } if deploymentInAKO.ProcessArgs != nil && !reflect.DeepEqual(deploymentInAKO.ProcessArgs, deploymentInAtlas.ProcessArgs) { - err = r.deploymentService.UpdateProcessArgs(ctx.Context, deploymentInAKO) + err = deploymentService.UpdateProcessArgs(ctx.Context, deploymentInAKO) if err != nil { - return r.transitionFromLegacy(ctx, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), err) + return r.transitionFromLegacy(ctx, deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), err) } - return r.transitionFromLegacy(ctx, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), nil) + return r.transitionFromLegacy(ctx, deploymentService, deploymentInAKO.GetProjectID(), deploymentInAKO.GetCustomResource(), nil) } return nil diff --git a/pkg/controller/atlasdeployment/advanced_deployment_test.go b/pkg/controller/atlasdeployment/advanced_deployment_test.go index d4b639e1c6..f070e84374 100644 --- a/pkg/controller/atlasdeployment/advanced_deployment_test.go +++ b/pkg/controller/atlasdeployment/advanced_deployment_test.go @@ -20,8 +20,10 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/mocks/translation" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/deployment" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/project" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api" akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/reconciler" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow" ) @@ -725,9 +727,10 @@ func TestHandleAdvancedDeployment(t *testing.T) { Build() logger := zaptest.NewLogger(t).Sugar() reconciler := &AtlasDeploymentReconciler{ - Client: k8sClient, - Log: logger, - deploymentService: tt.deploymentService(), + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + Log: logger, + }, } ctx := &workflow.Context{ Context: context.Background(), @@ -736,7 +739,8 @@ func TestHandleAdvancedDeployment(t *testing.T) { } deploymentInAKO := deployment.NewDeployment("project-id", tt.atlasDeployment).(*deployment.Cluster) - result, err := reconciler.handleAdvancedDeployment(ctx, deploymentInAKO, tt.deploymentInAtlas) + var projectService project.ProjectService // nil projetc service + result, err := reconciler.handleAdvancedDeployment(ctx, projectService, tt.deploymentService(), deploymentInAKO, tt.deploymentInAtlas) require.NoError(t, err) assert.Equal(t, tt.expectedResult, result) assert.True( diff --git a/pkg/controller/atlasdeployment/atlasdeployment_controller.go b/pkg/controller/atlasdeployment/atlasdeployment_controller.go index e8e92244f9..d99f579c6f 100644 --- a/pkg/controller/atlasdeployment/atlasdeployment_controller.go +++ b/pkg/controller/atlasdeployment/atlasdeployment_controller.go @@ -48,6 +48,7 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/atlas" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/connectionsecret" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/customresource" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/reconciler" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/statushandler" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/validate" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow" @@ -56,8 +57,7 @@ import ( // AtlasDeploymentReconciler reconciles an AtlasDeployment object type AtlasDeploymentReconciler struct { - Client client.Client - Log *zap.SugaredLogger + reconciler.AtlasReconciler Scheme *runtime.Scheme GlobalPredicates []predicate.Predicate EventRecorder record.EventRecorder @@ -65,9 +65,6 @@ type AtlasDeploymentReconciler struct { ObjectDeletionProtection bool SubObjectDeletionProtection bool independentSyncPeriod time.Duration - - deploymentService deployment.AtlasDeploymentsService - projectService project.ProjectService } // +kubebuilder:rbac:groups=atlas.mongodb.com,resources=atlasdeployments,verbs=get;list;watch;create;update;patch;delete @@ -134,18 +131,31 @@ func (r *AtlasDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Requ return result.ReconcileResult(), nil } - var atlasProject *project.Project - var err error - if atlasDeployment.Spec.ExternalProjectRef != nil { - atlasProject, err = r.getProjectFromAtlas(workflowCtx, atlasDeployment) - } else { - atlasProject, err = r.getProjectFromKube(workflowCtx, atlasDeployment) + credentials, err := r.ResolveCredentials(workflowCtx.Context, atlasDeployment) + if err != nil { + return r.terminate(workflowCtx, workflow.AtlasAPIAccessNotConfigured, err) + } + sdkClient, orgID, err := r.AtlasProvider.SdkClient(workflowCtx.Context, credentials, r.Log) + if err != nil { + return r.terminate(workflowCtx, workflow.AtlasAPIAccessNotConfigured, err) + } + workflowCtx.SdkClient = sdkClient + workflowCtx.SdkClientSet, _, err = r.AtlasProvider.SdkClientSet(workflowCtx.Context, credentials, r.Log) + if err != nil { + return r.terminate(workflowCtx, workflow.AtlasAPIAccessNotConfigured, err) + } + workflowCtx.Client, _, err = r.AtlasProvider.Client(workflowCtx.Context, credentials, r.Log) + if err != nil { + return r.terminate(workflowCtx, workflow.AtlasAPIAccessNotConfigured, err) } + projectService := project.NewProjectAPIService(sdkClient.ProjectsApi) + deploymentService := deployment.NewAtlasDeployments(sdkClient.ClustersApi, sdkClient.ServerlessInstancesApi, sdkClient.GlobalClustersApi, r.AtlasProvider.IsCloudGov()) + atlasProject, err := r.ResolveProject(workflowCtx.Context, sdkClient, atlasDeployment, orgID) if err != nil { return r.terminate(workflowCtx, workflow.AtlasAPIAccessNotConfigured, err) } - if err = validate.AtlasDeployment(atlasDeployment, r.AtlasProvider.IsCloudGov(), atlasProject.RegionUsageRestrictions); err != nil { + if err := validate.AtlasDeployment(atlasDeployment, r.AtlasProvider.IsCloudGov(), atlasProject.RegionUsageRestrictions); err != nil { result = workflow.Terminate(workflow.Internal, err.Error()) workflowCtx.SetConditionFromResult(api.ValidationSucceeded, result) return result.ReconcileResult(), nil @@ -153,7 +163,7 @@ func (r *AtlasDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Requ workflowCtx.SetConditionTrue(api.ValidationSucceeded) deploymentInAKO := deployment.NewDeployment(atlasProject.ID, atlasDeployment) - deploymentInAtlas, err := r.deploymentService.GetDeployment(workflowCtx.Context, atlasProject.ID, atlasDeployment.GetDeploymentName()) + deploymentInAtlas, err := deploymentService.GetDeployment(workflowCtx.Context, atlasProject.ID, atlasDeployment.GetDeploymentName()) if err != nil { return r.terminate(workflowCtx, workflow.Internal, err) } @@ -168,7 +178,7 @@ func (r *AtlasDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Requ switch { case existsInAtlas && wasDeleted: - return r.delete(workflowCtx, deploymentInAKO) + return r.delete(workflowCtx, deploymentService, deploymentInAKO) case !existsInAtlas && wasDeleted: return r.unmanage(workflowCtx, atlasDeployment) case !wasDeleted && isServerless: @@ -176,99 +186,21 @@ func (r *AtlasDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Requ if existsInAtlas { serverlessDeployment = deploymentInAtlas.(*deployment.Serverless) } - return r.handleServerlessInstance(workflowCtx, deploymentInAKO.(*deployment.Serverless), serverlessDeployment) + return r.handleServerlessInstance(workflowCtx, projectService, deploymentService, deploymentInAKO.(*deployment.Serverless), serverlessDeployment) case !wasDeleted && !isServerless: var clusterDeployment *deployment.Cluster if existsInAtlas { clusterDeployment = deploymentInAtlas.(*deployment.Cluster) } - return r.handleAdvancedDeployment(workflowCtx, deploymentInAKO.(*deployment.Cluster), clusterDeployment) + return r.handleAdvancedDeployment(workflowCtx, projectService, deploymentService, deploymentInAKO.(*deployment.Cluster), clusterDeployment) } return workflow.OK().ReconcileResult(), nil } -func (r *AtlasDeploymentReconciler) getProjectFromAtlas(ctx *workflow.Context, atlasDeployment *akov2.AtlasDeployment) (*project.Project, error) { - sdkClient, orgID, err := r.AtlasProvider.SdkClient( - ctx.Context, - &client.ObjectKey{Namespace: atlasDeployment.Namespace, Name: atlasDeployment.Credentials().Name}, - r.Log, - ) - if err != nil { - return nil, err - } - - sdkClientSet, _, err := r.AtlasProvider.SdkClientSet( - ctx.Context, - &client.ObjectKey{Namespace: atlasDeployment.Namespace, Name: atlasDeployment.Credentials().Name}, - r.Log) - if err != nil { - return nil, err - } - - ctx.SdkClient = sdkClient - ctx.SdkClientSet = sdkClientSet - ctx.OrgID = orgID - - r.projectService = project.NewProjectAPIService(sdkClient.ProjectsApi) - r.deploymentService = deployment.NewAtlasDeployments(sdkClient.ClustersApi, sdkClient.ServerlessInstancesApi, sdkClient.GlobalClustersApi, r.AtlasProvider.IsCloudGov()) - - atlasProject, err := r.projectService.GetProject(ctx.Context, atlasDeployment.Spec.ExternalProjectRef.ID) - if err != nil { - return nil, err - } - - // Need to still set old client for component not yet migrated - ctx.Client, _, err = r.AtlasProvider.Client( - ctx.Context, - &client.ObjectKey{Namespace: atlasDeployment.Namespace, Name: atlasDeployment.Credentials().Name}, - r.Log, - ) - if err != nil { - return nil, err - } - - return atlasProject, nil -} - -func (r *AtlasDeploymentReconciler) getProjectFromKube(ctx *workflow.Context, atlasDeployment *akov2.AtlasDeployment) (*project.Project, error) { - atlasProject := &akov2.AtlasProject{} - if err := r.Client.Get(ctx.Context, atlasDeployment.AtlasProjectObjectKey(), atlasProject); err != nil { - return nil, err - } - - credentialsSecret, err := customresource.ComputeSecret(atlasProject, atlasDeployment) - if err != nil { - return nil, err - } - - sdkClient, orgID, err := r.AtlasProvider.SdkClient(ctx.Context, credentialsSecret, r.Log) - if err != nil { - return nil, err - } - - sdkClientSet, _, err := r.AtlasProvider.SdkClientSet(ctx.Context, credentialsSecret, r.Log) - if err != nil { - return nil, err - } - - // Need to still set old client for component not yet migrated - ctx.Client, _, err = r.AtlasProvider.Client(ctx.Context, credentialsSecret, r.Log) - if err != nil { - return nil, err - } - ctx.SdkClientSet = sdkClientSet - ctx.SdkClient = sdkClient - ctx.OrgID = orgID - - r.deploymentService = deployment.NewAtlasDeployments(sdkClient.ClustersApi, sdkClient.ServerlessInstancesApi, sdkClient.GlobalClustersApi, r.AtlasProvider.IsCloudGov()) - r.projectService = project.NewProjectAPIService(sdkClient.ProjectsApi) - - return project.NewProject(atlasProject, orgID), nil -} - func (r *AtlasDeploymentReconciler) delete( ctx *workflow.Context, + deploymentService deployment.AtlasDeploymentsService, deployment deployment.Deployment, // this must be the original non converted deployment ) (ctrl.Result, error) { if err := r.cleanupBindings(ctx.Context, deployment); err != nil { @@ -285,7 +217,7 @@ func (r *AtlasDeploymentReconciler) delete( ctx.Log.Info(msg) r.EventRecorder.Event(deployment.GetCustomResource(), "Warning", "AtlasDeploymentTermination", msg) default: - if err := r.deleteDeploymentFromAtlas(ctx, deployment); err != nil { + if err := r.deleteDeploymentFromAtlas(ctx, deploymentService, deployment); err != nil { return r.terminate(ctx, workflow.Internal, fmt.Errorf("failed to remove deployment from Atlas: %w", err)) } } @@ -309,7 +241,7 @@ func isTerminationProtectionEnabled(deployment *akov2.AtlasDeployment) bool { deployment.Spec.ServerlessSpec.TerminationProtectionEnabled) } -func (r *AtlasDeploymentReconciler) deleteDeploymentFromAtlas(ctx *workflow.Context, deployment deployment.Deployment) error { +func (r *AtlasDeploymentReconciler) deleteDeploymentFromAtlas(ctx *workflow.Context, deploymentService deployment.AtlasDeploymentsService, deployment deployment.Deployment) error { ctx.Log.Infow("-> Starting AtlasDeployment deletion", "spec", deployment) err := r.deleteConnectionStrings(ctx, deployment) @@ -317,7 +249,7 @@ func (r *AtlasDeploymentReconciler) deleteDeploymentFromAtlas(ctx *workflow.Cont return err } - err = r.deploymentService.DeleteDeployment(ctx.Context, deployment) + err = deploymentService.DeleteDeployment(ctx.Context, deployment) if err != nil { ctx.Log.Errorw("Cannot delete Atlas deployment", "error", err) return err @@ -360,13 +292,13 @@ func (r *AtlasDeploymentReconciler) removeDeletionFinalizer(context context.Cont type transitionFn func(reason workflow.ConditionReason) (ctrl.Result, error) -func (r *AtlasDeploymentReconciler) transitionFromLegacy(ctx *workflow.Context, projectID string, atlasDeployment *akov2.AtlasDeployment, err error) transitionFn { +func (r *AtlasDeploymentReconciler) transitionFromLegacy(ctx *workflow.Context, deploymentService deployment.AtlasDeploymentsService, projectID string, atlasDeployment *akov2.AtlasDeployment, err error) transitionFn { return func(reason workflow.ConditionReason) (ctrl.Result, error) { if err != nil { return r.terminate(ctx, reason, err) } - deploymentInAtlas, err := r.deploymentService.GetDeployment(ctx.Context, projectID, atlasDeployment.GetDeploymentName()) + deploymentInAtlas, err := deploymentService.GetDeployment(ctx.Context, projectID, atlasDeployment.GetDeploymentName()) if err != nil { return r.terminate(ctx, workflow.Internal, err) } @@ -375,10 +307,10 @@ func (r *AtlasDeploymentReconciler) transitionFromLegacy(ctx *workflow.Context, } } -func (r *AtlasDeploymentReconciler) transitionFromResult(ctx *workflow.Context, projectID string, atlasDeployment *akov2.AtlasDeployment, result workflow.Result) transitionFn { +func (r *AtlasDeploymentReconciler) transitionFromResult(ctx *workflow.Context, deploymentService deployment.AtlasDeploymentsService, projectID string, atlasDeployment *akov2.AtlasDeployment, result workflow.Result) transitionFn { if result.IsInProgress() { return func(reason workflow.ConditionReason) (ctrl.Result, error) { - deploymentInAtlas, err := r.deploymentService.GetDeployment(ctx.Context, projectID, atlasDeployment.GetDeploymentName()) + deploymentInAtlas, err := deploymentService.GetDeployment(ctx.Context, projectID, atlasDeployment.GetDeploymentName()) if err != nil { return r.terminate(ctx, workflow.Internal, err) } @@ -485,11 +417,13 @@ func NewAtlasDeploymentReconciler( suggaredLogger := logger.Named("controllers").Named("AtlasDeployment").Sugar() return &AtlasDeploymentReconciler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: mgr.GetClient(), + Log: suggaredLogger, + }, Scheme: mgr.GetScheme(), - Client: mgr.GetClient(), EventRecorder: mgr.GetEventRecorderFor("AtlasDeployment"), GlobalPredicates: predicates, - Log: suggaredLogger, AtlasProvider: atlasProvider, ObjectDeletionProtection: deletionProtection, independentSyncPeriod: independentSyncPeriod, diff --git a/pkg/controller/atlasdeployment/atlasdeployment_controller_test.go b/pkg/controller/atlasdeployment/atlasdeployment_controller_test.go index 9acc748991..e9b3ad8b7b 100644 --- a/pkg/controller/atlasdeployment/atlasdeployment_controller_test.go +++ b/pkg/controller/atlasdeployment/atlasdeployment_controller_test.go @@ -18,7 +18,6 @@ package atlasdeployment import ( "context" - "errors" "fmt" "net/http" "reflect" @@ -38,7 +37,6 @@ import ( "go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest/observer" corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -58,6 +56,7 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/status" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/atlas" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/customresource" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/reconciler" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/indexer" ) @@ -65,8 +64,10 @@ import ( func TestCleanupBindings(t *testing.T) { t.Run("without backup references, nothing happens on cleanup", func(t *testing.T) { r := &AtlasDeploymentReconciler{ - Log: testLog(t), - Client: testK8sClient(), + AtlasReconciler: reconciler.AtlasReconciler{ + Log: testLog(t), + Client: testK8sClient(), + }, } d := testDeployment("cluster", nil) @@ -76,8 +77,10 @@ func TestCleanupBindings(t *testing.T) { t.Run("with unreferenced backups, still nothing happens on cleanup", func(t *testing.T) { r := &AtlasDeploymentReconciler{ - Log: testLog(t), - Client: testK8sClient(), + AtlasReconciler: reconciler.AtlasReconciler{ + Log: testLog(t), + Client: testK8sClient(), + }, } d := testDeployment("cluster", nil) require.NoError(t, r.Client.Create(context.Background(), d)) @@ -104,8 +107,10 @@ func TestCleanupBindings(t *testing.T) { }, } r := &AtlasDeploymentReconciler{ - Log: testLog(t), - Client: testK8sClient(), + AtlasReconciler: reconciler.AtlasReconciler{ + Log: testLog(t), + Client: testK8sClient(), + }, AtlasProvider: atlasProvider, } policy := testBackupPolicy() // deployment -> schedule -> policy @@ -136,8 +141,10 @@ func TestCleanupBindings(t *testing.T) { }, } r := &AtlasDeploymentReconciler{ - Log: testLog(t), - Client: testK8sClient(), + AtlasReconciler: reconciler.AtlasReconciler{ + Log: testLog(t), + Client: testK8sClient(), + }, AtlasProvider: atlasProvider, } policy := testBackupPolicy() // deployment + deployment2 -> schedule -> policy @@ -173,8 +180,10 @@ func TestCleanupBindings(t *testing.T) { }, } r := &AtlasDeploymentReconciler{ - Log: testLog(t), - Client: testK8sClient(), + AtlasReconciler: reconciler.AtlasReconciler{ + Log: testLog(t), + Client: testK8sClient(), + }, AtlasProvider: atlasProvider, } policy := testBackupPolicy() // deployment -> schedule + schedule2 -> policy @@ -537,8 +546,10 @@ func TestRegularClusterReconciliation(t *testing.T) { } reconciler := &AtlasDeploymentReconciler{ - Client: k8sClient, - Log: logger.Sugar(), + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + Log: logger.Sugar(), + }, AtlasProvider: atlasProvider, EventRecorder: record.NewFakeRecorder(10), ObjectDeletionProtection: false, @@ -668,8 +679,10 @@ func TestServerlessInstanceReconciliation(t *testing.T) { } reconciler := &AtlasDeploymentReconciler{ - Client: k8sClient, - Log: logger.Sugar(), + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + Log: logger.Sugar(), + }, AtlasProvider: atlasProvider, EventRecorder: record.NewFakeRecorder(10), ObjectDeletionProtection: false, @@ -839,8 +852,10 @@ func TestDeletionReconciliation(t *testing.T) { } reconciler := &AtlasDeploymentReconciler{ - Client: k8sClient, - Log: logger, + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + Log: logger, + }, AtlasProvider: atlasProvider, EventRecorder: record.NewFakeRecorder(10), ObjectDeletionProtection: false, @@ -867,7 +882,9 @@ func TestFindDeploymentsForSearchIndexConfig(t *testing.T) { t.Run("should fail when watching wrong object", func(t *testing.T) { core, logs := observer.New(zap.DebugLevel) reconciler := &AtlasDeploymentReconciler{ - Log: zap.New(core).Sugar(), + AtlasReconciler: reconciler.AtlasReconciler{ + Log: zap.New(core).Sugar(), + }, } assert.Nil(t, reconciler.findDeploymentsForSearchIndexConfig(context.Background(), &akov2.AtlasProject{})) @@ -936,8 +953,10 @@ func TestFindDeploymentsForSearchIndexConfig(t *testing.T) { ). Build() reconciler := &AtlasDeploymentReconciler{ - Client: k8sClient, - Log: zaptest.NewLogger(t).Sugar(), + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + Log: zaptest.NewLogger(t).Sugar(), + }, } requests := reconciler.findDeploymentsForSearchIndexConfig(context.Background(), connection) @@ -1030,8 +1049,10 @@ func TestFindDeploymentsForBackupPolicy(t *testing.T) { WithIndex(deploymentIndexer.Object(), deploymentIndexer.Name(), deploymentIndexer.Keys). Build() reconciler := &AtlasDeploymentReconciler{ - Log: zaptest.NewLogger(t).Sugar(), - Client: k8sClient, + AtlasReconciler: reconciler.AtlasReconciler{ + Log: zaptest.NewLogger(t).Sugar(), + Client: k8sClient, + }, } got := reconciler.findDeploymentsForBackupPolicy(context.Background(), tc.obj) if !reflect.DeepEqual(got, tc.want) { @@ -1096,8 +1117,10 @@ func TestFindDeploymentsForBackupSchedule(t *testing.T) { WithIndex(deploymentIndexer.Object(), deploymentIndexer.Name(), deploymentIndexer.Keys). Build() reconciler := &AtlasDeploymentReconciler{ - Log: zaptest.NewLogger(t).Sugar(), - Client: k8sClient, + AtlasReconciler: reconciler.AtlasReconciler{ + Log: zaptest.NewLogger(t).Sugar(), + Client: k8sClient, + }, } got := reconciler.findDeploymentsForBackupSchedule(context.Background(), tc.obj) if !reflect.DeepEqual(got, tc.want) { @@ -1107,346 +1130,6 @@ func TestFindDeploymentsForBackupSchedule(t *testing.T) { } } -func TestGetProjectFromAtlas(t *testing.T) { - tests := map[string]struct { - atlasDeployment *akov2.AtlasDeployment - deploymentSecret *corev1.Secret - atlasProvider atlas.Provider - expectedErr error - }{ - "failed to create atlas client": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ExternalProjectRef: &akov2.ExternalProjectReference{ - ID: "project-id", - }, - LocalCredentialHolder: api.LocalCredentialHolder{ - ConnectionSecret: &api.LocalObjectReference{ - Name: "project-creds", - }, - }, - }, - }, - atlasProvider: &atlasmock.TestProvider{ - SdkClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*admin.APIClient, string, error) { - return nil, "", errors.New("failed to create client") - }, - }, - expectedErr: errors.New("failed to create client"), - }, - "failed to get project from atlas": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster0", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ExternalProjectRef: &akov2.ExternalProjectReference{ - ID: "project-id", - }, - LocalCredentialHolder: api.LocalCredentialHolder{ - ConnectionSecret: &api.LocalObjectReference{ - Name: "project-creds", - }, - }, - }, - }, - atlasProvider: &atlasmock.TestProvider{ - IsCloudGovFunc: func() bool { - return false - }, - SdkSetClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*atlas.ClientSet, string, error) { - return &atlas.ClientSet{}, "", nil - }, - SdkClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*admin.APIClient, string, error) { - projectAPI := mockadmin.NewProjectsApi(t) - projectAPI.EXPECT().GetProject(context.Background(), "project-id"). - Return(admin.GetProjectApiRequest{ApiService: projectAPI}) - projectAPI.EXPECT().GetProjectExecute(mock.AnythingOfType("admin.GetProjectApiRequest")). - Return(nil, nil, errors.New("failed to get project")) - - return &admin.APIClient{ProjectsApi: projectAPI}, "", nil - }, - }, - expectedErr: errors.New("failed to get project"), - }, - "get project": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "user1", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - ExternalProjectRef: &akov2.ExternalProjectReference{ - ID: "project-id", - }, - LocalCredentialHolder: api.LocalCredentialHolder{ - ConnectionSecret: &api.LocalObjectReference{ - Name: "project-creds", - }, - }, - }, - }, - deploymentSecret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "user-pass", - Namespace: "default", - Labels: map[string]string{ - "atlas.mongodb.com/type": "credentials", - }, - }, - Data: map[string][]byte{ - "password": []byte("Passw0rd!"), - }, - }, - atlasProvider: &atlasmock.TestProvider{ - IsCloudGovFunc: func() bool { - return false - }, - SdkSetClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*atlas.ClientSet, string, error) { - return &atlas.ClientSet{}, "", nil - }, - SdkClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*admin.APIClient, string, error) { - projectAPI := mockadmin.NewProjectsApi(t) - projectAPI.EXPECT().GetProject(context.Background(), "project-id"). - Return(admin.GetProjectApiRequest{ApiService: projectAPI}) - projectAPI.EXPECT().GetProjectExecute(mock.AnythingOfType("admin.GetProjectApiRequest")). - Return(&admin.Group{Id: pointer.MakePtr("project-id")}, nil, nil) - - return &admin.APIClient{ProjectsApi: projectAPI}, "", nil - }, - ClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*mongodbatlas.Client, string, error) { - return &mongodbatlas.Client{}, "", nil - }, - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - testScheme := runtime.NewScheme() - assert.NoError(t, akov2.AddToScheme(testScheme)) - assert.NoError(t, corev1.AddToScheme(testScheme)) - k8sClient := fake.NewClientBuilder(). - WithScheme(testScheme). - WithObjects(tt.atlasDeployment). - WithStatusSubresource(tt.atlasDeployment) - - if tt.deploymentSecret != nil { - k8sClient.WithObjects(tt.deploymentSecret) - } - - logger := zaptest.NewLogger(t).Sugar() - r := AtlasDeploymentReconciler{ - Client: k8sClient.Build(), - AtlasProvider: tt.atlasProvider, - Log: logger, - } - ctx := &workflow.Context{ - Context: context.Background(), - Log: logger, - } - - _, err := r.getProjectFromAtlas(ctx, tt.atlasDeployment) - assert.Equal(t, tt.expectedErr, err) - }) - } -} - -func TestGetProjectFromKube(t *testing.T) { - tests := map[string]struct { - atlasDeployment *akov2.AtlasDeployment - project *akov2.AtlasProject - projectSecret *corev1.Secret - atlasProvider atlas.Provider - expectedErr error - }{ - "failed to get project": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cluster0", - Namespace: "default", - Labels: map[string]string{ - "mongodb.com/atlas-resource-version": "2.4.1", - }, - }, - Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", - Namespace: "default", - }, - }, - }, - atlasProvider: &atlasmock.TestProvider{ - IsSupportedFunc: func() bool { - return true - }, - }, - expectedErr: &k8serrors.StatusError{ - ErrStatus: metav1.Status{ - Status: "Failure", - Message: "atlasprojects.atlas.mongodb.com \"my-project\" not found", - Reason: "NotFound", - Code: 404, - Details: &metav1.StatusDetails{ - Group: "atlas.mongodb.com", - Kind: "atlasprojects", - Name: "my-project", - }, - }, - }, - }, - "failed to create atlas sdk": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "user1", - Namespace: "default", - Labels: map[string]string{ - "mongodb.com/atlas-resource-version": "2.4.1", - }, - }, - Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", - Namespace: "default", - }, - }, - }, - project: &akov2.AtlasProject{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-project", - Namespace: "default", - }, - Spec: akov2.AtlasProjectSpec{ - Name: "my-project", - ConnectionSecret: &common.ResourceRefNamespaced{ - Name: "project-secret", - }, - }, - }, - projectSecret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "project-secret", - Namespace: "default", - Labels: map[string]string{ - "atlas.mongodb.com/type": "credentials", - }, - }, - Data: map[string][]byte{ - "publicKey": []byte("publicKey"), - "privateKey": []byte("privateKey"), - "orgID": []byte("orgID"), - }, - }, - atlasProvider: &atlasmock.TestProvider{ - IsSupportedFunc: func() bool { - return true - }, - SdkClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*admin.APIClient, string, error) { - return nil, "", errors.New("failed to create client") - }, - }, - expectedErr: errors.New("failed to create client"), - }, - "get project": { - atlasDeployment: &akov2.AtlasDeployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "user1", - Namespace: "default", - }, - Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", - }, - LocalCredentialHolder: api.LocalCredentialHolder{ - ConnectionSecret: &api.LocalObjectReference{ - Name: "project-creds", - }, - }, - }, - }, - project: &akov2.AtlasProject{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-project", - Namespace: "default", - }, - Spec: akov2.AtlasProjectSpec{ - Name: "my-project", - ConnectionSecret: &common.ResourceRefNamespaced{ - Name: "project-secret", - }, - }, - }, - projectSecret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "project-secret", - Namespace: "default", - Labels: map[string]string{ - "atlas.mongodb.com/type": "credentials", - }, - }, - Data: map[string][]byte{ - "publicKey": []byte("publicKey"), - "privateKey": []byte("privateKey"), - "orgID": []byte("orgID"), - }, - }, - atlasProvider: &atlasmock.TestProvider{ - IsCloudGovFunc: func() bool { - return false - }, - SdkSetClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*atlas.ClientSet, string, error) { - return &atlas.ClientSet{}, "", nil - }, - SdkClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*admin.APIClient, string, error) { - return &admin.APIClient{}, "", nil - }, - ClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*mongodbatlas.Client, string, error) { - return &mongodbatlas.Client{}, "", nil - }, - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - testScheme := runtime.NewScheme() - assert.NoError(t, akov2.AddToScheme(testScheme)) - assert.NoError(t, corev1.AddToScheme(testScheme)) - k8sClient := fake.NewClientBuilder(). - WithScheme(testScheme). - WithObjects(tt.atlasDeployment). - WithStatusSubresource(tt.atlasDeployment) - - if tt.project != nil { - k8sClient.WithObjects(tt.project) - } - - if tt.projectSecret != nil { - k8sClient.WithObjects(tt.projectSecret) - } - - logger := zaptest.NewLogger(t).Sugar() - r := AtlasDeploymentReconciler{ - Client: k8sClient.Build(), - AtlasProvider: tt.atlasProvider, - Log: logger, - } - ctx := &workflow.Context{ - Context: context.Background(), - Log: logger, - } - - _, err := r.getProjectFromKube(ctx, tt.atlasDeployment) - assert.Equal(t, tt.expectedErr, err) - }) - } -} - func TestChangeDeploymentType(t *testing.T) { tests := map[string]struct { deployment *akov2.AtlasDeployment @@ -1458,9 +1141,11 @@ func TestChangeDeploymentType(t *testing.T) { Namespace: "default", }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", - Namespace: "default", + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + Namespace: "default", + }, }, ServerlessSpec: &akov2.ServerlessSpec{ Name: "cluster0", @@ -1482,9 +1167,11 @@ func TestChangeDeploymentType(t *testing.T) { Namespace: "default", }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", - Namespace: "default", + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + Namespace: "default", + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ Name: "cluster0", @@ -1585,9 +1272,11 @@ func TestChangeDeploymentType(t *testing.T) { } r := &AtlasDeploymentReconciler{ - Client: k8sClient, + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + Log: logger.Sugar(), + }, AtlasProvider: atlasProvider, - Log: logger.Sugar(), EventRecorder: record.NewFakeRecorder(1), } result, err := r.Reconcile( diff --git a/pkg/controller/atlasdeployment/backup.go b/pkg/controller/atlasdeployment/backup.go index 17ced54433..20b5f54f1d 100644 --- a/pkg/controller/atlasdeployment/backup.go +++ b/pkg/controller/atlasdeployment/backup.go @@ -13,6 +13,7 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/compat" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/kube" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/deployment" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/status" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/customresource" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow" @@ -27,6 +28,7 @@ import ( func (r *AtlasDeploymentReconciler) ensureBackupScheduleAndPolicy( service *workflow.Context, + deploymentService deployment.AtlasDeploymentsService, projectID string, deployment *akov2.AtlasDeployment, ) transitionFn { @@ -35,26 +37,26 @@ func (r *AtlasDeploymentReconciler) ensureBackupScheduleAndPolicy( err := r.garbageCollectBackupResource(service.Context, deployment.GetDeploymentName()) if err != nil { - return r.transitionFromLegacy(service, projectID, deployment, err) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, err) } return nil } if deployment.Spec.DeploymentSpec.BackupEnabled == nil || !*deployment.Spec.DeploymentSpec.BackupEnabled { - return r.transitionFromLegacy(service, projectID, deployment, fmt.Errorf("can not proceed with backup configuration. Backups are not enabled for cluster %s", deployment.GetDeploymentName())) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, fmt.Errorf("can not proceed with backup configuration. Backups are not enabled for cluster %s", deployment.GetDeploymentName())) } bSchedule, err := r.ensureBackupSchedule(service, deployment) if err != nil { - return r.transitionFromLegacy(service, projectID, deployment, err) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, err) } bPolicy, err := r.ensureBackupPolicy(service, bSchedule) if err != nil { - return r.transitionFromLegacy(service, projectID, deployment, err) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, err) } - return r.updateBackupScheduleAndPolicy(service.Context, service, projectID, deployment, bSchedule, bPolicy) + return r.updateBackupScheduleAndPolicy(service.Context, service, deploymentService, projectID, deployment, bSchedule, bPolicy) } func (r *AtlasDeploymentReconciler) ensureBackupSchedule( @@ -164,6 +166,7 @@ func (r *AtlasDeploymentReconciler) ensureBackupPolicy(service *workflow.Context func (r *AtlasDeploymentReconciler) updateBackupScheduleAndPolicy( ctx context.Context, service *workflow.Context, + deploymentService deployment.AtlasDeploymentsService, projectID string, deployment *akov2.AtlasDeployment, bSchedule *akov2.AtlasBackupSchedule, @@ -174,11 +177,11 @@ func (r *AtlasDeploymentReconciler) updateBackupScheduleAndPolicy( if err != nil { errMessage := "unable to get current backup configuration for project" r.Log.Debugf("%s: %s:%s, %v", errMessage, projectID, clusterName, err) - return r.transitionFromLegacy(service, projectID, deployment, fmt.Errorf("%s: %s:%s, %w", errMessage, projectID, clusterName, err)) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, fmt.Errorf("%s: %s:%s, %w", errMessage, projectID, clusterName, err)) } if currentSchedule == nil && response != nil { - return r.transitionFromLegacy(service, projectID, deployment, fmt.Errorf("can not get сurrent backup configuration. response status: %s", response.Status)) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, fmt.Errorf("can not get сurrent backup configuration. response status: %s", response.Status)) } r.Log.Debugf("successfully received backup configuration: %v", currentSchedule) @@ -192,7 +195,7 @@ func (r *AtlasDeploymentReconciler) updateBackupScheduleAndPolicy( equal, err := backupSchedulesAreEqual(currentSchedule, apiScheduleReq) if err != nil { - return r.transitionFromLegacy(service, projectID, deployment, fmt.Errorf("can not compare BackupSchedule resources: %w", err)) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, fmt.Errorf("can not compare BackupSchedule resources: %w", err)) } if equal { @@ -202,10 +205,10 @@ func (r *AtlasDeploymentReconciler) updateBackupScheduleAndPolicy( r.Log.Debugf("applying backup configuration: %v", *bSchedule) if _, _, err := service.Client.CloudProviderSnapshotBackupPolicies.Update(ctx, projectID, clusterName, apiScheduleReq); err != nil { - return r.transitionFromLegacy(service, projectID, deployment, fmt.Errorf("unable to create backup schedule %s. e: %w", client.ObjectKeyFromObject(bSchedule).String(), err)) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, fmt.Errorf("unable to create backup schedule %s. e: %w", client.ObjectKeyFromObject(bSchedule).String(), err)) } r.Log.Infof("successfully updated backup configuration for deployment %v", clusterName) - return r.transitionFromLegacy(service, projectID, deployment, nil) + return r.transitionFromLegacy(service, deploymentService, projectID, deployment, nil) } func backupSchedulesAreEqual(currentSchedule *mongodbatlas.CloudProviderSnapshotBackupPolicy, newSchedule *mongodbatlas.CloudProviderSnapshotBackupPolicy) (bool, error) { diff --git a/pkg/controller/atlasdeployment/customzonemapping.go b/pkg/controller/atlasdeployment/customzonemapping.go index 13024666a2..89e8205934 100644 --- a/pkg/controller/atlasdeployment/customzonemapping.go +++ b/pkg/controller/atlasdeployment/customzonemapping.go @@ -3,14 +3,15 @@ package atlasdeployment import ( "fmt" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/deployment" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api" akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/status" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow" ) -func (r *AtlasDeploymentReconciler) ensureCustomZoneMapping(service *workflow.Context, groupID string, customZoneMappings []akov2.CustomZoneMapping, deploymentName string) workflow.Result { - result := r.syncCustomZoneMapping(service, groupID, deploymentName, customZoneMappings) +func (r *AtlasDeploymentReconciler) ensureCustomZoneMapping(service *workflow.Context, deploymentService deployment.AtlasDeploymentsService, groupID string, customZoneMappings []akov2.CustomZoneMapping, deploymentName string) workflow.Result { + result := r.syncCustomZoneMapping(service, deploymentService, groupID, deploymentName, customZoneMappings) if !result.IsOk() { service.SetConditionFromResult(api.CustomZoneMappingReadyType, result) return result @@ -26,19 +27,19 @@ func (r *AtlasDeploymentReconciler) ensureCustomZoneMapping(service *workflow.Co return result } -func (r *AtlasDeploymentReconciler) syncCustomZoneMapping(service *workflow.Context, groupID string, deploymentName string, customZoneMappings []akov2.CustomZoneMapping) workflow.Result { +func (r *AtlasDeploymentReconciler) syncCustomZoneMapping(service *workflow.Context, deploymentService deployment.AtlasDeploymentsService, groupID string, deploymentName string, customZoneMappings []akov2.CustomZoneMapping) workflow.Result { logger := service.Log err := verifyZoneMapping(customZoneMappings) if err != nil { return workflow.Terminate(workflow.CustomZoneMappingReady, err.Error()) } - existingZoneMapping, err := r.deploymentService.GetCustomZones(service.Context, groupID, deploymentName) + existingZoneMapping, err := deploymentService.GetCustomZones(service.Context, groupID, deploymentName) if err != nil { return workflow.Terminate(workflow.CustomZoneMappingReady, fmt.Sprintf("Failed to get zone mapping state: %v", err)) } logger.Debugf("Existing zone mapping: %v", existingZoneMapping) var customZoneMappingStatus status.CustomZoneMapping - zoneMappingMap, err := r.deploymentService.GetZoneMapping(service.Context, groupID, deploymentName) + zoneMappingMap, err := deploymentService.GetZoneMapping(service.Context, groupID, deploymentName) if err != nil { return workflow.Terminate(workflow.CustomZoneMappingReady, fmt.Sprintf("Failed to get zone mapping map: %v", err)) } @@ -46,7 +47,7 @@ func (r *AtlasDeploymentReconciler) syncCustomZoneMapping(service *workflow.Cont if shouldAdd, shouldDelete := compareZoneMappingStates(existingZoneMapping, customZoneMappings, zoneMappingMap); shouldDelete || shouldAdd { skipAdd := false if shouldDelete { - err = r.deploymentService.DeleteCustomZones(service.Context, groupID, deploymentName) + err = deploymentService.DeleteCustomZones(service.Context, groupID, deploymentName) if err != nil { skipAdd = true logger.Errorf("failed to sync zone mapping: %v", err) @@ -59,7 +60,7 @@ func (r *AtlasDeploymentReconciler) syncCustomZoneMapping(service *workflow.Cont } if shouldAdd && !skipAdd { - zoneMapping, err := r.deploymentService.CreateCustomZones(service.Context, groupID, deploymentName, customZoneMappings) + zoneMapping, err := deploymentService.CreateCustomZones(service.Context, groupID, deploymentName, customZoneMappings) if err != nil { logger.Errorf("failed to sync zone mapping: %v", err) customZoneMappingStatus.ZoneMappingErrMessage = fmt.Sprintf("Failed to sync zone mapping: %v", err) diff --git a/pkg/controller/atlasdeployment/customzonemapping_test.go b/pkg/controller/atlasdeployment/customzonemapping_test.go index 41026df02e..f67bca8f99 100644 --- a/pkg/controller/atlasdeployment/customzonemapping_test.go +++ b/pkg/controller/atlasdeployment/customzonemapping_test.go @@ -289,9 +289,7 @@ func TestEnsureCustomZoneMapping(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - r := &AtlasDeploymentReconciler{ - deploymentService: tc.deploymentAPI, - } + r := &AtlasDeploymentReconciler{} ctx := &workflow.Context{ Log: zaptest.NewLogger(t).Sugar(), Context: context.Background(), @@ -299,6 +297,7 @@ func TestEnsureCustomZoneMapping(t *testing.T) { result := r.ensureCustomZoneMapping( ctx, + tc.deploymentAPI, projectID, tc.customZoneMappings, deploymentName, diff --git a/pkg/controller/atlasdeployment/deployment_test.go b/pkg/controller/atlasdeployment/deployment_test.go index 62bc7e28f6..4d11a306a1 100644 --- a/pkg/controller/atlasdeployment/deployment_test.go +++ b/pkg/controller/atlasdeployment/deployment_test.go @@ -14,8 +14,10 @@ func CreateBasicDeployment(name string) *akov2.AtlasDeployment { Name: name, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ Name: "cluster-basics", diff --git a/pkg/controller/atlasdeployment/managed_namespaces.go b/pkg/controller/atlasdeployment/managed_namespaces.go index a70fc78a00..b480743c98 100644 --- a/pkg/controller/atlasdeployment/managed_namespaces.go +++ b/pkg/controller/atlasdeployment/managed_namespaces.go @@ -12,12 +12,12 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow" ) -func (r *AtlasDeploymentReconciler) ensureManagedNamespaces(service *workflow.Context, groupID string, clusterType string, managedNamespace []akov2.ManagedNamespace, deploymentName string) workflow.Result { +func (r *AtlasDeploymentReconciler) ensureManagedNamespaces(service *workflow.Context, deploymentService deployment.AtlasDeploymentsService, groupID string, clusterType string, managedNamespace []akov2.ManagedNamespace, deploymentName string) workflow.Result { if clusterType != string(akov2.TypeGeoSharded) && managedNamespace != nil { return workflow.Terminate(workflow.ManagedNamespacesReady, "Managed namespace is only supported by GeoSharded clusters") } - result := r.syncManagedNamespaces(service, groupID, deploymentName, managedNamespace) + result := r.syncManagedNamespaces(service, deploymentService, groupID, deploymentName, managedNamespace) if !result.IsOk() { service.SetConditionFromResult(api.ManagedNamespacesReadyType, result) return result @@ -32,21 +32,21 @@ func (r *AtlasDeploymentReconciler) ensureManagedNamespaces(service *workflow.Co return result } -func (r *AtlasDeploymentReconciler) syncManagedNamespaces(service *workflow.Context, groupID string, deploymentName string, managedNamespaces []akov2.ManagedNamespace) workflow.Result { +func (r *AtlasDeploymentReconciler) syncManagedNamespaces(service *workflow.Context, deploymentService deployment.AtlasDeploymentsService, groupID string, deploymentName string, managedNamespaces []akov2.ManagedNamespace) workflow.Result { logger := service.Log - existingManagedNamespaces, err := r.deploymentService.GetManagedNamespaces(service.Context, groupID, deploymentName) + existingManagedNamespaces, err := deploymentService.GetManagedNamespaces(service.Context, groupID, deploymentName) logger.Debugf("Syncing managed namespaces %s", deploymentName) if err != nil { return workflow.Terminate(workflow.ManagedNamespacesReady, fmt.Sprintf("Failed to get managed namespaces: %v", err)) } diff := sortManagedNamespaces(existingManagedNamespaces, managedNamespaces) logger.Debugw("diff", "To create: %v", diff.ToCreate, "To delete: %v", diff.ToDelete, "To update status: %v", diff.ToUpdateStatus) - err = deleteManagedNamespaces(service.Context, r.deploymentService, groupID, deploymentName, diff.ToDelete) + err = deleteManagedNamespaces(service.Context, deploymentService, groupID, deploymentName, diff.ToDelete) if err != nil { logger.Errorf("failed to delete managed namespaces: %v", err) return workflow.Terminate(workflow.ManagedNamespacesReady, fmt.Sprintf("Failed to delete managed namespaces: %v", err)) } - nsStatuses := createManagedNamespaces(service.Context, r.deploymentService, groupID, deploymentName, diff.ToCreate) + nsStatuses := createManagedNamespaces(service.Context, deploymentService, groupID, deploymentName, diff.ToCreate) for _, ns := range diff.ToUpdateStatus { nsStatuses = append(nsStatuses, akov2.NewCreatedManagedNamespaceStatus(ns)) } diff --git a/pkg/controller/atlasdeployment/managed_namespaces_test.go b/pkg/controller/atlasdeployment/managed_namespaces_test.go index c49f758b3e..6fbce44b5e 100644 --- a/pkg/controller/atlasdeployment/managed_namespaces_test.go +++ b/pkg/controller/atlasdeployment/managed_namespaces_test.go @@ -149,9 +149,7 @@ func TestEnsureManagedNamespaces(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - r := &AtlasDeploymentReconciler{ - deploymentService: tc.deploymentAPI, - } + r := &AtlasDeploymentReconciler{} ctx := &workflow.Context{ Log: zaptest.NewLogger(t).Sugar(), Context: context.Background(), @@ -159,6 +157,7 @@ func TestEnsureManagedNamespaces(t *testing.T) { result := r.ensureManagedNamespaces( ctx, + tc.deploymentAPI, projectID, string(akov2.TypeGeoSharded), tc.managedNamespaces, diff --git a/pkg/controller/atlasdeployment/serverless_deployment.go b/pkg/controller/atlasdeployment/serverless_deployment.go index 5c3fed8832..ca2d40e80f 100644 --- a/pkg/controller/atlasdeployment/serverless_deployment.go +++ b/pkg/controller/atlasdeployment/serverless_deployment.go @@ -8,15 +8,16 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/deployment" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/project" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/status" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/customresource" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow" ) -func (r *AtlasDeploymentReconciler) handleServerlessInstance(ctx *workflow.Context, deploymentInAKO, deploymentInAtlas *deployment.Serverless) (ctrl.Result, error) { +func (r *AtlasDeploymentReconciler) handleServerlessInstance(ctx *workflow.Context, projectService project.ProjectService, deploymentService deployment.AtlasDeploymentsService, deploymentInAKO, deploymentInAtlas *deployment.Serverless) (ctrl.Result, error) { if deploymentInAtlas == nil { ctx.Log.Infof("Serverless Instance %s doesn't exist in Atlas - creating", deploymentInAKO.GetName()) - newServerlessDeployment, err := r.deploymentService.CreateDeployment(ctx.Context, deploymentInAKO) + newServerlessDeployment, err := deploymentService.CreateDeployment(ctx.Context, deploymentInAKO) if err != nil { return r.terminate(ctx, workflow.DeploymentNotCreatedInAtlas, err) } @@ -27,7 +28,7 @@ func (r *AtlasDeploymentReconciler) handleServerlessInstance(ctx *workflow.Conte switch deploymentInAtlas.GetState() { case status.StateIDLE: if !reflect.DeepEqual(deploymentInAKO.ServerlessSpec, deploymentInAtlas.ServerlessSpec) { - _, err := r.deploymentService.UpdateDeployment(ctx.Context, deploymentInAKO) + _, err := deploymentService.UpdateDeployment(ctx.Context, deploymentInAKO) if err != nil { return r.terminate(ctx, workflow.DeploymentNotUpdatedInAtlas, err) } @@ -35,7 +36,7 @@ func (r *AtlasDeploymentReconciler) handleServerlessInstance(ctx *workflow.Conte return r.inProgress(ctx, deploymentInAKO.GetCustomResource(), deploymentInAtlas, workflow.DeploymentUpdating, "deployment is updating") } - err := r.ensureConnectionSecrets(ctx, deploymentInAKO, deploymentInAtlas.GetConnection()) + err := r.ensureConnectionSecrets(ctx, projectService, deploymentInAKO, deploymentInAtlas.GetConnection()) if err != nil { return r.terminate(ctx, workflow.DeploymentConnectionSecretsNotCreated, err) } diff --git a/pkg/controller/atlasdeployment/serverless_deployment_test.go b/pkg/controller/atlasdeployment/serverless_deployment_test.go index 9faf63fc62..841f2dfdb0 100644 --- a/pkg/controller/atlasdeployment/serverless_deployment_test.go +++ b/pkg/controller/atlasdeployment/serverless_deployment_test.go @@ -21,10 +21,12 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/mocks/translation" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/deployment" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/project" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api" akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/common" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/provider" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/reconciler" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/workflow" "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/indexer" ) @@ -477,8 +479,10 @@ func TestHandleServerlessInstance(t *testing.T) { Namespace: "default", }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, }, ServerlessSpec: &akov2.ServerlessSpec{ Name: "instance0", @@ -559,8 +563,10 @@ func TestHandleServerlessInstance(t *testing.T) { Namespace: "default", }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, }, ServerlessSpec: &akov2.ServerlessSpec{ Name: "instance0", @@ -646,8 +652,10 @@ func TestHandleServerlessInstance(t *testing.T) { Namespace: "default", }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, }, ServerlessSpec: &akov2.ServerlessSpec{ Name: "instance0", @@ -718,8 +726,10 @@ func TestHandleServerlessInstance(t *testing.T) { Namespace: "default", }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: "my-project", + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: "my-project", + }, }, ServerlessSpec: &akov2.ServerlessSpec{ Name: "instance0", @@ -791,9 +801,10 @@ func TestHandleServerlessInstance(t *testing.T) { WithIndex(dbUserProjectIndexer.Object(), dbUserProjectIndexer.Name(), dbUserProjectIndexer.Keys). Build() reconciler := &AtlasDeploymentReconciler{ - Client: k8sClient, - Log: logger.Sugar(), - deploymentService: tt.deploymentService(), + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + Log: logger.Sugar(), + }, } workflowCtx := &workflow.Context{ Context: ctx, @@ -802,7 +813,8 @@ func TestHandleServerlessInstance(t *testing.T) { } deploymentInAKO := deployment.NewDeployment("project-id", tt.atlasDeployment).(*deployment.Serverless) - result, err := reconciler.handleServerlessInstance(workflowCtx, deploymentInAKO, tt.deploymentInAtlas) + var projectService project.ProjectService + result, err := reconciler.handleServerlessInstance(workflowCtx, projectService, tt.deploymentService(), deploymentInAKO, tt.deploymentInAtlas) require.NoError(t, err) assert.Equal(t, tt.expectedResult, result) assert.True( diff --git a/test/e2e/atlas_gov_test.go b/test/e2e/atlas_gov_test.go index 443ebe316c..fcc3795ec7 100644 --- a/test/e2e/atlas_gov_test.go +++ b/test/e2e/atlas_gov_test.go @@ -448,9 +448,11 @@ var _ = Describe("Atlas for Government", Label("atlas-gov"), func() { Namespace: testData.Resources.Namespace, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: projectName, - Namespace: testData.Resources.Namespace, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: projectName, + Namespace: testData.Resources.Namespace, + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ Name: clusterName, @@ -633,9 +635,11 @@ var _ = Describe("Atlas for Government", Label("atlas-gov"), func() { Namespace: testData.Resources.Namespace, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: projectName, - Namespace: testData.Resources.Namespace, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: projectName, + Namespace: testData.Resources.Namespace, + }, }, ServerlessSpec: &akov2.ServerlessSpec{ Name: clusterName, diff --git a/test/e2e/operator_type_wide_test.go b/test/e2e/operator_type_wide_test.go index 534b23b946..fbe9085785 100644 --- a/test/e2e/operator_type_wide_test.go +++ b/test/e2e/operator_type_wide_test.go @@ -98,7 +98,7 @@ var _ = Describe("Deployment wide operator can work with resources in different deployment := NortonData.InitialDeployments[0] if deployment.Namespace == "" { deployment.Namespace = NortonData.Resources.Namespace - deployment.Spec.Project.Namespace = NortonData.Resources.Namespace + deployment.Spec.ProjectRef.Namespace = NortonData.Resources.Namespace } err := k8sClient.Create(ctx, deployment) Expect(err).ShouldNot(HaveOccurred(), fmt.Sprintf("Deployment was not created: %v", deployment)) @@ -110,7 +110,7 @@ var _ = Describe("Deployment wide operator can work with resources in different deployment := NimnulData.InitialDeployments[0] if deployment.Namespace == "" { deployment.Namespace = NimnulData.Resources.Namespace - deployment.Spec.Project.Namespace = NimnulData.Resources.Namespace + deployment.Spec.ProjectRef.Namespace = NimnulData.Resources.Namespace } err := k8sClient.Create(ctx, deployment) Expect(err).ShouldNot(HaveOccurred(), fmt.Sprintf("Deployment was not created: %v", deployment)) diff --git a/test/helper/e2e/actions/deploy/deploy_operator.go b/test/helper/e2e/actions/deploy/deploy_operator.go index 999f3a9e10..c43de75cc7 100644 --- a/test/helper/e2e/actions/deploy/deploy_operator.go +++ b/test/helper/e2e/actions/deploy/deploy_operator.go @@ -83,7 +83,7 @@ func CreateInitialDeployments(testData *model.TestDataProvider) { for _, deployment := range testData.InitialDeployments { if deployment.Namespace == "" { deployment.Namespace = testData.Resources.Namespace - deployment.Spec.Project.Namespace = testData.Resources.Namespace + deployment.Spec.ProjectRef.Namespace = testData.Resources.Namespace } err := testData.K8SClient.Create(testData.Context, deployment) Expect(err).ShouldNot(HaveOccurred(), fmt.Sprintf("Deployment was not created: %v", deployment)) diff --git a/test/helper/e2e/actions/steps.go b/test/helper/e2e/actions/steps.go index d77623fdb0..8110207f16 100644 --- a/test/helper/e2e/actions/steps.go +++ b/test/helper/e2e/actions/steps.go @@ -404,7 +404,7 @@ func PrepareUsersConfigurations(data *model.TestDataProvider) { }) if len(data.Resources.Deployments) > 0 { By("Create deployment spec", func() { - data.Resources.Deployments[0].Spec.Project.Name = data.Resources.Project.GetK8sMetaName() + data.Resources.Deployments[0].Spec.ProjectRef.Name = data.Resources.Project.GetK8sMetaName() utils.SaveToFile( data.Resources.Deployments[0].DeploymentFileName(data.Resources), utils.JSONToYAMLConvert(data.Resources.Deployments[0]), diff --git a/test/helper/e2e/data/deployments.go b/test/helper/e2e/data/deployments.go index 9e694c94cc..915017d254 100644 --- a/test/helper/e2e/data/deployments.go +++ b/test/helper/e2e/data/deployments.go @@ -35,8 +35,10 @@ func CreateAdvancedGeoshardedDeployment(name string) *akov2.AtlasDeployment { Name: name, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: ProjectName, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: ProjectName, + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ ClusterType: "GEOSHARDED", @@ -84,8 +86,10 @@ func CreateServerlessDeployment(name string, providerName string, regionName str Name: name, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: ProjectName, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: ProjectName, + }, }, ServerlessSpec: &akov2.ServerlessSpec{ Name: name, @@ -105,8 +109,10 @@ func CreateBasicDeployment(name string) *akov2.AtlasDeployment { Name: name, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: ProjectName, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: ProjectName, + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ ClusterType: "REPLICASET", @@ -139,8 +145,10 @@ func CreateDeploymentWithBackup(name string) *akov2.AtlasDeployment { Name: name, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: ProjectName, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: ProjectName, + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ ClusterType: "REPLICASET", @@ -171,8 +179,10 @@ func CreateDeploymentWithBackup(name string) *akov2.AtlasDeployment { func NewDeploymentWithBackupSpec() akov2.AtlasDeploymentSpec { return akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: ProjectName, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: ProjectName, + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ Name: "deployment-backup", @@ -230,8 +240,10 @@ func CreateDeploymentWithMultiregion(name string, providerName provider.Provider Name: name, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: ProjectName, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: ProjectName, + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ Name: "deployment-multiregion", @@ -276,8 +288,10 @@ func CreateFreeAdvancedDeployment(name string) *akov2.AtlasDeployment { Name: name, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: ProjectName, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: ProjectName, + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ Name: name, @@ -317,8 +331,10 @@ func CreateAdvancedDeployment(name string) *akov2.AtlasDeployment { Name: name, }, Spec: akov2.AtlasDeploymentSpec{ - Project: &common.ResourceRefNamespaced{ - Name: ProjectName, + ProjectDualReference: akov2.ProjectDualReference{ + ProjectRef: &common.ResourceRefNamespaced{ + Name: ProjectName, + }, }, DeploymentSpec: &akov2.AdvancedDeploymentSpec{ Name: name, diff --git a/test/helper/e2e/model/deployment.go b/test/helper/e2e/model/deployment.go index 32bb3671ce..2deba23014 100644 --- a/test/helper/e2e/model/deployment.go +++ b/test/helper/e2e/model/deployment.go @@ -33,7 +33,7 @@ func LoadUserDeploymentConfig(path string) AtlasDeployment { func (ad *AtlasDeployment) DeploymentFileName(input UserInputs) string { // return "data/deployment-" + ac.ObjectMeta.Name + "-" + ac.Spec.Project.Name + ".yaml" - return filepath.Dir(input.ProjectPath) + "/" + ad.ObjectMeta.Name + "-" + ad.Spec.Project.Name + ".yaml" + return filepath.Dir(input.ProjectPath) + "/" + ad.ObjectMeta.Name + "-" + ad.Spec.ProjectRef.Name + ".yaml" } func (ad *AtlasDeployment) GetDeploymentNameResource() string { diff --git a/test/int/clusterwide/dbuser_test.go b/test/int/clusterwide/dbuser_test.go index dc5c106489..b30c66a6b9 100644 --- a/test/int/clusterwide/dbuser_test.go +++ b/test/int/clusterwide/dbuser_test.go @@ -109,7 +109,7 @@ var _ = Describe("clusterwide", Label("int", "clusterwide"), func() { createdDeploymentAWS = akov2.DefaultAWSDeployment(deploymentNS.Name, createdProject.Name).Lightweight() // The project namespace is different from the deployment one - need to specify explicitly - createdDeploymentAWS.Spec.Project.Namespace = namespace.Name + createdDeploymentAWS.Spec.ProjectRef.Namespace = namespace.Name Expect(k8sClient.Create(context.Background(), createdDeploymentAWS)).ToNot(HaveOccurred()) diff --git a/test/int/deployment_independent_test.go b/test/int/deployment_independent_test.go index acfedf40d4..f39c05ad78 100644 --- a/test/int/deployment_independent_test.go +++ b/test/int/deployment_independent_test.go @@ -39,17 +39,15 @@ var _ = Describe("AtlasDeployment", Label("int", "AtlasDeployment", "independent deployment.Spec.ExternalProjectRef = &akov2.ExternalProjectReference{ ID: project.ID(), } - deployment.Spec.LocalCredentialHolder = api.LocalCredentialHolder{ - ConnectionSecret: &api.LocalObjectReference{ - Name: project.Spec.ConnectionSecret.Name, - }, + deployment.Spec.ConnectionSecret = &api.LocalObjectReference{ + Name: project.Spec.ConnectionSecret.Name, } Expect(k8sClient.Create(ctx, deployment)).ToNot(Succeed()) }) By("Creating a independent deployment resource", func() { - deployment.Spec.Project = nil + deployment.Spec.ProjectRef = nil Expect(k8sClient.Create(ctx, deployment)).To(Succeed()) Eventually(func(g Gomega) bool { return resources.CheckCondition(k8sClient, deployment, api.TrueCondition(api.ReadyType), validateDeploymentCreatingFunc(g))