diff --git a/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml b/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml index 34bc5199ea..2d339cc7ba 100644 --- a/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml +++ b/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml @@ -231,6 +231,28 @@ spec: type: array rootCertType: type: string + tags: + description: Key-value pairs for resource tagging. + items: + description: TagSpec holds a key-value pair for resource tagging + on this deployment. + properties: + key: + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9][a-zA-Z0-9 @_.+`;`-]*$ + type: string + value: + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9][a-zA-Z0-9@_.+`;`-]*$ + type: string + required: + - key + - value + type: object + maxItems: 50 + type: array versionReleaseSystem: type: string type: object @@ -574,6 +596,28 @@ spec: type: string type: object type: array + tags: + description: Key-value pairs for resource tagging. + items: + description: TagSpec holds a key-value pair for resource tagging + on this deployment. + properties: + key: + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9][a-zA-Z0-9 @_.+`;`-]*$ + type: string + value: + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9][a-zA-Z0-9@_.+`;`-]*$ + type: string + required: + - key + - value + type: object + maxItems: 50 + type: array required: - name - providerSettings @@ -747,6 +791,28 @@ spec: required: - providerName type: object + tags: + description: Key-value pairs for resource tagging. + items: + description: TagSpec holds a key-value pair for resource tagging + on this deployment. + properties: + key: + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9][a-zA-Z0-9 @_.+`;`-]*$ + type: string + value: + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9][a-zA-Z0-9@_.+`;`-]*$ + type: string + required: + - key + - value + type: object + maxItems: 50 + type: array required: - name - providerSettings diff --git a/go.sum b/go.sum index ef3493cec3..c660658348 100644 --- a/go.sum +++ b/go.sum @@ -322,6 +322,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= diff --git a/pkg/api/v1/atlasdeployment_types.go b/pkg/api/v1/atlasdeployment_types.go index f50b323c34..e54dd2a702 100644 --- a/pkg/api/v1/atlasdeployment_types.go +++ b/pkg/api/v1/atlasdeployment_types.go @@ -119,6 +119,11 @@ type DeploymentSpec struct { // +kubebuilder:validation:Pattern:=^[a-zA-Z0-9][a-zA-Z0-9-]*$ Name string `json:"name"` + // Key-value pairs for resource tagging. + // +kubebuilder:validation:MaxItems=50 + // +optional + Tags []*TagSpec `json:"tags,omitempty"` + // Positive integer that specifies the number of shards to deploy for a sharded deployment. // The parameter is required if replicationSpecs are configured // +kubebuilder:validation:Minimum=1 @@ -163,12 +168,16 @@ type AdvancedDeploymentSpec struct { // After Atlas creates the deployment, you can't change its name. // Can only contain ASCII letters, numbers, and hyphens. // +kubebuilder:validation:Pattern:=^[a-zA-Z0-9][a-zA-Z0-9-]*$ - Name string `json:"name,omitempty"` - Paused *bool `json:"paused,omitempty"` - PitEnabled *bool `json:"pitEnabled,omitempty"` - ReplicationSpecs []*AdvancedReplicationSpec `json:"replicationSpecs,omitempty"` - RootCertType string `json:"rootCertType,omitempty"` - VersionReleaseSystem string `json:"versionReleaseSystem,omitempty"` + Name string `json:"name,omitempty"` + Paused *bool `json:"paused,omitempty"` + PitEnabled *bool `json:"pitEnabled,omitempty"` + ReplicationSpecs []*AdvancedReplicationSpec `json:"replicationSpecs,omitempty"` + RootCertType string `json:"rootCertType,omitempty"` + // Key-value pairs for resource tagging. + // +kubebuilder:validation:MaxItems=50 + // +optional + Tags []*TagSpec `json:"tags,omitempty"` + VersionReleaseSystem string `json:"versionReleaseSystem,omitempty"` // +optional CustomZoneMapping []CustomZoneMapping `json:"customZoneMapping,omitempty"` // +optional @@ -205,9 +214,12 @@ type ServerlessSpec struct { // +kubebuilder:validation:Pattern:=^[a-zA-Z0-9][a-zA-Z0-9-]*$ Name string `json:"name"` // Configuration for the provisioned hosts on which MongoDB runs. The available options are specific to the cloud service provider. - ProviderSettings *ProviderSettingsSpec `json:"providerSettings"` - + ProviderSettings *ProviderSettingsSpec `json:"providerSettings"` PrivateEndpoints []ServerlessPrivateEndpoint `json:"privateEndpoints,omitempty"` + // Key-value pairs for resource tagging. + // +kubebuilder:validation:MaxItems=50 + // +optional + Tags []*TagSpec `json:"tags,omitempty"` } // ToAtlas converts the ServerlessSpec to native Atlas client Cluster format. @@ -223,6 +235,18 @@ type BiConnector struct { ReadPreference string `json:"readPreference,omitempty"` } +// TagSpec holds a key-value pair for resource tagging on this deployment. +type TagSpec struct { + // +kubebuilder:validation:MaxLength:=255 + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:Pattern:=^[a-zA-Z0-9][a-zA-Z0-9 @_.+`;`-]*$ + Key string `json:"key"` + // +kubebuilder:validation:MaxLength:=255 + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:Pattern:=^[a-zA-Z0-9][a-zA-Z0-9@_.+`;`-]*$ + Value string `json:"value"` +} + // ConnectionStrings configuration for applications use to connect to this deployment. type ConnectionStrings struct { Standard string `json:"standard,omitempty"` diff --git a/pkg/api/v1/atlasdeployment_types_test.go b/pkg/api/v1/atlasdeployment_types_test.go index 34655b6237..b84947e70e 100644 --- a/pkg/api/v1/atlasdeployment_types_test.go +++ b/pkg/api/v1/atlasdeployment_types_test.go @@ -55,7 +55,6 @@ func init() { excludedClusterFieldsTheirs["createDate"] = true excludedClusterFieldsTheirs["versionReleaseSystem"] = true excludedClusterFieldsTheirs["serverlessBackupOptions"] = true - excludedClusterFieldsTheirs["tags"] = true } func TestCompatibility(t *testing.T) { diff --git a/pkg/api/v1/zz_generated.deepcopy.go b/pkg/api/v1/zz_generated.deepcopy.go index 855f9791fa..03055b580b 100644 --- a/pkg/api/v1/zz_generated.deepcopy.go +++ b/pkg/api/v1/zz_generated.deepcopy.go @@ -14,9 +14,10 @@ a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package v1 import ( + "k8s.io/apimachinery/pkg/runtime" + "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/common" "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1/project" - "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -125,6 +126,17 @@ func (in *AdvancedDeploymentSpec) DeepCopyInto(out *AdvancedDeploymentSpec) { } } } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]*TagSpec, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TagSpec) + **out = **in + } + } + } if in.CustomZoneMapping != nil { in, out := &in.CustomZoneMapping, &out.CustomZoneMapping *out = make([]CustomZoneMapping, len(*in)) @@ -1373,6 +1385,17 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { *out = make([]common.LabelSpec, len(*in)) copy(*out, *in) } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]*TagSpec, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TagSpec) + **out = **in + } + } + } if in.NumShards != nil { in, out := &in.NumShards, &out.NumShards *out = new(int) @@ -1968,6 +1991,17 @@ func (in *ServerlessPrivateEndpoint) DeepCopy() *ServerlessPrivateEndpoint { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerlessSpec) DeepCopyInto(out *ServerlessSpec) { *out = *in + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]*TagSpec, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TagSpec) + **out = **in + } + } + } if in.ProviderSettings != nil { in, out := &in.ProviderSettings, &out.ProviderSettings *out = new(ProviderSettingsSpec) @@ -2064,6 +2098,21 @@ func (in *Store) DeepCopy() *Store { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TagSpec) DeepCopyInto(out *TagSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TagSpec. +func (in *TagSpec) DeepCopy() *TagSpec { + if in == nil { + return nil + } + out := new(TagSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Team) DeepCopyInto(out *Team) { *out = *in diff --git a/pkg/controller/atlasdeployment/atlasdeployment_controller.go b/pkg/controller/atlasdeployment/atlasdeployment_controller.go index 79b4665a1e..21ed7843a5 100644 --- a/pkg/controller/atlasdeployment/atlasdeployment_controller.go +++ b/pkg/controller/atlasdeployment/atlasdeployment_controller.go @@ -180,6 +180,13 @@ func (r *AtlasDeploymentReconciler) Reconcile(context context.Context, req ctrl. deployment.Spec.DeploymentSpec = nil } + if err := uniqueKey(&deployment.Spec); err != nil { + log.Errorw("failed to validate tags", "error", err) + result := workflow.Terminate(workflow.Internal, err.Error()) + workflowCtx.SetConditionFromResult(status.DeploymentReadyType, result) + return result.ReconcileResult(), nil + } + handleDeployment := r.selectDeploymentHandler(deployment) if result, _ := handleDeployment(workflowCtx, project, deployment, req); !result.IsOk() { workflowCtx.SetConditionFromResult(status.DeploymentReadyType, result) @@ -192,7 +199,6 @@ func (r *AtlasDeploymentReconciler) Reconcile(context context.Context, req ctrl. return result.ReconcileResult(), nil } } - return workflow.OK().ReconcileResult(), nil } @@ -680,3 +686,23 @@ func advancedDeploymentMatchesSpec(log *zap.SugaredLogger, atlasSpec *mongodbatl return d == "", nil } + +// Parse through tags and verfiy that all keys are unique. Return error otherwise. +func uniqueKey(deploymentSpec *mdbv1.AtlasDeploymentSpec) error { + store := make(map[string]string) + var arrTags []*mdbv1.TagSpec + + if deploymentSpec.AdvancedDeploymentSpec != nil { + arrTags = deploymentSpec.AdvancedDeploymentSpec.Tags + } else { + arrTags = deploymentSpec.ServerlessSpec.Tags + } + for _, currTag := range arrTags { + if store[currTag.Key] == "" { + store[currTag.Key] = currTag.Value + } else { + return errors.New("duplicate keys found in tags, this is forbidden") + } + } + return nil +} diff --git a/pkg/controller/atlasdeployment/atlasdeployment_controller_test.go b/pkg/controller/atlasdeployment/atlasdeployment_controller_test.go index 01f43d5f1f..59fb678fd9 100644 --- a/pkg/controller/atlasdeployment/atlasdeployment_controller_test.go +++ b/pkg/controller/atlasdeployment/atlasdeployment_controller_test.go @@ -736,3 +736,42 @@ func testBackupPolicy() *v1.AtlasBackupPolicy { }, } } + +func TestUniqueKey(t *testing.T) { + t.Run("Test duplicates in Advanced Deployment", func(t *testing.T) { + deploymentSpec := &v1.AtlasDeploymentSpec{ + AdvancedDeploymentSpec: &v1.AdvancedDeploymentSpec{ + Tags: []*v1.TagSpec{{Key: "foo", Value: "true"}, {Key: "foo", Value: "false"}}, + }, + } + err := uniqueKey(deploymentSpec) + assert.Error(t, err) + }) + t.Run("Test no duplicates in Advanced Deployment", func(t *testing.T) { + deploymentSpec := &v1.AtlasDeploymentSpec{ + AdvancedDeploymentSpec: &v1.AdvancedDeploymentSpec{ + Tags: []*v1.TagSpec{{Key: "foo", Value: "true"}, {Key: "bar", Value: "false"}, {Key: "foobar", Value: "false"}}, + }, + } + err := uniqueKey(deploymentSpec) + assert.NoError(t, err) + }) + t.Run("Test duplicates in Serverless Instance", func(t *testing.T) { + deploymentSpec := &v1.AtlasDeploymentSpec{ + ServerlessSpec: &v1.ServerlessSpec{ + Tags: []*v1.TagSpec{{Key: "foo", Value: "true"}, {Key: "bar", Value: "false"}, {Key: "foo", Value: "false"}}, + }, + } + err := uniqueKey(deploymentSpec) + assert.Error(t, err) + }) + t.Run("Test no duplicates in Serverless Instance", func(t *testing.T) { + deploymentSpec := &v1.AtlasDeploymentSpec{ + ServerlessSpec: &v1.ServerlessSpec{ + Tags: []*v1.TagSpec{{Key: "foo", Value: "true"}, {Key: "bar", Value: "false"}}, + }, + } + err := uniqueKey(deploymentSpec) + assert.NoError(t, err) + }) +} diff --git a/pkg/controller/atlasdeployment/deployment.go b/pkg/controller/atlasdeployment/deployment.go index 1e0222eeee..8fba14136f 100644 --- a/pkg/controller/atlasdeployment/deployment.go +++ b/pkg/controller/atlasdeployment/deployment.go @@ -28,6 +28,7 @@ func ConvertLegacyDeployment(deploymentSpec *mdbv1.AtlasDeploymentSpec) error { Labels: legacy.Labels, MongoDBMajorVersion: legacy.MongoDBMajorVersion, Name: legacy.Name, + Tags: legacy.Tags, Paused: legacy.Paused, PitEnabled: legacy.PitEnabled, ReplicationSpecs: replicationSpecs, diff --git a/pkg/controller/atlasdeployment/serverless_deployment.go b/pkg/controller/atlasdeployment/serverless_deployment.go index 42dbc6bfaf..45fe350c4e 100644 --- a/pkg/controller/atlasdeployment/serverless_deployment.go +++ b/pkg/controller/atlasdeployment/serverless_deployment.go @@ -24,6 +24,10 @@ func ensureServerlessInstanceState(ctx *workflow.Context, project *mdbv1.AtlasPr return atlasDeployment, workflow.Terminate(workflow.DeploymentNotCreatedInAtlas, err.Error()) } + atlasDeployment, err = serverlessSpec.ToAtlas() + if err != nil { + return atlasDeployment, workflow.Terminate(workflow.Internal, err.Error()) + } ctx.Log.Infof("Serverless Instance %s doesn't exist in Atlas - creating", serverlessSpec.Name) atlasDeployment, _, err = ctx.Client.ServerlessInstances.Create(context.Background(), project.Status.ID, &mongodbatlas.ServerlessCreateRequestParams{ Name: serverlessSpec.Name, @@ -32,6 +36,7 @@ func ensureServerlessInstanceState(ctx *workflow.Context, project *mdbv1.AtlasPr ProviderName: string(serverlessSpec.ProviderSettings.ProviderName), RegionName: serverlessSpec.ProviderSettings.RegionName, }, + Tag: atlasDeployment.Tags, }) if err != nil { return atlasDeployment, workflow.Terminate(workflow.DeploymentNotCreatedInAtlas, err.Error()) @@ -40,8 +45,26 @@ func ensureServerlessInstanceState(ctx *workflow.Context, project *mdbv1.AtlasPr switch atlasDeployment.StateName { case status.StateIDLE: + convertedDeployment, err := serverlessSpec.ToAtlas() + if err != nil { + return atlasDeployment, workflow.Terminate(workflow.Internal, err.Error()) + } + if convertedDeployment.Tags == nil { + convertedDeployment.Tags = &[]*mongodbatlas.Tag{} + } + if !isTagsEqual(*(atlasDeployment.Tags), *(convertedDeployment.Tags)) { + atlasDeployment, _, err = ctx.Client.ServerlessInstances.Update(context.Background(), project.Status.ID, serverlessSpec.Name, &mongodbatlas.ServerlessUpdateRequestParams{ + // TODO: include ServerlessBackupOptions and TerminationProtectionEnabled + Tag: convertedDeployment.Tags, + }) + if err != nil { + return atlasDeployment, workflow.Terminate(workflow.DeploymentNotUpdatedInAtlas, err.Error()) + } + return atlasDeployment, workflow.InProgress(workflow.DeploymentUpdating, "deployment is updating") + } result := ensureServerlessPrivateEndpoints(ctx, project.ID(), serverlessSpec, atlasDeployment.Name) return atlasDeployment, result + case status.StateCREATING: return atlasDeployment, workflow.InProgress(workflow.DeploymentCreating, "deployment is provisioning") @@ -54,3 +77,15 @@ func ensureServerlessInstanceState(ctx *workflow.Context, project *mdbv1.AtlasPr return atlasDeployment, workflow.Terminate(workflow.Internal, fmt.Sprintf("unknown deployment state %q", atlasDeployment.StateName)) } } + +func isTagsEqual(a []*mongodbatlas.Tag, c []*mongodbatlas.Tag) bool { + if len(a) == len(c) { + for i, aTags := range a { + if aTags.Key != c[i].Key || aTags.Value != c[i].Value { + return false + } + } + return true + } + return false +} diff --git a/pkg/controller/atlasdeployment/serverless_deployment_test.go b/pkg/controller/atlasdeployment/serverless_deployment_test.go new file mode 100644 index 0000000000..c061058bb8 --- /dev/null +++ b/pkg/controller/atlasdeployment/serverless_deployment_test.go @@ -0,0 +1,23 @@ +package atlasdeployment + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.mongodb.org/atlas/mongodbatlas" +) + +func TestIsTagsEqual(t *testing.T) { + t.Run("Test tags are equal and in same order", func(t *testing.T) { + k8sCluster := &mongodbatlas.Cluster{Tags: &[]*mongodbatlas.Tag{{Key: "foo", Value: "true"}, {Key: "bar", Value: "false"}}} + atlasCluster := &mongodbatlas.Cluster{Tags: &[]*mongodbatlas.Tag{{Key: "foo", Value: "true"}, {Key: "bar", Value: "false"}}} + areEqual := isTagsEqual(*(atlasCluster.Tags), *(k8sCluster.Tags)) + assert.True(t, areEqual, "Deployments should be equal") + }) + t.Run("Test tags are different lengths", func(t *testing.T) { + k8sCluster := &mongodbatlas.Cluster{Tags: &[]*mongodbatlas.Tag{{Key: "foo", Value: "true"}, {Key: "bar", Value: "false"}, {Key: "foobar", Value: "true"}}} + atlasCluster := &mongodbatlas.Cluster{Tags: &[]*mongodbatlas.Tag{{Key: "foo", Value: "true"}, {Key: "bar", Value: "false"}}} + areEqual := isTagsEqual(*(atlasCluster.Tags), *(k8sCluster.Tags)) + assert.False(t, areEqual, "Deployments should not be equal") + }) +} diff --git a/test/int/deployment_test.go b/test/int/deployment_test.go index 0b31c411da..a57768f565 100644 --- a/test/int/deployment_test.go +++ b/test/int/deployment_test.go @@ -211,7 +211,6 @@ var _ = Describe("AtlasDeployment", Label("int", "AtlasDeployment", "deployment- performUpdate := func(timeout time.Duration) { Expect(k8sClient.Update(context.Background(), createdDeployment)).To(Succeed()) - Eventually(func(g Gomega) bool { return testutil.CheckCondition(k8sClient, createdDeployment, status.TrueCondition(status.ReadyType), validateDeploymentUpdatingFunc(g)) }).WithTimeout(timeout).WithPolling(interval).Should(BeTrue()) @@ -556,6 +555,44 @@ var _ = Describe("AtlasDeployment", Label("int", "AtlasDeployment", "deployment- checkAtlasState() }) + By("Updating the Deployment tags", func() { + createdDeployment.Spec.DeploymentSpec.Tags = []*mdbv1.TagSpec{{Key: "test-1", Value: "value-1"}, {Key: "test-2", Value: "value-2"}} + performUpdate(20 * time.Minute) + doDeploymentStatusChecks() + checkAtlasState(func(c *mongodbatlas.AdvancedCluster) { + for i, tag := range createdDeployment.Spec.DeploymentSpec.Tags { + Expect(c.Tags[i].Key == tag.Key).To(BeTrue()) + Expect(c.Tags[i].Value == tag.Value).To(BeTrue()) + } + }) + }) + + By("Updating the order of Deployment tags", func() { + createdDeployment.Spec.DeploymentSpec.Tags = []*mdbv1.TagSpec{{Key: "test-2", Value: "value-2"}, {Key: "test-1", Value: "value-1"}} + performUpdate(20 * time.Minute) + doDeploymentStatusChecks() + checkAtlasState(func(c *mongodbatlas.AdvancedCluster) { + for i, tag := range createdDeployment.Spec.DeploymentSpec.Tags { + Expect(c.Tags[i].Key == tag.Key).To(BeTrue()) + Expect(c.Tags[i].Value == tag.Value).To(BeTrue()) + } + }) + }) + + By("Updating the Deployment tags with a duplicate key and removing all tags", func() { + createdDeployment.Spec.DeploymentSpec.Tags = []*mdbv1.TagSpec{{Key: "test-1", Value: "value-1"}, {Key: "test-1", Value: "value-2"}} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Update(context.Background(), createdDeployment)).To(Succeed()) + }).WithTimeout(5 * time.Minute).WithPolling(10 * time.Second).Should(Succeed()) + Eventually(func() bool { + return testutil.CheckCondition(k8sClient, createdDeployment, status.FalseCondition(status.DeploymentReadyType)) + }).WithTimeout(DeploymentUpdateTimeout).Should(BeTrue()) + lastGeneration++ + // Removing tags for next tests + createdDeployment.Spec.DeploymentSpec.Tags = []*mdbv1.TagSpec{} + performUpdate(20 * time.Minute) + }) + By("Updating the Deployment backups settings", func() { createdDeployment.Spec.DeploymentSpec.ProviderBackupEnabled = boolptr(true) performUpdate(20 * time.Minute) @@ -991,11 +1028,50 @@ var _ = Describe("AtlasDeployment", Label("int", "AtlasDeployment", "deployment- Describe("Create serverless instance", func() { It("Should Succeed", func() { createdDeployment = mdbv1.NewDefaultAWSServerlessInstance(namespace.Name, createdProject.Name) - + createdDeployment.Spec.ServerlessSpec.Tags = []*mdbv1.TagSpec{} By(fmt.Sprintf("Creating the Serverless Instance %s", kube.ObjectKeyFromObject(createdDeployment)), func() { performCreate(createdDeployment, 30*time.Minute) + doServerlessDeploymentStatusChecks() + }) + + By("Updating the Instance tags", func() { + createdDeployment.Spec.ServerlessSpec.Tags = []*mdbv1.TagSpec{{Key: "test-1", Value: "value-1"}, {Key: "test-2", Value: "value-2"}} + performUpdate(20 * time.Minute) + doServerlessDeploymentStatusChecks() + atlasDeployment, _, _ := atlasClient.ServerlessInstances.Get(context.Background(), createdProject.Status.ID, createdDeployment.Spec.ServerlessSpec.Name) + if createdDeployment != nil { + for i, tag := range createdDeployment.Spec.ServerlessSpec.Tags { + Expect((*atlasDeployment.Tags)[i].Key == tag.Key).To(BeTrue()) + Expect((*atlasDeployment.Tags)[i].Value == tag.Value).To(BeTrue()) + } + } + }) + By("Updating the order of Instance tags", func() { + createdDeployment.Spec.ServerlessSpec.Tags = []*mdbv1.TagSpec{{Key: "test-2", Value: "value-2"}, {Key: "test-1", Value: "value-1"}} + performUpdate(20 * time.Minute) doServerlessDeploymentStatusChecks() + atlasDeployment, _, _ := atlasClient.ServerlessInstances.Get(context.Background(), createdProject.Status.ID, createdDeployment.Spec.ServerlessSpec.Name) + if createdDeployment != nil { + for i, tag := range createdDeployment.Spec.ServerlessSpec.Tags { + Expect((*atlasDeployment.Tags)[i].Key == tag.Key).To(BeTrue()) + Expect((*atlasDeployment.Tags)[i].Value == tag.Value).To(BeTrue()) + } + } + }) + + By("Updating the Instance tags with a duplicate key and removing all tags", func() { + createdDeployment.Spec.ServerlessSpec.Tags = []*mdbv1.TagSpec{{Key: "test-1", Value: "value-1"}, {Key: "test-1", Value: "value-2"}} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Update(context.Background(), createdDeployment)).To(Succeed()) + }).WithTimeout(5 * time.Minute).WithPolling(10 * time.Second).Should(Succeed()) + Eventually(func() bool { + return testutil.CheckCondition(k8sClient, createdDeployment, status.FalseCondition(status.DeploymentReadyType)) + }).WithTimeout(DeploymentUpdateTimeout).Should(BeTrue()) + lastGeneration++ + // Removing tags + createdDeployment.Spec.ServerlessSpec.Tags = []*mdbv1.TagSpec{} + performUpdate(20 * time.Minute) }) }) })