diff --git a/docs/guide/ingress/annotations.md b/docs/guide/ingress/annotations.md index 54c26079b..768d773fe 100644 --- a/docs/guide/ingress/annotations.md +++ b/docs/guide/ingress/annotations.md @@ -85,7 +85,8 @@ You can add annotations to kubernetes Ingress and Service objects to customize t | [alb.ingress.kubernetes.io/frontend-nlb-healthcheck-unhealthy-threshold-count](#frontend-nlb-healthcheck-unhealthy-threshold-count) | integer |3| Ingress | N/A | | [alb.ingress.kubernetes.io/frontend-nlb-healthcheck-success-codes](#frontend-nlb-healthcheck-success-codes) | string |200| Ingress | N/A | | [alb.ingress.kubernetes.io/frontend-nlb-tags](#frontend-nlb-tags) | stringMap | N/A | Ingress | Exclusive | -| [alb.ingress.kubernetes.io/frontend-nlb-eip-allocations](#frontend-nlb-eip-allocations) | stringList |200| Ingress | N/A | +| [alb.ingress.kubernetes.io/frontend-nlb-eip-allocation](#frontend-nlb-eip-allocation) | stringList |N/A| Ingress | N/A | +| [alb.ingress.kubernetes.io/frontend-nlb-attributes](#frontend-nlb-attributes) | stringList |N/A| Ingress | N/A | ## IngressGroup IngressGroup feature enables you to group multiple Ingress resources together. @@ -1355,3 +1356,30 @@ When this option is set to true, the controller will automatically provision a N ``` alb.ingress.kubernetes.io/frontend-nlb-eip-allocations: eipalloc-xyz, eipalloc-zzz ``` + +- `alb.ingress.kubernetes.io/frontend-nlb-attributes` specifies [Load Balancer Attributes](http://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_LoadBalancerAttribute.html) that should be applied to the ALB. + + !!!warning "" + Only attributes defined in the annotation will be updated. To unset any AWS defaults(e.g. Disabling access logs after having them enabled once), the values need to be explicitly set to the original values(`access_logs.s3.enabled=false`) and omitting them is not sufficient. + + !!!note "" + - If `deletion_protection.enabled=true` is in annotation, the controller will not be able to delete the ALB during reconciliation. Once the attribute gets edited to `deletion_protection.enabled=false` during reconciliation, the deployer will force delete the resource. + - Please note, if the deletion protection is not enabled via annotation (e.g. via AWS console), the controller still deletes the underlying resource. + + !!!example + - enable access log to s3 + ``` + service.beta.kubernetes.io/aws-load-balancer-attributes: access_logs.s3.enabled=true,access_logs.s3.bucket=my-access-log-bucket,access_logs.s3.prefix=my-app + ``` + - enable NLB deletion protection + ``` + service.beta.kubernetes.io/aws-load-balancer-attributes: deletion_protection.enabled=true + ``` + - enable cross zone load balancing + ``` + service.beta.kubernetes.io/aws-load-balancer-attributes: load_balancing.cross_zone.enabled=true + ``` + - enable client availability zone affinity + ``` + service.beta.kubernetes.io/aws-load-balancer-attributes: dns_record.client_routing_policy=availability_zone_affinity + ``` \ No newline at end of file diff --git a/pkg/annotations/constants.go b/pkg/annotations/constants.go index 09863b028..55c18012a 100644 --- a/pkg/annotations/constants.go +++ b/pkg/annotations/constants.go @@ -65,7 +65,7 @@ const ( IngressSuffixFrontendNlbSubnets = "frontend-nlb-subnets" IngressSuffixFrontendNlbSecurityGroups = "frontend-nlb-security-groups" IngressSuffixFrontendNlbListenerPortMapping = "frontend-nlb-listener-port-mapping" - IngressSuffixFrontendNlbEipAlloactions = "frontend-nlb-eip-allocations" + IngressSuffixFrontendNlbEipAllocations = "frontend-nlb-eip-allocations" IngressSuffixFrontendNlbHealthCheckPort = "frontend-nlb-healthcheck-port" IngressSuffixFrontendNlbHealthCheckProtocol = "frontend-nlb-healthcheck-protocol" IngressSuffixFrontendNlbHealthCheckPath = "frontend-nlb-healthcheck-path" @@ -74,6 +74,7 @@ const ( IngressSuffixFrontendNlbHealthCheckHealthyThresholdCount = "frontend-nlb-healthcheck-healthy-threshold-count" IngressSuffixFrontendNlHealthCheckbUnhealthyThresholdCount = "frontend-nlb-healthcheck-unhealthy-threshold-count" IngressSuffixFrontendNlbHealthCheckSuccessCodes = "frontend-nlb-healthcheck-success-codes" + IngressSuffixFrontendNlbAttributes = "frontend-nlb-attributes" IngressSuffixFrontendNlbTags = "frontend-nlb-tags" IngressSuffixUseRegexPathMatch = "use-regex-path-match" diff --git a/pkg/ingress/model_build_frontend_nlb.go b/pkg/ingress/model_build_frontend_nlb.go index 1ef2a9aba..aea82214f 100644 --- a/pkg/ingress/model_build_frontend_nlb.go +++ b/pkg/ingress/model_build_frontend_nlb.go @@ -8,6 +8,7 @@ import ( "strconv" "sigs.k8s.io/aws-load-balancer-controller/pkg/shared_constants" + "sigs.k8s.io/aws-load-balancer-controller/pkg/shared_utils" awssdk "github.com/aws/aws-sdk-go-v2/aws" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" @@ -161,7 +162,7 @@ func (t *defaultModelBuildTask) buildFrontendNlbSubnetMappings(ctx context.Conte explicitSubnetNameOrIDsList = append(explicitSubnetNameOrIDsList, rawSubnetNameOrIDs) } var rawEIP []string - if exists := t.annotationParser.ParseStringSliceAnnotation(annotations.IngressSuffixFrontendNlbEipAlloactions, &rawEIP, member.Ing.Annotations); exists { + if exists := t.annotationParser.ParseStringSliceAnnotation(annotations.IngressSuffixFrontendNlbEipAllocations, &rawEIP, member.Ing.Annotations); exists { eipAllocationsList = append(eipAllocationsList, rawEIP) } } @@ -211,6 +212,45 @@ func (t *defaultModelBuildTask) buildFrontendNlb(ctx context.Context, scheme elb return nil } +func (t *defaultModelBuildTask) buildFrontendNlbAttributes() ([]elbv2model.LoadBalancerAttribute, error) { + loadBalancerAttributes, err := t.getFrontendNlbAttributes() + if err != nil { + return []elbv2model.LoadBalancerAttribute{}, err + } + return shared_utils.MakeAttributesSliceFromMap(loadBalancerAttributes), nil +} + +func (t *defaultModelBuildTask) getFrontendNlbAttributes() (map[string]string, error) { + var chosenAttributes map[string]string + for _, member := range t.ingGroup.Members { + var attributes map[string]string + if _, err := t.annotationParser.ParseStringMapAnnotation(annotations.IngressSuffixFrontendNlbAttributes, &attributes, member.Ing.Annotations); err != nil { + return nil, err + } + if chosenAttributes == nil { + chosenAttributes = attributes + } else { + if !cmp.Equal(chosenAttributes, attributes) { + return nil, errors.Errorf("conflicting frontend NLB attributes: %v | %v", chosenAttributes, attributes) + } + } + } + + dnsRecordClientRoutingPolicy, exists := chosenAttributes[shared_constants.LBAttributeLoadBalancingDnsClientRoutingPolicy] + if exists { + switch dnsRecordClientRoutingPolicy { + case shared_constants.LBAttributeAvailabilityZoneAffinity: + case shared_constants.LBAttributePartialAvailabilityZoneAffinity: + case shared_constants.LBAttributeAnyAvailabilityZone: + default: + return nil, errors.Errorf("invalid dns_record.client_routing_policy set in annotation %s: got '%s' expected one of ['%s', '%s', '%s']", + annotations.SvcLBSuffixLoadBalancerAttributes, dnsRecordClientRoutingPolicy, + shared_constants.LBAttributeAnyAvailabilityZone, shared_constants.LBAttributePartialAvailabilityZoneAffinity, shared_constants.LBAttributeAvailabilityZoneAffinity) + } + } + return chosenAttributes, nil +} + func (t *defaultModelBuildTask) buildFrontendNlbSpec(ctx context.Context, scheme elbv2model.LoadBalancerScheme, alb *elbv2model.LoadBalancer) (elbv2model.LoadBalancerSpec, error) { securityGroups, err := t.buildFrontendNlbSecurityGroups(ctx) @@ -238,14 +278,20 @@ func (t *defaultModelBuildTask) buildFrontendNlbSpec(ctx context.Context, scheme return elbv2model.LoadBalancerSpec{}, err } + lbAttributes, err := t.buildFrontendNlbAttributes() + if err != nil { + return elbv2model.LoadBalancerSpec{}, err + } + spec := elbv2model.LoadBalancerSpec{ - Name: name, - Type: elbv2model.LoadBalancerTypeNetwork, - Scheme: scheme, - IPAddressType: alb.Spec.IPAddressType, - SecurityGroups: securityGroups, - SubnetMappings: subnetMappings, - Tags: tags, + Name: name, + Type: elbv2model.LoadBalancerTypeNetwork, + Scheme: scheme, + IPAddressType: alb.Spec.IPAddressType, + LoadBalancerAttributes: lbAttributes, + SecurityGroups: securityGroups, + SubnetMappings: subnetMappings, + Tags: tags, } return spec, nil @@ -808,7 +854,7 @@ func buildFrontendNlbResourceID(resourceType string, protocol elbv2model.Protoco if port != nil && protocol != "" { return fmt.Sprintf("FrontendNlb-%s-%v-%v", resourceType, protocol, *port) } - return fmt.Sprintf("FrontendNlb") + return "FrontendNlb" } func mergeHealthCheckField[T comparable](fieldName string, finalValue **T, currentValue *T, explicit map[string]bool, explicitFields map[string]bool, configIndex int) error { diff --git a/pkg/ingress/model_build_frontend_nlb_test.go b/pkg/ingress/model_build_frontend_nlb_test.go index c7bde65ae..409e94b79 100644 --- a/pkg/ingress/model_build_frontend_nlb_test.go +++ b/pkg/ingress/model_build_frontend_nlb_test.go @@ -1347,3 +1347,257 @@ func Test_defaultModelBuildTask_buildFrontendNlbListeners(t *testing.T) { }) } } + +func Test_defaultModelBuildTask_getFrontendNlbAttributes(t *testing.T) { + tests := []struct { + name string + ingGroup Group + wantAttributes map[string]string + wantErr bool + expectedErrMsg string + }{ + { + name: "no attributes specified", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + }, + }, + }, + }, + }, + wantAttributes: nil, + wantErr: false, + }, + { + name: "valid DNS client routing policy - availability_zone_affinity", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=availability_zone_affinity", + }, + }, + }, + }, + }, + }, + wantAttributes: map[string]string{ + "dns_record.client_routing_policy": "availability_zone_affinity", + }, + wantErr: false, + }, + { + name: "valid DNS client routing policy - partial_availability_zone_affinity", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=partial_availability_zone_affinity", + }, + }, + }, + }, + }, + }, + wantAttributes: map[string]string{ + "dns_record.client_routing_policy": "partial_availability_zone_affinity", + }, + wantErr: false, + }, + { + name: "valid DNS client routing policy - any_availability_zone", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=any_availability_zone", + }, + }, + }, + }, + }, + }, + wantAttributes: map[string]string{ + "dns_record.client_routing_policy": "any_availability_zone", + }, + wantErr: false, + }, + { + name: "invalid DNS client routing policy", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=invalid_policy", + }, + }, + }, + }, + }, + }, + wantAttributes: nil, + wantErr: true, + expectedErrMsg: "invalid dns_record.client_routing_policy set in annotation", + }, + { + name: "multiple attributes with valid DNS policy", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=availability_zone_affinity,cross_zone.enabled=true", + }, + }, + }, + }, + }, + }, + wantAttributes: map[string]string{ + "dns_record.client_routing_policy": "availability_zone_affinity", + "cross_zone.enabled": "true", + }, + wantErr: false, + }, + { + name: "consistent attributes across multiple ingresses", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=availability_zone_affinity", + }, + }, + }, + }, + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress2", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=availability_zone_affinity", + }, + }, + }, + }, + }, + }, + wantAttributes: map[string]string{ + "dns_record.client_routing_policy": "availability_zone_affinity", + }, + wantErr: false, + }, + { + name: "conflicting attributes across multiple ingresses", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=availability_zone_affinity", + }, + }, + }, + }, + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress2", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=any_availability_zone", + }, + }, + }, + }, + }, + }, + wantAttributes: nil, + wantErr: true, + expectedErrMsg: "conflicting frontend NLB attributes", + }, + { + name: "mixed ingresses - some with attributes, some without", + ingGroup: Group{ + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress1", + Namespace: "default", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/frontend-nlb-attributes": "dns_record.client_routing_policy=availability_zone_affinity", + }, + }, + }, + }, + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress2", + Namespace: "default", + }, + }, + }, + }, + }, + wantAttributes: nil, + wantErr: true, + expectedErrMsg: "conflicting frontend NLB attributes", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parser := annotations.NewSuffixAnnotationParser("alb.ingress.kubernetes.io") + task := &defaultModelBuildTask{ + annotationParser: parser, + ingGroup: tt.ingGroup, + } + + gotAttributes, err := task.getFrontendNlbAttributes() + + if tt.wantErr { + assert.Error(t, err) + if tt.expectedErrMsg != "" { + assert.Contains(t, err.Error(), tt.expectedErrMsg) + } + } else { + assert.NoError(t, err) + assert.Equal(t, tt.wantAttributes, gotAttributes) + } + }) + } +} diff --git a/pkg/service/model_build_load_balancer.go b/pkg/service/model_build_load_balancer.go index af2ea703a..21b798f09 100644 --- a/pkg/service/model_build_load_balancer.go +++ b/pkg/service/model_build_load_balancer.go @@ -7,8 +7,6 @@ import ( "fmt" "net/netip" "regexp" - "sigs.k8s.io/aws-load-balancer-controller/pkg/shared_constants" - "sort" "strconv" awssdk "github.com/aws/aws-sdk-go-v2/aws" @@ -24,17 +22,8 @@ import ( "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/networking" -) - -const ( - lbAttrsAccessLogsS3Enabled = "access_logs.s3.enabled" - lbAttrsAccessLogsS3Bucket = "access_logs.s3.bucket" - lbAttrsAccessLogsS3Prefix = "access_logs.s3.prefix" - lbAttrsLoadBalancingCrossZoneEnabled = "load_balancing.cross_zone.enabled" - lbAttrsLoadBalancingDnsClientRoutingPolicy = "dns_record.client_routing_policy" - availabilityZoneAffinity = "availability_zone_affinity" - partialAvailabilityZoneAffinity = "partial_availability_zone_affinity" - anyAvailabilityZone = "any_availability_zone" + "sigs.k8s.io/aws-load-balancer-controller/pkg/shared_constants" + "sigs.k8s.io/aws-load-balancer-controller/pkg/shared_utils" ) func (t *defaultModelBuildTask) buildLoadBalancer(ctx context.Context, scheme elbv2model.LoadBalancerScheme) error { @@ -497,7 +486,7 @@ func (t *defaultModelBuildTask) buildLoadBalancerAttributes(_ context.Context) ( return []elbv2model.LoadBalancerAttribute{}, err } mergedAttributes := algorithm.MergeStringMap(specificAttributes, loadBalancerAttributes) - return makeAttributesSliceFromMap(mergedAttributes), nil + return shared_utils.MakeAttributesSliceFromMap(mergedAttributes), nil } func (t *defaultModelBuildTask) buildLoadBalancerMinimumCapacity(_ context.Context) (*elbv2model.MinimumLoadBalancerCapacity, error) { @@ -527,35 +516,21 @@ func (t *defaultModelBuildTask) buildLoadBalancerMinimumCapacity(_ context.Conte return minimumLoadBalancerCapacity, nil } -func makeAttributesSliceFromMap(loadBalancerAttributesMap map[string]string) []elbv2model.LoadBalancerAttribute { - attributes := make([]elbv2model.LoadBalancerAttribute, 0, len(loadBalancerAttributesMap)) - for attrKey, attrValue := range loadBalancerAttributesMap { - attributes = append(attributes, elbv2model.LoadBalancerAttribute{ - Key: attrKey, - Value: attrValue, - }) - } - sort.Slice(attributes, func(i, j int) bool { - return attributes[i].Key < attributes[j].Key - }) - return attributes -} - func (t *defaultModelBuildTask) getLoadBalancerAttributes() (map[string]string, error) { var attributes map[string]string if _, err := t.annotationParser.ParseStringMapAnnotation(annotations.SvcLBSuffixLoadBalancerAttributes, &attributes, t.service.Annotations); err != nil { return nil, err } - dnsRecordClientRoutingPolicy, exists := attributes[lbAttrsLoadBalancingDnsClientRoutingPolicy] + dnsRecordClientRoutingPolicy, exists := attributes[shared_constants.LBAttributeLoadBalancingDnsClientRoutingPolicy] if exists { switch dnsRecordClientRoutingPolicy { - case availabilityZoneAffinity: - case partialAvailabilityZoneAffinity: - case anyAvailabilityZone: + case shared_constants.LBAttributeAvailabilityZoneAffinity: + case shared_constants.LBAttributePartialAvailabilityZoneAffinity: + case shared_constants.LBAttributeAnyAvailabilityZone: default: return nil, errors.Errorf("invalid dns_record.client_routing_policy set in annotation %s: got '%s' expected one of ['%s', '%s', '%s']", annotations.SvcLBSuffixLoadBalancerAttributes, dnsRecordClientRoutingPolicy, - anyAvailabilityZone, partialAvailabilityZoneAffinity, availabilityZoneAffinity) + shared_constants.LBAttributeAnyAvailabilityZone, shared_constants.LBAttributePartialAvailabilityZoneAffinity, shared_constants.LBAttributeAvailabilityZoneAffinity) } } return attributes, nil @@ -573,12 +548,12 @@ func (t *defaultModelBuildTask) getAnnotationSpecificLbAttributes() (map[string] return nil, err } if exists && accessLogEnabled { - annotationSpecificAttrs[lbAttrsAccessLogsS3Enabled] = strconv.FormatBool(accessLogEnabled) + annotationSpecificAttrs[shared_constants.LBAttributeAccessLogsS3Enabled] = strconv.FormatBool(accessLogEnabled) if exists := t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixAccessLogS3BucketName, &bucketName, t.service.Annotations); exists { - annotationSpecificAttrs[lbAttrsAccessLogsS3Bucket] = bucketName + annotationSpecificAttrs[shared_constants.LBAttributeAccessLogsS3Bucket] = bucketName } if exists := t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixAccessLogS3BucketPrefix, &bucketPrefix, t.service.Annotations); exists { - annotationSpecificAttrs[lbAttrsAccessLogsS3Prefix] = bucketPrefix + annotationSpecificAttrs[shared_constants.LBAttributeAccessLogsS3Prefix] = bucketPrefix } } exists, err = t.annotationParser.ParseBoolAnnotation(annotations.SvcLBSuffixCrossZoneLoadBalancingEnabled, &crossZoneEnabled, t.service.Annotations) @@ -586,7 +561,7 @@ func (t *defaultModelBuildTask) getAnnotationSpecificLbAttributes() (map[string] return nil, err } if exists { - annotationSpecificAttrs[lbAttrsLoadBalancingCrossZoneEnabled] = strconv.FormatBool(crossZoneEnabled) + annotationSpecificAttrs[shared_constants.LBAttributeLoadBalancingCrossZoneEnabled] = strconv.FormatBool(crossZoneEnabled) } return annotationSpecificAttrs, nil } diff --git a/pkg/service/model_build_load_balancer_test.go b/pkg/service/model_build_load_balancer_test.go index 022182602..2271e1807 100644 --- a/pkg/service/model_build_load_balancer_test.go +++ b/pkg/service/model_build_load_balancer_test.go @@ -61,19 +61,19 @@ func Test_defaultModelBuilderTask_buildLBAttributes(t *testing.T) { wantError: false, wantValue: []elbv2.LoadBalancerAttribute{ { - Key: lbAttrsAccessLogsS3Enabled, + Key: shared_constants.LBAttributeAccessLogsS3Enabled, Value: "true", }, { - Key: lbAttrsAccessLogsS3Bucket, + Key: shared_constants.LBAttributeAccessLogsS3Bucket, Value: "nlb-bucket", }, { - Key: lbAttrsAccessLogsS3Prefix, + Key: shared_constants.LBAttributeAccessLogsS3Prefix, Value: "bkt-pfx", }, { - Key: lbAttrsLoadBalancingCrossZoneEnabled, + Key: shared_constants.LBAttributeLoadBalancingCrossZoneEnabled, Value: "true", }, { @@ -95,19 +95,19 @@ func Test_defaultModelBuilderTask_buildLBAttributes(t *testing.T) { wantError: false, wantValue: []elbv2.LoadBalancerAttribute{ { - Key: lbAttrsAccessLogsS3Enabled, + Key: shared_constants.LBAttributeAccessLogsS3Enabled, Value: "true", }, { - Key: lbAttrsAccessLogsS3Bucket, + Key: shared_constants.LBAttributeAccessLogsS3Bucket, Value: "nlb-bucket", }, { - Key: lbAttrsAccessLogsS3Prefix, + Key: shared_constants.LBAttributeAccessLogsS3Prefix, Value: "bkt-pfx", }, { - Key: lbAttrsLoadBalancingCrossZoneEnabled, + Key: shared_constants.LBAttributeLoadBalancingCrossZoneEnabled, Value: "true", }, { @@ -115,8 +115,8 @@ func Test_defaultModelBuilderTask_buildLBAttributes(t *testing.T) { Value: "true", }, { - Key: lbAttrsLoadBalancingDnsClientRoutingPolicy, - Value: availabilityZoneAffinity, + Key: shared_constants.LBAttributeLoadBalancingDnsClientRoutingPolicy, + Value: shared_constants.LBAttributeAvailabilityZoneAffinity, }, }, }, @@ -137,19 +137,19 @@ func Test_defaultModelBuilderTask_buildLBAttributes(t *testing.T) { wantError: false, wantValue: []elbv2.LoadBalancerAttribute{ { - Key: lbAttrsAccessLogsS3Enabled, + Key: shared_constants.LBAttributeAccessLogsS3Enabled, Value: "true", }, { - Key: lbAttrsAccessLogsS3Bucket, + Key: shared_constants.LBAttributeAccessLogsS3Bucket, Value: "overridden-nlb-bucket", }, { - Key: lbAttrsAccessLogsS3Prefix, + Key: shared_constants.LBAttributeAccessLogsS3Prefix, Value: "overridden-bkt-pfx", }, { - Key: lbAttrsLoadBalancingCrossZoneEnabled, + Key: shared_constants.LBAttributeLoadBalancingCrossZoneEnabled, Value: "false", }, }, diff --git a/pkg/shared_constants/attributes.go b/pkg/shared_constants/attributes.go index ffcc177d3..351a88128 100644 --- a/pkg/shared_constants/attributes.go +++ b/pkg/shared_constants/attributes.go @@ -1,8 +1,16 @@ package shared_constants const ( - // LBAttributeDeletionProtection deletion protection attribute name - LBAttributeDeletionProtection = "deletion_protection.enabled" + LBAttributeDeletionProtection = "deletion_protection.enabled" + LBAttributeAccessLogsS3Enabled = "access_logs.s3.enabled" + LBAttributeAccessLogsS3Bucket = "access_logs.s3.bucket" + LBAttributeAccessLogsS3Prefix = "access_logs.s3.prefix" + LBAttributeLoadBalancingCrossZoneEnabled = "load_balancing.cross_zone.enabled" + LBAttributeLoadBalancingDnsClientRoutingPolicy = "dns_record.client_routing_policy" + + LBAttributeAvailabilityZoneAffinity = "availability_zone_affinity" + LBAttributePartialAvailabilityZoneAffinity = "partial_availability_zone_affinity" + LBAttributeAnyAvailabilityZone = "any_availability_zone" ) const ( diff --git a/pkg/shared_utils/nlb_utils.go b/pkg/shared_utils/nlb_utils.go new file mode 100644 index 000000000..5971fc50a --- /dev/null +++ b/pkg/shared_utils/nlb_utils.go @@ -0,0 +1,20 @@ +package shared_utils + +import ( + elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" + "sort" +) + +func MakeAttributesSliceFromMap(loadBalancerAttributesMap map[string]string) []elbv2model.LoadBalancerAttribute { + attributes := make([]elbv2model.LoadBalancerAttribute, 0, len(loadBalancerAttributesMap)) + for attrKey, attrValue := range loadBalancerAttributesMap { + attributes = append(attributes, elbv2model.LoadBalancerAttribute{ + Key: attrKey, + Value: attrValue, + }) + } + sort.Slice(attributes, func(i, j int) bool { + return attributes[i].Key < attributes[j].Key + }) + return attributes +} diff --git a/pkg/shared_utils/nlb_utils_test.go b/pkg/shared_utils/nlb_utils_test.go new file mode 100644 index 000000000..0c81af79d --- /dev/null +++ b/pkg/shared_utils/nlb_utils_test.go @@ -0,0 +1,53 @@ +package shared_utils + +import ( + "reflect" + "testing" + + elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" +) + +func TestMakeAttributesSliceFromMap(t *testing.T) { + tests := []struct { + name string + input map[string]string + want []elbv2model.LoadBalancerAttribute + }{ + { + name: "empty map", + input: map[string]string{}, + want: []elbv2model.LoadBalancerAttribute{}, + }, + { + name: "single attribute", + input: map[string]string{ + "key1": "value1", + }, + want: []elbv2model.LoadBalancerAttribute{ + {Key: "key1", Value: "value1"}, + }, + }, + { + name: "multiple attributes, unordered input", + input: map[string]string{ + "zeta": "last", + "alpha": "first", + "mid": "middle", + }, + want: []elbv2model.LoadBalancerAttribute{ + {Key: "alpha", Value: "first"}, + {Key: "mid", Value: "middle"}, + {Key: "zeta", Value: "last"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MakeAttributesSliceFromMap(tt.input) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MakeAttributesSliceFromMap() = %v, want %v", got, tt.want) + } + }) + } +}