diff --git a/apis/v1alpha1/ack-generate-metadata.yaml b/apis/v1alpha1/ack-generate-metadata.yaml index 6a903c3..6c89682 100755 --- a/apis/v1alpha1/ack-generate-metadata.yaml +++ b/apis/v1alpha1/ack-generate-metadata.yaml @@ -1,13 +1,13 @@ ack_generate_info: - build_date: "2023-09-18T21:10:37Z" - build_hash: 892f29d00a4c4ad21a2fa32919921de18190979d - go_version: go1.20.3 - version: v0.27.1 -api_directory_checksum: 885f952f7ca2ce7a676b9bbf8eb262de71de6238 + build_date: "2023-11-22T07:12:46Z" + build_hash: 1cc9b5172d3d1676af578a3411e8672698ec29ce + go_version: go1.21.4 + version: v0.27.1-5-g1cc9b51-dirty +api_directory_checksum: bd34f72147706f1dbc990acf4a6c4f6615c1bddb api_version: v1alpha1 aws_sdk_go_version: v1.44.93 generator_config_info: - file_checksum: bd93202f4d05393bb2ff98344e8e603e3228cc51 + file_checksum: 3f88502d4b7623890c8eff789285f98fa3d84553 original_file_name: generator.yaml last_modification: reason: API generation diff --git a/apis/v1alpha1/cache_subnet_group.go b/apis/v1alpha1/cache_subnet_group.go index 813498e..0286553 100644 --- a/apis/v1alpha1/cache_subnet_group.go +++ b/apis/v1alpha1/cache_subnet_group.go @@ -40,8 +40,8 @@ type CacheSubnetGroupSpec struct { // +kubebuilder:validation:Required CacheSubnetGroupName *string `json:"cacheSubnetGroupName"` // A list of VPC subnet IDs for the cache subnet group. - // +kubebuilder:validation:Required - SubnetIDs []*string `json:"subnetIDs"` + SubnetIDs []*string `json:"subnetIDs,omitempty"` + SubnetRefs []*ackv1alpha1.AWSResourceReferenceWrapper `json:"subnetRefs,omitempty"` // A list of tags to be added to this resource. A tag is a key-value pair. A // tag key must be accompanied by a tag value, although null is accepted. Tags []*Tag `json:"tags,omitempty"` diff --git a/apis/v1alpha1/generator.yaml b/apis/v1alpha1/generator.yaml index 44a15a6..7ac728f 100644 --- a/apis/v1alpha1/generator.yaml +++ b/apis/v1alpha1/generator.yaml @@ -13,6 +13,11 @@ resources: - InvalidParameterValue - InvalidParameterCombination fields: + SubnetIDs: + references: + service_name: ec2 + resource: Subnet + path: Status.SubnetID Events: is_read_only: true from: @@ -50,6 +55,19 @@ resources: AutomaticFailoverEnabled: compare: is_ignored: true + CacheParameterGroupName: + references: + resource: CacheParameterGroup + path: Spec.CacheParameterGroupName + CacheSubnetGroupName: + references: + resource: CacheSubnetGroup + path: Spec.CacheSubnetGroupName + SecurityGroupIDs: + references: + resource: SecurityGroup + service_name: ec2 + path: Status.ID Events: is_read_only: true from: diff --git a/apis/v1alpha1/replication_group.go b/apis/v1alpha1/replication_group.go index c0cb275..48f9bfb 100644 --- a/apis/v1alpha1/replication_group.go +++ b/apis/v1alpha1/replication_group.go @@ -133,7 +133,8 @@ type ReplicationGroupSpec struct { // - To create a Redis (cluster mode disabled) replication group, use CacheParameterGroupName=default.redis3.2. // // - To create a Redis (cluster mode enabled) replication group, use CacheParameterGroupName=default.redis3.2.cluster.on. - CacheParameterGroupName *string `json:"cacheParameterGroupName,omitempty"` + CacheParameterGroupName *string `json:"cacheParameterGroupName,omitempty"` + CacheParameterGroupRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"cacheParameterGroupRef,omitempty"` // A list of cache security group names to associate with this replication group. CacheSecurityGroupNames []*string `json:"cacheSecurityGroupNames,omitempty"` // The name of the cache subnet group to be used for the replication group. @@ -141,7 +142,8 @@ type ReplicationGroupSpec struct { // If you're going to launch your cluster in an Amazon VPC, you need to create // a subnet group before you start creating a cluster. For more information, // see Subnets and Subnet Groups (https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/SubnetGroups.html). - CacheSubnetGroupName *string `json:"cacheSubnetGroupName,omitempty"` + CacheSubnetGroupName *string `json:"cacheSubnetGroupName,omitempty"` + CacheSubnetGroupRef *ackv1alpha1.AWSResourceReferenceWrapper `json:"cacheSubnetGroupRef,omitempty"` // Enables data tiering. Data tiering is only supported for replication groups // using the r6gd node type. This parameter must be set to true when using r6gd // nodes. For more information, see Data tiering (https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/data-tiering.html). @@ -263,7 +265,8 @@ type ReplicationGroupSpec struct { // // Use this parameter only when you are creating a replication group in an Amazon // Virtual Private Cloud (Amazon VPC). - SecurityGroupIDs []*string `json:"securityGroupIDs,omitempty"` + SecurityGroupIDs []*string `json:"securityGroupIDs,omitempty"` + SecurityGroupRefs []*ackv1alpha1.AWSResourceReferenceWrapper `json:"securityGroupRefs,omitempty"` // A list of Amazon Resource Names (ARN) that uniquely identify the Redis RDB // snapshot files stored in Amazon S3. The snapshot files are used to populate // the new replication group. The Amazon S3 object name in the ARN cannot contain diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index bbe5ee1..5d3605d 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -800,6 +800,17 @@ func (in *CacheSubnetGroupSpec) DeepCopyInto(out *CacheSubnetGroupSpec) { } } } + if in.SubnetRefs != nil { + in, out := &in.SubnetRefs, &out.SubnetRefs + *out = make([]*corev1alpha1.AWSResourceReferenceWrapper, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + } + } if in.Tags != nil { in, out := &in.Tags, &out.Tags *out = make([]*Tag, len(*in)) @@ -2123,6 +2134,11 @@ func (in *ReplicationGroupSpec) DeepCopyInto(out *ReplicationGroupSpec) { *out = new(string) **out = **in } + if in.CacheParameterGroupRef != nil { + in, out := &in.CacheParameterGroupRef, &out.CacheParameterGroupRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } if in.CacheSecurityGroupNames != nil { in, out := &in.CacheSecurityGroupNames, &out.CacheSecurityGroupNames *out = make([]*string, len(*in)) @@ -2139,6 +2155,11 @@ func (in *ReplicationGroupSpec) DeepCopyInto(out *ReplicationGroupSpec) { *out = new(string) **out = **in } + if in.CacheSubnetGroupRef != nil { + in, out := &in.CacheSubnetGroupRef, &out.CacheSubnetGroupRef + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } if in.DataTieringEnabled != nil { in, out := &in.DataTieringEnabled, &out.DataTieringEnabled *out = new(bool) @@ -2248,6 +2269,17 @@ func (in *ReplicationGroupSpec) DeepCopyInto(out *ReplicationGroupSpec) { } } } + if in.SecurityGroupRefs != nil { + in, out := &in.SecurityGroupRefs, &out.SecurityGroupRefs + *out = make([]*corev1alpha1.AWSResourceReferenceWrapper, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(corev1alpha1.AWSResourceReferenceWrapper) + (*in).DeepCopyInto(*out) + } + } + } if in.SnapshotARNs != nil { in, out := &in.SnapshotARNs, &out.SnapshotARNs *out = make([]*string, len(*in)) diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 1adb9ce..f10ec23 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -18,6 +18,7 @@ package main import ( "os" + ec2apitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" ackcfg "github.com/aws-controllers-k8s/runtime/pkg/config" ackrt "github.com/aws-controllers-k8s/runtime/pkg/runtime" @@ -58,6 +59,7 @@ func init() { _ = svctypes.AddToScheme(scheme) _ = ackv1alpha1.AddToScheme(scheme) + _ = ec2apitypes.AddToScheme(scheme) } func main() { diff --git a/config/crd/bases/elasticache.services.k8s.aws_cachesubnetgroups.yaml b/config/crd/bases/elasticache.services.k8s.aws_cachesubnetgroups.yaml index 239672d..f1062f9 100644 --- a/config/crd/bases/elasticache.services.k8s.aws_cachesubnetgroups.yaml +++ b/config/crd/bases/elasticache.services.k8s.aws_cachesubnetgroups.yaml @@ -50,6 +50,22 @@ spec: items: type: string type: array + subnetRefs: + items: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using 'from' field Ex: APIIDRef: \n from: name: + my-api" + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + type: array tags: description: A list of tags to be added to this resource. A tag is a key-value pair. A tag key must be accompanied by a tag value, @@ -72,7 +88,6 @@ spec: required: - cacheSubnetGroupDescription - cacheSubnetGroupName - - subnetIDs type: object status: description: CacheSubnetGroupStatus defines the observed state of CacheSubnetGroup diff --git a/config/crd/bases/elasticache.services.k8s.aws_replicationgroups.yaml b/config/crd/bases/elasticache.services.k8s.aws_replicationgroups.yaml index da9dbed..b74f11b 100644 --- a/config/crd/bases/elasticache.services.k8s.aws_replicationgroups.yaml +++ b/config/crd/bases/elasticache.services.k8s.aws_replicationgroups.yaml @@ -140,6 +140,19 @@ spec: \n * To create a Redis (cluster mode enabled) replication group, use CacheParameterGroupName=default.redis3.2.cluster.on." type: string + cacheParameterGroupRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using 'from' field Ex: APIIDRef: \n from: name: my-api" + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object cacheSecurityGroupNames: description: A list of cache security group names to associate with this replication group. @@ -153,6 +166,19 @@ spec: creating a cluster. For more information, see Subnets and Subnet Groups (https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/SubnetGroups.html)." type: string + cacheSubnetGroupRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using 'from' field Ex: APIIDRef: \n from: name: my-api" + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object dataTieringEnabled: description: Enables data tiering. Data tiering is only supported for replication groups using the r6gd node type. This parameter @@ -326,6 +352,22 @@ spec: items: type: string type: array + securityGroupRefs: + items: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using 'from' field Ex: APIIDRef: \n from: name: + my-api" + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + type: array snapshotARNs: description: "A list of Amazon Resource Names (ARN) that uniquely identify the Redis RDB snapshot files stored in Amazon S3. The snapshot diff --git a/config/rbac/cluster-role-controller.yaml b/config/rbac/cluster-role-controller.yaml index 220b950..34b9b70 100644 --- a/config/rbac/cluster-role-controller.yaml +++ b/config/rbac/cluster-role-controller.yaml @@ -31,6 +31,34 @@ rules: - list - patch - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - securitygroups + verbs: + - get + - list +- apiGroups: + - ec2.services.k8s.aws + resources: + - securitygroups/status + verbs: + - get + - list +- apiGroups: + - ec2.services.k8s.aws + resources: + - subnets + verbs: + - get + - list +- apiGroups: + - ec2.services.k8s.aws + resources: + - subnets/status + verbs: + - get + - list - apiGroups: - elasticache.services.k8s.aws resources: diff --git a/generator.yaml b/generator.yaml index 44a15a6..7ac728f 100644 --- a/generator.yaml +++ b/generator.yaml @@ -13,6 +13,11 @@ resources: - InvalidParameterValue - InvalidParameterCombination fields: + SubnetIDs: + references: + service_name: ec2 + resource: Subnet + path: Status.SubnetID Events: is_read_only: true from: @@ -50,6 +55,19 @@ resources: AutomaticFailoverEnabled: compare: is_ignored: true + CacheParameterGroupName: + references: + resource: CacheParameterGroup + path: Spec.CacheParameterGroupName + CacheSubnetGroupName: + references: + resource: CacheSubnetGroup + path: Spec.CacheSubnetGroupName + SecurityGroupIDs: + references: + resource: SecurityGroup + service_name: ec2 + path: Status.ID Events: is_read_only: true from: diff --git a/go.mod b/go.mod index f2260b5..0891e6f 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( ) require ( + github.com/aws-controllers-k8s/ec2-controller v1.0.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect diff --git a/go.sum b/go.sum index dc3bf48..c056a5a 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/aws-controllers-k8s/ec2-controller v1.0.7 h1:7MDu2bq8NFKbgzzgHYPFRT7bf+SmTchgWuq8ixKK0Jc= +github.com/aws-controllers-k8s/ec2-controller v1.0.7/go.mod h1:PvsQehgncHgcu9FiY13M45+GkVsKI98g7G83SrgH7vY= github.com/aws-controllers-k8s/runtime v0.27.1 h1:tvJRQDioBFkob0kF4DwgS7MsoXZKwkG5QCHWxFEh+2o= github.com/aws-controllers-k8s/runtime v0.27.1/go.mod h1:oSCqCzbzJLUrzv+cx4TIxCuSUvL75ABJmhxBc87IRqc= github.com/aws/aws-sdk-go v1.44.93 h1:hAgd9fuaptBatSft27/5eBMdcA8+cIMqo96/tZ6rKl8= diff --git a/helm/crds/elasticache.services.k8s.aws_cachesubnetgroups.yaml b/helm/crds/elasticache.services.k8s.aws_cachesubnetgroups.yaml index ddf6f43..da3e7af 100644 --- a/helm/crds/elasticache.services.k8s.aws_cachesubnetgroups.yaml +++ b/helm/crds/elasticache.services.k8s.aws_cachesubnetgroups.yaml @@ -50,6 +50,22 @@ spec: items: type: string type: array + subnetRefs: + items: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using 'from' field Ex: APIIDRef: \n from: name: + my-api" + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + type: array tags: description: A list of tags to be added to this resource. A tag is a key-value pair. A tag key must be accompanied by a tag value, @@ -72,7 +88,6 @@ spec: required: - cacheSubnetGroupDescription - cacheSubnetGroupName - - subnetIDs type: object status: description: CacheSubnetGroupStatus defines the observed state of CacheSubnetGroup diff --git a/helm/crds/elasticache.services.k8s.aws_replicationgroups.yaml b/helm/crds/elasticache.services.k8s.aws_replicationgroups.yaml index 2b805e5..6e6e17a 100644 --- a/helm/crds/elasticache.services.k8s.aws_replicationgroups.yaml +++ b/helm/crds/elasticache.services.k8s.aws_replicationgroups.yaml @@ -140,6 +140,19 @@ spec: \n - To create a Redis (cluster mode enabled) replication group, use CacheParameterGroupName=default.redis3.2.cluster.on." type: string + cacheParameterGroupRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using 'from' field Ex: APIIDRef: \n from: name: my-api" + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object cacheSecurityGroupNames: description: A list of cache security group names to associate with this replication group. @@ -153,6 +166,19 @@ spec: creating a cluster. For more information, see Subnets and Subnet Groups (https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/SubnetGroups.html)." type: string + cacheSubnetGroupRef: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using 'from' field Ex: APIIDRef: \n from: name: my-api" + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object dataTieringEnabled: description: Enables data tiering. Data tiering is only supported for replication groups using the r6gd node type. This parameter @@ -326,6 +352,22 @@ spec: items: type: string type: array + securityGroupRefs: + items: + description: "AWSResourceReferenceWrapper provides a wrapper around + *AWSResourceReference type to provide more user friendly syntax + for references using 'from' field Ex: APIIDRef: \n from: name: + my-api" + properties: + from: + description: AWSResourceReference provides all the values necessary + to reference another k8s resource for finding the identifier(Id/ARN/Name) + properties: + name: + type: string + type: object + type: object + type: array snapshotARNs: description: "A list of Amazon Resource Names (ARN) that uniquely identify the Redis RDB snapshot files stored in Amazon S3. The snapshot diff --git a/helm/templates/cluster-role-controller.yaml b/helm/templates/cluster-role-controller.yaml index 4b59a55..a7ad81c 100644 --- a/helm/templates/cluster-role-controller.yaml +++ b/helm/templates/cluster-role-controller.yaml @@ -46,6 +46,34 @@ rules: - list - patch - watch +- apiGroups: + - ec2.services.k8s.aws + resources: + - securitygroups + verbs: + - get + - list +- apiGroups: + - ec2.services.k8s.aws + resources: + - securitygroups/status + verbs: + - get + - list +- apiGroups: + - ec2.services.k8s.aws + resources: + - subnets + verbs: + - get + - list +- apiGroups: + - ec2.services.k8s.aws + resources: + - subnets/status + verbs: + - get + - list - apiGroups: - elasticache.services.k8s.aws resources: diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index f40b5f2..cea38ec 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -123,7 +123,7 @@ spec: readOnly: true {{- end }} {{- if .Values.deployment.extraVolumeMounts -}} - {{ toYaml .Values.deployment.extraVolumeMounts | nindent 12 }} + {{ toYaml .Values.deployment.extraVolumeMounts | nindent 10 }} {{- end }} securityContext: allowPrivilegeEscalation: false @@ -151,11 +151,11 @@ spec: hostNetwork: {{ .Values.deployment.hostNetwork }} dnsPolicy: {{ .Values.deployment.dnsPolicy }} volumes: - {{- if .Values.aws.credentials.secretName -}} + {{- if .Values.aws.credentials.secretName }} - name: {{ .Values.aws.credentials.secretName }} secret: secretName: {{ .Values.aws.credentials.secretName }} - {{ end -}} + {{- end }} {{- if .Values.deployment.extraVolumes }} {{ toYaml .Values.deployment.extraVolumes | indent 8}} {{- end }} diff --git a/pkg/resource/cache_subnet_group/delta.go b/pkg/resource/cache_subnet_group/delta.go index d278389..6c93e24 100644 --- a/pkg/resource/cache_subnet_group/delta.go +++ b/pkg/resource/cache_subnet_group/delta.go @@ -60,6 +60,9 @@ func newResourceDelta( if !ackcompare.SliceStringPEqual(a.ko.Spec.SubnetIDs, b.ko.Spec.SubnetIDs) { delta.Add("Spec.SubnetIDs", a.ko.Spec.SubnetIDs, b.ko.Spec.SubnetIDs) } + if !reflect.DeepEqual(a.ko.Spec.SubnetRefs, b.ko.Spec.SubnetRefs) { + delta.Add("Spec.SubnetRefs", a.ko.Spec.SubnetRefs, b.ko.Spec.SubnetRefs) + } if !ackcompare.MapStringStringEqual(ToACKTags(a.ko.Spec.Tags), ToACKTags(b.ko.Spec.Tags)) { delta.Add("Spec.Tags", a.ko.Spec.Tags, b.ko.Spec.Tags) } diff --git a/pkg/resource/cache_subnet_group/references.go b/pkg/resource/cache_subnet_group/references.go index 0450bcd..bc719a3 100644 --- a/pkg/resource/cache_subnet_group/references.go +++ b/pkg/resource/cache_subnet_group/references.go @@ -17,13 +17,23 @@ package cache_subnet_group import ( "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + ec2apitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" svcapitypes "github.com/aws-controllers-k8s/elasticache-controller/apis/v1alpha1" ) +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=subnets,verbs=get;list +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=subnets/status,verbs=get;list + // ClearResolvedReferences removes any reference values that were made // concrete in the spec. It returns a copy of the input AWSResource which // contains the original *Ref values, but none of their respective concrete @@ -31,6 +41,10 @@ import ( func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { ko := rm.concreteResource(res).ko.DeepCopy() + if len(ko.Spec.SubnetRefs) > 0 { + ko.Spec.SubnetIDs = nil + } + return &resource{ko} } @@ -46,11 +60,111 @@ func (rm *resourceManager) ResolveReferences( apiReader client.Reader, res acktypes.AWSResource, ) (acktypes.AWSResource, bool, error) { - return res, false, nil + namespace := res.MetaObject().GetNamespace() + ko := rm.concreteResource(res).ko + + resourceHasReferences := false + err := validateReferenceFields(ko) + if fieldHasReferences, err := rm.resolveReferenceForSubnetIDs(ctx, apiReader, namespace, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + return &resource{ko}, resourceHasReferences, err } // validateReferenceFields validates the reference field and corresponding // identifier field. func validateReferenceFields(ko *svcapitypes.CacheSubnetGroup) error { + + if len(ko.Spec.SubnetRefs) > 0 && len(ko.Spec.SubnetIDs) > 0 { + return ackerr.ResourceReferenceAndIDNotSupportedFor("SubnetIDs", "SubnetRefs") + } + if len(ko.Spec.SubnetRefs) == 0 && len(ko.Spec.SubnetIDs) == 0 { + return ackerr.ResourceReferenceOrIDRequiredFor("SubnetIDs", "SubnetRefs") + } + return nil +} + +// resolveReferenceForSubnetIDs reads the resource referenced +// from SubnetRefs field and sets the SubnetIDs +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForSubnetIDs( + ctx context.Context, + apiReader client.Reader, + namespace string, + ko *svcapitypes.CacheSubnetGroup, +) (hasReferences bool, err error) { + for _, f0iter := range ko.Spec.SubnetRefs { + if f0iter != nil && f0iter.From != nil { + hasReferences = true + arr := f0iter.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: SubnetRefs") + } + obj := &ec2apitypes.Subnet{} + if err := getReferencedResourceState_Subnet(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + if ko.Spec.SubnetIDs == nil { + ko.Spec.SubnetIDs = make([]*string, 0, 1) + } + ko.Spec.SubnetIDs = append(ko.Spec.SubnetIDs, (*string)(obj.Status.SubnetID)) + } + } + + return hasReferences, nil +} + +// getReferencedResourceState_Subnet looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_Subnet( + ctx context.Context, + apiReader client.Reader, + obj *ec2apitypes.Subnet, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceSynced, refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "Subnet", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "Subnet", + namespace, name) + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "Subnet", + namespace, name) + } + if obj.Status.SubnetID == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "Subnet", + namespace, name, + "Status.SubnetID") + } return nil } diff --git a/pkg/resource/replication_group/delta.go b/pkg/resource/replication_group/delta.go index a7ea05a..cd06970 100644 --- a/pkg/resource/replication_group/delta.go +++ b/pkg/resource/replication_group/delta.go @@ -71,6 +71,9 @@ func newResourceDelta( delta.Add("Spec.CacheParameterGroupName", a.ko.Spec.CacheParameterGroupName, b.ko.Spec.CacheParameterGroupName) } } + if !reflect.DeepEqual(a.ko.Spec.CacheParameterGroupRef, b.ko.Spec.CacheParameterGroupRef) { + delta.Add("Spec.CacheParameterGroupRef", a.ko.Spec.CacheParameterGroupRef, b.ko.Spec.CacheParameterGroupRef) + } if !ackcompare.SliceStringPEqual(a.ko.Spec.CacheSecurityGroupNames, b.ko.Spec.CacheSecurityGroupNames) { delta.Add("Spec.CacheSecurityGroupNames", a.ko.Spec.CacheSecurityGroupNames, b.ko.Spec.CacheSecurityGroupNames) } @@ -81,6 +84,9 @@ func newResourceDelta( delta.Add("Spec.CacheSubnetGroupName", a.ko.Spec.CacheSubnetGroupName, b.ko.Spec.CacheSubnetGroupName) } } + if !reflect.DeepEqual(a.ko.Spec.CacheSubnetGroupRef, b.ko.Spec.CacheSubnetGroupRef) { + delta.Add("Spec.CacheSubnetGroupRef", a.ko.Spec.CacheSubnetGroupRef, b.ko.Spec.CacheSubnetGroupRef) + } if ackcompare.HasNilDifference(a.ko.Spec.DataTieringEnabled, b.ko.Spec.DataTieringEnabled) { delta.Add("Spec.DataTieringEnabled", a.ko.Spec.DataTieringEnabled, b.ko.Spec.DataTieringEnabled) } else if a.ko.Spec.DataTieringEnabled != nil && b.ko.Spec.DataTieringEnabled != nil { @@ -167,6 +173,9 @@ func newResourceDelta( if !ackcompare.SliceStringPEqual(a.ko.Spec.SecurityGroupIDs, b.ko.Spec.SecurityGroupIDs) { delta.Add("Spec.SecurityGroupIDs", a.ko.Spec.SecurityGroupIDs, b.ko.Spec.SecurityGroupIDs) } + if !reflect.DeepEqual(a.ko.Spec.SecurityGroupRefs, b.ko.Spec.SecurityGroupRefs) { + delta.Add("Spec.SecurityGroupRefs", a.ko.Spec.SecurityGroupRefs, b.ko.Spec.SecurityGroupRefs) + } if !ackcompare.SliceStringPEqual(a.ko.Spec.SnapshotARNs, b.ko.Spec.SnapshotARNs) { delta.Add("Spec.SnapshotARNs", a.ko.Spec.SnapshotARNs, b.ko.Spec.SnapshotARNs) } diff --git a/pkg/resource/replication_group/references.go b/pkg/resource/replication_group/references.go index 2486dcc..10a6261 100644 --- a/pkg/resource/replication_group/references.go +++ b/pkg/resource/replication_group/references.go @@ -17,13 +17,23 @@ package replication_group import ( "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + ec2apitypes "github.com/aws-controllers-k8s/ec2-controller/apis/v1alpha1" + ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1" + ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors" acktypes "github.com/aws-controllers-k8s/runtime/pkg/types" svcapitypes "github.com/aws-controllers-k8s/elasticache-controller/apis/v1alpha1" ) +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=securitygroups,verbs=get;list +// +kubebuilder:rbac:groups=ec2.services.k8s.aws,resources=securitygroups/status,verbs=get;list + // ClearResolvedReferences removes any reference values that were made // concrete in the spec. It returns a copy of the input AWSResource which // contains the original *Ref values, but none of their respective concrete @@ -31,6 +41,18 @@ import ( func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource { ko := rm.concreteResource(res).ko.DeepCopy() + if ko.Spec.CacheParameterGroupRef != nil { + ko.Spec.CacheParameterGroupName = nil + } + + if ko.Spec.CacheSubnetGroupRef != nil { + ko.Spec.CacheSubnetGroupName = nil + } + + if len(ko.Spec.SecurityGroupRefs) > 0 { + ko.Spec.SecurityGroupIDs = nil + } + return &resource{ko} } @@ -46,11 +68,282 @@ func (rm *resourceManager) ResolveReferences( apiReader client.Reader, res acktypes.AWSResource, ) (acktypes.AWSResource, bool, error) { - return res, false, nil + namespace := res.MetaObject().GetNamespace() + ko := rm.concreteResource(res).ko + + resourceHasReferences := false + err := validateReferenceFields(ko) + if fieldHasReferences, err := rm.resolveReferenceForCacheParameterGroupName(ctx, apiReader, namespace, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + if fieldHasReferences, err := rm.resolveReferenceForCacheSubnetGroupName(ctx, apiReader, namespace, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + if fieldHasReferences, err := rm.resolveReferenceForSecurityGroupIDs(ctx, apiReader, namespace, ko); err != nil { + return &resource{ko}, (resourceHasReferences || fieldHasReferences), err + } else { + resourceHasReferences = resourceHasReferences || fieldHasReferences + } + + return &resource{ko}, resourceHasReferences, err } // validateReferenceFields validates the reference field and corresponding // identifier field. func validateReferenceFields(ko *svcapitypes.ReplicationGroup) error { + + if ko.Spec.CacheParameterGroupRef != nil && ko.Spec.CacheParameterGroupName != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("CacheParameterGroupName", "CacheParameterGroupRef") + } + + if ko.Spec.CacheSubnetGroupRef != nil && ko.Spec.CacheSubnetGroupName != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("CacheSubnetGroupName", "CacheSubnetGroupRef") + } + + if len(ko.Spec.SecurityGroupRefs) > 0 && len(ko.Spec.SecurityGroupIDs) > 0 { + return ackerr.ResourceReferenceAndIDNotSupportedFor("SecurityGroupIDs", "SecurityGroupRefs") + } + return nil +} + +// resolveReferenceForCacheParameterGroupName reads the resource referenced +// from CacheParameterGroupRef field and sets the CacheParameterGroupName +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForCacheParameterGroupName( + ctx context.Context, + apiReader client.Reader, + namespace string, + ko *svcapitypes.ReplicationGroup, +) (hasReferences bool, err error) { + if ko.Spec.CacheParameterGroupRef != nil && ko.Spec.CacheParameterGroupRef.From != nil { + hasReferences = true + arr := ko.Spec.CacheParameterGroupRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: CacheParameterGroupRef") + } + obj := &svcapitypes.CacheParameterGroup{} + if err := getReferencedResourceState_CacheParameterGroup(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.CacheParameterGroupName = (*string)(obj.Spec.CacheParameterGroupName) + } + + return hasReferences, nil +} + +// getReferencedResourceState_CacheParameterGroup looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_CacheParameterGroup( + ctx context.Context, + apiReader client.Reader, + obj *svcapitypes.CacheParameterGroup, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceSynced, refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "CacheParameterGroup", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "CacheParameterGroup", + namespace, name) + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "CacheParameterGroup", + namespace, name) + } + if obj.Spec.CacheParameterGroupName == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "CacheParameterGroup", + namespace, name, + "Spec.CacheParameterGroupName") + } + return nil +} + +// resolveReferenceForCacheSubnetGroupName reads the resource referenced +// from CacheSubnetGroupRef field and sets the CacheSubnetGroupName +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForCacheSubnetGroupName( + ctx context.Context, + apiReader client.Reader, + namespace string, + ko *svcapitypes.ReplicationGroup, +) (hasReferences bool, err error) { + if ko.Spec.CacheSubnetGroupRef != nil && ko.Spec.CacheSubnetGroupRef.From != nil { + hasReferences = true + arr := ko.Spec.CacheSubnetGroupRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: CacheSubnetGroupRef") + } + obj := &svcapitypes.CacheSubnetGroup{} + if err := getReferencedResourceState_CacheSubnetGroup(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.CacheSubnetGroupName = (*string)(obj.Spec.CacheSubnetGroupName) + } + + return hasReferences, nil +} + +// getReferencedResourceState_CacheSubnetGroup looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_CacheSubnetGroup( + ctx context.Context, + apiReader client.Reader, + obj *svcapitypes.CacheSubnetGroup, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceSynced, refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "CacheSubnetGroup", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "CacheSubnetGroup", + namespace, name) + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "CacheSubnetGroup", + namespace, name) + } + if obj.Spec.CacheSubnetGroupName == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "CacheSubnetGroup", + namespace, name, + "Spec.CacheSubnetGroupName") + } + return nil +} + +// resolveReferenceForSecurityGroupIDs reads the resource referenced +// from SecurityGroupRefs field and sets the SecurityGroupIDs +// from referenced resource. Returns a boolean indicating whether a reference +// contains references, or an error +func (rm *resourceManager) resolveReferenceForSecurityGroupIDs( + ctx context.Context, + apiReader client.Reader, + namespace string, + ko *svcapitypes.ReplicationGroup, +) (hasReferences bool, err error) { + for _, f0iter := range ko.Spec.SecurityGroupRefs { + if f0iter != nil && f0iter.From != nil { + hasReferences = true + arr := f0iter.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: SecurityGroupRefs") + } + obj := &ec2apitypes.SecurityGroup{} + if err := getReferencedResourceState_SecurityGroup(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + if ko.Spec.SecurityGroupIDs == nil { + ko.Spec.SecurityGroupIDs = make([]*string, 0, 1) + } + ko.Spec.SecurityGroupIDs = append(ko.Spec.SecurityGroupIDs, (*string)(obj.Status.ID)) + } + } + + return hasReferences, nil +} + +// getReferencedResourceState_SecurityGroup looks up whether a referenced resource +// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is +// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or +// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state. +func getReferencedResourceState_SecurityGroup( + ctx context.Context, + apiReader client.Reader, + obj *ec2apitypes.SecurityGroup, + name string, // the Kubernetes name of the referenced resource + namespace string, // the Kubernetes namespace of the referenced resource +) error { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := apiReader.Get(ctx, namespacedName, obj) + if err != nil { + return err + } + var refResourceSynced, refResourceTerminal bool + for _, cond := range obj.Status.Conditions { + if cond.Type == ackv1alpha1.ConditionTypeResourceSynced && + cond.Status == corev1.ConditionTrue { + refResourceSynced = true + } + if cond.Type == ackv1alpha1.ConditionTypeTerminal && + cond.Status == corev1.ConditionTrue { + return ackerr.ResourceReferenceTerminalFor( + "SecurityGroup", + namespace, name) + } + } + if refResourceTerminal { + return ackerr.ResourceReferenceTerminalFor( + "SecurityGroup", + namespace, name) + } + if !refResourceSynced { + return ackerr.ResourceReferenceNotSyncedFor( + "SecurityGroup", + namespace, name) + } + if obj.Status.ID == nil { + return ackerr.ResourceReferenceMissingTargetFieldFor( + "SecurityGroup", + namespace, name, + "Status.ID") + } return nil }