From 0ba2ab48344018aea0a6ed593c20e42c3a2f1a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Panti=C4=87?= Date: Fri, 6 Sep 2024 17:00:01 +0200 Subject: [PATCH 1/2] feat(AwsRedisInstance): make authEnabled mutable --- .../v1beta1/redisinstance_types.go | 2 +- .../v1beta1/awsredisinstance_types.go | 2 +- ...ontrol.kyma-project.io_redisinstances.yaml | 8 ++- ...ces.kyma-project.io_awsredisinstances.yaml | 8 +-- ...ontrol.kyma-project.io_redisinstances.yaml | 8 ++- ...ces.kyma-project.io_awsredisinstances.yaml | 8 +-- config/patchAfterMakeManifests.sh | 2 +- .../cloud-control/redisinstance_aws_test.go | 8 +++ pkg/kcp/provider/aws/meta/metadata.go | 9 +++ .../aws/mock/elastiCacheClientFake.go | 71 +++++++++++++++++++ pkg/kcp/provider/aws/mock/server.go | 2 + .../redisinstance/client/elastiCacheClient.go | 63 ++++++++++++++++ .../aws/redisinstance/createUserGroup.go | 48 +++++++++++++ .../aws/redisinstance/deleteUserGroup.go | 33 +++++++++ .../aws/redisinstance/loadAuthTokenSecret.go | 6 -- .../aws/redisinstance/loadUserGroup.go | 32 +++++++++ .../aws/redisinstance/modifyAuthEnabled.go | 43 +++++++++++ .../modifyPreferredMaintenanceWindow.go | 3 + pkg/kcp/provider/aws/redisinstance/new.go | 6 ++ pkg/kcp/provider/aws/redisinstance/state.go | 15 ++++ pkg/kcp/provider/aws/redisinstance/util.go | 4 ++ .../redisinstance/waitElastiCacheAvailable.go | 2 +- .../aws/redisinstance/waitUserGroupActive.go | 27 +++++++ .../aws/redisinstance/waitUserGroupDeleted.go | 42 +++++++++++ .../modifyKcpRedisInstance.go | 1 + pkg/skr/awsredisinstance/state.go | 2 + 26 files changed, 431 insertions(+), 24 deletions(-) create mode 100644 pkg/kcp/provider/aws/redisinstance/createUserGroup.go create mode 100644 pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go create mode 100644 pkg/kcp/provider/aws/redisinstance/loadUserGroup.go create mode 100644 pkg/kcp/provider/aws/redisinstance/modifyAuthEnabled.go create mode 100644 pkg/kcp/provider/aws/redisinstance/waitUserGroupActive.go create mode 100644 pkg/kcp/provider/aws/redisinstance/waitUserGroupDeleted.go diff --git a/api/cloud-control/v1beta1/redisinstance_types.go b/api/cloud-control/v1beta1/redisinstance_types.go index e70e575c0..b9d0e9bd7 100644 --- a/api/cloud-control/v1beta1/redisinstance_types.go +++ b/api/cloud-control/v1beta1/redisinstance_types.go @@ -211,6 +211,7 @@ type RedisInstanceAzure struct { ReplicasPerPrimary int `json:"replicasPerPrimary,omitempty"` } +// +kubebuilder:validation:XValidation:rule=(self.authEnabled == false || self.transitEncryptionEnabled == true), message="authEnabled can only be true if TransitEncryptionEnabled is also true" type RedisInstanceAws struct { // +kubebuilder:validation:Required CacheNodeType string `json:"cacheNodeType"` @@ -230,7 +231,6 @@ type RedisInstanceAws struct { // +optional // +kubebuilder:default=false - // +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="AuthEnabled is immutable." AuthEnabled bool `json:"authEnabled"` // Specifies the weekly time range during which maintenance on the cluster is diff --git a/api/cloud-resources/v1beta1/awsredisinstance_types.go b/api/cloud-resources/v1beta1/awsredisinstance_types.go index b43350c8e..adaedcf1a 100644 --- a/api/cloud-resources/v1beta1/awsredisinstance_types.go +++ b/api/cloud-resources/v1beta1/awsredisinstance_types.go @@ -22,6 +22,7 @@ import ( ) // AwsRedisInstanceSpec defines the desired state of AwsRedisInstance +// +kubebuilder:validation:XValidation:rule=(self.authEnabled == false || self.transitEncryptionEnabled == true), message="authEnabled can only be true if TransitEncryptionEnabled is also true" type AwsRedisInstanceSpec struct { // +optional IpRange IpRangeRef `json:"ipRange"` @@ -48,7 +49,6 @@ type AwsRedisInstanceSpec struct { // +optional // +kubebuilder:default=false - // +kubebuilder:validation:XValidation:rule=(self == oldSelf), message="AuthEnabled is immutable." AuthEnabled bool `json:"authEnabled"` // Specifies the weekly time range during which maintenance on the cluster is diff --git a/config/crd/bases/cloud-control.kyma-project.io_redisinstances.yaml b/config/crd/bases/cloud-control.kyma-project.io_redisinstances.yaml index 7055cf944..e89f03254 100644 --- a/config/crd/bases/cloud-control.kyma-project.io_redisinstances.yaml +++ b/config/crd/bases/cloud-control.kyma-project.io_redisinstances.yaml @@ -48,9 +48,6 @@ spec: authEnabled: default: false type: boolean - x-kubernetes-validations: - - message: AuthEnabled is immutable. - rule: (self == oldSelf) autoMinorVersionUpgrade: default: false type: boolean @@ -84,6 +81,11 @@ spec: required: - cacheNodeType type: object + x-kubernetes-validations: + - message: authEnabled can only be true if TransitEncryptionEnabled + is also true + rule: (self.authEnabled == false || self.transitEncryptionEnabled + == true) azure: properties: enableNonSslPort: diff --git a/config/crd/bases/cloud-resources.kyma-project.io_awsredisinstances.yaml b/config/crd/bases/cloud-resources.kyma-project.io_awsredisinstances.yaml index bc64d6838..88617653c 100644 --- a/config/crd/bases/cloud-resources.kyma-project.io_awsredisinstances.yaml +++ b/config/crd/bases/cloud-resources.kyma-project.io_awsredisinstances.yaml @@ -4,7 +4,7 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.15.0 - cloud-resources.kyma-project.io/version: v0.0.11 + cloud-resources.kyma-project.io/version: v0.0.12 name: awsredisinstances.cloud-resources.kyma-project.io spec: group: cloud-resources.kyma-project.io @@ -47,9 +47,6 @@ spec: authEnabled: default: false type: boolean - x-kubernetes-validations: - - message: AuthEnabled is immutable. - rule: (self == oldSelf) authSecret: properties: annotations: @@ -106,6 +103,9 @@ spec: required: - cacheNodeType type: object + x-kubernetes-validations: + - message: authEnabled can only be true if TransitEncryptionEnabled is also true + rule: (self.authEnabled == false || self.transitEncryptionEnabled == true) status: description: AwsRedisInstanceStatus defines the observed state of AwsRedisInstance properties: diff --git a/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_redisinstances.yaml b/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_redisinstances.yaml index 7055cf944..e89f03254 100644 --- a/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_redisinstances.yaml +++ b/config/dist/kcp/crd/bases/cloud-control.kyma-project.io_redisinstances.yaml @@ -48,9 +48,6 @@ spec: authEnabled: default: false type: boolean - x-kubernetes-validations: - - message: AuthEnabled is immutable. - rule: (self == oldSelf) autoMinorVersionUpgrade: default: false type: boolean @@ -84,6 +81,11 @@ spec: required: - cacheNodeType type: object + x-kubernetes-validations: + - message: authEnabled can only be true if TransitEncryptionEnabled + is also true + rule: (self.authEnabled == false || self.transitEncryptionEnabled + == true) azure: properties: enableNonSslPort: diff --git a/config/dist/skr/crd/bases/providers/aws/cloud-resources.kyma-project.io_awsredisinstances.yaml b/config/dist/skr/crd/bases/providers/aws/cloud-resources.kyma-project.io_awsredisinstances.yaml index bc64d6838..88617653c 100644 --- a/config/dist/skr/crd/bases/providers/aws/cloud-resources.kyma-project.io_awsredisinstances.yaml +++ b/config/dist/skr/crd/bases/providers/aws/cloud-resources.kyma-project.io_awsredisinstances.yaml @@ -4,7 +4,7 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.15.0 - cloud-resources.kyma-project.io/version: v0.0.11 + cloud-resources.kyma-project.io/version: v0.0.12 name: awsredisinstances.cloud-resources.kyma-project.io spec: group: cloud-resources.kyma-project.io @@ -47,9 +47,6 @@ spec: authEnabled: default: false type: boolean - x-kubernetes-validations: - - message: AuthEnabled is immutable. - rule: (self == oldSelf) authSecret: properties: annotations: @@ -106,6 +103,9 @@ spec: required: - cacheNodeType type: object + x-kubernetes-validations: + - message: authEnabled can only be true if TransitEncryptionEnabled is also true + rule: (self.authEnabled == false || self.transitEncryptionEnabled == true) status: description: AwsRedisInstanceStatus defines the observed state of AwsRedisInstance properties: diff --git a/config/patchAfterMakeManifests.sh b/config/patchAfterMakeManifests.sh index 24422b237..1f261f585 100755 --- a/config/patchAfterMakeManifests.sh +++ b/config/patchAfterMakeManifests.sh @@ -8,7 +8,7 @@ echo "Patching CRDs..." yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.1.0"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_ipranges.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.2"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_awsnfsvolumes.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.2"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_awsnfsvolumebackups.yaml -yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.11"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_awsredisinstances.yaml +yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.12"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_awsredisinstances.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.4"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_gcpnfsvolumes.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.9"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_gcpredisinstances.yaml yq -i '.metadata.annotations."cloud-resources.kyma-project.io/version" = "v0.0.1"' $SCRIPT_DIR/crd/bases/cloud-resources.kyma-project.io_azurevpcpeerings.yaml diff --git a/internal/controller/cloud-control/redisinstance_aws_test.go b/internal/controller/cloud-control/redisinstance_aws_test.go index e86b0a2ec..100eeed06 100644 --- a/internal/controller/cloud-control/redisinstance_aws_test.go +++ b/internal/controller/cloud-control/redisinstance_aws_test.go @@ -106,6 +106,10 @@ var _ = Describe("Feature: KCP RedisInstance", func() { infra.AwsMock().SetAwsElastiCacheLifeCycleState(*awsElastiCacheClusterInstance.ReplicationGroupId, awsmeta.ElastiCache_AVAILABLE) }) + By("And when AWS Redis UserGroup is Active", func() { + infra.AwsMock().SetAwsElastiCacheUserGroupLifeCycleState(*awsElastiCacheClusterInstance.ReplicationGroupId, awsmeta.ElastiCache_UserGroup_ACTIVE) + }) + By("Then RedisInstance has Ready condition", func() { Eventually(LoadAndCheck). WithArguments(infra.Ctx(), infra.KCP().Client(), redisInstance, @@ -139,6 +143,10 @@ var _ = Describe("Feature: KCP RedisInstance", func() { infra.AwsMock().DeleteAwsElastiCacheByName(*awsElastiCacheClusterInstance.ReplicationGroupId) }) + By("And When AWS Redis user group is deleted", func() { + infra.AwsMock().DeleteAwsElastiCacheUserGroupByName(*awsElastiCacheClusterInstance.ReplicationGroupId) + }) + By("Then RedisInstance does not exist", func() { Eventually(IsDeleted, 5*time.Second). WithArguments(infra.Ctx(), infra.KCP().Client(), redisInstance). diff --git a/pkg/kcp/provider/aws/meta/metadata.go b/pkg/kcp/provider/aws/meta/metadata.go index 3275ffbc3..2b617aca2 100644 --- a/pkg/kcp/provider/aws/meta/metadata.go +++ b/pkg/kcp/provider/aws/meta/metadata.go @@ -125,3 +125,12 @@ const ( ElastiCache_CREATE_FAILED ElastiCacheState = "create-failed" ElastiCache_SNAPSHOTTING ElastiCacheState = "snapshotting" ) + +type ElastiCacheUserGroupState = string + +const ( + ElastiCache_UserGroup_ACTIVE ElastiCacheUserGroupState = "active" + ElastiCache_UserGroup_CREATING ElastiCacheUserGroupState = "creating" + ElastiCache_UserGroup_DELETING ElastiCacheUserGroupState = "deleting" + ElastiCache_UserGroup_MODIFYING ElastiCacheUserGroupState = "modifying" +) diff --git a/pkg/kcp/provider/aws/mock/elastiCacheClientFake.go b/pkg/kcp/provider/aws/mock/elastiCacheClientFake.go index 849730898..3e6b9ad19 100644 --- a/pkg/kcp/provider/aws/mock/elastiCacheClientFake.go +++ b/pkg/kcp/provider/aws/mock/elastiCacheClientFake.go @@ -19,7 +19,9 @@ import ( type AwsElastiCacheMockUtils interface { GetAwsElastiCacheByName(name string) *elasticacheTypes.ReplicationGroup SetAwsElastiCacheLifeCycleState(name string, state awsmeta.ElastiCacheState) + SetAwsElastiCacheUserGroupLifeCycleState(name string, state awsmeta.ElastiCacheUserGroupState) DeleteAwsElastiCacheByName(name string) + DeleteAwsElastiCacheUserGroupByName(name string) DescribeAwsElastiCacheParametersByName(groupName string) map[string]string } @@ -45,11 +47,13 @@ type elastiCacheClientFake struct { parameterGroupMutex *sync.Mutex elasticacheMutex *sync.Mutex secretStoreMutex *sync.Mutex + userGroupsMutex *sync.Mutex replicationGroups map[string]*elasticacheTypes.ReplicationGroup cacheClusters map[string]*elasticacheTypes.CacheCluster parameters map[string]map[string]elasticacheTypes.Parameter parameterGroups map[string]*elasticacheTypes.CacheParameterGroup subnetGroups map[string]*elasticacheTypes.CacheSubnetGroup + userGroups map[string]*elasticacheTypes.UserGroup secretStore map[string]*secretsmanager.GetSecretValueOutput } @@ -63,6 +67,12 @@ func (client *elastiCacheClientFake) SetAwsElastiCacheLifeCycleState(name string } } +func (client *elastiCacheClientFake) SetAwsElastiCacheUserGroupLifeCycleState(name string, state awsmeta.ElastiCacheUserGroupState) { + if instance, ok := client.userGroups[name]; ok { + instance.Status = ptr.To(state) + } +} + func (client *elastiCacheClientFake) DeleteAwsElastiCacheByName(name string) { client.elasticacheMutex.Lock() defer client.elasticacheMutex.Unlock() @@ -70,6 +80,13 @@ func (client *elastiCacheClientFake) DeleteAwsElastiCacheByName(name string) { delete(client.replicationGroups, name) } +func (client *elastiCacheClientFake) DeleteAwsElastiCacheUserGroupByName(name string) { + client.userGroupsMutex.Lock() + defer client.userGroupsMutex.Unlock() + + delete(client.userGroups, name) +} + func (client *elastiCacheClientFake) DescribeAwsElastiCacheParametersByName(groupName string) map[string]string { result := map[string]string{} @@ -225,13 +242,20 @@ func (client *elastiCacheClientFake) CreateElastiCacheReplicationGroup(ctx conte PreferredMaintenanceWindow: options.PreferredMaintenanceWindow, } + authTokenEnabled := false + if options.AuthTokenSecretString != nil { + authTokenEnabled = true + } + client.replicationGroups[options.Name] = &elasticacheTypes.ReplicationGroup{ ReplicationGroupId: ptr.To(options.Name), Status: ptr.To("creating"), CacheNodeType: ptr.To(options.CacheNodeType), AutoMinorVersionUpgrade: ptr.To(options.AutoMinorVersionUpgrade), TransitEncryptionEnabled: ptr.To(options.TransitEncryptionEnabled), + AuthTokenEnabled: ptr.To(authTokenEnabled), MemberClusters: []string{options.Name}, + UserGroupIds: []string{}, NodeGroups: []elasticacheTypes.NodeGroup{ { PrimaryEndpoint: &elasticacheTypes.Endpoint{ @@ -275,6 +299,19 @@ func (client *elastiCacheClientFake) ModifyElastiCacheReplicationGroup(ctx conte if options.TransitEncryptionMode != nil { instance.TransitEncryptionMode = *options.TransitEncryptionMode } + + if len(options.UserGroupIdsToAdd) > 0 { + instance.UserGroupIds = append(instance.UserGroupIds, options.UserGroupIdsToAdd...) + instance.AuthTokenEnabled = ptr.To(false) + } + if len(options.UserGroupIdsToRemove) > 0 { + _, remaining := pie.Diff(instance.UserGroupIds, options.UserGroupIdsToRemove) + instance.UserGroupIds = remaining + } + + if options.AuthTokenSecretString != nil { + instance.AuthTokenEnabled = ptr.To(true) + } } return &elasticache.ModifyReplicationGroupOutput{}, nil @@ -303,3 +340,37 @@ func (client *elastiCacheClientFake) DescribeElastiCacheCluster(ctx context.Cont return []elasticacheTypes.CacheCluster{*cacheCluster}, nil } + +func (client *elastiCacheClientFake) DescribeUserGroup(ctx context.Context, id string) (*elasticacheTypes.UserGroup, error) { + client.userGroupsMutex.Lock() + defer client.userGroupsMutex.Unlock() + + userGroup := client.userGroups[id] + + return userGroup, nil +} + +func (client *elastiCacheClientFake) CreateUserGroup(ctx context.Context, id string, tags []elasticacheTypes.Tag) (*elasticache.CreateUserGroupOutput, error) { + client.userGroupsMutex.Lock() + defer client.userGroupsMutex.Unlock() + + client.userGroups[id] = &elasticacheTypes.UserGroup{ + Engine: ptr.To("redis"), + UserGroupId: ptr.To(id), + Status: ptr.To("creating"), + UserIds: []string{"default"}, + } + + return &elasticache.CreateUserGroupOutput{UserGroupId: ptr.To(id)}, nil +} + +func (client *elastiCacheClientFake) DeleteUserGroup(ctx context.Context, id string) error { + client.userGroupsMutex.Lock() + defer client.userGroupsMutex.Unlock() + + if instance, ok := client.userGroups[id]; ok { + instance.Status = ptr.To("deleting") + } + + return nil +} diff --git a/pkg/kcp/provider/aws/mock/server.go b/pkg/kcp/provider/aws/mock/server.go index 6000b2f49..c0e85fe3c 100644 --- a/pkg/kcp/provider/aws/mock/server.go +++ b/pkg/kcp/provider/aws/mock/server.go @@ -28,12 +28,14 @@ func New() Server { subnetGroupMutex: &sync.Mutex{}, parameterGroupMutex: &sync.Mutex{}, secretStoreMutex: &sync.Mutex{}, + userGroupsMutex: &sync.Mutex{}, replicationGroups: map[string]*elasticacheTypes.ReplicationGroup{}, cacheClusters: map[string]*elasticacheTypes.CacheCluster{}, subnetGroups: map[string]*elasticacheTypes.CacheSubnetGroup{}, parameterGroups: map[string]*elasticacheTypes.CacheParameterGroup{}, parameters: map[string]map[string]elasticacheTypes.Parameter{}, secretStore: map[string]*secretsmanager.GetSecretValueOutput{}, + userGroups: map[string]*elasticacheTypes.UserGroup{}, }, } } diff --git a/pkg/kcp/provider/aws/redisinstance/client/elastiCacheClient.go b/pkg/kcp/provider/aws/redisinstance/client/elastiCacheClient.go index 3fb106336..640a0fd53 100644 --- a/pkg/kcp/provider/aws/redisinstance/client/elastiCacheClient.go +++ b/pkg/kcp/provider/aws/redisinstance/client/elastiCacheClient.go @@ -52,6 +52,9 @@ type ModifyElastiCacheClusterOptions struct { PreferredMaintenanceWindow *string TransitEncryptionEnabled *bool TransitEncryptionMode *elasticacheTypes.TransitEncryptionMode + AuthTokenSecretString *string + UserGroupIdsToAdd []string + UserGroupIdsToRemove []string } type ElastiCacheClient interface { @@ -75,6 +78,10 @@ type ElastiCacheClient interface { ModifyElastiCacheReplicationGroup(ctx context.Context, id string, options ModifyElastiCacheClusterOptions) (*elasticache.ModifyReplicationGroupOutput, error) DeleteElastiCacheReplicationGroup(ctx context.Context, id string) error DescribeElastiCacheCluster(ctx context.Context, id string) ([]elasticacheTypes.CacheCluster, error) + + DescribeUserGroup(ctx context.Context, id string) (*elasticacheTypes.UserGroup, error) + CreateUserGroup(ctx context.Context, id string, tags []elasticacheTypes.Tag) (*elasticache.CreateUserGroupOutput, error) + DeleteUserGroup(ctx context.Context, id string) error } func newClient(ec2Svc *ec2.Client, elastiCacheSvc *elasticache.Client, secretsManagerSvc *secretsmanager.Client) ElastiCacheClient { @@ -350,6 +357,16 @@ func (c *client) ModifyElastiCacheReplicationGroup(ctx context.Context, id strin if options.TransitEncryptionMode != nil { params.TransitEncryptionMode = *options.TransitEncryptionMode } + if options.AuthTokenSecretString != nil { + params.AuthToken = options.AuthTokenSecretString + } + if len(options.UserGroupIdsToAdd) > 0 { + params.UserGroupIdsToAdd = options.UserGroupIdsToAdd + params.AuthTokenUpdateStrategy = elasticacheTypes.AuthTokenUpdateStrategyTypeDelete + } + if len(options.UserGroupIdsToRemove) > 0 { + params.UserGroupIdsToRemove = options.UserGroupIdsToRemove + } res, err := c.elastiCacheSvc.ModifyReplicationGroup(ctx, params) @@ -384,3 +401,49 @@ func (c *client) DescribeElastiCacheCluster(ctx context.Context, id string) ([]e } return out.CacheClusters, nil } + +func (c *client) DescribeUserGroup(ctx context.Context, id string) (*elasticacheTypes.UserGroup, error) { + res, err := c.elastiCacheSvc.DescribeUserGroups(ctx, &elasticache.DescribeUserGroupsInput{ + UserGroupId: ptr.To(id), + }) + + if err != nil { + if awsmeta.IsNotFound(err) { + return nil, nil + } + + return nil, err + } + + if len(res.UserGroups) == 0 { + return nil, nil + } + + return ptr.To(res.UserGroups[0]), nil +} + +func (c *client) CreateUserGroup(ctx context.Context, id string, tags []elasticacheTypes.Tag) (*elasticache.CreateUserGroupOutput, error) { + res, err := c.elastiCacheSvc.CreateUserGroup(ctx, &elasticache.CreateUserGroupInput{ + UserGroupId: ptr.To(id), + Engine: ptr.To("redis"), + Tags: tags, + UserIds: []string{"default"}, + }) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *client) DeleteUserGroup(ctx context.Context, id string) error { + _, err := c.elastiCacheSvc.DeleteUserGroup(ctx, &elasticache.DeleteUserGroupInput{ + UserGroupId: ptr.To(id), + }) + + if err != nil { + return err + } + + return nil +} diff --git a/pkg/kcp/provider/aws/redisinstance/createUserGroup.go b/pkg/kcp/provider/aws/redisinstance/createUserGroup.go new file mode 100644 index 000000000..39dfcf259 --- /dev/null +++ b/pkg/kcp/provider/aws/redisinstance/createUserGroup.go @@ -0,0 +1,48 @@ +package redisinstance + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/elasticache/types" + "github.com/kyma-project/cloud-manager/pkg/common" + "github.com/kyma-project/cloud-manager/pkg/composed" + awsmeta "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/meta" + "k8s.io/utils/ptr" +) + +func createUserGroup(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + if state.userGroup != nil { + return nil, nil + } + + logger := composed.LoggerFromCtx(ctx) + redisInstance := state.ObjAsRedisInstance() + + out, err := state.awsClient.CreateUserGroup(ctx, GetAwsElastiCacheParameterGroupName(state.Obj().GetName()), []types.Tag{ + { + Key: ptr.To(common.TagCloudManagerName), + Value: ptr.To(state.Name().String()), + }, + { + Key: ptr.To(common.TagCloudManagerRemoteName), + Value: ptr.To(redisInstance.Spec.RemoteRef.String()), + }, + { + Key: ptr.To(common.TagScope), + Value: ptr.To(redisInstance.Spec.Scope.Name), + }, + { + Key: ptr.To(common.TagShoot), + Value: ptr.To(state.Scope().Spec.ShootName), + }, + }) + if err != nil { + return awsmeta.LogErrorAndReturn(err, "Error creating user group", ctx) + } + + logger = logger.WithValues("userGroupId", out.UserGroupId) + logger.Info("User group created") + + return composed.StopWithRequeue, nil +} diff --git a/pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go b/pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go new file mode 100644 index 000000000..292f43d72 --- /dev/null +++ b/pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go @@ -0,0 +1,33 @@ +package redisinstance + +import ( + "context" + + "github.com/kyma-project/cloud-manager/pkg/composed" + awsmeta "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/meta" + "github.com/kyma-project/cloud-manager/pkg/util" + "k8s.io/utils/ptr" +) + +func deleteUserGroup(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.userGroup == nil { + return nil, nil + } + + userGroupState := ptr.Deref(state.userGroup.Status, "") + if userGroupState == awsmeta.ElastiCache_UserGroup_DELETING { + return nil, nil + } + + logger.Info("Deleting userGroup") + + err := state.awsClient.DeleteUserGroup(ctx, *state.userGroup.UserGroupId) + if err != nil { + return awsmeta.LogErrorAndReturn(err, "Error deleting userGroup secret", ctx) + } + + return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil +} diff --git a/pkg/kcp/provider/aws/redisinstance/loadAuthTokenSecret.go b/pkg/kcp/provider/aws/redisinstance/loadAuthTokenSecret.go index d45eadf3c..e6bd31460 100644 --- a/pkg/kcp/provider/aws/redisinstance/loadAuthTokenSecret.go +++ b/pkg/kcp/provider/aws/redisinstance/loadAuthTokenSecret.go @@ -13,12 +13,6 @@ func loadAuthTokenSecret(ctx context.Context, st composed.State) (error, context return nil, nil } - redisInstance := state.ObjAsRedisInstance() - - if !redisInstance.Spec.Instance.Aws.AuthEnabled { - return nil, nil - } - logger := composed.LoggerFromCtx(ctx) authTokenValue, err := state.awsClient.GetAuthTokenSecretValue(ctx, GetAwsAuthTokenSecretName(state.Obj().GetName())) diff --git a/pkg/kcp/provider/aws/redisinstance/loadUserGroup.go b/pkg/kcp/provider/aws/redisinstance/loadUserGroup.go new file mode 100644 index 000000000..8ac7e3779 --- /dev/null +++ b/pkg/kcp/provider/aws/redisinstance/loadUserGroup.go @@ -0,0 +1,32 @@ +package redisinstance + +import ( + "context" + + "github.com/kyma-project/cloud-manager/pkg/composed" + awsmeta "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/meta" +) + +func loadUserGroup(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + if state.userGroup != nil { + return nil, nil + } + + logger := composed.LoggerFromCtx(ctx) + + userGroup, err := state.awsClient.DescribeUserGroup(ctx, GetAwsElastiCacheUserGroupName(state.Obj().GetName())) + if err != nil { + return awsmeta.LogErrorAndReturn(err, "Error getting user group", ctx) + } + + if userGroup == nil { + logger.Info("ElastiCache User group not found") + return nil, nil + } + + state.userGroup = userGroup + logger.Info("ElastiCache user group found and loaded") + + return nil, nil +} diff --git a/pkg/kcp/provider/aws/redisinstance/modifyAuthEnabled.go b/pkg/kcp/provider/aws/redisinstance/modifyAuthEnabled.go new file mode 100644 index 000000000..3e83f0547 --- /dev/null +++ b/pkg/kcp/provider/aws/redisinstance/modifyAuthEnabled.go @@ -0,0 +1,43 @@ +package redisinstance + +import ( + "context" + + "github.com/kyma-project/cloud-manager/pkg/composed" + awsmeta "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/meta" + "k8s.io/utils/ptr" +) + +func modifyAuthEnabled(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + + redisInstance := state.ObjAsRedisInstance() + logger := composed.LoggerFromCtx(ctx) + + if state.elastiCacheReplicationGroup == nil { + return composed.StopWithRequeue, nil + } + + currentAuthEnabled := ptr.Deref(state.elastiCacheReplicationGroup.AuthTokenEnabled, false) + desiredAuthEnabled := redisInstance.Spec.Instance.Aws.AuthEnabled + + if currentAuthEnabled == desiredAuthEnabled && len(state.elastiCacheReplicationGroup.UserGroupIds) == 0 { + return nil, nil + } + + if desiredAuthEnabled && state.authTokenValue == nil { + return composed.StopWithRequeue, nil + } + + if !desiredAuthEnabled && state.authTokenValue != nil { + logger.Info("Deleting authToken secret") + err := state.awsClient.DeleteAuthTokenSecret(ctx, *state.authTokenValue.Name) + if err != nil { + return awsmeta.LogErrorAndReturn(err, "Error deleting authToken secret", ctx) + } + } + + state.UpdateAuthEnabled(desiredAuthEnabled) + + return nil, nil +} diff --git a/pkg/kcp/provider/aws/redisinstance/modifyPreferredMaintenanceWindow.go b/pkg/kcp/provider/aws/redisinstance/modifyPreferredMaintenanceWindow.go index 5304095e8..4b05a91a2 100644 --- a/pkg/kcp/provider/aws/redisinstance/modifyPreferredMaintenanceWindow.go +++ b/pkg/kcp/provider/aws/redisinstance/modifyPreferredMaintenanceWindow.go @@ -43,6 +43,9 @@ func modifyPreferredMaintenanceWindow(ctx context.Context, st composed.State) (e if currentPreferredMaintenanceWindow == desiredPreferredMaintenanceWindow { return nil, nil } + if desiredPreferredMaintenanceWindow == "" { + return nil, nil + } state.UpdatePreferredMaintenanceWindow(desiredPreferredMaintenanceWindow) diff --git a/pkg/kcp/provider/aws/redisinstance/new.go b/pkg/kcp/provider/aws/redisinstance/new.go index c60aaa802..f8b9a991e 100644 --- a/pkg/kcp/provider/aws/redisinstance/new.go +++ b/pkg/kcp/provider/aws/redisinstance/new.go @@ -29,6 +29,7 @@ func New(stateFactory StateFactory) composed.Action { loadSubnetGroup, loadParameterGroup, loadAuthTokenSecret, + loadUserGroup, loadElastiCacheCluster, composed.IfElse(composed.Not(composed.MarkedForDeletionPredicate), composed.ComposeActions( @@ -37,13 +38,16 @@ func New(stateFactory StateFactory) composed.Action { createParameterGroup, modifyParameterGroup, createAuthTokenSecret, + createUserGroup, createElastiCacheCluster, updateStatusId, waitElastiCacheAvailable, + waitUserGroupActive, modifyCacheNodeType, modifyAutoMinorVersionUpgrade, modifyTransitEncryptionEnabled, modifyPreferredMaintenanceWindow, + modifyAuthEnabled, updateElastiCacheCluster, updateStatus, ), @@ -52,6 +56,8 @@ func New(stateFactory StateFactory) composed.Action { removeReadyCondition, deleteElastiCacheCluster, waitElastiCacheDeleted, + deleteUserGroup, + waitUserGroupDeleted, deleteAuthTokenSecret, deleteParameterGroup, deleteSubnetGroup, diff --git a/pkg/kcp/provider/aws/redisinstance/state.go b/pkg/kcp/provider/aws/redisinstance/state.go index 3eac04f6e..e3c498ad3 100644 --- a/pkg/kcp/provider/aws/redisinstance/state.go +++ b/pkg/kcp/provider/aws/redisinstance/state.go @@ -21,6 +21,7 @@ type State struct { parameterGroup *elasticacheTypes.CacheParameterGroup elastiCacheReplicationGroup *elasticacheTypes.ReplicationGroup authTokenValue *secretsmanager.GetSecretValueOutput + userGroup *elasticacheTypes.UserGroup modifyElastiCacheClusterOptions client.ModifyElastiCacheClusterOptions updateMask []string @@ -111,3 +112,17 @@ func (s *State) UpdatePreferredMaintenanceWindow(preferredMaintenanceWindow stri s.modifyElastiCacheClusterOptions.PreferredMaintenanceWindow = ptr.To(preferredMaintenanceWindow) s.updateMask = append(s.updateMask, "preferredMaintenanceWindow") } + +func (s *State) UpdateAuthEnabled(authEnabled bool) { + s.updateMask = append(s.updateMask, "authEnabled") + if authEnabled { + s.modifyElastiCacheClusterOptions.AuthTokenSecretString = s.authTokenValue.SecretString + } else { + if len(s.elastiCacheReplicationGroup.UserGroupIds) < 1 { + s.modifyElastiCacheClusterOptions.UserGroupIdsToAdd = []string{ptr.Deref(s.userGroup.UserGroupId, "")} + } else { + s.modifyElastiCacheClusterOptions.UserGroupIdsToRemove = []string{ptr.Deref(s.userGroup.UserGroupId, "")} + } + } + +} diff --git a/pkg/kcp/provider/aws/redisinstance/util.go b/pkg/kcp/provider/aws/redisinstance/util.go index bb27289d9..2ea58926e 100644 --- a/pkg/kcp/provider/aws/redisinstance/util.go +++ b/pkg/kcp/provider/aws/redisinstance/util.go @@ -16,6 +16,10 @@ func GetAwsElastiCacheParameterGroupName(name string) string { return fmt.Sprintf("cm-%s", name) } +func GetAwsElastiCacheUserGroupName(name string) string { + return fmt.Sprintf("cm-%s", name) +} + func GetAwsElastiCacheParameterGroupFamily(engineVersion string) string { if strings.HasPrefix(engineVersion, "2.6") { return "redis2.6" diff --git a/pkg/kcp/provider/aws/redisinstance/waitElastiCacheAvailable.go b/pkg/kcp/provider/aws/redisinstance/waitElastiCacheAvailable.go index 03aabc66c..1eb91fd0f 100644 --- a/pkg/kcp/provider/aws/redisinstance/waitElastiCacheAvailable.go +++ b/pkg/kcp/provider/aws/redisinstance/waitElastiCacheAvailable.go @@ -16,7 +16,7 @@ func waitElastiCacheAvailable(ctx context.Context, st composed.State) (error, co logger := composed.LoggerFromCtx(ctx) if state.elastiCacheReplicationGroup == nil { - errorMsg := "Error: elkasti cache cluster instance is not loaded" + errorMsg := "Error: elasti cache cluster instance is not loaded" redisInstance := st.Obj().(*v1beta1.RedisInstance) return composed.UpdateStatus(redisInstance). SetExclusiveConditions(metav1.Condition{ diff --git a/pkg/kcp/provider/aws/redisinstance/waitUserGroupActive.go b/pkg/kcp/provider/aws/redisinstance/waitUserGroupActive.go new file mode 100644 index 000000000..0475554e3 --- /dev/null +++ b/pkg/kcp/provider/aws/redisinstance/waitUserGroupActive.go @@ -0,0 +1,27 @@ +package redisinstance + +import ( + "context" + + "github.com/kyma-project/cloud-manager/pkg/composed" + awsmeta "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/meta" + "github.com/kyma-project/cloud-manager/pkg/util" + "k8s.io/utils/ptr" +) + +func waitUserGroupActive(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.userGroup == nil { + return nil, nil + } + + userGroupState := ptr.Deref(state.userGroup.Status, "") + if userGroupState == awsmeta.ElastiCache_UserGroup_ACTIVE { + return nil, nil + } + + logger.Info("Redis elasticache user group is not ready yet, requeueing with delay") + return composed.StopWithRequeueDelay(util.Timing.T60000ms()), nil +} diff --git a/pkg/kcp/provider/aws/redisinstance/waitUserGroupDeleted.go b/pkg/kcp/provider/aws/redisinstance/waitUserGroupDeleted.go new file mode 100644 index 000000000..fafc42995 --- /dev/null +++ b/pkg/kcp/provider/aws/redisinstance/waitUserGroupDeleted.go @@ -0,0 +1,42 @@ +package redisinstance + +import ( + "context" + "fmt" + + "github.com/kyma-project/cloud-manager/api/cloud-control/v1beta1" + "github.com/kyma-project/cloud-manager/pkg/composed" + awsmeta "github.com/kyma-project/cloud-manager/pkg/kcp/provider/aws/meta" + "github.com/kyma-project/cloud-manager/pkg/util" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +func waitUserGroupDeleted(ctx context.Context, st composed.State) (error, context.Context) { + state := st.(*State) + logger := composed.LoggerFromCtx(ctx) + + if state.userGroup == nil { + return nil, nil + } + + cacheState := ptr.Deref(state.userGroup.Status, "") + + if cacheState != awsmeta.ElastiCache_UserGroup_DELETING { + errorMsg := fmt.Sprintf("Error: unexpected aws elasticache user group state: %s", cacheState) + redisInstance := st.Obj().(*v1beta1.RedisInstance) + return composed.UpdateStatus(redisInstance). + SetExclusiveConditions(metav1.Condition{ + Type: v1beta1.ConditionTypeError, + Status: metav1.ConditionTrue, + Reason: v1beta1.ConditionTypeError, + Message: errorMsg, + }). + SuccessError(composed.StopAndForget). + SuccessLogMsg(errorMsg). + Run(ctx, st) + } + + logger.Info("User group is still being deleted, requeueing with delay") + return composed.StopWithRequeueDelay(util.Timing.T60000ms()), nil +} diff --git a/pkg/skr/awsredisinstance/modifyKcpRedisInstance.go b/pkg/skr/awsredisinstance/modifyKcpRedisInstance.go index dc347c78f..dfc1b907d 100644 --- a/pkg/skr/awsredisinstance/modifyKcpRedisInstance.go +++ b/pkg/skr/awsredisinstance/modifyKcpRedisInstance.go @@ -28,6 +28,7 @@ func modifyKcpRedisInstance(ctx context.Context, st composed.State) (error, cont state.KcpRedisInstance.Spec.Instance.Aws.CacheNodeType = awsRedisInstance.Spec.CacheNodeType state.KcpRedisInstance.Spec.Instance.Aws.AutoMinorVersionUpgrade = awsRedisInstance.Spec.AutoMinorVersionUpgrade state.KcpRedisInstance.Spec.Instance.Aws.TransitEncryptionEnabled = awsRedisInstance.Spec.TransitEncryptionEnabled + state.KcpRedisInstance.Spec.Instance.Aws.AuthEnabled = awsRedisInstance.Spec.AuthEnabled state.KcpRedisInstance.Spec.Instance.Aws.PreferredMaintenanceWindow = awsRedisInstance.Spec.PreferredMaintenanceWindow err := state.KcpCluster.K8sClient().Update(ctx, state.KcpRedisInstance) diff --git a/pkg/skr/awsredisinstance/state.go b/pkg/skr/awsredisinstance/state.go index 30f2bbef9..c384b1349 100644 --- a/pkg/skr/awsredisinstance/state.go +++ b/pkg/skr/awsredisinstance/state.go @@ -71,11 +71,13 @@ func (s *State) ShouldModifyKcp() bool { areCacheNodeTypesDifferent := s.KcpRedisInstance.Spec.Instance.Aws.CacheNodeType != awsRedisInstance.Spec.CacheNodeType isAutoMinorVersionUpgradeDifferent := s.KcpRedisInstance.Spec.Instance.Aws.AutoMinorVersionUpgrade != awsRedisInstance.Spec.AutoMinorVersionUpgrade isTransitEncryptionEnabledDifferent := s.KcpRedisInstance.Spec.Instance.Aws.TransitEncryptionEnabled != awsRedisInstance.Spec.TransitEncryptionEnabled + isAuthEnabledDifferent := s.KcpRedisInstance.Spec.Instance.Aws.AuthEnabled != awsRedisInstance.Spec.AuthEnabled arePreferredMaintenanceWindowDifferent := ptr.Deref(s.KcpRedisInstance.Spec.Instance.Aws.PreferredMaintenanceWindow, "") != ptr.Deref(awsRedisInstance.Spec.PreferredMaintenanceWindow, "") return areMapsDifferent(s.KcpRedisInstance.Spec.Instance.Aws.Parameters, awsRedisInstance.Spec.Parameters) || areCacheNodeTypesDifferent || isAutoMinorVersionUpgradeDifferent || isTransitEncryptionEnabledDifferent || + isAuthEnabledDifferent || arePreferredMaintenanceWindowDifferent } From c0f16bbddab9790ce5df46cda9fc62412ba85d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Panti=C4=87?= Date: Mon, 9 Sep 2024 11:42:30 +0200 Subject: [PATCH 2/2] chore: typo --- pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go b/pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go index 292f43d72..ce9453c8c 100644 --- a/pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go +++ b/pkg/kcp/provider/aws/redisinstance/deleteUserGroup.go @@ -26,7 +26,7 @@ func deleteUserGroup(ctx context.Context, st composed.State) (error, context.Con err := state.awsClient.DeleteUserGroup(ctx, *state.userGroup.UserGroupId) if err != nil { - return awsmeta.LogErrorAndReturn(err, "Error deleting userGroup secret", ctx) + return awsmeta.LogErrorAndReturn(err, "Error deleting userGroup", ctx) } return composed.StopWithRequeueDelay(util.Timing.T10000ms()), nil