From 64e498fd7afd53b1f84dbce618e4ff57f06ab5ae Mon Sep 17 00:00:00 2001 From: chriswachira Date: Tue, 12 Nov 2024 07:02:44 +0000 Subject: [PATCH 01/30] feat(NLB): Introduce annotation to allow ICMP for Path MTU Discovery --- docs/guide/service/annotations.md | 8 +++++ pkg/annotations/constants.go | 5 ++-- pkg/service/model_build_load_balancer.go | 4 +-- pkg/service/model_build_managed_sg.go | 38 ++++++++++++++++++++++++ pkg/service/model_builder_test.go | 6 ++-- 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/docs/guide/service/annotations.md b/docs/guide/service/annotations.md index a27c8c781..702107e78 100644 --- a/docs/guide/service/annotations.md +++ b/docs/guide/service/annotations.md @@ -57,6 +57,7 @@ | [service.beta.kubernetes.io/aws-load-balancer-enable-prefix-for-ipv6-source-nat](#enable-prefix-for-ipv6-source-nat) | string | off | Optional annotation. dualstack lb only. Allowed values - on and off | | [service.beta.kubernetes.io/aws-load-balancer-source-nat-ipv6-prefixes](#source-nat-ipv6-prefixes) | stringList | | Optional annotation. dualstack lb only. This annotation is only applicable when user has to set the service.beta.kubernetes.io/aws-load-balancer-enable-prefix-for-ipv6-source-nat to "on". Length must match the number of subnets | | [service.beta.kubernetes.io/aws-load-balancer-minimum-load-balancer-capacity](#load-balancer-capacity-reservation) | stringMap | | +| [service.beta.kubernetes.io/aws-load-balancer-enable-icmp-for-path-mtu-discovery](#icmp-path-mtu-discovery) | string | | If specified, a security group rule is added to the managed security group to allow explicit ICMP traffic for [Path MTU discovery](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/network_mtu.html#path_mtu_discovery) for IPv4 and dual-stack VPCs. Creates a rule for each source range if `service.beta.kubernetes.io/load-balancer-source-ranges` is present. | ## Traffic Routing Traffic Routing can be controlled with following annotations: @@ -192,6 +193,13 @@ on the load balancer. service.beta.kubernetes.io/aws-load-balancer-ipv6-addresses: 2600:1f13:837:8501::1, 2600:1f13:837:8504::1 ``` +- `service.beta.kubernetes.io/aws-load-balancer-enable-icmp-for-path-mtu-discovery` enables the creation of security group rules to the managed security group to allow explicit ICMP traffic for [Path MTU discovery](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/network_mtu.html#path_mtu_discovery) for IPv4 and dual-stack VPCs. Creates a rule for each source range if `service.beta.kubernetes.io/load-balancer-source-ranges` is present. + + !!!example + ``` + service.beta.kubernetes.io/aws-load-balancer-enable-icmp-for-path-mtu-discovery: "on" + ``` + ## Traffic Listening Traffic Listening can be controlled with following annotations: diff --git a/pkg/annotations/constants.go b/pkg/annotations/constants.go index b2bc9aad1..ba7021654 100644 --- a/pkg/annotations/constants.go +++ b/pkg/annotations/constants.go @@ -100,7 +100,8 @@ const ( SvcLBSuffixSecurityGroupPrefixLists = "aws-load-balancer-security-group-prefix-lists" SvcLBSuffixlsAttsAnnotationPrefix = "aws-load-balancer-listener-attributes" SvcLBSuffixMultiClusterTargetGroup = "aws-load-balancer-multi-cluster-target-group" - ScvLBSuffixEnablePrefixForIpv6SourceNat = "aws-load-balancer-enable-prefix-for-ipv6-source-nat" - ScvLBSuffixSourceNatIpv6Prefixes = "aws-load-balancer-source-nat-ipv6-prefixes" + SvcLBSuffixEnablePrefixForIpv6SourceNat = "aws-load-balancer-enable-prefix-for-ipv6-source-nat" + SvcLBSuffixSourceNatIpv6Prefixes = "aws-load-balancer-source-nat-ipv6-prefixes" SvcLBSuffixLoadBalancerCapacityReservation = "aws-load-balancer-minimum-load-balancer-capacity" + SvcLBSuffixEnableIcmpForPathMtuDiscovery = "aws-load-balancer-enable-icmp-for-path-mtu-discovery" ) diff --git a/pkg/service/model_build_load_balancer.go b/pkg/service/model_build_load_balancer.go index 3fe52ba89..8d67a72c6 100644 --- a/pkg/service/model_build_load_balancer.go +++ b/pkg/service/model_build_load_balancer.go @@ -199,7 +199,7 @@ func (t *defaultModelBuildTask) buildLoadBalancerIPAddressType(_ context.Context func (t *defaultModelBuildTask) buildLoadBalancerEnablePrefixForIpv6SourceNat(_ context.Context, ipAddressType elbv2model.IPAddressType, ec2Subnets []ec2types.Subnet) (elbv2model.EnablePrefixForIpv6SourceNat, error) { rawEnablePrefixForIpv6SourceNat := "" - if exists := t.annotationParser.ParseStringAnnotation(annotations.ScvLBSuffixEnablePrefixForIpv6SourceNat, &rawEnablePrefixForIpv6SourceNat, t.service.Annotations); !exists { + if exists := t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixEnablePrefixForIpv6SourceNat, &rawEnablePrefixForIpv6SourceNat, t.service.Annotations); !exists { return elbv2model.EnablePrefixForIpv6SourceNatOff, nil } @@ -382,7 +382,7 @@ func (t *defaultModelBuildTask) buildLoadBalancerSubnetMappings(_ context.Contex var isPrefixForIpv6SourceNatEnabled = enablePrefixForIpv6SourceNat == elbv2model.EnablePrefixForIpv6SourceNatOn var sourceNatIpv6Prefixes []string - sourceNatIpv6PrefixesConfigured := t.annotationParser.ParseStringSliceAnnotation(annotations.ScvLBSuffixSourceNatIpv6Prefixes, &sourceNatIpv6Prefixes, t.service.Annotations) + sourceNatIpv6PrefixesConfigured := t.annotationParser.ParseStringSliceAnnotation(annotations.SvcLBSuffixSourceNatIpv6Prefixes, &sourceNatIpv6Prefixes, t.service.Annotations) if sourceNatIpv6PrefixesConfigured { sourceNatIpv6PrefixesError := networking.ValidateSourceNatPrefixes(sourceNatIpv6Prefixes, ipAddressType, isPrefixForIpv6SourceNatEnabled, ec2Subnets) if sourceNatIpv6PrefixesError != nil { diff --git a/pkg/service/model_build_managed_sg.go b/pkg/service/model_build_managed_sg.go index a44a7ae34..fd66a2130 100644 --- a/pkg/service/model_build_managed_sg.go +++ b/pkg/service/model_build_managed_sg.go @@ -17,6 +17,15 @@ import ( ) const ( + icmpv4Protocol = "icmp" + icmpv6Protocol = "icmpv6" + + icmpv4TypeForPathMtu = 3 // https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml#icmp-parameters-codes-3 + icmpv4CodeForPathMtu = 4 + + icmpv6TypeForPathMtu = 2 // https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-codes-2 + icmpv6CodeForPathMtu = 0 + resourceIDManagedSecurityGroup = "ManagedLBSecurityGroup" ) @@ -65,7 +74,11 @@ func (t *defaultModelBuildTask) buildManagedSecurityGroupName(_ context.Context) func (t *defaultModelBuildTask) buildManagedSecurityGroupIngressPermissions(ctx context.Context, ipAddressType elbv2model.IPAddressType) ([]ec2model.IPPermission, error) { var permissions []ec2model.IPPermission var prefixListIDs []string + var icmpForPathMtuConfiguredFlag string + + icmpForPathMtuConfigured := t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixEnableIcmpForPathMtuDiscovery, &icmpForPathMtuConfiguredFlag, t.service.Annotations) prefixListsConfigured := t.annotationParser.ParseStringSliceAnnotation(annotations.SvcLBSuffixSecurityGroupPrefixLists, &prefixListIDs, t.service.Annotations) + cidrs, err := t.buildCIDRsFromSourceRanges(ctx, ipAddressType, prefixListsConfigured) if err != nil { return nil, err @@ -84,6 +97,18 @@ func (t *defaultModelBuildTask) buildManagedSecurityGroupIngressPermissions(ctx }, }, }) + if icmpForPathMtuConfigured && icmpForPathMtuConfiguredFlag == "on" { + permissions = append(permissions, ec2model.IPPermission{ + IPProtocol: string(icmpv4Protocol), + FromPort: awssdk.Int32(icmpv4TypeForPathMtu), + ToPort: awssdk.Int32(icmpv4CodeForPathMtu), + IPRanges: []ec2model.IPRange{ + { + CIDRIP: cidr, + }, + }, + }) + } } else { permissions = append(permissions, ec2model.IPPermission{ IPProtocol: strings.ToLower(string(port.Protocol)), @@ -95,6 +120,18 @@ func (t *defaultModelBuildTask) buildManagedSecurityGroupIngressPermissions(ctx }, }, }) + if icmpForPathMtuConfigured && icmpForPathMtuConfiguredFlag == "on" { + permissions = append(permissions, ec2model.IPPermission{ + IPProtocol: string(icmpv6Protocol), + FromPort: awssdk.Int32(icmpv6TypeForPathMtu), + ToPort: awssdk.Int32(icmpv6CodeForPathMtu), + IPv6Range: []ec2model.IPv6Range{ + { + CIDRIPv6: cidr, + }, + }, + }) + } } } if prefixListsConfigured { @@ -112,6 +149,7 @@ func (t *defaultModelBuildTask) buildManagedSecurityGroupIngressPermissions(ctx } } } + return permissions, nil } diff --git a/pkg/service/model_builder_test.go b/pkg/service/model_builder_test.go index 0ed7f9b77..d65579122 100644 --- a/pkg/service/model_builder_test.go +++ b/pkg/service/model_builder_test.go @@ -2,11 +2,12 @@ package service import ( "context" - ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" - elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "testing" "time" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + awssdk "github.com/aws/aws-sdk-go-v2/aws" "github.com/go-logr/logr" "github.com/golang/mock/gomock" @@ -6502,6 +6503,7 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { "scheme":"internet-facing", "securityGroupsInboundRulesOnPrivateLink":"on", "ipAddressType":"ipv4", + "enablePrefixForIpv6SourceNat": "off", "subnetMapping":[ { "subnetID":"subnet-1" From b0e9edd341bcf81c4c519795381ff5e573128376 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Mon, 9 Dec 2024 18:01:49 -0800 Subject: [PATCH 02/30] feat: add advertise ca for mtls listener --- docs/guide/ingress/annotations.md | 1 + go.mod | 8 +- go.sum | 24 +-- pkg/deploy/elbv2/listener_manager.go | 15 +- pkg/deploy/elbv2/listener_manager_test.go | 2 + pkg/ingress/model_build_listener.go | 22 ++- pkg/ingress/model_build_listener_test.go | 192 +++++++++++++++++++++- pkg/model/elbv2/listener.go | 3 +- pkg/service/model_builder_test.go | 1 + 9 files changed, 242 insertions(+), 26 deletions(-) diff --git a/docs/guide/ingress/annotations.md b/docs/guide/ingress/annotations.md index 7035dc80f..2d51c29b8 100644 --- a/docs/guide/ingress/annotations.md +++ b/docs/guide/ingress/annotations.md @@ -805,6 +805,7 @@ TLS support can be controlled with the following annotations: - Both ARN and Name of trustStore are supported values. - `trustStore` is required when mode is `verify`. - `ignoreClientCertificateExpiry : true | false (default)` + - `advertiseTrustStoreCaNames : "on" | "off" (default)` - Once the Mutual Authentication is set, to turn it off, you will have to explicitly pass in this annotation with `mode : "off"`. !!!example diff --git a/go.mod b/go.mod index 1e05dbf7d..baafc0f46 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ go 1.22.8 require ( github.com/aws/aws-sdk-go v1.55.5 - github.com/aws/aws-sdk-go-v2 v1.32.5 + github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.27.27 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 github.com/aws/aws-sdk-go-v2/service/acm v1.28.4 github.com/aws/aws-sdk-go-v2/service/appmesh v1.27.7 github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 - github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.42.0 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1 github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.23.3 github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.31.7 github.com/aws/aws-sdk-go-v2/service/shield v1.27.3 @@ -57,8 +57,8 @@ require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect diff --git a/go.sum b/go.sum index b13e21262..7f48d636b 100644 --- a/go.sum +++ b/go.sum @@ -38,24 +38,18 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= -github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= -github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= -github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= +github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/acm v1.28.4 h1:wiW1Y6/1lysA0eJZRq0I53YYKuV9MNAzL15z2eZRlEE= @@ -64,8 +58,8 @@ github.com/aws/aws-sdk-go-v2/service/appmesh v1.27.7 h1:q44a6kysAfej9zZwRnraOg9s github.com/aws/aws-sdk-go-v2/service/appmesh v1.27.7/go.mod h1:ZYSmrgAMp0rTCHH+SGsoxZo+PPbgsDqBzewTp3tSJ60= github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 h1:ta62lid9JkIpKZtZZXSj6rP2AqY5x1qYGq53ffxqD9Q= github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.42.0 h1:C4/D90/j3EF/SokpC4HO1aPMkZV1dgqUbmejdpxQiAE= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.42.0/go.mod h1:pZP3I+Ts+XuhJJtZE49+ABVjfxm7u9/hxcNUYSpY3OE= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1 h1:L9Wt9zgtoYKIlaeFTy+EztGjL4oaXBBGtVXA+jaeYko= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1/go.mod h1:yxzLdxt7bVGvIOPYIKFtiaJCJnx2ChlIIvlhW4QgI6M= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= @@ -86,8 +80,6 @@ github.com/aws/aws-sdk-go-v2/service/wafregional v1.23.3 h1:7dr6En0/6KRFoz8VmnYk github.com/aws/aws-sdk-go-v2/service/wafregional v1.23.3/go.mod h1:24TtlRsv4LKAE3VnRJQhpatr8cpX0yj8NSzg8/lxOCw= github.com/aws/aws-sdk-go-v2/service/wafv2 v1.51.4 h1:1khBA5uryBRJoCb4G2iR5RT06BkfPEjjDCHAiRb8P3Q= github.com/aws/aws-sdk-go-v2/service/wafv2 v1.51.4/go.mod h1:QpFImaPGKNwa+MiZ+oo6LbV1PVQBapc0CnrAMRScoxM= -github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= -github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/pkg/deploy/elbv2/listener_manager.go b/pkg/deploy/elbv2/listener_manager.go index a9db44077..0a6c92d53 100644 --- a/pkg/deploy/elbv2/listener_manager.go +++ b/pkg/deploy/elbv2/listener_manager.go @@ -365,11 +365,17 @@ func buildSDKMutualAuthenticationConfig(modelMutualAuthenticationCfg *elbv2model if modelMutualAuthenticationCfg == nil { return nil } - return &elbv2types.MutualAuthenticationAttributes{ + attributes := &elbv2types.MutualAuthenticationAttributes{ IgnoreClientCertificateExpiry: modelMutualAuthenticationCfg.IgnoreClientCertificateExpiry, Mode: awssdk.String(modelMutualAuthenticationCfg.Mode), TrustStoreArn: modelMutualAuthenticationCfg.TrustStoreArn, } + + if modelMutualAuthenticationCfg.Mode == string(elbv2model.MutualAuthenticationVerifyMode) { + attributes.AdvertiseTrustStoreCaNames = translateAdvertiseCAToEnum(modelMutualAuthenticationCfg.AdvertiseTrustStoreCaNames) + } + + return attributes } func buildResListenerStatus(sdkLS ListenerWithTags) elbv2model.ListenerStatus { @@ -396,3 +402,10 @@ func getRegionFromARN(arn string) string { func isIsolatedRegion(region string) bool { return strings.Contains(strings.ToLower(region), "-iso-") } + +func translateAdvertiseCAToEnum(s *string) elbv2types.AdvertiseTrustStoreCaNamesEnum { + if s == nil { + return elbv2types.AdvertiseTrustStoreCaNamesEnumOff + } + return elbv2types.AdvertiseTrustStoreCaNamesEnum(*s) +} diff --git a/pkg/deploy/elbv2/listener_manager_test.go b/pkg/deploy/elbv2/listener_manager_test.go index e096b500c..cf7521df7 100644 --- a/pkg/deploy/elbv2/listener_manager_test.go +++ b/pkg/deploy/elbv2/listener_manager_test.go @@ -235,6 +235,7 @@ func Test_isSDKListenerSettingsDrifted(t *testing.T) { Mode: awssdk.String("verify"), TrustStoreArn: awssdk.String("arn:aws:elasticloadbalancing:us-east-1:123456789123:truststore/ts-1/8786hghf"), IgnoreClientCertificateExpiry: awssdk.Bool(false), + AdvertiseTrustStoreCaNames: elbv2types.AdvertiseTrustStoreCaNamesEnumOff, }, }, }, @@ -260,6 +261,7 @@ func Test_isSDKListenerSettingsDrifted(t *testing.T) { Mode: awssdk.String("verify"), TrustStoreArn: awssdk.String("arn:aws:elasticloadbalancing:us-east-1:123456789123:truststore/ts-1/8786hghf"), IgnoreClientCertificateExpiry: awssdk.Bool(false), + AdvertiseTrustStoreCaNames: elbv2types.AdvertiseTrustStoreCaNamesEnumOff, }, }, }, diff --git a/pkg/ingress/model_build_listener.go b/pkg/ingress/model_build_listener.go index 80f848ba3..32c118452 100644 --- a/pkg/ingress/model_build_listener.go +++ b/pkg/ingress/model_build_listener.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "net" "strings" @@ -283,6 +284,7 @@ type MutualAuthenticationConfig struct { Mode string `json:"mode"` TrustStore *string `json:"trustStore,omitempty"` IgnoreClientCertificateExpiry *bool `json:"ignoreClientCertificateExpiry,omitempty"` + AdvertiseTrustStoreCaNames *string `json:"advertiseTrustStoreCaNames,omitempty"` } func (t *defaultModelBuildTask) computeIngressMutualAuthentication(ctx context.Context, ing *ClassifiedIngress) (map[int32]*elbv2model.MutualAuthenticationAttributes, error) { @@ -319,8 +321,9 @@ func (t *defaultModelBuildTask) parseMtlsConfigEntries(_ context.Context, entrie mode := mutualAuthenticationConfig.Mode truststoreNameOrArn := awssdk.ToString(mutualAuthenticationConfig.TrustStore) ignoreClientCert := mutualAuthenticationConfig.IgnoreClientCertificateExpiry + advertiseTrustStoreCaNames := mutualAuthenticationConfig.AdvertiseTrustStoreCaNames - err := t.validateMutualAuthenticationConfig(port, mode, truststoreNameOrArn, ignoreClientCert) + err := t.validateMutualAuthenticationConfig(port, mode, truststoreNameOrArn, ignoreClientCert, advertiseTrustStoreCaNames) if err != nil { return nil, err } @@ -328,12 +331,12 @@ func (t *defaultModelBuildTask) parseMtlsConfigEntries(_ context.Context, entrie if mode == string(elbv2model.MutualAuthenticationVerifyMode) && ignoreClientCert == nil { ignoreClientCert = awssdk.Bool(false) } - portAndMtlsAttributes[port] = &elbv2model.MutualAuthenticationAttributes{Mode: mode, TrustStoreArn: awssdk.String(truststoreNameOrArn), IgnoreClientCertificateExpiry: ignoreClientCert} + portAndMtlsAttributes[port] = &elbv2model.MutualAuthenticationAttributes{Mode: mode, TrustStoreArn: awssdk.String(truststoreNameOrArn), IgnoreClientCertificateExpiry: ignoreClientCert, AdvertiseTrustStoreCaNames: advertiseTrustStoreCaNames} } return portAndMtlsAttributes, nil } -func (t *defaultModelBuildTask) validateMutualAuthenticationConfig(port int32, mode string, truststoreNameOrArn string, ignoreClientCert *bool) error { +func (t *defaultModelBuildTask) validateMutualAuthenticationConfig(port int32, mode string, truststoreNameOrArn string, ignoreClientCert *bool, advertiseTrustStoreCaNames *string) error { // Verify port value is valid for ALB: [1, 65535] if port < 1 || port > 65535 { return errors.Errorf("listen port must be within [1, 65535]: %v", port) @@ -360,6 +363,19 @@ func (t *defaultModelBuildTask) validateMutualAuthenticationConfig(port int32, m return errors.Errorf("Mutual Authentication mode %s does not support ignoring client certificate expiry for port %v", mode, port) } + // Verify advertise trust ca names. + // The value (if specified) must be "on" or "off" + // The value can be only specified when using verify mode on the listener. + if advertiseTrustStoreCaNames != nil { + if mode != string(elbv2model.MutualAuthenticationVerifyMode) { + return errors.Errorf("Mutual Authentication mode %s does not support advertiseTrustStoreCaNames for port %v", mode, port) + } + + if *advertiseTrustStoreCaNames != string(elbv2types.AdvertiseTrustStoreCaNamesEnumOff) && *advertiseTrustStoreCaNames != string(elbv2types.AdvertiseTrustStoreCaNamesEnumOn) { + return errors.Errorf("advertiseTrustStoreCaNames only supports the values \"on\" and \"off\" got value %s for port %v", *advertiseTrustStoreCaNames, port) + } + } + return nil } diff --git a/pkg/ingress/model_build_listener_test.go b/pkg/ingress/model_build_listener_test.go index 9514b8f6e..52ce53709 100644 --- a/pkg/ingress/model_build_listener_test.go +++ b/pkg/ingress/model_build_listener_test.go @@ -2,6 +2,8 @@ package ingress import ( "context" + awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go/aws" "testing" "github.com/stretchr/testify/assert" @@ -54,7 +56,6 @@ func Test_computeIngressListenPortConfigByPort_MutualAuthentication(t *testing.T }, want: []WantStruct{{port: 443, mutualAuth: &(elbv2.MutualAuthenticationAttributes{Mode: "off", TrustStoreArn: nil, IgnoreClientCertificateExpiry: nil})}, {port: 80, mutualAuth: &(elbv2.MutualAuthenticationAttributes{Mode: "passthrough", TrustStoreArn: nil, IgnoreClientCertificateExpiry: nil})}}, }, - { name: "Listener Config when MutualAuthentication annotation is not specified", @@ -79,6 +80,54 @@ func Test_computeIngressListenPortConfigByPort_MutualAuthentication(t *testing.T }, want: []WantStruct{{port: 443, mutualAuth: nil}, {port: 80, mutualAuth: nil}}, }, + { + name: "Listener Config when MutualAuthentication annotation is specified with advertise trust store CA not set", + fields: fields{ + ingGroup: Group{ + ID: GroupID{Name: "explicit-group"}, + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "awesome-ns", + Name: "ing-1", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/listen-ports": `[{"HTTPS": 443}, {"HTTPS": 80}]`, + "alb.ingress.kubernetes.io/mutual-authentication": `[{"port":443,"mode":"off"}, {"port":80,"mode":"passthrough"}]`, + "alb.ingress.kubernetes.io/certificate-arn": "arn:aws:iam::123456789:server-certificate/new-clb-cert", + }, + }, + }, + }, + }, + }, + }, + want: []WantStruct{{port: 443, mutualAuth: &(elbv2.MutualAuthenticationAttributes{Mode: "off", TrustStoreArn: nil, IgnoreClientCertificateExpiry: nil})}, {port: 80, mutualAuth: &(elbv2.MutualAuthenticationAttributes{Mode: "passthrough", TrustStoreArn: nil, IgnoreClientCertificateExpiry: nil})}}, + }, + { + name: "Listener Config when MutualAuthentication annotation is specified with advertise trust store CA set", + fields: fields{ + ingGroup: Group{ + ID: GroupID{Name: "explicit-group"}, + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "awesome-ns", + Name: "ing-1", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/listen-ports": `[{"HTTPS": 443}, {"HTTPS": 80}]`, + "alb.ingress.kubernetes.io/mutual-authentication": `[{"port":443,"mode":"off"}, {"port":80,"mode":"verify", "advertiseTrustStoreCaNames": "on", "trustStore": "arn:aws:elasticloadbalancing:trustStoreArn"}]`, + "alb.ingress.kubernetes.io/certificate-arn": "arn:aws:iam::123456789:server-certificate/new-clb-cert", + }, + }, + }, + }, + }, + }, + }, + want: []WantStruct{{port: 443, mutualAuth: &(elbv2.MutualAuthenticationAttributes{Mode: "off", TrustStoreArn: nil, IgnoreClientCertificateExpiry: nil})}, {port: 80, mutualAuth: &(elbv2.MutualAuthenticationAttributes{Mode: "verify", TrustStoreArn: awssdk.String("arn:aws:elasticloadbalancing:trustStoreArn"), AdvertiseTrustStoreCaNames: awssdk.String("on"), IgnoreClientCertificateExpiry: nil})}}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -96,6 +145,11 @@ func Test_computeIngressListenPortConfigByPort_MutualAuthentication(t *testing.T mutualAuth := tt.want[i].mutualAuth if mutualAuth != nil { assert.Equal(t, mutualAuth.Mode, got[port].mutualAuthentication.Mode) + + if mutualAuth.AdvertiseTrustStoreCaNames != nil { + assert.Equal(t, *mutualAuth.AdvertiseTrustStoreCaNames, *got[port].mutualAuthentication.AdvertiseTrustStoreCaNames) + } + } else { assert.Equal(t, mutualAuth, got[port].mutualAuthentication) } @@ -376,3 +430,139 @@ func Test_buildListenerAttributes(t *testing.T) { }) } } + +func Test_validateMutualAuthenticationConfig(t *testing.T) { + tests := []struct { + name string + port int32 + mode string + trustStoreARN string + ignoreClientCert *bool + advertiseCANames *string + expectedErrorMessage *string + }{ + { + name: "happy path no validation error off mode", + port: 800, + mode: string(elbv2model.MutualAuthenticationOffMode), + }, + { + name: "happy path no validation error pass through mode", + port: 800, + mode: string(elbv2model.MutualAuthenticationPassthroughMode), + }, + { + name: "happy path no validation error verify mode", + port: 800, + mode: string(elbv2model.MutualAuthenticationVerifyMode), + trustStoreARN: "truststore", + }, + { + name: "happy path no validation error verify mode, with ignore client cert expiry", + port: 800, + mode: string(elbv2model.MutualAuthenticationVerifyMode), + trustStoreARN: "truststore", + ignoreClientCert: aws.Bool(true), + }, + { + name: "happy path no validation error verify mode, with ignore client cert expiry false", + port: 800, + mode: string(elbv2model.MutualAuthenticationVerifyMode), + trustStoreARN: "truststore", + ignoreClientCert: aws.Bool(false), + }, + { + name: "happy path no validation error verify mode, with advertise ca on", + port: 800, + mode: string(elbv2model.MutualAuthenticationVerifyMode), + trustStoreARN: "truststore", + advertiseCANames: aws.String("on"), + }, + { + name: "happy path no validation error verify mode, with advertise ca off", + port: 800, + mode: string(elbv2model.MutualAuthenticationVerifyMode), + trustStoreARN: "truststore", + advertiseCANames: aws.String("off"), + }, + { + name: "no mode", + port: 800, + expectedErrorMessage: awssdk.String("mutualAuthentication mode cannot be empty for port 800"), + }, + { + name: "unknown mode", + port: 800, + mode: "foo", + expectedErrorMessage: awssdk.String("mutualAuthentication mode value must be among"), + }, + { + name: "port invalid", + port: 800000, + mode: string(elbv2model.MutualAuthenticationOffMode), + expectedErrorMessage: awssdk.String("listen port must be within [1, 65535]: 800000"), + }, + { + name: "missing truststore arn for verify", + port: 800, + mode: string(elbv2model.MutualAuthenticationVerifyMode), + expectedErrorMessage: awssdk.String("trustStore is required when mutualAuthentication mode is verify for port 800"), + }, + { + name: "truststore arn set but mode not verify", + port: 800, + mode: string(elbv2model.MutualAuthenticationOffMode), + trustStoreARN: "truststore", + expectedErrorMessage: awssdk.String("Mutual Authentication mode off does not support trustStore for port 800"), + }, + { + name: "ignore client cert expiry set for off mode", + port: 800, + mode: string(elbv2model.MutualAuthenticationOffMode), + ignoreClientCert: awssdk.Bool(true), + expectedErrorMessage: awssdk.String("Mutual Authentication mode off does not support ignoring client certificate expiry for port 800"), + }, + { + name: "ignore client cert expiry set for passthrough mode", + port: 800, + mode: string(elbv2model.MutualAuthenticationPassthroughMode), + ignoreClientCert: awssdk.Bool(true), + expectedErrorMessage: awssdk.String("Mutual Authentication mode passthrough does not support ignoring client certificate expiry for port 800"), + }, + { + name: "advertise ca set for off mode", + port: 800, + mode: string(elbv2model.MutualAuthenticationOffMode), + advertiseCANames: awssdk.String("on"), + expectedErrorMessage: awssdk.String("Authentication mode off does not support advertiseTrustStoreCaNames for port 800"), + }, + { + name: "advertise ca set for passthrough mode", + port: 800, + mode: string(elbv2model.MutualAuthenticationPassthroughMode), + advertiseCANames: awssdk.String("on"), + expectedErrorMessage: awssdk.String("Authentication mode passthrough does not support advertiseTrustStoreCaNames for port 800"), + }, + { + name: "advertise ca set with invalid value", + port: 800, + mode: string(elbv2model.MutualAuthenticationVerifyMode), + trustStoreARN: "truststore", + advertiseCANames: awssdk.String("foo"), + expectedErrorMessage: awssdk.String("advertiseTrustStoreCaNames only supports the values \"on\" and \"off\" got value foo for port 800"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + task := &defaultModelBuildTask{} + res := task.validateMutualAuthenticationConfig(tt.port, tt.mode, tt.trustStoreARN, tt.ignoreClientCert, tt.advertiseCANames) + + if tt.expectedErrorMessage == nil { + assert.Nil(t, res) + } else { + assert.Contains(t, res.Error(), *tt.expectedErrorMessage) + } + }) + } +} diff --git a/pkg/model/elbv2/listener.go b/pkg/model/elbv2/listener.go index 19aeef5e1..7de19c7d2 100644 --- a/pkg/model/elbv2/listener.go +++ b/pkg/model/elbv2/listener.go @@ -104,7 +104,8 @@ type MutualAuthenticationAttributes struct { TrustStoreArn *string `json:"trustStoreArn,omitempty"` - IgnoreClientCertificateExpiry *bool `json:"ignoreClientCertificateExpiry,omitempty"` + IgnoreClientCertificateExpiry *bool `json:"ignoreClientCertificateExpiry,omitempty"` + AdvertiseTrustStoreCaNames *string `json:"advertiseTrustStoreCaNames,omitempty"` } type AuthenticateCognitoActionConditionalBehavior string diff --git a/pkg/service/model_builder_test.go b/pkg/service/model_builder_test.go index 0ed7f9b77..44db576ae 100644 --- a/pkg/service/model_builder_test.go +++ b/pkg/service/model_builder_test.go @@ -6501,6 +6501,7 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { "type":"network", "scheme":"internet-facing", "securityGroupsInboundRulesOnPrivateLink":"on", + "enablePrefixForIpv6SourceNat": "off", "ipAddressType":"ipv4", "subnetMapping":[ { From 0555ac885d5b0b96e02c155d36a3f46db58ebb30 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Tue, 10 Dec 2024 14:20:30 -0800 Subject: [PATCH 03/30] fix failing test --- pkg/service/model_builder_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/service/model_builder_test.go b/pkg/service/model_builder_test.go index 0ed7f9b77..44db576ae 100644 --- a/pkg/service/model_builder_test.go +++ b/pkg/service/model_builder_test.go @@ -6501,6 +6501,7 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { "type":"network", "scheme":"internet-facing", "securityGroupsInboundRulesOnPrivateLink":"on", + "enablePrefixForIpv6SourceNat": "off", "ipAddressType":"ipv4", "subnetMapping":[ { From 643f33d6b30849e4aaf2a6e7d50003e56435a1ad Mon Sep 17 00:00:00 2001 From: Weiwei Li Date: Sun, 15 Dec 2024 23:42:41 -0800 Subject: [PATCH 04/30] fix: Cannot set the IPv6 addresses in dualstack mode during modification --- pkg/deploy/elbv2/load_balancer_manager.go | 24 ++++++++- .../elbv2/load_balancer_manager_test.go | 51 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/pkg/deploy/elbv2/load_balancer_manager.go b/pkg/deploy/elbv2/load_balancer_manager.go index 742a987eb..bc76c45aa 100644 --- a/pkg/deploy/elbv2/load_balancer_manager.go +++ b/pkg/deploy/elbv2/load_balancer_manager.go @@ -158,6 +158,7 @@ func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithIPAddressType(ctx func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSubnetMappings(ctx context.Context, resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) error { desiredSubnets := sets.NewString() + desiredIPv6Addresses := sets.NewString() desiredSubnetsSourceNATPrefixes := sets.NewString() currentSubnetsSourceNATPrefixes := sets.NewString() for _, mapping := range resLB.Spec.SubnetMappings { @@ -165,13 +166,20 @@ func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSubnetMappings(ctx if mapping.SourceNatIpv6Prefix != nil { desiredSubnetsSourceNATPrefixes.Insert(awssdk.ToString(mapping.SourceNatIpv6Prefix)) } + if mapping.IPv6Address != nil { + desiredIPv6Addresses.Insert(awssdk.ToString(mapping.IPv6Address)) + } } currentSubnets := sets.NewString() + currentIPv6Addresses := sets.NewString() for _, az := range sdkLB.LoadBalancer.AvailabilityZones { currentSubnets.Insert(awssdk.ToString(az.SubnetId)) if len(az.SourceNatIpv6Prefixes) != 0 { currentSubnetsSourceNATPrefixes.Insert(az.SourceNatIpv6Prefixes[0]) } + if len(az.LoadBalancerAddresses) > 0 && az.LoadBalancerAddresses[0].IPv6Address != nil { + currentIPv6Addresses.Insert(awssdk.ToString(az.LoadBalancerAddresses[0].IPv6Address)) + } } sdkLBEnablePrefixForIpv6SourceNatValue := string(elbv2model.EnablePrefixForIpv6SourceNatOff) resLBEnablePrefixForIpv6SourceNatValue := string(elbv2model.EnablePrefixForIpv6SourceNatOff) @@ -180,7 +188,9 @@ func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSubnetMappings(ctx resLBEnablePrefixForIpv6SourceNatValue = string(resLB.Spec.EnablePrefixForIpv6SourceNat) - if desiredSubnets.Equal(currentSubnets) && desiredSubnetsSourceNATPrefixes.Equal(currentSubnetsSourceNATPrefixes) && ((sdkLBEnablePrefixForIpv6SourceNatValue == resLBEnablePrefixForIpv6SourceNatValue) || (resLBEnablePrefixForIpv6SourceNatValue == "")) { + isFirstTimeIPv6Setup := currentIPv6Addresses.Len() == 0 && desiredIPv6Addresses.Len() > 0 + needsDualstackIPv6Update := isIPv4ToDualstackUpdate(resLB, sdkLB) && isFirstTimeIPv6Setup + if !needsDualstackIPv6Update && desiredSubnets.Equal(currentSubnets) && desiredSubnetsSourceNATPrefixes.Equal(currentSubnetsSourceNATPrefixes) && ((sdkLBEnablePrefixForIpv6SourceNatValue == resLBEnablePrefixForIpv6SourceNatValue) || (resLBEnablePrefixForIpv6SourceNatValue == "")) { return nil } req := &elbv2sdk.SetSubnetsInput{ @@ -355,3 +365,15 @@ func isEnforceSGInboundRulesOnPrivateLinkUpdated(resLB *elbv2model.LoadBalancer, return true, currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic, desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic } + +func isIPv4ToDualstackUpdate(resLB *elbv2model.LoadBalancer, sdkLB LoadBalancerWithTags) bool { + if &resLB.Spec.IPAddressType == nil { + return false + } + desiredIPAddressType := string(resLB.Spec.IPAddressType) + currentIPAddressType := sdkLB.LoadBalancer.IpAddressType + isIPAddressTypeUpdated := desiredIPAddressType != string(currentIPAddressType) + return isIPAddressTypeUpdated && + resLB.Spec.Type == elbv2model.LoadBalancerTypeNetwork && + desiredIPAddressType == string(elbv2model.IPAddressTypeDualStack) +} diff --git a/pkg/deploy/elbv2/load_balancer_manager_test.go b/pkg/deploy/elbv2/load_balancer_manager_test.go index 520dc9e85..e2b8567d1 100644 --- a/pkg/deploy/elbv2/load_balancer_manager_test.go +++ b/pkg/deploy/elbv2/load_balancer_manager_test.go @@ -5,6 +5,7 @@ import ( "testing" elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/aws/aws-sdk-go/aws" "github.com/go-logr/logr" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" @@ -612,6 +613,55 @@ func Test_defaultLoadBalancerManager_updateSDKLoadBalancerWithSubnetMappings(t * }, wantErr: nil, }, + { + name: "Set NLB IPv6 address in dualstack mode during ip address type modification ", + fields: fields{ + setSubnetsWithContextCall: setSubnetsWithContextCall{ + req: &elbv2sdk.SetSubnetsInput{ + LoadBalancerArn: awssdk.String("LoadBalancerArn"), + SubnetMappings: []elbv2types.SubnetMapping{ + { + SubnetId: awssdk.String("subnet-A"), + IPv6Address: aws.String("2600:1f18::1"), + }, + { + SubnetId: awssdk.String("subnet-B"), + IPv6Address: aws.String("2600:1f18::2"), + }, + }, + }, + resp: &elbv2sdk.SetSubnetsOutput{}, + }, + }, + args: args{ + resLB: &elbv2model.LoadBalancer{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::LoadBalancer", "id-1"), + Spec: elbv2model.LoadBalancerSpec{ + SubnetMappings: []elbv2model.SubnetMapping{ + { + SubnetID: "subnet-A", + IPv6Address: aws.String("2600:1f18::1"), + }, + { + SubnetID: "subnet-B", + IPv6Address: aws.String("2600:1f18::2"), + }, + }, + Type: elbv2model.LoadBalancerTypeNetwork, + IPAddressType: elbv2model.IPAddressTypeDualStack, + }, + }, + sdkLB: LoadBalancerWithTags{ + LoadBalancer: &elbv2types.LoadBalancer{ + LoadBalancerArn: awssdk.String("LoadBalancerArn"), + Type: elbv2types.LoadBalancerTypeEnumNetwork, + AvailabilityZones: []elbv2types.AvailabilityZone{{SubnetId: awssdk.String("subnet-A")}, {SubnetId: awssdk.String("subnet-B")}}, + IpAddressType: elbv2types.IpAddressTypeIpv4, + }, + }, + }, + wantErr: nil, + }, } for _, tt := range tests { @@ -632,6 +682,7 @@ func Test_defaultLoadBalancerManager_updateSDKLoadBalancerWithSubnetMappings(t * } else { assert.NoError(t, err) } + }) } } From b25ade55d5d620dafba41671f84e0031464628cc Mon Sep 17 00:00:00 2001 From: Yann Soubeyrand Date: Mon, 16 Dec 2024 15:19:33 +0100 Subject: [PATCH 05/30] fix(helm): change topologySpreadConstraints default value topologySpreadConstraints is a list not a map. --- helm/aws-load-balancer-controller/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/aws-load-balancer-controller/values.yaml b/helm/aws-load-balancer-controller/values.yaml index c2f465bcd..098a838ba 100644 --- a/helm/aws-load-balancer-controller/values.yaml +++ b/helm/aws-load-balancer-controller/values.yaml @@ -91,7 +91,7 @@ configureDefaultAffinity: true # nodes, and other user-defined topology domains. # # more details here: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ -topologySpreadConstraints: {} +topologySpreadConstraints: [] updateStrategy: {} # type: RollingUpdate From 31ffe10da0f0e50d63022e9ac55535be00ef6a04 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Mon, 18 Nov 2024 09:36:13 -0800 Subject: [PATCH 06/30] bug fix: try SG permission add prior to revoke --- pkg/networking/security_group_manager.go | 2 +- pkg/networking/security_group_reconciler.go | 41 +- .../security_group_reconciler_test.go | 378 ++++++++++++++++++ 3 files changed, 411 insertions(+), 10 deletions(-) diff --git a/pkg/networking/security_group_manager.go b/pkg/networking/security_group_manager.go index c90ba36cb..18b4bc194 100644 --- a/pkg/networking/security_group_manager.go +++ b/pkg/networking/security_group_manager.go @@ -33,7 +33,7 @@ func (opts *FetchSGInfoOptions) ApplyOptions(options ...FetchSGInfoOption) { type FetchSGInfoOption func(opts *FetchSGInfoOptions) -// WithReloadIgnoringCache is a option that sets the ReloadIgnoringCache to true. +// WithReloadIgnoringCache is an option that sets the ReloadIgnoringCache to true. func WithReloadIgnoringCache() FetchSGInfoOption { return func(opts *FetchSGInfoOptions) { opts.ReloadIgnoringCache = true diff --git a/pkg/networking/security_group_reconciler.go b/pkg/networking/security_group_reconciler.go index 8b9e5731f..1a54a8655 100644 --- a/pkg/networking/security_group_reconciler.go +++ b/pkg/networking/security_group_reconciler.go @@ -77,23 +77,24 @@ func (r *defaultSecurityGroupReconciler) ReconcileIngress(ctx context.Context, s return err } sgInfo := sgInfoByID[sgID] - if err := r.reconcileIngressWithSGInfo(ctx, sgInfo, desiredPermissions, reconcileOpts); err != nil { + + if err := r.reconcileIngressWithSGInfo(ctx, sgInfo, desiredPermissions, false, reconcileOpts); err != nil { if !r.shouldRetryWithoutCache(err) { return err } + revokeFirst := r.shouldRemoveSGRulesFirst(err) + r.logger.Info("Retrying ReconcileIngress without using cache", "revokeFirst", revokeFirst) sgInfoByID, err := r.sgManager.FetchSGInfosByID(ctx, []string{sgID}, WithReloadIgnoringCache()) if err != nil { return err } sgInfo := sgInfoByID[sgID] - if err := r.reconcileIngressWithSGInfo(ctx, sgInfo, desiredPermissions, reconcileOpts); err != nil { - return err - } + return r.reconcileIngressWithSGInfo(ctx, sgInfo, desiredPermissions, revokeFirst, reconcileOpts) } return nil } -func (r *defaultSecurityGroupReconciler) reconcileIngressWithSGInfo(ctx context.Context, sgInfo SecurityGroupInfo, desiredPermissions []IPPermissionInfo, reconcileOpts SecurityGroupReconcileOptions) error { +func (r *defaultSecurityGroupReconciler) reconcileIngressWithSGInfo(ctx context.Context, sgInfo SecurityGroupInfo, desiredPermissions []IPPermissionInfo, revokeFirst bool, reconcileOpts SecurityGroupReconcileOptions) error { extraPermissions := diffIPPermissionInfos(sgInfo.Ingress, desiredPermissions) permissionsToRevoke := make([]IPPermissionInfo, 0, len(extraPermissions)) for _, permission := range extraPermissions { @@ -102,16 +103,29 @@ func (r *defaultSecurityGroupReconciler) reconcileIngressWithSGInfo(ctx context. } } permissionsToGrant := diffIPPermissionInfos(desiredPermissions, sgInfo.Ingress) - if len(permissionsToRevoke) > 0 && !reconcileOpts.AuthorizeOnly { - if err := r.sgManager.RevokeSGIngress(ctx, sgInfo.SecurityGroupID, permissionsToRevoke); err != nil { - return err + + if revokeFirst { + if len(permissionsToRevoke) > 0 && !reconcileOpts.AuthorizeOnly { + if err := r.sgManager.RevokeSGIngress(ctx, sgInfo.SecurityGroupID, permissionsToRevoke); err != nil { + return err + } } } + if len(permissionsToGrant) > 0 { if err := r.sgManager.AuthorizeSGIngress(ctx, sgInfo.SecurityGroupID, permissionsToGrant); err != nil { return err } } + + if !revokeFirst { + if len(permissionsToRevoke) > 0 && !reconcileOpts.AuthorizeOnly { + if err := r.sgManager.RevokeSGIngress(ctx, sgInfo.SecurityGroupID, permissionsToRevoke); err != nil { + return err + } + } + } + return nil } @@ -119,7 +133,16 @@ func (r *defaultSecurityGroupReconciler) reconcileIngressWithSGInfo(ctx context. func (r *defaultSecurityGroupReconciler) shouldRetryWithoutCache(err error) bool { var apiErr smithy.APIError if errors.As(err, &apiErr) { - return apiErr.ErrorCode() == "InvalidPermission.Duplicate" || apiErr.ErrorCode() == "InvalidPermission.NotFound" + return apiErr.ErrorCode() == "InvalidPermission.Duplicate" || apiErr.ErrorCode() == "InvalidPermission.NotFound" || apiErr.ErrorCode() == "RulesPerSecurityGroupLimitExceeded" + } + return false +} + +// shouldRemoveSGRulesFirst tests whether we should retry SecurityGroup rules reconcile but revoking rules prior to adding new rules. +func (r *defaultSecurityGroupReconciler) shouldRemoveSGRulesFirst(err error) bool { + var apiErr smithy.APIError + if errors.As(err, &apiErr) { + return apiErr.ErrorCode() == "RulesPerSecurityGroupLimitExceeded" } return false } diff --git a/pkg/networking/security_group_reconciler_test.go b/pkg/networking/security_group_reconciler_test.go index 2bb3c7124..34f119db3 100644 --- a/pkg/networking/security_group_reconciler_test.go +++ b/pkg/networking/security_group_reconciler_test.go @@ -1,10 +1,15 @@ package networking import ( + "context" + "errors" awssdk "github.com/aws/aws-sdk-go-v2/aws" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/smithy-go" + "github.com/go-logr/logr" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "sigs.k8s.io/controller-runtime/pkg/log" "testing" ) @@ -31,6 +36,13 @@ func Test_defaultSecurityGroupReconciler_shouldRetryWithoutCache(t *testing.T) { }, want: true, }, + { + name: "should retry without cache when got too many rules error", + args: args{ + err: &smithy.GenericAPIError{Code: "RulesPerSecurityGroupLimitExceeded", Message: ""}, + }, + want: true, + }, { name: "shouldn't retry when got some other error", args: args{ @@ -215,3 +227,369 @@ func Test_diffIPPermissionInfos(t *testing.T) { }) } } + +func TestReconcileSGIngress(t *testing.T) { + sgId := "sgId" + type fetchSGInfosByIDCall struct { + req []string + resp map[string]SecurityGroupInfo + err error + } + + tests := []struct { + name string + inputSGRules []IPPermissionInfo + sgGetCall fetchSGInfosByIDCall + authorizeData []IPPermissionInfo + revokeData []IPPermissionInfo + revokeCalls int + authorizeCalls int + authorizeError error + revokeError error + expectErr bool + }{ + { + name: "no permissions in either ec2 or kube", + inputSGRules: []IPPermissionInfo{}, + sgGetCall: fetchSGInfosByIDCall{ + req: []string{sgId}, + resp: map[string]SecurityGroupInfo{ + sgId: { + SecurityGroupID: sgId, + }, + }, + }, + }, + { + name: "sg get failure blocks revoke / authorize", + inputSGRules: []IPPermissionInfo{}, + sgGetCall: fetchSGInfosByIDCall{ + req: []string{sgId}, + resp: map[string]SecurityGroupInfo{ + sgId: { + SecurityGroupID: sgId, + }, + }, + err: errors.New("bad thing"), + }, + expectErr: true, + }, + { + name: "permission present in kube but not ec2 should lead to authorize call", + inputSGRules: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + sgGetCall: fetchSGInfosByIDCall{ + req: []string{sgId}, + resp: map[string]SecurityGroupInfo{ + sgId: { + SecurityGroupID: sgId, + }, + }, + }, + authorizeData: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + authorizeCalls: 1, + }, + { + name: "permission present in ec2 but not kube should lead to revoke call", + inputSGRules: []IPPermissionInfo{}, + sgGetCall: fetchSGInfosByIDCall{ + req: []string{sgId}, + resp: map[string]SecurityGroupInfo{ + sgId: { + SecurityGroupID: sgId, + Ingress: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + }, + }, + }, + revokeData: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + revokeCalls: 1, + }, + { + name: "revoke and authorize together", + inputSGRules: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(12), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + sgGetCall: fetchSGInfosByIDCall{ + req: []string{sgId}, + resp: map[string]SecurityGroupInfo{ + sgId: { + SecurityGroupID: sgId, + Ingress: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + }, + }, + }, + authorizeData: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(12), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + authorizeCalls: 1, + revokeData: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + revokeCalls: 1, + }, + { + name: "authorize error should block revoke call", + inputSGRules: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(12), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + sgGetCall: fetchSGInfosByIDCall{ + req: []string{sgId}, + resp: map[string]SecurityGroupInfo{ + sgId: { + SecurityGroupID: sgId, + Ingress: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + }, + }, + }, + authorizeError: errors.New("authorize error"), + expectErr: true, + authorizeData: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(12), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + authorizeCalls: 1, + }, + { + name: "revoke error should not block authorize call", + inputSGRules: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(12), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + sgGetCall: fetchSGInfosByIDCall{ + req: []string{sgId}, + resp: map[string]SecurityGroupInfo{ + sgId: { + SecurityGroupID: sgId, + Ingress: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + }, + }, + }, + revokeError: errors.New("revoke error"), + expectErr: true, + authorizeData: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(12), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + authorizeCalls: 1, + revokeData: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + revokeCalls: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + + sgManager := NewMockSecurityGroupManager(ctrl) + reconciler := &defaultSecurityGroupReconciler{ + sgManager: sgManager, + logger: logr.New(&log.NullLogSink{}), + } + + ctx := context.Background() + sgManager.EXPECT().FetchSGInfosByID(gomock.Any(), tt.sgGetCall.req, gomock.Any()).Return(tt.sgGetCall.resp, tt.sgGetCall.err) + + sgManager.EXPECT().AuthorizeSGIngress(gomock.Eq(ctx), gomock.Eq(sgId), gomock.Eq(tt.authorizeData)).Return(tt.authorizeError).Times(tt.authorizeCalls) + sgManager.EXPECT().RevokeSGIngress(gomock.Eq(ctx), gomock.Eq(sgId), gomock.Eq(tt.revokeData)).Return(tt.revokeError).Times(tt.revokeCalls) + + err := reconciler.ReconcileIngress(ctx, sgId, tt.inputSGRules) + ctrl.Finish() + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestReconcileSGIngress_RehydrateCache(t *testing.T) { + + testCases := []struct { + name string + initialAuthorizeError error + secondAuthorizeError error + revokeFirst bool + }{ + { + name: "not found error leads to cache re-hydrate", + initialAuthorizeError: &smithy.GenericAPIError{Code: "InvalidPermission.NotFound", Message: ""}, + revokeFirst: false, + }, + { + name: "too many rules error leads to cache re-hydrate and inverse operations", + initialAuthorizeError: &smithy.GenericAPIError{Code: "RulesPerSecurityGroupLimitExceeded", Message: ""}, + revokeFirst: true, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + sgId := "sgId" + ctrl := gomock.NewController(t) + + sgManager := NewMockSecurityGroupManager(ctrl) + reconciler := &defaultSecurityGroupReconciler{ + sgManager: sgManager, + logger: logr.New(&log.NullLogSink{}), + } + + ctx := context.Background() + + sgManager.EXPECT().FetchSGInfosByID(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(map[string]SecurityGroupInfo{ + sgId: { + SecurityGroupID: sgId, + Ingress: []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + }, + }, + }, nil) + + revokeData := []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(10), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + } + + inputSGRules := []IPPermissionInfo{ + { + Permission: ec2types.IpPermission{ + FromPort: awssdk.Int32(12), + ToPort: awssdk.Int32(15), + IpProtocol: awssdk.String("tcp"), + }, + }, + } + + if tt.revokeFirst { + gomock.InOrder( + sgManager.EXPECT().AuthorizeSGIngress(gomock.Eq(ctx), gomock.Eq(sgId), gomock.Eq(inputSGRules)).Times(1).Return(tt.initialAuthorizeError), + sgManager.EXPECT().RevokeSGIngress(gomock.Eq(ctx), gomock.Eq(sgId), gomock.Eq(revokeData)).Return(nil).Times(1), + sgManager.EXPECT().AuthorizeSGIngress(gomock.Eq(ctx), gomock.Eq(sgId), gomock.Eq(inputSGRules)).Times(1).Return(tt.secondAuthorizeError), + ) + } else { + gomock.InOrder( + sgManager.EXPECT().AuthorizeSGIngress(gomock.Eq(ctx), gomock.Eq(sgId), gomock.Eq(inputSGRules)).Times(1).Return(tt.initialAuthorizeError), + sgManager.EXPECT().AuthorizeSGIngress(gomock.Eq(ctx), gomock.Eq(sgId), gomock.Eq(inputSGRules)).Times(1).Return(tt.secondAuthorizeError), + sgManager.EXPECT().RevokeSGIngress(gomock.Eq(ctx), gomock.Eq(sgId), gomock.Eq(revokeData)).Return(nil).Times(1), + ) + } + + err := reconciler.ReconcileIngress(ctx, sgId, inputSGRules) + ctrl.Finish() + assert.NoError(t, err) + }) + } +} From 411778c01f99ff476d71e79a795c3155d0bf2dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pinkava?= Date: Sun, 20 Oct 2024 13:49:43 +0200 Subject: [PATCH 07/30] Add helm chart values for --aws-vpc-tags argument --- helm/aws-load-balancer-controller/README.md | 1 + helm/aws-load-balancer-controller/templates/deployment.yaml | 3 +++ helm/aws-load-balancer-controller/values.yaml | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/helm/aws-load-balancer-controller/README.md b/helm/aws-load-balancer-controller/README.md index a86383dba..4b544f58c 100644 --- a/helm/aws-load-balancer-controller/README.md +++ b/helm/aws-load-balancer-controller/README.md @@ -212,6 +212,7 @@ The default values set by the application itself can be confirmed [here](https:/ | `ingressClassParams.spec` | IngressClassParams defined ingress specifications | {} | | `region` | The AWS region for the kubernetes cluster | None | | `vpcId` | The VPC ID for the Kubernetes cluster | None | +| `vpcTags` | This is alternative to vpcId. Set this when your pods are unable to use the metadata service to determine VPC automatically. | None | `awsApiEndpoints` | Custom AWS API Endpoints | None | | `awsApiThrottle` | Custom AWS API throttle settings | None | | `awsMaxRetries` | Maximum retries for AWS APIs | None | diff --git a/helm/aws-load-balancer-controller/templates/deployment.yaml b/helm/aws-load-balancer-controller/templates/deployment.yaml index 4506d489e..49e228975 100644 --- a/helm/aws-load-balancer-controller/templates/deployment.yaml +++ b/helm/aws-load-balancer-controller/templates/deployment.yaml @@ -173,6 +173,9 @@ spec: {{- if .Values.loadBalancerClass }} - --load-balancer-class={{ .Values.loadBalancerClass }} {{- end }} + {{- if .Values.vpcTags }} + - --aws-vpc-tags={{ include "aws-load-balancer-controller.convertMapToCsv" .Values.vpcTags | trimSuffix "," }} + {{- end }} {{- if or .Values.env .Values.envSecretName }} env: {{- if .Values.env}} diff --git a/helm/aws-load-balancer-controller/values.yaml b/helm/aws-load-balancer-controller/values.yaml index c2f465bcd..79c29fb02 100644 --- a/helm/aws-load-balancer-controller/values.yaml +++ b/helm/aws-load-balancer-controller/values.yaml @@ -161,6 +161,10 @@ region: # The VPC ID for the Kubernetes cluster. Set this manually when your pods are unable to use the metadata service to determine this automatically vpcId: +# This is alternative to vpcId. Set this when your pods are unable to use the metadata service to determine VPC automatically. +vpcTags: {} +# Name: tagValue + # Custom AWS API Endpoints (serviceID1=URL1,serviceID2=URL2) awsApiEndpoints: From 06cf08db70a37a04436313fac5beb08643d8bc37 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Mon, 6 Jan 2025 12:22:00 -0800 Subject: [PATCH 08/30] bug fix: use reasonable buckets for readiness gate flip metrics --- pkg/metrics/lbc/instruments.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/metrics/lbc/instruments.go b/pkg/metrics/lbc/instruments.go index 0a38b7f77..8fafd2f98 100644 --- a/pkg/metrics/lbc/instruments.go +++ b/pkg/metrics/lbc/instruments.go @@ -29,6 +29,7 @@ func newInstruments(registerer prometheus.Registerer) *instruments { Subsystem: metricSubsystem, Name: MetricPodReadinessGateReady, Help: "Latency from pod getting added to the load balancer until the readiness gate is flipped to healthy.", + Buckets: []float64{10, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600}, }, []string{labelNamespace, labelName}) registerer.MustRegister(podReadinessFlipSeconds) From 0529accf586a2e32d945696144fdfcbdb4ca95d3 Mon Sep 17 00:00:00 2001 From: Aida Khalelova Date: Wed, 8 Jan 2025 12:53:33 -0500 Subject: [PATCH 09/30] docs: added annotation behaviour note --- docs/guide/ingress/annotations.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/guide/ingress/annotations.md b/docs/guide/ingress/annotations.md index 2d51c29b8..0dd09ed56 100644 --- a/docs/guide/ingress/annotations.md +++ b/docs/guide/ingress/annotations.md @@ -174,6 +174,10 @@ Traffic Routing can be controlled with following annotations: - Once defined on a single Ingress, it impacts every Ingress within the IngressGroup. + !!!note "Annotation Behavior" + + - This annotation **takes effect only during the creation** of the Ingress. If the Ingress already exists, the change will not be applied until the Ingress is **deleted and recreated**. + !!!example ``` alb.ingress.kubernetes.io/load-balancer-name: custom-name From e9445ade06a78b8334defad8db9c17f6a27456ad Mon Sep 17 00:00:00 2001 From: Shraddha Bang <18206078+shraddhabang@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:16:29 -0800 Subject: [PATCH 10/30] Fix CVE-2024-45338 - golang.org/x/net (#4010) --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index baafc0f46..bd865ccc0 100644 --- a/go.mod +++ b/go.mod @@ -169,14 +169,14 @@ require ( go.opentelemetry.io/otel/trace v1.19.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/grpc v1.58.3 // indirect diff --git a/go.sum b/go.sum index 7f48d636b..6aee0098a 100644 --- a/go.sum +++ b/go.sum @@ -532,8 +532,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= @@ -565,8 +565,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -578,8 +578,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -607,21 +607,21 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 10e5f7285670f3fc136bb7cd38870720d1727b1f Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Thu, 9 Jan 2025 16:33:44 -0800 Subject: [PATCH 11/30] bump sigs.k8s.io/controller-runtime to v0.19.3 --- controllers/elbv2/eventhandlers/endpoints.go | 10 +- .../elbv2/eventhandlers/endpoints_test.go | 10 +- .../elbv2/eventhandlers/endpointslices.go | 10 +- .../eventhandlers/endpointslices_test.go | 10 +- controllers/elbv2/eventhandlers/node.go | 10 +- controllers/elbv2/eventhandlers/service.go | 10 +- .../elbv2/eventhandlers/service_test.go | 10 +- .../elbv2/targetgroupbinding_controller.go | 7 +- .../eventhandlers/ingress_class_events.go | 13 +- .../ingress_class_params_events.go | 13 +- .../ingress/eventhandlers/ingress_events.go | 15 +- .../ingress/eventhandlers/secret_events.go | 13 +- .../ingress/eventhandlers/service_events.go | 13 +- controllers/ingress/group_controller.go | 5 +- .../service/eventhandlers/service_events.go | 10 +- controllers/service/service_controller.go | 5 +- go.mod | 81 ++++---- go.sum | 190 +++++++++--------- pkg/ingress/group.go | 8 +- pkg/ingress/group_test.go | 14 +- pkg/testutils/event_handler_test_utils.go | 8 +- 21 files changed, 233 insertions(+), 232 deletions(-) diff --git a/controllers/elbv2/eventhandlers/endpoints.go b/controllers/elbv2/eventhandlers/endpoints.go index 622e1455e..79df3672b 100644 --- a/controllers/elbv2/eventhandlers/endpoints.go +++ b/controllers/elbv2/eventhandlers/endpoints.go @@ -33,13 +33,13 @@ type enqueueRequestsForEndpointsEvent struct { } // Create is called in response to an create event - e.g. Pod Creation. -func (h *enqueueRequestsForEndpointsEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForEndpointsEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { epNew := e.Object.(*corev1.Endpoints) h.enqueueImpactedTargetGroupBindings(queue, epNew) } // Update is called in response to an update event - e.g. Pod Updated. -func (h *enqueueRequestsForEndpointsEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForEndpointsEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { epOld := e.ObjectOld.(*corev1.Endpoints) epNew := e.ObjectNew.(*corev1.Endpoints) if !equality.Semantic.DeepEqual(epOld.Subsets, epNew.Subsets) { @@ -48,17 +48,17 @@ func (h *enqueueRequestsForEndpointsEvent) Update(ctx context.Context, e event.U } // Delete is called in response to a delete event - e.g. Pod Deleted. -func (h *enqueueRequestsForEndpointsEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForEndpointsEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { epOld := e.Object.(*corev1.Endpoints) h.enqueueImpactedTargetGroupBindings(queue, epOld) } // Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or // external trigger request - e.g. reconcile AutoScaling, or a WebHook. -func (h *enqueueRequestsForEndpointsEvent) Generic(context.Context, event.GenericEvent, workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForEndpointsEvent) Generic(context.Context, event.GenericEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { } -func (h *enqueueRequestsForEndpointsEvent) enqueueImpactedTargetGroupBindings(queue workqueue.RateLimitingInterface, ep *corev1.Endpoints) { +func (h *enqueueRequestsForEndpointsEvent) enqueueImpactedTargetGroupBindings(queue workqueue.TypedRateLimitingInterface[reconcile.Request], ep *corev1.Endpoints) { tgbList := &elbv2api.TargetGroupBindingList{} if err := h.k8sClient.List(context.Background(), tgbList, client.InNamespace(ep.Namespace), diff --git a/controllers/elbv2/eventhandlers/endpoints_test.go b/controllers/elbv2/eventhandlers/endpoints_test.go index 102ea7f55..306dc20ce 100644 --- a/controllers/elbv2/eventhandlers/endpoints_test.go +++ b/controllers/elbv2/eventhandlers/endpoints_test.go @@ -2,6 +2,7 @@ package eventhandlers import ( "context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "testing" "github.com/go-logr/logr" @@ -15,7 +16,6 @@ import ( elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" mock_client "sigs.k8s.io/aws-load-balancer-controller/mocks/controller-runtime/client" "sigs.k8s.io/aws-load-balancer-controller/pkg/testutils" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllertest" "sigs.k8s.io/controller-runtime/pkg/log" @@ -40,7 +40,7 @@ func Test_enqueueRequestsForEndpointsEvent_enqueueImpactedTargetGroupBindings(t name string fields fields args args - wantRequests []ctrl.Request + wantRequests []reconcile.Request }{ { name: "service event should enqueue impacted ip TargetType TGBs", @@ -91,7 +91,7 @@ func Test_enqueueRequestsForEndpointsEvent_enqueueImpactedTargetGroupBindings(t }, }, }, - wantRequests: []ctrl.Request{ + wantRequests: []reconcile.Request{ { NamespacedName: types.NamespacedName{Namespace: "awesome-ns", Name: "tgb-1"}, }, @@ -140,7 +140,7 @@ func Test_enqueueRequestsForEndpointsEvent_enqueueImpactedTargetGroupBindings(t }, }, }, - wantRequests: []ctrl.Request{ + wantRequests: []reconcile.Request{ { NamespacedName: types.NamespacedName{Namespace: "awesome-ns", Name: "tgb-1"}, }, @@ -172,7 +172,7 @@ func Test_enqueueRequestsForEndpointsEvent_enqueueImpactedTargetGroupBindings(t k8sClient: k8sClient, logger: logr.New(&log.NullLogSink{}), } - queue := &controllertest.Queue{Interface: workqueue.New()} + queue := &controllertest.TypedQueue[reconcile.Request]{TypedInterface: workqueue.NewTyped[reconcile.Request]()} h.enqueueImpactedTargetGroupBindings(queue, tt.args.eps) gotRequests := testutils.ExtractCTRLRequestsFromQueue(queue) assert.True(t, cmp.Equal(tt.wantRequests, gotRequests), diff --git a/controllers/elbv2/eventhandlers/endpointslices.go b/controllers/elbv2/eventhandlers/endpointslices.go index b9250a7a5..01ef2bab9 100644 --- a/controllers/elbv2/eventhandlers/endpointslices.go +++ b/controllers/elbv2/eventhandlers/endpointslices.go @@ -36,14 +36,14 @@ type enqueueRequestsForEndpointSlicesEvent struct { } // Create is called in response to an create event - e.g. EndpointSlice Creation. -func (h *enqueueRequestsForEndpointSlicesEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForEndpointSlicesEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { epNew := e.Object.(*discv1.EndpointSlice) h.logger.V(1).Info("Create event for EndpointSlices", "name", epNew.Name) h.enqueueImpactedTargetGroupBindings(ctx, queue, epNew) } // Update is called in response to an update event - e.g. EndpointSlice Updated. -func (h *enqueueRequestsForEndpointSlicesEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForEndpointSlicesEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { epOld := e.ObjectOld.(*discv1.EndpointSlice) epNew := e.ObjectNew.(*discv1.EndpointSlice) h.logger.V(1).Info("Update event for EndpointSlices", "name", epNew.Name) @@ -54,7 +54,7 @@ func (h *enqueueRequestsForEndpointSlicesEvent) Update(ctx context.Context, e ev } // Delete is called in response to a delete event - e.g. EndpointSlice Deleted. -func (h *enqueueRequestsForEndpointSlicesEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForEndpointSlicesEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { epOld := e.Object.(*discv1.EndpointSlice) h.logger.V(1).Info("Deletion event for EndpointSlices", "name", epOld.Name) h.enqueueImpactedTargetGroupBindings(ctx, queue, epOld) @@ -62,10 +62,10 @@ func (h *enqueueRequestsForEndpointSlicesEvent) Delete(ctx context.Context, e ev // Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or // external trigger request - e.g. reconcile AutoScaling, or a WebHook. -func (h *enqueueRequestsForEndpointSlicesEvent) Generic(context.Context, event.GenericEvent, workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForEndpointSlicesEvent) Generic(context.Context, event.GenericEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { } -func (h *enqueueRequestsForEndpointSlicesEvent) enqueueImpactedTargetGroupBindings(ctx context.Context, queue workqueue.RateLimitingInterface, epSlice *discv1.EndpointSlice) { +func (h *enqueueRequestsForEndpointSlicesEvent) enqueueImpactedTargetGroupBindings(ctx context.Context, queue workqueue.TypedRateLimitingInterface[reconcile.Request], epSlice *discv1.EndpointSlice) { tgbList := &elbv2api.TargetGroupBindingList{} svcName, present := epSlice.Labels[svcNameLabel] if !present { diff --git a/controllers/elbv2/eventhandlers/endpointslices_test.go b/controllers/elbv2/eventhandlers/endpointslices_test.go index dd8856358..82746b92b 100644 --- a/controllers/elbv2/eventhandlers/endpointslices_test.go +++ b/controllers/elbv2/eventhandlers/endpointslices_test.go @@ -2,6 +2,7 @@ package eventhandlers import ( "context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "testing" "github.com/go-logr/logr" @@ -15,7 +16,6 @@ import ( elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" mock_client "sigs.k8s.io/aws-load-balancer-controller/mocks/controller-runtime/client" "sigs.k8s.io/aws-load-balancer-controller/pkg/testutils" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllertest" "sigs.k8s.io/controller-runtime/pkg/log" @@ -40,7 +40,7 @@ func Test_enqueueRequestsForEndpointSlicesEvent_enqueueImpactedTargetGroupBindin name string fields fields args args - wantRequests []ctrl.Request + wantRequests []reconcile.Request }{ { name: "service event should enqueue impacted ip TargetType TGBs", @@ -92,7 +92,7 @@ func Test_enqueueRequestsForEndpointSlicesEvent_enqueueImpactedTargetGroupBindin }, }, }, - wantRequests: []ctrl.Request{ + wantRequests: []reconcile.Request{ { NamespacedName: types.NamespacedName{Namespace: "awesome-ns", Name: "tgb-1"}, }, @@ -142,7 +142,7 @@ func Test_enqueueRequestsForEndpointSlicesEvent_enqueueImpactedTargetGroupBindin }, }, }, - wantRequests: []ctrl.Request{ + wantRequests: []reconcile.Request{ { NamespacedName: types.NamespacedName{Namespace: "awesome-ns", Name: "tgb-1"}, }, @@ -174,7 +174,7 @@ func Test_enqueueRequestsForEndpointSlicesEvent_enqueueImpactedTargetGroupBindin k8sClient: k8sClient, logger: logr.New(&log.NullLogSink{}), } - queue := &controllertest.Queue{Interface: workqueue.New()} + queue := &controllertest.TypedQueue[reconcile.Request]{TypedInterface: workqueue.NewTyped[reconcile.Request]()} h.enqueueImpactedTargetGroupBindings(context.Background(), queue, tt.args.epslice) gotRequests := testutils.ExtractCTRLRequestsFromQueue(queue) assert.True(t, cmp.Equal(tt.wantRequests, gotRequests), diff --git a/controllers/elbv2/eventhandlers/node.go b/controllers/elbv2/eventhandlers/node.go index 3a63aae78..97c3730c1 100644 --- a/controllers/elbv2/eventhandlers/node.go +++ b/controllers/elbv2/eventhandlers/node.go @@ -31,32 +31,32 @@ type enqueueRequestsForNodeEvent struct { } // Create is called in response to an create event - e.g. Pod Creation. -func (h *enqueueRequestsForNodeEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForNodeEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { nodeNew := e.Object.(*corev1.Node) h.enqueueImpactedTargetGroupBindings(ctx, queue, nil, nodeNew) } // Update is called in response to an update event - e.g. Pod Updated. -func (h *enqueueRequestsForNodeEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForNodeEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { nodeOld := e.ObjectOld.(*corev1.Node) nodeNew := e.ObjectNew.(*corev1.Node) h.enqueueImpactedTargetGroupBindings(ctx, queue, nodeOld, nodeNew) } // Delete is called in response to a delete event - e.g. Pod Deleted. -func (h *enqueueRequestsForNodeEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForNodeEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { nodeOld := e.Object.(*corev1.Node) h.enqueueImpactedTargetGroupBindings(ctx, queue, nodeOld, nil) } // Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or // external trigger request - e.g. reconcile AutoScaling, or a WebHook. -func (h *enqueueRequestsForNodeEvent) Generic(context.Context, event.GenericEvent, workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForNodeEvent) Generic(context.Context, event.GenericEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { // nothing to do here } // enqueueImpactedTargetGroupBindings will enqueue all impacted TargetGroupBindings for node events. -func (h *enqueueRequestsForNodeEvent) enqueueImpactedTargetGroupBindings(ctx context.Context, queue workqueue.RateLimitingInterface, nodeOld *corev1.Node, nodeNew *corev1.Node) { +func (h *enqueueRequestsForNodeEvent) enqueueImpactedTargetGroupBindings(ctx context.Context, queue workqueue.TypedRateLimitingInterface[reconcile.Request], nodeOld *corev1.Node, nodeNew *corev1.Node) { var nodeKey types.NamespacedName nodeOldSuitableAsTrafficProxy := false nodeNewSuitableAsTrafficProxy := false diff --git a/controllers/elbv2/eventhandlers/service.go b/controllers/elbv2/eventhandlers/service.go index 2f1ed492f..b083a436c 100644 --- a/controllers/elbv2/eventhandlers/service.go +++ b/controllers/elbv2/eventhandlers/service.go @@ -31,13 +31,13 @@ type enqueueRequestsForServiceEvent struct { } // Create is called in response to an create event - e.g. Pod Creation. -func (h *enqueueRequestsForServiceEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { svcNew := e.Object.(*corev1.Service) h.enqueueImpactedTargetGroupBindings(ctx, queue, svcNew) } // Update is called in response to an update event - e.g. Pod Updated. -func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { svcOld := e.ObjectOld.(*corev1.Service) svcNew := e.ObjectNew.(*corev1.Service) if !equality.Semantic.DeepEqual(svcOld.Spec.Ports, svcNew.Spec.Ports) { @@ -46,19 +46,19 @@ func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.Upd } // Delete is called in response to a delete event - e.g. Pod Deleted. -func (h *enqueueRequestsForServiceEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { svcOld := e.Object.(*corev1.Service) h.enqueueImpactedTargetGroupBindings(ctx, queue, svcOld) } // Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or // external trigger request - e.g. reconcile AutoScaling, or a WebHook. -func (h *enqueueRequestsForServiceEvent) Generic(context.Context, event.GenericEvent, workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Generic(context.Context, event.GenericEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { // nothing to do here } // enqueueImpactedEndpointBindings will enqueue all impacted TargetGroupBindings for service events. -func (h *enqueueRequestsForServiceEvent) enqueueImpactedTargetGroupBindings(ctx context.Context, queue workqueue.RateLimitingInterface, svc *corev1.Service) { +func (h *enqueueRequestsForServiceEvent) enqueueImpactedTargetGroupBindings(ctx context.Context, queue workqueue.TypedRateLimitingInterface[reconcile.Request], svc *corev1.Service) { tgbList := &elbv2api.TargetGroupBindingList{} if err := h.k8sClient.List(context.Background(), tgbList, client.InNamespace(svc.Namespace), diff --git a/controllers/elbv2/eventhandlers/service_test.go b/controllers/elbv2/eventhandlers/service_test.go index f3f8e66f9..f1b110389 100644 --- a/controllers/elbv2/eventhandlers/service_test.go +++ b/controllers/elbv2/eventhandlers/service_test.go @@ -2,6 +2,7 @@ package eventhandlers import ( "context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "testing" "github.com/go-logr/logr" @@ -16,7 +17,6 @@ import ( elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" mock_client "sigs.k8s.io/aws-load-balancer-controller/mocks/controller-runtime/client" "sigs.k8s.io/aws-load-balancer-controller/pkg/testutils" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllertest" "sigs.k8s.io/controller-runtime/pkg/log" @@ -41,7 +41,7 @@ func Test_enqueueRequestsForServiceEvent_enqueueImpactedTargetGroupBindings(t *t name string fields fields args args - wantRequests []ctrl.Request + wantRequests []reconcile.Request }{ { name: "service event should enqueue impacted instance TargetType TGBs", @@ -102,7 +102,7 @@ func Test_enqueueRequestsForServiceEvent_enqueueImpactedTargetGroupBindings(t *t }, }, }, - wantRequests: []ctrl.Request{ + wantRequests: []reconcile.Request{ { NamespacedName: types.NamespacedName{Namespace: "awesome-ns", Name: "tgb-1"}, }, @@ -161,7 +161,7 @@ func Test_enqueueRequestsForServiceEvent_enqueueImpactedTargetGroupBindings(t *t }, }, }, - wantRequests: []ctrl.Request{ + wantRequests: []reconcile.Request{ { NamespacedName: types.NamespacedName{Namespace: "awesome-ns", Name: "tgb-1"}, }, @@ -193,7 +193,7 @@ func Test_enqueueRequestsForServiceEvent_enqueueImpactedTargetGroupBindings(t *t k8sClient: k8sClient, logger: logr.New(&log.NullLogSink{}), } - queue := &controllertest.Queue{Interface: workqueue.New()} + queue := &controllertest.TypedQueue[reconcile.Request]{TypedInterface: workqueue.NewTyped[reconcile.Request]()} h.enqueueImpactedTargetGroupBindings(context.Background(), queue, tt.args.svc) gotRequests := testutils.ExtractCTRLRequestsFromQueue(queue) assert.True(t, cmp.Equal(tt.wantRequests, gotRequests), diff --git a/controllers/elbv2/targetgroupbinding_controller.go b/controllers/elbv2/targetgroupbinding_controller.go index d80b90982..5533133d1 100644 --- a/controllers/elbv2/targetgroupbinding_controller.go +++ b/controllers/elbv2/targetgroupbinding_controller.go @@ -21,6 +21,7 @@ import ( "fmt" discv1 "k8s.io/api/discovery/v1" "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -91,12 +92,12 @@ type targetGroupBindingReconciler struct { // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch // +kubebuilder:rbac:groups="discovery.k8s.io",resources=endpointslices,verbs=get;list;watch -func (r *targetGroupBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *targetGroupBindingReconciler) Reconcile(ctx context.Context, req reconcile.Request) (ctrl.Result, error) { r.logger.V(1).Info("Reconcile request", "name", req.Name) return runtime.HandleReconcileError(r.reconcile(ctx, req), r.logger) } -func (r *targetGroupBindingReconciler) reconcile(ctx context.Context, req ctrl.Request) error { +func (r *targetGroupBindingReconciler) reconcile(ctx context.Context, req reconcile.Request) error { tgb := &elbv2api.TargetGroupBinding{} if err := r.k8sClient.Get(ctx, req.NamespacedName, tgb); err != nil { return client.IgnoreNotFound(err) @@ -194,7 +195,7 @@ func (r *targetGroupBindingReconciler) SetupWithManager(ctx context.Context, mgr Watches(&corev1.Node{}, nodeEventsHandler). WithOptions(controller.Options{ MaxConcurrentReconciles: r.maxConcurrentReconciles, - RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, r.maxExponentialBackoffDelay)}). + RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](5*time.Millisecond, r.maxExponentialBackoffDelay)}). Complete(r) } diff --git a/controllers/ingress/eventhandlers/ingress_class_events.go b/controllers/ingress/eventhandlers/ingress_class_events.go index 7de224548..c76c1d2ac 100644 --- a/controllers/ingress/eventhandlers/ingress_class_events.go +++ b/controllers/ingress/eventhandlers/ingress_class_events.go @@ -2,6 +2,7 @@ package eventhandlers import ( "context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" networking "k8s.io/api/networking/v1" @@ -17,7 +18,7 @@ import ( // NewEnqueueRequestsForIngressClassEvent constructs new enqueueRequestsForIngressClassEvent. func NewEnqueueRequestsForIngressClassEvent(ingEventChan chan<- event.TypedGenericEvent[*networking.Ingress], - k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*networking.IngressClass] { + k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*networking.IngressClass, reconcile.Request] { return &enqueueRequestsForIngressClassEvent{ ingEventChan: ingEventChan, k8sClient: k8sClient, @@ -26,7 +27,7 @@ func NewEnqueueRequestsForIngressClassEvent(ingEventChan chan<- event.TypedGener } } -var _ handler.TypedEventHandler[*networking.IngressClass] = (*enqueueRequestsForIngressClassEvent)(nil) +var _ handler.TypedEventHandler[*networking.IngressClass, reconcile.Request] = (*enqueueRequestsForIngressClassEvent)(nil) type enqueueRequestsForIngressClassEvent struct { ingEventChan chan<- event.TypedGenericEvent[*networking.Ingress] @@ -35,12 +36,12 @@ type enqueueRequestsForIngressClassEvent struct { logger logr.Logger } -func (h *enqueueRequestsForIngressClassEvent) Create(ctx context.Context, e event.TypedCreateEvent[*networking.IngressClass], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressClassEvent) Create(ctx context.Context, e event.TypedCreateEvent[*networking.IngressClass], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { ingClassNew := e.Object h.enqueueImpactedIngresses(ingClassNew) } -func (h *enqueueRequestsForIngressClassEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*networking.IngressClass], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressClassEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*networking.IngressClass], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { ingClassOld := e.ObjectOld ingClassNew := e.ObjectNew @@ -55,12 +56,12 @@ func (h *enqueueRequestsForIngressClassEvent) Update(ctx context.Context, e even h.enqueueImpactedIngresses(ingClassNew) } -func (h *enqueueRequestsForIngressClassEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*networking.IngressClass], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressClassEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*networking.IngressClass], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { ingClassOld := e.Object h.enqueueImpactedIngresses(ingClassOld) } -func (h *enqueueRequestsForIngressClassEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*networking.IngressClass], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressClassEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*networking.IngressClass], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { ingClass := e.Object h.enqueueImpactedIngresses(ingClass) } diff --git a/controllers/ingress/eventhandlers/ingress_class_params_events.go b/controllers/ingress/eventhandlers/ingress_class_params_events.go index b01360dae..d574197ca 100644 --- a/controllers/ingress/eventhandlers/ingress_class_params_events.go +++ b/controllers/ingress/eventhandlers/ingress_class_params_events.go @@ -2,6 +2,7 @@ package eventhandlers import ( "context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" networking "k8s.io/api/networking/v1" @@ -17,7 +18,7 @@ import ( // NewEnqueueRequestsForIngressClassParamsEvent constructs new enqueueRequestsForIngressClassParamsEvent. func NewEnqueueRequestsForIngressClassParamsEvent(ingClassEventChan chan<- event.TypedGenericEvent[*networking.IngressClass], - k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*elbv2api.IngressClassParams] { + k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*elbv2api.IngressClassParams, reconcile.Request] { return &enqueueRequestsForIngressClassParamsEvent{ ingClassEventChan: ingClassEventChan, k8sClient: k8sClient, @@ -26,7 +27,7 @@ func NewEnqueueRequestsForIngressClassParamsEvent(ingClassEventChan chan<- event } } -var _ handler.TypedEventHandler[*elbv2api.IngressClassParams] = (*enqueueRequestsForIngressClassParamsEvent)(nil) +var _ handler.TypedEventHandler[*elbv2api.IngressClassParams, reconcile.Request] = (*enqueueRequestsForIngressClassParamsEvent)(nil) type enqueueRequestsForIngressClassParamsEvent struct { ingClassEventChan chan<- event.TypedGenericEvent[*networking.IngressClass] @@ -35,12 +36,12 @@ type enqueueRequestsForIngressClassParamsEvent struct { logger logr.Logger } -func (h *enqueueRequestsForIngressClassParamsEvent) Create(ctx context.Context, e event.TypedCreateEvent[*elbv2api.IngressClassParams], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressClassParamsEvent) Create(ctx context.Context, e event.TypedCreateEvent[*elbv2api.IngressClassParams], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { ingClassParamsNew := e.Object h.enqueueImpactedIngressClasses(ctx, ingClassParamsNew) } -func (h *enqueueRequestsForIngressClassParamsEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*elbv2api.IngressClassParams], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressClassParamsEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*elbv2api.IngressClassParams], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { ingClassParamsOld := e.ObjectOld ingClassParamsNew := e.ObjectNew @@ -55,12 +56,12 @@ func (h *enqueueRequestsForIngressClassParamsEvent) Update(ctx context.Context, h.enqueueImpactedIngressClasses(ctx, ingClassParamsNew) } -func (h *enqueueRequestsForIngressClassParamsEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*elbv2api.IngressClassParams], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressClassParamsEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*elbv2api.IngressClassParams], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { ingClassParamsOld := e.Object h.enqueueImpactedIngressClasses(ctx, ingClassParamsOld) } -func (h *enqueueRequestsForIngressClassParamsEvent) Generic(context.Context, event.TypedGenericEvent[*elbv2api.IngressClassParams], workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressClassParamsEvent) Generic(context.Context, event.TypedGenericEvent[*elbv2api.IngressClassParams], workqueue.TypedRateLimitingInterface[reconcile.Request]) { // we don't have any generic event for secrets. } diff --git a/controllers/ingress/eventhandlers/ingress_events.go b/controllers/ingress/eventhandlers/ingress_events.go index 8aeb25ef1..81cf363d9 100644 --- a/controllers/ingress/eventhandlers/ingress_events.go +++ b/controllers/ingress/eventhandlers/ingress_events.go @@ -3,6 +3,7 @@ package eventhandlers import ( "context" "fmt" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -17,7 +18,7 @@ import ( ) func NewEnqueueRequestsForIngressEvent(groupLoader ingress.GroupLoader, eventRecorder record.EventRecorder, - logger logr.Logger) handler.TypedEventHandler[*networking.Ingress] { + logger logr.Logger) handler.TypedEventHandler[*networking.Ingress, reconcile.Request] { return &enqueueRequestsForIngressEvent{ groupLoader: groupLoader, eventRecorder: eventRecorder, @@ -25,7 +26,7 @@ func NewEnqueueRequestsForIngressEvent(groupLoader ingress.GroupLoader, eventRec } } -var _ handler.TypedEventHandler[*networking.Ingress] = (*enqueueRequestsForIngressEvent)(nil) +var _ handler.TypedEventHandler[*networking.Ingress, reconcile.Request] = (*enqueueRequestsForIngressEvent)(nil) type enqueueRequestsForIngressEvent struct { groupLoader ingress.GroupLoader @@ -33,11 +34,11 @@ type enqueueRequestsForIngressEvent struct { logger logr.Logger } -func (h *enqueueRequestsForIngressEvent) Create(ctx context.Context, e event.TypedCreateEvent[*networking.Ingress], queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressEvent) Create(ctx context.Context, e event.TypedCreateEvent[*networking.Ingress], queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { h.enqueueIfBelongsToGroup(ctx, queue, e.Object) } -func (h *enqueueRequestsForIngressEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*networking.Ingress], queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*networking.Ingress], queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { ingOld := e.ObjectOld ingNew := e.ObjectNew @@ -56,18 +57,18 @@ func (h *enqueueRequestsForIngressEvent) Update(ctx context.Context, e event.Typ h.enqueueIfBelongsToGroup(ctx, queue, ingNew) } -func (h *enqueueRequestsForIngressEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*networking.Ingress], queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*networking.Ingress], queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { // since we'll always attach an finalizer before doing any reconcile action, // user triggered delete action will actually be an update action with deletionTimestamp set, // which will be handled by update event handler. // so we'll just ignore delete events to avoid unnecessary reconcile call. } -func (h *enqueueRequestsForIngressEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*networking.Ingress], queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForIngressEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*networking.Ingress], queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { h.enqueueIfBelongsToGroup(ctx, queue, e.Object) } -func (h *enqueueRequestsForIngressEvent) enqueueIfBelongsToGroup(ctx context.Context, queue workqueue.RateLimitingInterface, ing *networking.Ingress) { +func (h *enqueueRequestsForIngressEvent) enqueueIfBelongsToGroup(ctx context.Context, queue workqueue.TypedRateLimitingInterface[reconcile.Request], ing *networking.Ingress) { ingKey := k8s.NamespacedName(ing) groupIDsSet := make(map[ingress.GroupID]struct{}) diff --git a/controllers/ingress/eventhandlers/secret_events.go b/controllers/ingress/eventhandlers/secret_events.go index 866527428..2b949e01f 100644 --- a/controllers/ingress/eventhandlers/secret_events.go +++ b/controllers/ingress/eventhandlers/secret_events.go @@ -2,6 +2,7 @@ package eventhandlers import ( "context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -18,7 +19,7 @@ import ( // NewEnqueueRequestsForSecretEvent constructs new enqueueRequestsForSecretEvent. func NewEnqueueRequestsForSecretEvent(ingEventChan chan<- event.TypedGenericEvent[*networking.Ingress], svcEventChan chan<- event.TypedGenericEvent[*corev1.Service], - k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*corev1.Secret] { + k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*corev1.Secret, reconcile.Request] { return &enqueueRequestsForSecretEvent{ ingEventChan: ingEventChan, svcEventChan: svcEventChan, @@ -28,7 +29,7 @@ func NewEnqueueRequestsForSecretEvent(ingEventChan chan<- event.TypedGenericEven } } -var _ handler.TypedEventHandler[*corev1.Secret] = (*enqueueRequestsForSecretEvent)(nil) +var _ handler.TypedEventHandler[*corev1.Secret, reconcile.Request] = (*enqueueRequestsForSecretEvent)(nil) type enqueueRequestsForSecretEvent struct { ingEventChan chan<- event.TypedGenericEvent[*networking.Ingress] @@ -38,12 +39,12 @@ type enqueueRequestsForSecretEvent struct { logger logr.Logger } -func (h *enqueueRequestsForSecretEvent) Create(ctx context.Context, e event.TypedCreateEvent[*corev1.Secret], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForSecretEvent) Create(ctx context.Context, e event.TypedCreateEvent[*corev1.Secret], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { secretNew := e.Object h.enqueueImpactedObjects(ctx, secretNew) } -func (h *enqueueRequestsForSecretEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*corev1.Secret], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForSecretEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*corev1.Secret], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { secretOld := e.ObjectOld secretNew := e.ObjectNew @@ -58,12 +59,12 @@ func (h *enqueueRequestsForSecretEvent) Update(ctx context.Context, e event.Type h.enqueueImpactedObjects(ctx, secretNew) } -func (h *enqueueRequestsForSecretEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*corev1.Secret], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForSecretEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*corev1.Secret], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { secretOld := e.Object h.enqueueImpactedObjects(ctx, secretOld) } -func (h *enqueueRequestsForSecretEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*corev1.Secret], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForSecretEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*corev1.Secret], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { secretObj := e.Object h.enqueueImpactedObjects(ctx, secretObj) } diff --git a/controllers/ingress/eventhandlers/service_events.go b/controllers/ingress/eventhandlers/service_events.go index 20f0790f1..15fbd7d5f 100644 --- a/controllers/ingress/eventhandlers/service_events.go +++ b/controllers/ingress/eventhandlers/service_events.go @@ -2,6 +2,7 @@ package eventhandlers import ( "context" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -18,7 +19,7 @@ import ( // NewEnqueueRequestsForServiceEvent constructs new enqueueRequestsForServiceEvent. func NewEnqueueRequestsForServiceEvent(ingEventChan chan<- event.TypedGenericEvent[*networking.Ingress], - k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*corev1.Service] { + k8sClient client.Client, eventRecorder record.EventRecorder, logger logr.Logger) handler.TypedEventHandler[*corev1.Service, reconcile.Request] { return &enqueueRequestsForServiceEvent{ ingEventChan: ingEventChan, k8sClient: k8sClient, @@ -27,7 +28,7 @@ func NewEnqueueRequestsForServiceEvent(ingEventChan chan<- event.TypedGenericEve } } -var _ handler.TypedEventHandler[*corev1.Service] = (*enqueueRequestsForServiceEvent)(nil) +var _ handler.TypedEventHandler[*corev1.Service, reconcile.Request] = (*enqueueRequestsForServiceEvent)(nil) type enqueueRequestsForServiceEvent struct { ingEventChan chan<- event.TypedGenericEvent[*networking.Ingress] @@ -36,12 +37,12 @@ type enqueueRequestsForServiceEvent struct { logger logr.Logger } -func (h *enqueueRequestsForServiceEvent) Create(ctx context.Context, e event.TypedCreateEvent[*corev1.Service], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Create(ctx context.Context, e event.TypedCreateEvent[*corev1.Service], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { svcNew := e.Object h.enqueueImpactedIngresses(ctx, svcNew) } -func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*corev1.Service], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.TypedUpdateEvent[*corev1.Service], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { svcOld := e.ObjectOld svcNew := e.ObjectNew @@ -58,12 +59,12 @@ func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.Typ h.enqueueImpactedIngresses(ctx, svcNew) } -func (h *enqueueRequestsForServiceEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*corev1.Service], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Delete(ctx context.Context, e event.TypedDeleteEvent[*corev1.Service], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { svcOld := e.Object h.enqueueImpactedIngresses(ctx, svcOld) } -func (h *enqueueRequestsForServiceEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*corev1.Service], _ workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Generic(ctx context.Context, e event.TypedGenericEvent[*corev1.Service], _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { svc := e.Object h.enqueueImpactedIngresses(ctx, svc) } diff --git a/controllers/ingress/group_controller.go b/controllers/ingress/group_controller.go index 185faa124..68b1f96da 100644 --- a/controllers/ingress/group_controller.go +++ b/controllers/ingress/group_controller.go @@ -3,6 +3,7 @@ package ingress import ( "context" "fmt" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" "github.com/pkg/errors" @@ -114,11 +115,11 @@ type groupReconciler struct { // +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch -func (r *groupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *groupReconciler) Reconcile(ctx context.Context, req reconcile.Request) (ctrl.Result, error) { return runtime.HandleReconcileError(r.reconcile(ctx, req), r.logger) } -func (r *groupReconciler) reconcile(ctx context.Context, req ctrl.Request) error { +func (r *groupReconciler) reconcile(ctx context.Context, req reconcile.Request) error { ingGroupID := ingress.DecodeGroupIDFromReconcileRequest(req) ingGroup, err := r.groupLoader.Load(ctx, ingGroupID) if err != nil { diff --git a/controllers/service/eventhandlers/service_events.go b/controllers/service/eventhandlers/service_events.go index 34416ed3d..040255b79 100644 --- a/controllers/service/eventhandlers/service_events.go +++ b/controllers/service/eventhandlers/service_events.go @@ -33,11 +33,11 @@ type enqueueRequestsForServiceEvent struct { logger logr.Logger } -func (h *enqueueRequestsForServiceEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Create(ctx context.Context, e event.CreateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { h.enqueueManagedService(ctx, queue, e.Object.(*corev1.Service)) } -func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.UpdateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { oldSvc := e.ObjectOld.(*corev1.Service) newSvc := e.ObjectNew.(*corev1.Service) @@ -52,16 +52,16 @@ func (h *enqueueRequestsForServiceEvent) Update(ctx context.Context, e event.Upd h.enqueueManagedService(ctx, queue, newSvc) } -func (h *enqueueRequestsForServiceEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Delete(ctx context.Context, e event.DeleteEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { // We attach a finalizer during reconcile, and handle the user triggered delete action during the update event. // In case of delete, there will first be an update event with nonzero deletionTimestamp set on the object. Since // deletion is already taken care of during update event, we will ignore this event. } -func (h *enqueueRequestsForServiceEvent) Generic(ctx context.Context, e event.GenericEvent, queue workqueue.RateLimitingInterface) { +func (h *enqueueRequestsForServiceEvent) Generic(ctx context.Context, e event.GenericEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { } -func (h *enqueueRequestsForServiceEvent) enqueueManagedService(ctx context.Context, queue workqueue.RateLimitingInterface, service *corev1.Service) { +func (h *enqueueRequestsForServiceEvent) enqueueManagedService(ctx context.Context, queue workqueue.TypedRateLimitingInterface[reconcile.Request], service *corev1.Service) { // Check if the svc needs to be handled if !h.serviceUtils.IsServicePendingFinalization(service) && !h.serviceUtils.IsServiceSupported(service) { return diff --git a/controllers/service/service_controller.go b/controllers/service/service_controller.go index 18dd36de3..e701a92ea 100644 --- a/controllers/service/service_controller.go +++ b/controllers/service/service_controller.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" "github.com/pkg/errors" @@ -88,11 +89,11 @@ type serviceReconciler struct { // +kubebuilder:rbac:groups="",resources=services/status,verbs=update;patch // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch -func (r *serviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *serviceReconciler) Reconcile(ctx context.Context, req reconcile.Request) (ctrl.Result, error) { return runtime.HandleReconcileError(r.reconcile(ctx, req), r.logger) } -func (r *serviceReconciler) reconcile(ctx context.Context, req ctrl.Request) error { +func (r *serviceReconciler) reconcile(ctx context.Context, req reconcile.Request) error { svc := &corev1.Service{} if err := r.k8sClient.Get(ctx, req.NamespacedName, svc); err != nil { return client.IgnoreNotFound(err) diff --git a/go.mod b/go.mod index bd865ccc0..2ae3eec13 100644 --- a/go.mod +++ b/go.mod @@ -19,27 +19,27 @@ require ( github.com/aws/smithy-go v1.22.1 github.com/evanphx/json-patch v5.7.0+incompatible github.com/gavv/httpexpect/v2 v2.9.0 - github.com/go-logr/logr v1.4.1 + github.com/go-logr/logr v1.4.2 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.3.0 - github.com/onsi/ginkgo/v2 v2.17.1 - github.com/onsi/gomega v1.32.0 + github.com/google/uuid v1.6.0 + github.com/onsi/ginkgo/v2 v2.19.0 + github.com/onsi/gomega v1.33.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.20.4 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.26.0 - golang.org/x/time v0.3.0 + golang.org/x/time v0.5.0 gomodules.xyz/jsonpatch/v2 v2.4.0 helm.sh/helm/v3 v3.15.0 - k8s.io/api v0.30.0 - k8s.io/apimachinery v0.30.0 + k8s.io/api v0.31.3 + k8s.io/apimachinery v0.31.3 k8s.io/cli-runtime v0.30.0 - k8s.io/client-go v0.30.0 - k8s.io/klog/v2 v2.120.1 - k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 - sigs.k8s.io/controller-runtime v0.18.2 + k8s.io/client-go v0.31.3 + k8s.io/klog/v2 v2.130.1 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 + sigs.k8s.io/controller-runtime v0.19.3 sigs.k8s.io/yaml v1.4.0 ) @@ -71,7 +71,7 @@ require ( github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/cli v24.0.6+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker v24.0.9+incompatible // indirect @@ -79,21 +79,21 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/fatih/structs v1.1.0 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -102,16 +102,16 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect + github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/mux v1.8.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/huandu/xstrings v1.4.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/imkira/go-interpol v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -125,13 +125,14 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/miekg/dns v1.1.62 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect - github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/spdystream v0.4.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -142,7 +143,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -153,9 +154,10 @@ require ( github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.34.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect @@ -163,31 +165,32 @@ require ( github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.58.3 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.66.2 // indirect google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.30.0 // indirect - k8s.io/apiserver v0.30.0 // indirect - k8s.io/component-base v0.30.0 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/apiextensions-apiserver v0.31.1 // indirect + k8s.io/apiserver v0.31.1 // indirect + k8s.io/component-base v0.31.1 // indirect + k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect k8s.io/kubectl v0.30.0 // indirect moul.io/http2curl/v2 v2.3.0 // indirect oras.land/oras-go v1.2.4 // indirect diff --git a/go.sum b/go.sum index 6aee0098a..292ee96ce 100644 --- a/go.sum +++ b/go.sum @@ -111,16 +111,16 @@ github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= @@ -141,8 +141,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= @@ -153,19 +153,19 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fasthttp/websocket v1.4.3-rc.6 h1:omHqsl8j+KXpmzRjF8bmzOSYJ8GnS0E3efi1wYT+niY= github.com/fasthttp/websocket v1.4.3-rc.6/go.mod h1:43W9OM2T8FeXpCWMsBd9Cb7nE2CACNqNvCqQCoty/Lc= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gavv/httpexpect/v2 v2.9.0 h1:LVUnUqvcwjn/tpEG7l8P1RaZKuMkkzBcymV2ZIF7Drk= github.com/gavv/httpexpect/v2 v2.9.0/go.mod h1:ra5Uy9iyQe0CljXH6LQJ00u8aeY1SKN2f4I6jPiD4Ng= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -176,23 +176,23 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= @@ -243,21 +243,21 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -274,10 +274,9 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -307,7 +306,6 @@ github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ib github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -334,23 +332,20 @@ github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2 github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= -github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -361,8 +356,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -386,11 +381,11 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= -github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= -github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -404,8 +399,9 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -427,8 +423,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -449,14 +445,12 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -465,11 +459,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.0/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= @@ -479,6 +469,8 @@ github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfY github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -508,14 +500,14 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1 github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -535,8 +527,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -545,8 +537,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -588,9 +580,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -599,7 +588,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -607,6 +595,7 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -622,8 +611,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -635,8 +624,8 @@ golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -648,13 +637,13 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -670,6 +659,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -684,7 +675,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= @@ -693,34 +683,34 @@ helm.sh/helm/v3 v3.15.0 h1:gcLxHeFp0Hfo7lYi6KIZ84ZyvlAnfFRSJ8lTL3zvG5U= helm.sh/helm/v3 v3.15.0/go.mod h1:fvfoRcB8UKRUV5jrIfOTaN/pG1TPhuqSb56fjYdTKXg= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= -k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= -k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= -k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= -k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= -k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= -k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= +k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= +k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= +k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= +k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= +k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= +k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= +k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48= k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg= -k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= -k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= -k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= -k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= +k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= +k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= +k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 h1:Q8Z7VlGhcJgBHJHYugJ/K/7iB8a2eSxCyxdVjJp+lLY= +k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk= k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= -sigs.k8s.io/controller-runtime v0.18.2 h1:RqVW6Kpeaji67CY5nPEfRz6ZfFMk0lWQlNrLqlNpx+Q= -sigs.k8s.io/controller-runtime v0.18.2/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= +sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= +sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= diff --git a/pkg/ingress/group.go b/pkg/ingress/group.go index d15ba2842..b300f38d9 100644 --- a/pkg/ingress/group.go +++ b/pkg/ingress/group.go @@ -2,10 +2,10 @@ package ingress import ( "fmt" + "sigs.k8s.io/controller-runtime/pkg/reconcile" networking "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" ) // GroupID is the unique identifier for an IngressGroup within cluster. @@ -41,12 +41,12 @@ func NewGroupIDForImplicitGroup(ingKey types.NamespacedName) GroupID { } // EncodeGroupIDToReconcileRequest encodes a GroupID into a controller-runtime reconcile request -func EncodeGroupIDToReconcileRequest(gID GroupID) ctrl.Request { - return ctrl.Request{NamespacedName: types.NamespacedName(gID)} +func EncodeGroupIDToReconcileRequest(gID GroupID) reconcile.Request { + return reconcile.Request{NamespacedName: types.NamespacedName(gID)} } // DecodeGroupIDFromReconcileRequest decodes a GroupID from a controller-runtime reconcile request -func DecodeGroupIDFromReconcileRequest(request ctrl.Request) GroupID { +func DecodeGroupIDFromReconcileRequest(request reconcile.Request) GroupID { return GroupID(request.NamespacedName) } diff --git a/pkg/ingress/group_test.go b/pkg/ingress/group_test.go index 13de4d3ad..4cef8ca9b 100644 --- a/pkg/ingress/group_test.go +++ b/pkg/ingress/group_test.go @@ -1,11 +1,11 @@ package ingress import ( + "sigs.k8s.io/controller-runtime/pkg/reconcile" "testing" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" ) func TestGroupID_IsExplicit(t *testing.T) { @@ -123,7 +123,7 @@ func TestEncodeGroupIDToReconcileRequest(t *testing.T) { tests := []struct { name string groupID GroupID - want ctrl.Request + want reconcile.Request }{ { name: "explicit group", @@ -131,7 +131,7 @@ func TestEncodeGroupIDToReconcileRequest(t *testing.T) { Namespace: "", Name: "awesome-group", }, - want: ctrl.Request{NamespacedName: types.NamespacedName{ + want: reconcile.Request{NamespacedName: types.NamespacedName{ Namespace: "", Name: "awesome-group", }}, @@ -142,7 +142,7 @@ func TestEncodeGroupIDToReconcileRequest(t *testing.T) { Namespace: "namespace", Name: "ingress", }, - want: ctrl.Request{NamespacedName: types.NamespacedName{ + want: reconcile.Request{NamespacedName: types.NamespacedName{ Namespace: "namespace", Name: "ingress", }}, @@ -159,12 +159,12 @@ func TestEncodeGroupIDToReconcileRequest(t *testing.T) { func TestDecodeGroupIDFromReconcileRequest(t *testing.T) { tests := []struct { name string - request ctrl.Request + request reconcile.Request want GroupID }{ { name: "explicit group", - request: ctrl.Request{NamespacedName: types.NamespacedName{ + request: reconcile.Request{NamespacedName: types.NamespacedName{ Namespace: "", Name: "awesome-group", }}, @@ -175,7 +175,7 @@ func TestDecodeGroupIDFromReconcileRequest(t *testing.T) { }, { name: "implicit group", - request: ctrl.Request{NamespacedName: types.NamespacedName{ + request: reconcile.Request{NamespacedName: types.NamespacedName{ Namespace: "namespace", Name: "ingress", }}, diff --git a/pkg/testutils/event_handler_test_utils.go b/pkg/testutils/event_handler_test_utils.go index d3a51a485..157d4241c 100644 --- a/pkg/testutils/event_handler_test_utils.go +++ b/pkg/testutils/event_handler_test_utils.go @@ -2,15 +2,15 @@ package testutils import ( "k8s.io/client-go/util/workqueue" - ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -func ExtractCTRLRequestsFromQueue(queue workqueue.RateLimitingInterface) []ctrl.Request { - var requests []ctrl.Request +func ExtractCTRLRequestsFromQueue(queue workqueue.TypedRateLimitingInterface[reconcile.Request]) []reconcile.Request { + var requests []reconcile.Request for queue.Len() > 0 { item, _ := queue.Get() queue.Done(item) - requests = append(requests, item.(ctrl.Request)) + requests = append(requests, item) } return requests } From fab90f436c83ad051396e8b88e2a04ba1e0dedd3 Mon Sep 17 00:00:00 2001 From: Florin Vlaicu <19238716+fvlaicu@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:31:42 +0200 Subject: [PATCH 12/30] https://github.com/kubernetes-sigs/aws-load-balancer-controller/pull/3664 missed to point to the computed value. --- helm/aws-load-balancer-controller/templates/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/aws-load-balancer-controller/templates/deployment.yaml b/helm/aws-load-balancer-controller/templates/deployment.yaml index da672ab34..d1de13d43 100644 --- a/helm/aws-load-balancer-controller/templates/deployment.yaml +++ b/helm/aws-load-balancer-controller/templates/deployment.yaml @@ -68,7 +68,7 @@ spec: {{- end }} {{- $region := tpl (default "" .Values.region) . }} {{- if $region }} - - --aws-region={{ .Values.region }} + - --aws-region={{ $region }} {{- end }} {{- $vpcID := tpl (default "" .Values.vpcId) . }} {{- if $vpcID }} From 369bdace6df0dd0ae5a42b982be0625a37cd03e4 Mon Sep 17 00:00:00 2001 From: Marcos Diez Date: Wed, 15 Jan 2025 22:12:33 +0100 Subject: [PATCH 13/30] CI-14222_manipulate_target_groups_from_different_aws_accounts (#3691) --- .../elbv2/v1beta1/targetgroupbinding_types.go | 8 ++ controllers/ingress/group_controller.go | 4 +- controllers/service/service_controller.go | 4 +- docs/deploy/installation.md | 63 ++++++++- docs/guide/targetgroupbinding/spec.md | 21 ++- .../targetgroupbinding/targetgroupbinding.md | 23 +++ go.mod | 1 + pkg/aws/cloud.go | 131 +++++++++++++----- .../provider/default_aws_clients_provider.go | 12 +- pkg/aws/provider/provider.go | 3 + pkg/aws/services/cloudInterface.go | 34 +++++ pkg/aws/services/elbv2.go | 13 +- pkg/aws/services/elbv2_mocks.go | 14 ++ pkg/deploy/stack_deployer.go | 7 +- pkg/targetgroupbinding/resource_manager.go | 64 ++++++--- pkg/targetgroupbinding/targets_manager.go | 42 +++--- .../targets_manager_test.go | 49 +++++-- pkg/targetgroupbinding/utils.go | 20 +++ test/framework/framework.go | 3 +- webhooks/elbv2/targetgroupbinding_mutator.go | 28 ++-- .../elbv2/targetgroupbinding_mutator_test.go | 32 ++++- .../elbv2/targetgroupbinding_validator.go | 23 +-- .../targetgroupbinding_validator_test.go | 11 +- 23 files changed, 474 insertions(+), 136 deletions(-) create mode 100644 pkg/aws/services/cloudInterface.go diff --git a/apis/elbv2/v1beta1/targetgroupbinding_types.go b/apis/elbv2/v1beta1/targetgroupbinding_types.go index 337e89202..7a273a1d4 100644 --- a/apis/elbv2/v1beta1/targetgroupbinding_types.go +++ b/apis/elbv2/v1beta1/targetgroupbinding_types.go @@ -157,6 +157,14 @@ type TargetGroupBindingSpec struct { // VpcID is the VPC of the TargetGroup. If unspecified, it will be automatically inferred. // +optional VpcID string `json:"vpcID,omitempty"` + + // IAM Role ARN to assume when calling AWS APIs. Useful if the target group is in a different AWS account + // +optional + IamRoleArnToAssume string `json:"-"` // `json:"iamRoleArnToAssume,omitempty"` + + // IAM Role ARN to assume when calling AWS APIs. Needed to assume a role in another account and prevent the confused deputy problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html + // +optional + AssumeRoleExternalId string `json:"-"` // `json:"assumeRoleExternalId,omitempty"` } // TargetGroupBindingStatus defines the observed state of TargetGroupBinding diff --git a/controllers/ingress/group_controller.go b/controllers/ingress/group_controller.go index 68b1f96da..932541a8a 100644 --- a/controllers/ingress/group_controller.go +++ b/controllers/ingress/group_controller.go @@ -16,7 +16,7 @@ import ( elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/controllers/ingress/eventhandlers" "sigs.k8s.io/aws-load-balancer-controller/pkg/annotations" - "sigs.k8s.io/aws-load-balancer-controller/pkg/aws" + "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/config" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy" elbv2deploy "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2" @@ -44,7 +44,7 @@ const ( ) // NewGroupReconciler constructs new GroupReconciler -func NewGroupReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorder record.EventRecorder, +func NewGroupReconciler(cloud services.Cloud, k8sClient client.Client, eventRecorder record.EventRecorder, finalizerManager k8s.FinalizerManager, networkingSGManager networkingpkg.SecurityGroupManager, networkingSGReconciler networkingpkg.SecurityGroupReconciler, subnetsResolver networkingpkg.SubnetsResolver, elbv2TaggingManager elbv2deploy.TaggingManager, controllerConfig config.ControllerConfig, backendSGProvider networkingpkg.BackendSGProvider, diff --git a/controllers/service/service_controller.go b/controllers/service/service_controller.go index e701a92ea..dc80bad94 100644 --- a/controllers/service/service_controller.go +++ b/controllers/service/service_controller.go @@ -12,7 +12,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/aws-load-balancer-controller/controllers/service/eventhandlers" "sigs.k8s.io/aws-load-balancer-controller/pkg/annotations" - "sigs.k8s.io/aws-load-balancer-controller/pkg/aws" + "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/config" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy" elbv2deploy "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2" @@ -35,7 +35,7 @@ const ( controllerName = "service" ) -func NewServiceReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorder record.EventRecorder, +func NewServiceReconciler(cloud services.Cloud, k8sClient client.Client, eventRecorder record.EventRecorder, finalizerManager k8s.FinalizerManager, networkingSGManager networking.SecurityGroupManager, networkingSGReconciler networking.SecurityGroupReconciler, subnetsResolver networking.SubnetsResolver, vpcInfoProvider networking.VPCInfoProvider, elbv2TaggingManager elbv2deploy.TaggingManager, controllerConfig config.ControllerConfig, diff --git a/docs/deploy/installation.md b/docs/deploy/installation.md index 13ff8fdbd..c4fb97a33 100644 --- a/docs/deploy/installation.md +++ b/docs/deploy/installation.md @@ -7,10 +7,10 @@ The LBC is supported by AWS. Some clusters may be using the legacy "in-tree" fun !!!question "Existing AWS ALB Ingress Controller users" The AWS ALB Ingress controller must be uninstalled before installing the AWS Load Balancer Controller. Please follow our [migration guide](upgrade/migrate_v1_v2.md) to do a migration. - + !!!warning "When using AWS Load Balancer Controller v2.5+" - The AWS LBC provides a mutating webhook for service resources to set the `spec.loadBalancerClass` field for service of type LoadBalancer on create. - This makes the AWS LBC the **default controller for service** of type LoadBalancer. You can disable this feature and revert to set Cloud Controller Manager (in-tree controller) as the default by setting the helm chart value **enableServiceMutatorWebhook to false** with `--set enableServiceMutatorWebhook=false` . + The AWS LBC provides a mutating webhook for service resources to set the `spec.loadBalancerClass` field for service of type LoadBalancer on create. + This makes the AWS LBC the **default controller for service** of type LoadBalancer. You can disable this feature and revert to set Cloud Controller Manager (in-tree controller) as the default by setting the helm chart value **enableServiceMutatorWebhook to false** with `--set enableServiceMutatorWebhook=false` . You will no longer be able to provision new Classic Load Balancer (CLB) from your kubernetes service unless you disable this feature. Existing CLB will continue to work fine. ## Supported Kubernetes versions @@ -30,7 +30,7 @@ The LBC is supported by AWS. Some clusters may be using the legacy "in-tree" fun Isolated clusters are clusters without internet access, and instead reply on VPC endpoints for all required connects. When installing the AWS LBC in isolated clusters, you need to disable shield, waf and wafv2 via controller flags `--enable-shield=false, --enable-waf=false, --enable-wafv2=false` ### Using the Amazon EC2 instance metadata server version 2 (IMDSv2) -We recommend blocking the access to instance metadata by requiring the instance to use [IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html) only. For more information, please refer to the AWS guidance [here](https://aws.github.io/aws-eks-best-practices/security/docs/iam/#restrict-access-to-the-instance-profile-assigned-to-the-worker-node). If you are using the IMDSv2, set the hop limit to 2 or higher in order to allow the LBC to perform the metadata introspection. +We recommend blocking the access to instance metadata by requiring the instance to use [IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html) only. For more information, please refer to the AWS guidance [here](https://aws.github.io/aws-eks-best-practices/security/docs/iam/#restrict-access-to-the-instance-profile-assigned-to-the-worker-node). If you are using the IMDSv2, set the hop limit to 2 or higher in order to allow the LBC to perform the metadata introspection. You can set the IMDSv2 as follows: ``` @@ -127,6 +127,10 @@ If you're not setting up IAM roles for service accounts, apply the IAM policies curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.11.0/docs/install/iam_policy.json ``` +## Special IAM cases + +### You only want the LBC to add and remove IPs to already existing target groups: + The following IAM permissions subset is for those using `TargetGroupBinding` only and don't plan to use the LBC to manage security group rules: ``` @@ -152,6 +156,57 @@ The following IAM permissions subset is for those using `TargetGroupBinding` onl } ``` +### You only want the LBC to add and remove IPs to already existing target groups, also in other accounts, assuming roles + +On the other hand, if you plan to use the LBC to manage also target groups in different accounts, you will need to add `"sts:AssumeRole"` to your list of permissions, in other words: + +``` +{ + "Statement": [ + { + "Action": [ + "ec2:DescribeVpcs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeInstances", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets", + "sts:AssumeRole" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" +} +``` + +The assumed roles will need the exactly the same permissions, without `"sts:AssumeRole"`. The assumed role will need a to allow to be assumed by the main role, something like this: + +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::999999999999999:user/test-alb-controller" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "very-secret-string" + } + } + } + ] +} +``` + ## Network configuration Review the [worker nodes security group](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html) docs. Your node security group must permit incoming traffic on TCP port 9443 from the Kubernetes control plane. This is needed for webhook access. diff --git a/docs/guide/targetgroupbinding/spec.md b/docs/guide/targetgroupbinding/spec.md index de865b530..f2b9b80c5 100644 --- a/docs/guide/targetgroupbinding/spec.md +++ b/docs/guide/targetgroupbinding/spec.md @@ -52,10 +52,29 @@ Kubernetes meta/v1.ObjectMeta -Refer to the Kubernetes API documentation for the fields of the + + +
annotations + + + + + + +
alb.ingress.kubernetes.io/IamRoleArnToAssume
string
(Optional) In case the target group is in a differet AWS account, you put here the role that needs to be assumed in order to manipulate the target group. +
alb.ingress.kubernetes.io/AssumeRoleExternalId
string
(Optional) The external ID for the assume role operation. Optional, but recommended. It helps you to prevent the confused deputy problem. +
+ +
+Refer to the Kubernetes API documentation for the other fields of the metadata field. +
+ + + + spec
diff --git a/docs/guide/targetgroupbinding/targetgroupbinding.md b/docs/guide/targetgroupbinding/targetgroupbinding.md index cec6b6f02..0f3a055e7 100644 --- a/docs/guide/targetgroupbinding/targetgroupbinding.md +++ b/docs/guide/targetgroupbinding/targetgroupbinding.md @@ -109,6 +109,29 @@ spec: ... ``` +### AssumeRole + +Sometimes the AWS LoadBalancer controller needs to manipulate target groups from different AWS accounts. +The way to do that is assuming a role from such account. There are annotations that can help you with that: + +* `alb.ingress.kubernetes.io/IamRoleArnToAssume`: the ARN that you need to assume +* `alb.ingress.kubernetes.io/AssumeRoleExternalId`: the external ID for the assume role operation. Optional, but recommended. It helps you to prevent the confused deputy problem ( https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html ) + + +## Sample YAML + +```yaml +apiVersion: elbv2.k8s.aws/v1beta1 +kind: TargetGroupBinding +metadata: + name: my-tgb + annotations: + alb.ingress.kubernetes.io/IamRoleArnToAssume: "arn:aws:iam::999999999999:role/alb-controller-policy-to-assume" + alb.ingress.kubernetes.io/AssumeRoleExternalId: "some-magic-string" +spec: + ... +``` + ## MultiCluster Target Group TargetGroupBinding CRD supports sharing the same target group ARN among multiple clusters. Setting this flag will ensure the controller only operates on targets within the cluster. diff --git a/go.mod b/go.mod index 2ae3eec13..2346312e9 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/iam v1.36.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect diff --git a/pkg/aws/cloud.go b/pkg/aws/cloud.go index 41070e70d..1e9d50f87 100644 --- a/pkg/aws/cloud.go +++ b/pkg/aws/cloud.go @@ -3,17 +3,22 @@ package aws import ( "context" "fmt" + "log" + "net" + "os" + "strings" + awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/aws-sdk-go-v2/aws/ratelimit" "github.com/aws/aws-sdk-go-v2/aws/retry" "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/aws-sdk-go-v2/service/sts" + smithymiddleware "github.com/aws/smithy-go/middleware" - "net" - "os" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle" "sigs.k8s.io/aws-load-balancer-controller/pkg/version" - "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" @@ -29,37 +34,8 @@ import ( const userAgent = "elbv2.k8s.aws" -type Cloud interface { - // EC2 provides API to AWS EC2 - EC2() services.EC2 - - // ELBV2 provides API to AWS ELBV2 - ELBV2() services.ELBV2 - - // ACM provides API to AWS ACM - ACM() services.ACM - - // WAFv2 provides API to AWS WAFv2 - WAFv2() services.WAFv2 - - // WAFRegional provides API to AWS WAFRegional - WAFRegional() services.WAFRegional - - // Shield provides API to AWS Shield - Shield() services.Shield - - // RGT provides API to AWS RGT - RGT() services.RGT - - // Region for the kubernetes cluster - Region() string - - // VpcID for the LoadBalancer resources. - VpcID() string -} - // NewCloud constructs new Cloud implementation. -func NewCloud(cfg CloudConfig, metricsCollector *aws_metrics.Collector, logger logr.Logger, awsClientsProvider provider.AWSClientsProvider) (Cloud, error) { +func NewCloud(cfg CloudConfig, metricsCollector *aws_metrics.Collector, logger logr.Logger, awsClientsProvider provider.AWSClientsProvider) (services.Cloud, error) { hasIPv4 := true addrs, err := net.InterfaceAddrs() if err == nil { @@ -138,17 +114,26 @@ func NewCloud(cfg CloudConfig, metricsCollector *aws_metrics.Collector, logger l if err != nil { return nil, errors.Wrap(err, "failed to get VPC ID") } + cfg.VpcID = vpcID - return &defaultCloud{ + + thisObj := &defaultCloud{ cfg: cfg, ec2: ec2Service, - elbv2: services.NewELBV2(awsClientsProvider), acm: services.NewACM(awsClientsProvider), wafv2: services.NewWAFv2(awsClientsProvider), wafRegional: services.NewWAFRegional(awsClientsProvider, cfg.Region), shield: services.NewShield(awsClientsProvider), rgt: services.NewRGT(awsClientsProvider), - }, nil + + assumeRoleElbV2: make(map[string]services.ELBV2), + awsClientsProvider: awsClientsProvider, + logger: logger, + } + + thisObj.elbv2 = services.NewELBV2(awsClientsProvider, thisObj) + + return thisObj, nil } func getVpcID(cfg CloudConfig, ec2Service services.EC2, ec2Metadata services.EC2Metadata, logger logr.Logger) (string, error) { @@ -222,7 +207,7 @@ func inferVPCIDFromTags(ec2Service services.EC2, VpcNameTagKey string, VpcNameTa return *vpcs[0].VpcId, nil } -var _ Cloud = &defaultCloud{} +var _ services.Cloud = &defaultCloud{} type defaultCloud struct { cfg CloudConfig @@ -234,6 +219,78 @@ type defaultCloud struct { wafRegional services.WAFRegional shield services.Shield rgt services.RGT + + assumeRoleElbV2 map[string]services.ELBV2 + awsClientsProvider provider.AWSClientsProvider + logger logr.Logger +} + +// returns ELBV2 client for the given assumeRoleArn, or the default ELBV2 client if assumeRoleArn is empty +func (c *defaultCloud) GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn string, externalId string) services.ELBV2 { + + if assumeRoleArn == "" { + return c.elbv2 + } + + assumedRoleELBV2, exists := c.assumeRoleElbV2[assumeRoleArn] + if exists { + return assumedRoleELBV2 + } + c.logger.Info("awsCloud", "method", "GetAssumedRoleELBV2", "AssumeRoleArn", assumeRoleArn, "externalId", externalId) + + //////////////// + existingAwsConfig, _ := c.awsClientsProvider.GetAWSConfig(ctx, "GetAWSConfigForIAMRoleImpersonation") + + sourceAccount := sts.NewFromConfig(*existingAwsConfig) + response, err := sourceAccount.AssumeRole(ctx, &sts.AssumeRoleInput{ + RoleArn: aws.String(assumeRoleArn), + RoleSessionName: aws.String("aws-load-balancer-controller"), + ExternalId: aws.String(externalId), + }) + if err != nil { + log.Fatalf("Unable to assume target role, %v. Attempting to use default client", err) + return c.elbv2 + } + assumedRoleCreds := response.Credentials + newCreds := credentials.NewStaticCredentialsProvider(*assumedRoleCreds.AccessKeyId, *assumedRoleCreds.SecretAccessKey, *assumedRoleCreds.SessionToken) + newAwsConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(c.cfg.Region), config.WithCredentialsProvider(newCreds)) + if err != nil { + log.Fatalf("Unable to load static credentials for service client config, %v. Attempting to use default client", err) + return c.elbv2 + } + + existingAwsConfig.Credentials = newAwsConfig.Credentials // response.Credentials + + // // var assumedRoleCreds *stsTypes.Credentials = response.Credentials + + // // Create config with target service client, using assumed role + // cfg, err = config.LoadDefaultConfig(ctx, config.WithRegion(region), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(*assumedRoleCreds.AccessKeyId, *assumedRoleCreds.SecretAccessKey, *assumedRoleCreds.SessionToken))) + // if err != nil { + // log.Fatalf("unable to load static credentials for service client config, %v", err) + // } + + // //////////////// + // appCreds := stscreds.NewAssumeRoleProvider(client, assumeRoleArn) + // value, err := appCreds.Retrieve(context.TODO()) + // if err != nil { + // // handle error + // } + // ///////// + + // ///////////// OLD + // creds := stscreds.NewCredentials(c.session, assumeRoleArn, func(p *stscreds.AssumeRoleProvider) { + // p.ExternalID = &externalId + // }) + // ////////////// + + // c.awsConfig.Credentials = creds + // // newObj := services.NewELBV2(c.session, c, c.awsCFG) + // newObj := services.NewELBV2(*c.awsConfig, c.endpointsResolver, c) + + newObj := services.NewELBV2(c.awsClientsProvider, c) + c.assumeRoleElbV2[assumeRoleArn] = newObj + + return newObj } func (c *defaultCloud) EC2() services.EC2 { diff --git a/pkg/aws/provider/default_aws_clients_provider.go b/pkg/aws/provider/default_aws_clients_provider.go index b43bc2993..41cd78055 100644 --- a/pkg/aws/provider/default_aws_clients_provider.go +++ b/pkg/aws/provider/default_aws_clients_provider.go @@ -21,6 +21,8 @@ type defaultAWSClientsProvider struct { wafRegionClient *wafregional.Client shieldClient *shield.Client rgtClient *resourcegroupstaggingapi.Client + + awsConfig *aws.Config } func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.Resolver) (*defaultAWSClientsProvider, error) { @@ -56,7 +58,7 @@ func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.R o.Region = cfg.Region o.BaseEndpoint = wafregionalCustomEndpoint }) - sheildClient := shield.NewFromConfig(cfg, func(o *shield.Options) { + shieldClient := shield.NewFromConfig(cfg, func(o *shield.Options) { o.Region = cfg.Region o.BaseEndpoint = shieldCustomEndpoint }) @@ -72,8 +74,10 @@ func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.R acmClient: acmClient, wafv2Client: wafv2Client, wafRegionClient: wafregionalClient, - shieldClient: sheildClient, + shieldClient: shieldClient, rgtClient: rgtClient, + + awsConfig: &cfg, }, nil } @@ -107,3 +111,7 @@ func (p *defaultAWSClientsProvider) GetShieldClient(ctx context.Context, operati func (p *defaultAWSClientsProvider) GetRGTClient(ctx context.Context, operationName string) (*resourcegroupstaggingapi.Client, error) { return p.rgtClient, nil } + +func (p *defaultAWSClientsProvider) GetAWSConfig(ctx context.Context, operationName string) (*aws.Config, error) { + return p.awsConfig, nil +} diff --git a/pkg/aws/provider/provider.go b/pkg/aws/provider/provider.go index 95b1c4742..2cdff4574 100644 --- a/pkg/aws/provider/provider.go +++ b/pkg/aws/provider/provider.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/acm" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" @@ -19,4 +20,6 @@ type AWSClientsProvider interface { GetWAFRegionClient(ctx context.Context, operationName string) (*wafregional.Client, error) GetShieldClient(ctx context.Context, operationName string) (*shield.Client, error) GetRGTClient(ctx context.Context, operationName string) (*resourcegroupstaggingapi.Client, error) + + GetAWSConfig(ctx context.Context, operationName string) (*aws.Config, error) } diff --git a/pkg/aws/services/cloudInterface.go b/pkg/aws/services/cloudInterface.go new file mode 100644 index 000000000..3e0ff558e --- /dev/null +++ b/pkg/aws/services/cloudInterface.go @@ -0,0 +1,34 @@ +package services + +import "context" + +type Cloud interface { + // EC2 provides API to AWS EC2 + EC2() EC2 + + // ELBV2 provides API to AWS ELBV2 + ELBV2() ELBV2 + + // ACM provides API to AWS ACM + ACM() ACM + + // WAFv2 provides API to AWS WAFv2 + WAFv2() WAFv2 + + // WAFRegional provides API to AWS WAFRegional + WAFRegional() WAFRegional + + // Shield provides API to AWS Shield + Shield() Shield + + // RGT provides API to AWS RGT + RGT() RGT + + // Region for the kubernetes cluster + Region() string + + // VpcID for the LoadBalancer resources. + VpcID() string + + GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn string, externalId string) ELBV2 +} diff --git a/pkg/aws/services/elbv2.go b/pkg/aws/services/elbv2.go index b89b83d00..877cdd5f3 100644 --- a/pkg/aws/services/elbv2.go +++ b/pkg/aws/services/elbv2.go @@ -24,7 +24,6 @@ type ELBV2 interface { // wrapper to DescribeRulesWithContext API, which aggregates paged results into list. DescribeRulesAsList(ctx context.Context, input *elasticloadbalancingv2.DescribeRulesInput) ([]types.Rule, error) - AddTagsWithContext(ctx context.Context, input *elasticloadbalancingv2.AddTagsInput) (*elasticloadbalancingv2.AddTagsOutput, error) RemoveTagsWithContext(ctx context.Context, input *elasticloadbalancingv2.RemoveTagsInput) (*elasticloadbalancingv2.RemoveTagsOutput, error) DescribeTagsWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeTagsInput) (*elasticloadbalancingv2.DescribeTagsOutput, error) @@ -61,17 +60,27 @@ type ELBV2 interface { ModifyListenerAttributesWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyListenerAttributesInput) (*elasticloadbalancingv2.ModifyListenerAttributesOutput, error) ModifyCapacityReservationWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyCapacityReservationInput) (*elasticloadbalancingv2.ModifyCapacityReservationOutput, error) DescribeCapacityReservationWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeCapacityReservationInput) (*elasticloadbalancingv2.DescribeCapacityReservationOutput, error) + AssumeRole(ctx context.Context, assumeRoleArn string, externalId string) ELBV2 } -func NewELBV2(awsClientsProvider provider.AWSClientsProvider) ELBV2 { +func NewELBV2(awsClientsProvider provider.AWSClientsProvider, cloud Cloud) ELBV2 { return &elbv2Client{ awsClientsProvider: awsClientsProvider, + cloud: cloud, } } // default implementation for ELBV2. type elbv2Client struct { awsClientsProvider provider.AWSClientsProvider + cloud Cloud +} + +func (c *elbv2Client) AssumeRole(ctx context.Context, assumeRoleArn string, externalId string) ELBV2 { + if assumeRoleArn == "" { + return c + } + return c.cloud.GetAssumedRoleELBV2(ctx, assumeRoleArn, externalId) } func (c *elbv2Client) AddListenerCertificatesWithContext(ctx context.Context, input *elasticloadbalancingv2.AddListenerCertificatesInput) (*elasticloadbalancingv2.AddListenerCertificatesOutput, error) { diff --git a/pkg/aws/services/elbv2_mocks.go b/pkg/aws/services/elbv2_mocks.go index 805c0e8ac..8d5b540a7 100644 --- a/pkg/aws/services/elbv2_mocks.go +++ b/pkg/aws/services/elbv2_mocks.go @@ -66,6 +66,20 @@ func (mr *MockELBV2MockRecorder) AddTagsWithContext(arg0, arg1 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTagsWithContext", reflect.TypeOf((*MockELBV2)(nil).AddTagsWithContext), arg0, arg1) } +// AssumeRole mocks base method. +func (m *MockELBV2) AssumeRole(arg0 context.Context, arg1, arg2 string) ELBV2 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssumeRole", arg0, arg1, arg2) + ret0, _ := ret[0].(ELBV2) + return ret0 +} + +// AssumeRole indicates an expected call of AssumeRole. +func (mr *MockELBV2MockRecorder) AssumeRole(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRole", reflect.TypeOf((*MockELBV2)(nil).AssumeRole), arg0, arg1, arg2) +} + // CreateListenerWithContext mocks base method. func (m *MockELBV2) CreateListenerWithContext(arg0 context.Context, arg1 *elasticloadbalancingv2.CreateListenerInput) (*elasticloadbalancingv2.CreateListenerOutput, error) { m.ctrl.T.Helper() diff --git a/pkg/deploy/stack_deployer.go b/pkg/deploy/stack_deployer.go index bcfc6254f..eddba59a9 100644 --- a/pkg/deploy/stack_deployer.go +++ b/pkg/deploy/stack_deployer.go @@ -2,8 +2,9 @@ package deploy import ( "context" + "github.com/go-logr/logr" - "sigs.k8s.io/aws-load-balancer-controller/pkg/aws" + "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/config" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/ec2" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2" @@ -23,7 +24,7 @@ type StackDeployer interface { } // NewDefaultStackDeployer constructs new defaultStackDeployer. -func NewDefaultStackDeployer(cloud aws.Cloud, k8sClient client.Client, +func NewDefaultStackDeployer(cloud services.Cloud, k8sClient client.Client, networkingSGManager networking.SecurityGroupManager, networkingSGReconciler networking.SecurityGroupReconciler, elbv2TaggingManager elbv2.TaggingManager, config config.ControllerConfig, tagPrefix string, logger logr.Logger) *defaultStackDeployer { @@ -58,7 +59,7 @@ var _ StackDeployer = &defaultStackDeployer{} // defaultStackDeployer is the default implementation for StackDeployer type defaultStackDeployer struct { - cloud aws.Cloud + cloud services.Cloud k8sClient client.Client controllerConfig config.ControllerConfig addonsConfig config.AddonsConfig diff --git a/pkg/targetgroupbinding/resource_manager.go b/pkg/targetgroupbinding/resource_manager.go index af25a824f..666d25b1c 100644 --- a/pkg/targetgroupbinding/resource_manager.go +++ b/pkg/targetgroupbinding/resource_manager.go @@ -3,12 +3,13 @@ package targetgroupbinding import ( "context" "fmt" - elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" - "github.com/aws/smithy-go" "net/netip" lbcmetrics "sigs.k8s.io/aws-load-balancer-controller/pkg/metrics/lbc" "time" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/aws/smithy-go" + "k8s.io/client-go/tools/record" awssdk "github.com/aws/aws-sdk-go-v2/aws" @@ -95,6 +96,7 @@ func (m *defaultResourceManager) Reconcile(ctx context.Context, tgb *elbv2api.Ta var oldCheckPoint string var isDeferred bool var err error + AnnotationsToFields(tgb) if *tgb.Spec.TargetType == elbv2api.TargetTypeIP { newCheckPoint, oldCheckPoint, isDeferred, err = m.reconcileWithIPTargetType(ctx, tgb) @@ -114,6 +116,7 @@ func (m *defaultResourceManager) Reconcile(ctx context.Context, tgb *elbv2api.Ta } func (m *defaultResourceManager) Cleanup(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { + AnnotationsToFields(tgb) if err := m.cleanupTargets(ctx, tgb); err != nil { return err } @@ -168,9 +171,7 @@ func (m *defaultResourceManager) reconcileWithIPTargetType(ctx context.Context, return newCheckPoint, oldCheckPoint, true, nil } - tgARN := tgb.Spec.TargetGroupARN - vpcID := tgb.Spec.VpcID - targets, err := m.targetsManager.ListTargets(ctx, tgARN) + targets, err := m.targetsManager.ListTargets(ctx, tgb) if err != nil { return "", "", false, err } @@ -211,7 +212,7 @@ func (m *defaultResourceManager) reconcileWithIPTargetType(ctx context.Context, updateTrackedTargets := false if len(unmatchedTargets) > 0 { - updateTrackedTargets, err = m.deregisterTargets(ctx, tgb, tgARN, unmatchedTargets) + updateTrackedTargets, err = m.deregisterTargets(ctx, tgb, unmatchedTargets) if err != nil { return "", "", false, err } @@ -234,7 +235,7 @@ func (m *defaultResourceManager) reconcileWithIPTargetType(ctx context.Context, return "", "", false, err } - if err := m.registerPodEndpoints(ctx, tgARN, vpcID, unmatchedEndpoints); err != nil { + if err := m.registerPodEndpoints(ctx, tgb, unmatchedEndpoints); err != nil { return "", "", false, err } } @@ -298,8 +299,7 @@ func (m *defaultResourceManager) reconcileWithInstanceTargetType(ctx context.Con return newCheckPoint, oldCheckPoint, true, nil } - tgARN := tgb.Spec.TargetGroupARN - targets, err := m.targetsManager.ListTargets(ctx, tgARN) + targets, err := m.targetsManager.ListTargets(ctx, tgb) if err != nil { return "", "", false, err } @@ -325,7 +325,7 @@ func (m *defaultResourceManager) reconcileWithInstanceTargetType(ctx context.Con updateTrackedTargets := false if len(unmatchedTargets) > 0 { - updateTrackedTargets, err = m.deregisterTargets(ctx, tgb, tgARN, unmatchedTargets) + updateTrackedTargets, err = m.deregisterTargets(ctx, tgb, unmatchedTargets) if err != nil { return "", "", false, err } @@ -337,7 +337,7 @@ func (m *defaultResourceManager) reconcileWithInstanceTargetType(ctx context.Con return "", "", false, err } - if err := m.registerNodePortEndpoints(ctx, tgARN, unmatchedEndpoints); err != nil { + if err := m.registerNodePortEndpoints(ctx, tgb, unmatchedEndpoints); err != nil { return "", "", false, err } } @@ -351,7 +351,7 @@ func (m *defaultResourceManager) reconcileWithInstanceTargetType(ctx context.Con } func (m *defaultResourceManager) cleanupTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { - targets, err := m.targetsManager.ListTargets(ctx, tgb.Spec.TargetGroupARN) + targets, err := m.targetsManager.ListTargets(ctx, tgb) if err != nil { if isELBV2TargetGroupNotFoundError(err) { return nil @@ -361,7 +361,7 @@ func (m *defaultResourceManager) cleanupTargets(ctx context.Context, tgb *elbv2a return err } - _, err = m.deregisterTargets(ctx, tgb, tgb.Spec.TargetGroupARN, targets) + _, err = m.deregisterTargets(ctx, tgb, targets) if err != nil { if isELBV2TargetGroupNotFoundError(err) { @@ -527,7 +527,7 @@ func (m *defaultResourceManager) updatePodAsHealthyForDeletedTGB(ctx context.Con return nil } -func (m *defaultResourceManager) deregisterTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding, tgARN string, targets []TargetInfo) (bool, error) { +func (m *defaultResourceManager) deregisterTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding, targets []TargetInfo) (bool, error) { filteredTargets, updateTrackedTargets, err := m.multiClusterManager.FilterTargetsForDeregistration(ctx, tgb, targets) if err != nil { return false, err @@ -541,16 +541,34 @@ func (m *defaultResourceManager) deregisterTargets(ctx context.Context, tgb *elb for _, target := range filteredTargets { sdkTargets = append(sdkTargets, target.Target) } - return true, m.targetsManager.DeregisterTargets(ctx, tgARN, sdkTargets) + return true, m.targetsManager.DeregisterTargets(ctx, tgb, sdkTargets) } -func (m *defaultResourceManager) registerPodEndpoints(ctx context.Context, tgARN, tgVpcID string, endpoints []backend.PodEndpoint) error { +func (m *defaultResourceManager) registerPodEndpoints(ctx context.Context, tgb *elbv2api.TargetGroupBinding, endpoints []backend.PodEndpoint) error { vpcID := m.vpcID // Target group is in a different VPC from the cluster's VPC - if tgVpcID != "" && tgVpcID != m.vpcID { - vpcID = tgVpcID - m.logger.Info("registering endpoints using the targetGroup's vpcID", "TG VPC", tgVpcID, - "cluster's vpcID", m.vpcID) + if tgb.Spec.VpcID != "" && tgb.Spec.VpcID != m.vpcID { + vpcID = tgb.Spec.VpcID + m.logger.Info(fmt.Sprintf( + "registering endpoints using the targetGroup's vpcID %s which is different from the cluster's vpcID %s", tgb.Spec.VpcID, m.vpcID)) + + if tgb.Spec.IamRoleArnToAssume != "" { + // since we need to assume a role for this TGB, + // it is from a different account + // so the packets will need to leave the VPC and therefore + // target.AvailabilityZone = awssdk.String("all") must be set + // or else nothing will work + sdkTargets := make([]elbv2types.TargetDescription, 0, len(endpoints)) + for _, endpoint := range endpoints { + target := elbv2types.TargetDescription{ + Id: awssdk.String(endpoint.IP), + Port: awssdk.Int32(endpoint.Port), + } + target.AvailabilityZone = awssdk.String("all") + sdkTargets = append(sdkTargets, target) + } + return m.targetsManager.RegisterTargets(ctx, tgb, sdkTargets) + } } vpcInfo, err := m.vpcInfoProvider.FetchVPCInfo(ctx, vpcID) if err != nil { @@ -579,10 +597,10 @@ func (m *defaultResourceManager) registerPodEndpoints(ctx context.Context, tgARN } sdkTargets = append(sdkTargets, target) } - return m.targetsManager.RegisterTargets(ctx, tgARN, sdkTargets) + return m.targetsManager.RegisterTargets(ctx, tgb, sdkTargets) } -func (m *defaultResourceManager) registerNodePortEndpoints(ctx context.Context, tgARN string, endpoints []backend.NodePortEndpoint) error { +func (m *defaultResourceManager) registerNodePortEndpoints(ctx context.Context, tgb *elbv2api.TargetGroupBinding, endpoints []backend.NodePortEndpoint) error { sdkTargets := make([]elbv2types.TargetDescription, 0, len(endpoints)) for _, endpoint := range endpoints { sdkTargets = append(sdkTargets, elbv2types.TargetDescription{ @@ -590,7 +608,7 @@ func (m *defaultResourceManager) registerNodePortEndpoints(ctx context.Context, Port: awssdk.Int32(endpoint.Port), }) } - return m.targetsManager.RegisterTargets(ctx, tgARN, sdkTargets) + return m.targetsManager.RegisterTargets(ctx, tgb, sdkTargets) } func (m *defaultResourceManager) updateTGBCheckPoint(ctx context.Context, tgb *elbv2api.TargetGroupBinding, newCheckPoint, previousCheckPoint string) error { diff --git a/pkg/targetgroupbinding/targets_manager.go b/pkg/targetgroupbinding/targets_manager.go index b32eb350e..b00f95a9b 100644 --- a/pkg/targetgroupbinding/targets_manager.go +++ b/pkg/targetgroupbinding/targets_manager.go @@ -2,14 +2,16 @@ package targetgroupbinding import ( "context" + "sync" + "time" + "github.com/aws/aws-sdk-go-v2/aws" elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/util/cache" + elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" - "sync" - "time" ) const ( @@ -21,13 +23,13 @@ const ( // TargetsManager is an abstraction around ELBV2's targets API. type TargetsManager interface { // Register Targets into TargetGroup. - RegisterTargets(ctx context.Context, tgARN string, targets []elbv2types.TargetDescription) error + RegisterTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding, targets []elbv2types.TargetDescription) error // Deregister Targets from TargetGroup. - DeregisterTargets(ctx context.Context, tgARN string, targets []elbv2types.TargetDescription) error + DeregisterTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding, targets []elbv2types.TargetDescription) error // List Targets from TargetGroup. - ListTargets(ctx context.Context, tgARN string) ([]TargetInfo, error) + ListTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding) ([]TargetInfo, error) } // NewCachedTargetsManager constructs new cachedTargetsManager @@ -76,7 +78,8 @@ type targetsCacheItem struct { targets []TargetInfo } -func (m *cachedTargetsManager) RegisterTargets(ctx context.Context, tgARN string, targets []elbv2types.TargetDescription) error { +func (m *cachedTargetsManager) RegisterTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding, targets []elbv2types.TargetDescription) error { + tgARN := tgb.Spec.TargetGroupARN targetsChunks := chunkTargetDescriptions(targets, m.registerTargetsChunkSize) for _, targetsChunk := range targetsChunks { req := &elbv2sdk.RegisterTargetsInput{ @@ -86,7 +89,7 @@ func (m *cachedTargetsManager) RegisterTargets(ctx context.Context, tgARN string m.logger.Info("registering targets", "arn", tgARN, "targets", targetsChunk) - _, err := m.elbv2Client.RegisterTargetsWithContext(ctx, req) + _, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).RegisterTargetsWithContext(ctx, req) if err != nil { return err } @@ -97,7 +100,8 @@ func (m *cachedTargetsManager) RegisterTargets(ctx context.Context, tgARN string return nil } -func (m *cachedTargetsManager) DeregisterTargets(ctx context.Context, tgARN string, targets []elbv2types.TargetDescription) error { +func (m *cachedTargetsManager) DeregisterTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding, targets []elbv2types.TargetDescription) error { + tgARN := tgb.Spec.TargetGroupARN targetsChunks := chunkTargetDescriptions(targets, m.deregisterTargetsChunkSize) for _, targetsChunk := range targetsChunks { req := &elbv2sdk.DeregisterTargetsInput{ @@ -107,7 +111,7 @@ func (m *cachedTargetsManager) DeregisterTargets(ctx context.Context, tgARN stri m.logger.Info("deRegistering targets", "arn", tgARN, "targets", targetsChunk) - _, err := m.elbv2Client.DeregisterTargetsWithContext(ctx, req) + _, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).DeregisterTargetsWithContext(ctx, req) if err != nil { return err } @@ -118,7 +122,8 @@ func (m *cachedTargetsManager) DeregisterTargets(ctx context.Context, tgARN stri return nil } -func (m *cachedTargetsManager) ListTargets(ctx context.Context, tgARN string) ([]TargetInfo, error) { +func (m *cachedTargetsManager) ListTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding) ([]TargetInfo, error) { + tgARN := tgb.Spec.TargetGroupARN m.targetsCacheMutex.Lock() defer m.targetsCacheMutex.Unlock() @@ -126,7 +131,7 @@ func (m *cachedTargetsManager) ListTargets(ctx context.Context, tgARN string) ([ targetsCacheItem := rawTargetsCacheItem.(*targetsCacheItem) targetsCacheItem.mutex.Lock() defer targetsCacheItem.mutex.Unlock() - refreshedTargets, err := m.refreshUnhealthyTargets(ctx, tgARN, targetsCacheItem.targets) + refreshedTargets, err := m.refreshUnhealthyTargets(ctx, tgb, targetsCacheItem.targets) if err != nil { return nil, err } @@ -134,7 +139,7 @@ func (m *cachedTargetsManager) ListTargets(ctx context.Context, tgARN string) ([ return cloneTargetInfoSlice(refreshedTargets), nil } - refreshedTargets, err := m.refreshAllTargets(ctx, tgARN) + refreshedTargets, err := m.refreshAllTargets(ctx, tgb) if err != nil { return nil, err } @@ -147,8 +152,8 @@ func (m *cachedTargetsManager) ListTargets(ctx context.Context, tgARN string) ([ } // refreshAllTargets will refresh all targets for targetGroup. -func (m *cachedTargetsManager) refreshAllTargets(ctx context.Context, tgARN string) ([]TargetInfo, error) { - targets, err := m.listTargetsFromAWS(ctx, tgARN, nil) +func (m *cachedTargetsManager) refreshAllTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding) ([]TargetInfo, error) { + targets, err := m.listTargetsFromAWS(ctx, tgb, nil) if err != nil { return nil, err } @@ -158,7 +163,7 @@ func (m *cachedTargetsManager) refreshAllTargets(ctx context.Context, tgARN stri // refreshUnhealthyTargets will refresh targets that are not in healthy status for targetGroup. // To save API calls, we don't refresh targets that are already healthy since once a target turns healthy, we'll unblock it's readinessProbe. // we can do nothing from controller perspective when a healthy target becomes unhealthy. -func (m *cachedTargetsManager) refreshUnhealthyTargets(ctx context.Context, tgARN string, cachedTargets []TargetInfo) ([]TargetInfo, error) { +func (m *cachedTargetsManager) refreshUnhealthyTargets(ctx context.Context, tgb *elbv2api.TargetGroupBinding, cachedTargets []TargetInfo) ([]TargetInfo, error) { var refreshedTargets []TargetInfo var unhealthyTargets []elbv2types.TargetDescription for _, cachedTarget := range cachedTargets { @@ -172,7 +177,7 @@ func (m *cachedTargetsManager) refreshUnhealthyTargets(ctx context.Context, tgAR return refreshedTargets, nil } - refreshedUnhealthyTargets, err := m.listTargetsFromAWS(ctx, tgARN, unhealthyTargets) + refreshedUnhealthyTargets, err := m.listTargetsFromAWS(ctx, tgb, unhealthyTargets) if err != nil { return nil, err } @@ -188,12 +193,13 @@ func (m *cachedTargetsManager) refreshUnhealthyTargets(ctx context.Context, tgAR // listTargetsFromAWS will list targets for TargetGroup using ELBV2API. // if specified targets is non-empty, only these targets will be listed. // otherwise, all targets for targetGroup will be listed. -func (m *cachedTargetsManager) listTargetsFromAWS(ctx context.Context, tgARN string, targets []elbv2types.TargetDescription) ([]TargetInfo, error) { +func (m *cachedTargetsManager) listTargetsFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding, targets []elbv2types.TargetDescription) ([]TargetInfo, error) { + tgARN := tgb.Spec.TargetGroupARN req := &elbv2sdk.DescribeTargetHealthInput{ TargetGroupArn: aws.String(tgARN), Targets: pointerizeTargetDescriptions(targets), } - resp, err := m.elbv2Client.DescribeTargetHealthWithContext(ctx, req) + resp, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).DescribeTargetHealthWithContext(ctx, req) if err != nil { return nil, err } diff --git a/pkg/targetgroupbinding/targets_manager_test.go b/pkg/targetgroupbinding/targets_manager_test.go index 1a291ffd1..f4f476fe0 100644 --- a/pkg/targetgroupbinding/targets_manager_test.go +++ b/pkg/targetgroupbinding/targets_manager_test.go @@ -2,19 +2,34 @@ package targetgroupbinding import ( "context" + "sync" + "testing" + "time" + awssdk "github.com/aws/aws-sdk-go-v2/aws" elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + + "github.com/golang/mock/gomock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/cache" + elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/controller-runtime/pkg/log" - "sync" - "testing" - "time" ) +func makeTargetGroupBinding(tgARN string) *elbv2api.TargetGroupBinding { + return &elbv2api.TargetGroupBinding{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: tgARN, + }, + } +} + func Test_cachedTargetsManager_RegisterTargets(t *testing.T) { type registerTargetsWithContextCall struct { req *elbv2sdk.RegisterTargetsInput @@ -262,8 +277,10 @@ func Test_cachedTargetsManager_RegisterTargets(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() elbv2Client := services.NewMockELBV2(ctrl) + ctx := context.Background() for _, call := range tt.fields.registerTargetsWithContextCalls { elbv2Client.EXPECT().RegisterTargetsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) } targetsCache := cache.NewExpiring() @@ -282,8 +299,7 @@ func Test_cachedTargetsManager_RegisterTargets(t *testing.T) { logger: log.Log, } - ctx := context.Background() - err := m.RegisterTargets(ctx, tt.args.tgARN, tt.args.targets) + err := m.RegisterTargets(ctx, makeTargetGroupBinding(tt.args.tgARN), tt.args.targets) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) } else { @@ -507,8 +523,10 @@ func Test_cachedTargetsManager_DeregisterTargets(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() elbv2Client := services.NewMockELBV2(ctrl) + ctx := context.Background() for _, call := range tt.fields.deregisterTargetsWithContextCalls { elbv2Client.EXPECT().DeregisterTargetsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) } targetsCache := cache.NewExpiring() @@ -527,8 +545,7 @@ func Test_cachedTargetsManager_DeregisterTargets(t *testing.T) { logger: log.Log, } - ctx := context.Background() - err := m.DeregisterTargets(ctx, tt.args.tgARN, tt.args.targets) + err := m.DeregisterTargets(ctx, makeTargetGroupBinding(tt.args.tgARN), tt.args.targets) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) } else { @@ -770,10 +787,12 @@ func Test_cachedTargetsManager_ListTargets(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() + ctx := context.Background() elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetHealthWithContextCalls { elbv2Client.EXPECT().DescribeTargetHealthWithContext(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) } targetsCache := cache.NewExpiring() targetsCacheTTL := 1 * time.Minute @@ -790,8 +809,7 @@ func Test_cachedTargetsManager_ListTargets(t *testing.T) { targetsCacheTTL: targetsCacheTTL, } - ctx := context.Background() - got, err := m.ListTargets(ctx, tt.args.tgARN) + got, err := m.ListTargets(ctx, makeTargetGroupBinding(tt.args.tgARN)) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) } else { @@ -1180,16 +1198,17 @@ func Test_cachedTargetsManager_refreshUnhealthyTargets(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() + ctx := context.Background() elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetHealthWithContextCalls { elbv2Client.EXPECT().DescribeTargetHealthWithContext(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) } m := &cachedTargetsManager{ elbv2Client: elbv2Client, } - ctx := context.Background() - got, err := m.refreshUnhealthyTargets(ctx, tt.args.tgARN, tt.args.cachedTargets) + got, err := m.refreshUnhealthyTargets(ctx, makeTargetGroupBinding(tt.args.tgARN), tt.args.cachedTargets) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) } else { @@ -1341,18 +1360,20 @@ func Test_cachedTargetsManager_listTargetsFromAWS(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) + ctx := context.Background() + defer ctrl.Finish() elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetHealthWithContextCalls { elbv2Client.EXPECT().DescribeTargetHealthWithContext(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) } m := &cachedTargetsManager{ elbv2Client: elbv2Client, } - ctx := context.Background() - got, err := m.listTargetsFromAWS(ctx, tt.args.tgARN, tt.args.targets) + got, err := m.listTargetsFromAWS(ctx, makeTargetGroupBinding(tt.args.tgARN), tt.args.targets) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) } else { diff --git a/pkg/targetgroupbinding/utils.go b/pkg/targetgroupbinding/utils.go index 9b9cddabe..013c94081 100644 --- a/pkg/targetgroupbinding/utils.go +++ b/pkg/targetgroupbinding/utils.go @@ -3,6 +3,7 @@ package targetgroupbinding import ( "encoding/json" "fmt" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" @@ -24,8 +25,27 @@ const ( // Index Key for "ServiceReference" index. IndexKeyServiceRefName = "spec.serviceRef.name" + + // Annotation for IAM Role ARN to assume when calling AWS APIs. + AnnotationIamRoleArnToAssume = "alb.ingress.kubernetes.io/IamRoleArnToAssume" + + // Annotation for IAM Role External ID to use when calling AWS APIs. + AnnotationAssumeRoleExternalId = "alb.ingress.kubernetes.io/AssumeRoleExternalId" ) +// AnnotationsToFields converts annotations to fields. Currently it's tgb.Spec.IamRoleArnToAssume and tgb.Spec.AssumeRoleExternalId +func AnnotationsToFields(tgb *elbv2api.TargetGroupBinding) { + for key, value := range tgb.Annotations { + if key == AnnotationIamRoleArnToAssume { + tgb.Spec.IamRoleArnToAssume = value + } else { + if key == AnnotationAssumeRoleExternalId { + tgb.Spec.AssumeRoleExternalId = value + } + } + } +} + // BuildTargetHealthPodConditionType constructs the condition type for TargetHealth pod condition. func BuildTargetHealthPodConditionType(tgb *elbv2api.TargetGroupBinding) corev1.PodConditionType { return corev1.PodConditionType(fmt.Sprintf("%s/%s", TargetHealthPodConditionTypePrefix, tgb.Name)) diff --git a/test/framework/framework.go b/test/framework/framework.go index 52171817a..9830647bb 100644 --- a/test/framework/framework.go +++ b/test/framework/framework.go @@ -8,6 +8,7 @@ import ( "k8s.io/client-go/rest" elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws" + "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle" "sigs.k8s.io/aws-load-balancer-controller/test/framework/controller" "sigs.k8s.io/aws-load-balancer-controller/test/framework/helm" @@ -23,7 +24,7 @@ type Framework struct { Options Options RestCfg *rest.Config K8sClient client.Client - Cloud aws.Cloud + Cloud services.Cloud CTRLInstallationManager controller.InstallationManager NSManager k8sresources.NamespaceManager diff --git a/webhooks/elbv2/targetgroupbinding_mutator.go b/webhooks/elbv2/targetgroupbinding_mutator.go index dd9fb557e..7818ce39d 100644 --- a/webhooks/elbv2/targetgroupbinding_mutator.go +++ b/webhooks/elbv2/targetgroupbinding_mutator.go @@ -2,15 +2,18 @@ package elbv2 import ( "context" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" awssdk "github.com/aws/aws-sdk-go-v2/aws" elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" + "github.com/go-logr/logr" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" + "sigs.k8s.io/aws-load-balancer-controller/pkg/targetgroupbinding" "sigs.k8s.io/aws-load-balancer-controller/pkg/webhook" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -45,6 +48,7 @@ func (m *targetGroupBindingMutator) MutateCreate(ctx context.Context, obj runtim if err := m.getArnFromNameIfNeeded(ctx, tgb); err != nil { return nil, err } + targetgroupbinding.AnnotationsToFields(tgb) if err := m.defaultingTargetType(ctx, tgb); err != nil { return nil, err } @@ -76,8 +80,7 @@ func (m *targetGroupBindingMutator) defaultingTargetType(ctx context.Context, tg if tgb.Spec.TargetType != nil { return nil } - tgARN := tgb.Spec.TargetGroupARN - sdkTargetType, err := m.obtainSDKTargetTypeFromAWS(ctx, tgARN) + sdkTargetType, err := m.obtainSDKTargetTypeFromAWS(ctx, tgb) if err != nil { return errors.Wrap(err, "couldn't determine TargetType") } @@ -99,7 +102,7 @@ func (m *targetGroupBindingMutator) defaultingIPAddressType(ctx context.Context, if tgb.Spec.IPAddressType != nil { return nil } - targetGroupIPAddressType, err := m.getTargetGroupIPAddressTypeFromAWS(ctx, tgb.Spec.TargetGroupARN) + targetGroupIPAddressType, err := m.getTargetGroupIPAddressTypeFromAWS(ctx, tgb) if err != nil { return errors.Wrap(err, "unable to get target group IP address type") } @@ -111,7 +114,7 @@ func (m *targetGroupBindingMutator) defaultingVpcID(ctx context.Context, tgb *el if tgb.Spec.VpcID != "" { return nil } - vpcId, err := m.getVpcIDFromAWS(ctx, tgb.Spec.TargetGroupARN) + vpcId, err := m.getVpcIDFromAWS(ctx, tgb) if err != nil { return errors.Wrap(err, "unable to get target group VpcID") } @@ -119,8 +122,8 @@ func (m *targetGroupBindingMutator) defaultingVpcID(ctx context.Context, tgb *el return nil } -func (m *targetGroupBindingMutator) obtainSDKTargetTypeFromAWS(ctx context.Context, tgARN string) (string, error) { - targetGroup, err := m.getTargetGroupFromAWS(ctx, tgARN) +func (m *targetGroupBindingMutator) obtainSDKTargetTypeFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (string, error) { + targetGroup, err := m.getTargetGroupFromAWS(ctx, tgb) if err != nil { return "", err } @@ -128,8 +131,8 @@ func (m *targetGroupBindingMutator) obtainSDKTargetTypeFromAWS(ctx context.Conte } // getTargetGroupIPAddressTypeFromAWS returns the target group IP address type of AWS target group -func (m *targetGroupBindingMutator) getTargetGroupIPAddressTypeFromAWS(ctx context.Context, tgARN string) (elbv2api.TargetGroupIPAddressType, error) { - targetGroup, err := m.getTargetGroupFromAWS(ctx, tgARN) +func (m *targetGroupBindingMutator) getTargetGroupIPAddressTypeFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (elbv2api.TargetGroupIPAddressType, error) { + targetGroup, err := m.getTargetGroupFromAWS(ctx, tgb) if err != nil { return "", err } @@ -145,11 +148,12 @@ func (m *targetGroupBindingMutator) getTargetGroupIPAddressTypeFromAWS(ctx conte return ipAddressType, nil } -func (m *targetGroupBindingMutator) getTargetGroupFromAWS(ctx context.Context, tgARN string) (*elbv2types.TargetGroup, error) { +func (m *targetGroupBindingMutator) getTargetGroupFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (*elbv2types.TargetGroup, error) { + tgARN := tgb.Spec.TargetGroupARN req := &elbv2sdk.DescribeTargetGroupsInput{ TargetGroupArns: []string{tgARN}, } - tgList, err := m.elbv2Client.DescribeTargetGroupsAsList(ctx, req) + tgList, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).DescribeTargetGroupsAsList(ctx, req) if err != nil { return nil, err } @@ -173,8 +177,8 @@ func (m *targetGroupBindingMutator) getTargetGroupsByNameFromAWS(ctx context.Con return &tgList[0], nil } -func (m *targetGroupBindingMutator) getVpcIDFromAWS(ctx context.Context, tgARN string) (string, error) { - targetGroup, err := m.getTargetGroupFromAWS(ctx, tgARN) +func (m *targetGroupBindingMutator) getVpcIDFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (string, error) { + targetGroup, err := m.getTargetGroupFromAWS(ctx, tgb) if err != nil { return "", err } diff --git a/webhooks/elbv2/targetgroupbinding_mutator_test.go b/webhooks/elbv2/targetgroupbinding_mutator_test.go index f23692f80..032440ad1 100644 --- a/webhooks/elbv2/targetgroupbinding_mutator_test.go +++ b/webhooks/elbv2/targetgroupbinding_mutator_test.go @@ -2,20 +2,33 @@ package elbv2 import ( "context" - elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "testing" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + awssdk "github.com/aws/aws-sdk-go-v2/aws" elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" "github.com/go-logr/logr" "github.com/golang/mock/gomock" "github.com/pkg/errors" "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/controller-runtime/pkg/log" ) +func makeTargetGroupBinding(tgARN string) *elbv2api.TargetGroupBinding { + return &elbv2api.TargetGroupBinding{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: tgARN, + }, + } +} + func Test_targetGroupBindingMutator_MutateCreate(t *testing.T) { type describeTargetGroupsAsListCall struct { req *elbv2sdk.DescribeTargetGroupsInput @@ -292,8 +305,10 @@ func Test_targetGroupBindingMutator_MutateCreate(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() elbv2Client := services.NewMockELBV2(ctrl) + ctx := context.Background() for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err).AnyTimes() + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() } m := &targetGroupBindingMutator{ @@ -394,17 +409,20 @@ func Test_targetGroupBindingMutator_obtainSDKTargetTypeFromAWS(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) + ctx := context.Background() + defer ctrl.Finish() elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() } m := &targetGroupBindingMutator{ elbv2Client: elbv2Client, logger: logr.New(&log.NullLogSink{}), } - got, err := m.obtainSDKTargetTypeFromAWS(context.Background(), tt.args.tgARN) + got, err := m.obtainSDKTargetTypeFromAWS(context.Background(), makeTargetGroupBinding(tt.args.tgARN)) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) } else { @@ -521,17 +539,20 @@ func Test_targetGroupBindingMutator_getIPAddressTypeFromAWS(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) + ctx := context.Background() + defer ctrl.Finish() elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() } m := &targetGroupBindingMutator{ elbv2Client: elbv2Client, logger: logr.New(&log.NullLogSink{}), } - got, err := m.getTargetGroupIPAddressTypeFromAWS(context.Background(), tt.args.tgARN) + got, err := m.getTargetGroupIPAddressTypeFromAWS(context.Background(), makeTargetGroupBinding(tt.args.tgARN)) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) } else { @@ -604,17 +625,20 @@ func Test_targetGroupBindingMutator_obtainSDKVpcIDFromAWS(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) + ctx := context.Background() + defer ctrl.Finish() elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() } m := &targetGroupBindingMutator{ elbv2Client: elbv2Client, logger: logr.New(&log.NullLogSink{}), } - got, err := m.getVpcIDFromAWS(context.Background(), tt.args.tgARN) + got, err := m.getVpcIDFromAWS(context.Background(), makeTargetGroupBinding(tt.args.tgARN)) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) } else { diff --git a/webhooks/elbv2/targetgroupbinding_validator.go b/webhooks/elbv2/targetgroupbinding_validator.go index c4aa4df90..bb6ee770a 100644 --- a/webhooks/elbv2/targetgroupbinding_validator.go +++ b/webhooks/elbv2/targetgroupbinding_validator.go @@ -3,10 +3,11 @@ package elbv2 import ( "context" "fmt" - elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "regexp" "strings" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + awssdk "github.com/aws/aws-sdk-go-v2/aws" elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" "github.com/go-logr/logr" @@ -15,6 +16,7 @@ import ( elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" + "sigs.k8s.io/aws-load-balancer-controller/pkg/targetgroupbinding" "sigs.k8s.io/aws-load-balancer-controller/pkg/webhook" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -54,6 +56,7 @@ func (v *targetGroupBindingValidator) Prototype(_ admission.Request) (runtime.Ob func (v *targetGroupBindingValidator) ValidateCreate(ctx context.Context, obj runtime.Object) error { tgb := obj.(*elbv2api.TargetGroupBinding) + targetgroupbinding.AnnotationsToFields(tgb) if err := v.checkRequiredFields(ctx, tgb); err != nil { return err } @@ -75,6 +78,7 @@ func (v *targetGroupBindingValidator) ValidateCreate(ctx context.Context, obj ru func (v *targetGroupBindingValidator) ValidateUpdate(ctx context.Context, obj runtime.Object, oldObj runtime.Object) error { tgb := obj.(*elbv2api.TargetGroupBinding) oldTgb := oldObj.(*elbv2api.TargetGroupBinding) + targetgroupbinding.AnnotationsToFields(tgb) if err := v.checkRequiredFields(ctx, tgb); err != nil { return err } @@ -180,7 +184,7 @@ func (v *targetGroupBindingValidator) checkNodeSelector(tgb *elbv2api.TargetGrou // checkTargetGroupIPAddressType ensures IP address type matches with that on the AWS target group func (v *targetGroupBindingValidator) checkTargetGroupIPAddressType(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { - targetGroupIPAddressType, err := v.getTargetGroupIPAddressTypeFromAWS(ctx, tgb.Spec.TargetGroupARN) + targetGroupIPAddressType, err := v.getTargetGroupIPAddressTypeFromAWS(ctx, tgb) if err != nil { return errors.Wrap(err, "unable to get target group IP address type") } @@ -199,7 +203,7 @@ func (v *targetGroupBindingValidator) checkTargetGroupVpcID(ctx context.Context, if !vpcIDPatternRegex.MatchString(tgb.Spec.VpcID) { return errors.Errorf(vpcIDValidationErr, tgb.Spec.VpcID) } - vpcID, err := v.getVpcIDFromAWS(ctx, tgb.Spec.TargetGroupARN) + vpcID, err := v.getVpcIDFromAWS(ctx, tgb) if err != nil { return errors.Wrap(err, "unable to get target group VpcID") } @@ -210,8 +214,8 @@ func (v *targetGroupBindingValidator) checkTargetGroupVpcID(ctx context.Context, } // getTargetGroupIPAddressTypeFromAWS returns the target group IP address type of AWS target group -func (v *targetGroupBindingValidator) getTargetGroupIPAddressTypeFromAWS(ctx context.Context, tgARN string) (elbv2api.TargetGroupIPAddressType, error) { - targetGroup, err := v.getTargetGroupFromAWS(ctx, tgARN) +func (v *targetGroupBindingValidator) getTargetGroupIPAddressTypeFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (elbv2api.TargetGroupIPAddressType, error) { + targetGroup, err := v.getTargetGroupFromAWS(ctx, tgb) if err != nil { return "", err } @@ -228,11 +232,12 @@ func (v *targetGroupBindingValidator) getTargetGroupIPAddressTypeFromAWS(ctx con } // getTargetGroupFromAWS returns the AWS target group corresponding to the ARN -func (v *targetGroupBindingValidator) getTargetGroupFromAWS(ctx context.Context, tgARN string) (*elbv2types.TargetGroup, error) { +func (v *targetGroupBindingValidator) getTargetGroupFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (*elbv2types.TargetGroup, error) { + tgARN := tgb.Spec.TargetGroupARN req := &elbv2sdk.DescribeTargetGroupsInput{ TargetGroupArns: []string{tgARN}, } - tgList, err := v.elbv2Client.DescribeTargetGroupsAsList(ctx, req) + tgList, err := v.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).DescribeTargetGroupsAsList(ctx, req) if err != nil { return nil, err } @@ -242,8 +247,8 @@ func (v *targetGroupBindingValidator) getTargetGroupFromAWS(ctx context.Context, return &tgList[0], nil } -func (v *targetGroupBindingValidator) getVpcIDFromAWS(ctx context.Context, tgARN string) (string, error) { - targetGroup, err := v.getTargetGroupFromAWS(ctx, tgARN) +func (v *targetGroupBindingValidator) getVpcIDFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (string, error) { + targetGroup, err := v.getTargetGroupFromAWS(ctx, tgb) if err != nil { return "", err } diff --git a/webhooks/elbv2/targetgroupbinding_validator_test.go b/webhooks/elbv2/targetgroupbinding_validator_test.go index a023e526a..488960494 100644 --- a/webhooks/elbv2/targetgroupbinding_validator_test.go +++ b/webhooks/elbv2/targetgroupbinding_validator_test.go @@ -4,12 +4,13 @@ import ( "context" "crypto/rand" "fmt" - elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" - "github.com/google/uuid" "math/big" "strings" "testing" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/google/uuid" + awssdk "github.com/aws/aws-sdk-go-v2/aws" elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" "github.com/go-logr/logr" @@ -322,6 +323,8 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) + ctx := context.Background() + defer ctrl.Finish() k8sSchema := runtime.NewScheme() clientgoscheme.AddToScheme(k8sSchema) @@ -330,6 +333,7 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() } v := &targetGroupBindingValidator{ k8sClient: k8sClient, @@ -1381,6 +1385,8 @@ func Test_targetGroupBindingValidator_checkTargetGroupVpcID(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) + ctx := context.Background() + defer ctrl.Finish() k8sSchema := runtime.NewScheme() clientgoscheme.AddToScheme(k8sSchema) @@ -1389,6 +1395,7 @@ func Test_targetGroupBindingValidator_checkTargetGroupVpcID(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() } v := &targetGroupBindingValidator{ k8sClient: k8sClient, From ddc77cd464818c1aa24f1a0dfc81aa37316503a2 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Fri, 17 Jan 2025 11:10:14 -0800 Subject: [PATCH 14/30] resolve pr comments for multi account tgb --- .../v1alpha1/targetgroupbinding_types.go | 8 ++ .../elbv2/v1beta1/targetgroupbinding_types.go | 4 +- .../elbv2.k8s.aws_targetgroupbindings.yaml | 18 ++++ docs/guide/targetgroupbinding/spec.md | 9 -- .../targetgroupbinding/targetgroupbinding.md | 11 +-- go.sum | 1 + main.go | 2 +- pkg/aws/cloud.go | 92 +++++++++---------- pkg/aws/services/cloudInterface.go | 2 +- pkg/aws/services/elbv2.go | 6 +- pkg/aws/services/elbv2_mocks.go | 5 +- pkg/targetgroupbinding/resource_manager.go | 61 ++++++------ pkg/targetgroupbinding/targets_manager.go | 20 +++- .../targets_manager_test.go | 10 +- pkg/targetgroupbinding/utils.go | 19 ---- test/framework/framework.go | 2 +- webhooks/elbv2/targetgroupbinding_mutator.go | 27 ++++-- .../elbv2/targetgroupbinding_mutator_test.go | 8 +- .../elbv2/targetgroupbinding_validator.go | 11 ++- .../targetgroupbinding_validator_test.go | 4 +- 20 files changed, 173 insertions(+), 147 deletions(-) diff --git a/apis/elbv2/v1alpha1/targetgroupbinding_types.go b/apis/elbv2/v1alpha1/targetgroupbinding_types.go index 4af605afa..a0f47a568 100644 --- a/apis/elbv2/v1alpha1/targetgroupbinding_types.go +++ b/apis/elbv2/v1alpha1/targetgroupbinding_types.go @@ -128,6 +128,14 @@ type TargetGroupBindingSpec struct { // networking provides the networking setup for ELBV2 LoadBalancer to access targets in TargetGroup. // +optional Networking *TargetGroupBindingNetworking `json:"networking,omitempty"` + + // IAM Role ARN to assume when calling AWS APIs. Useful if the target group is in a different AWS account + // +optional + IamRoleArnToAssume string `json:"iamRoleArnToAssume,omitempty"` + + // IAM Role ARN to assume when calling AWS APIs. Needed to assume a role in another account and prevent the confused deputy problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html + // +optional + AssumeRoleExternalId string `json:"assumeRoleExternalId,omitempty"` } // TargetGroupBindingStatus defines the observed state of TargetGroupBinding diff --git a/apis/elbv2/v1beta1/targetgroupbinding_types.go b/apis/elbv2/v1beta1/targetgroupbinding_types.go index 7a273a1d4..4e5b109db 100644 --- a/apis/elbv2/v1beta1/targetgroupbinding_types.go +++ b/apis/elbv2/v1beta1/targetgroupbinding_types.go @@ -160,11 +160,11 @@ type TargetGroupBindingSpec struct { // IAM Role ARN to assume when calling AWS APIs. Useful if the target group is in a different AWS account // +optional - IamRoleArnToAssume string `json:"-"` // `json:"iamRoleArnToAssume,omitempty"` + IamRoleArnToAssume string `json:"iamRoleArnToAssume,omitempty"` // IAM Role ARN to assume when calling AWS APIs. Needed to assume a role in another account and prevent the confused deputy problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html // +optional - AssumeRoleExternalId string `json:"-"` // `json:"assumeRoleExternalId,omitempty"` + AssumeRoleExternalId string `json:"assumeRoleExternalId,omitempty"` } // TargetGroupBindingStatus defines the observed state of TargetGroupBinding diff --git a/config/crd/bases/elbv2.k8s.aws_targetgroupbindings.yaml b/config/crd/bases/elbv2.k8s.aws_targetgroupbindings.yaml index f9af43e6f..f5385c977 100644 --- a/config/crd/bases/elbv2.k8s.aws_targetgroupbindings.yaml +++ b/config/crd/bases/elbv2.k8s.aws_targetgroupbindings.yaml @@ -65,6 +65,15 @@ spec: spec: description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding properties: + assumeRoleExternalId: + description: IAM Role ARN to assume when calling AWS APIs. Needed + to assume a role in another account and prevent the confused deputy + problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html + type: string + iamRoleArnToAssume: + description: IAM Role ARN to assume when calling AWS APIs. Useful + if the target group is in a different AWS account + type: string multiClusterTargetGroup: description: MultiClusterTargetGroup Denotes if the TargetGroup is shared among multiple clusters @@ -242,6 +251,15 @@ spec: spec: description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding properties: + assumeRoleExternalId: + description: IAM Role ARN to assume when calling AWS APIs. Needed + to assume a role in another account and prevent the confused deputy + problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html + type: string + iamRoleArnToAssume: + description: IAM Role ARN to assume when calling AWS APIs. Useful + if the target group is in a different AWS account + type: string ipAddressType: description: ipAddressType specifies whether the target group is of type IPv4 or IPv6. If unspecified, it will be automatically inferred. diff --git a/docs/guide/targetgroupbinding/spec.md b/docs/guide/targetgroupbinding/spec.md index f2b9b80c5..96ee1963a 100644 --- a/docs/guide/targetgroupbinding/spec.md +++ b/docs/guide/targetgroupbinding/spec.md @@ -55,15 +55,6 @@ Kubernetes meta/v1.ObjectMeta
annotations - - - - - -
alb.ingress.kubernetes.io/IamRoleArnToAssume
string
(Optional) In case the target group is in a differet AWS account, you put here the role that needs to be assumed in order to manipulate the target group. -
alb.ingress.kubernetes.io/AssumeRoleExternalId
string
(Optional) The external ID for the assume role operation. Optional, but recommended. It helps you to prevent the confused deputy problem. -
-
Refer to the Kubernetes API documentation for the other fields of the metadata field. diff --git a/docs/guide/targetgroupbinding/targetgroupbinding.md b/docs/guide/targetgroupbinding/targetgroupbinding.md index 0f3a055e7..b534136a8 100644 --- a/docs/guide/targetgroupbinding/targetgroupbinding.md +++ b/docs/guide/targetgroupbinding/targetgroupbinding.md @@ -112,10 +112,10 @@ spec: ### AssumeRole Sometimes the AWS LoadBalancer controller needs to manipulate target groups from different AWS accounts. -The way to do that is assuming a role from such account. There are annotations that can help you with that: +The way to do that is assuming a role from such account. The following spec fields help you with that. -* `alb.ingress.kubernetes.io/IamRoleArnToAssume`: the ARN that you need to assume -* `alb.ingress.kubernetes.io/AssumeRoleExternalId`: the external ID for the assume role operation. Optional, but recommended. It helps you to prevent the confused deputy problem ( https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html ) +* `iamRoleArnToAssume`: the ARN that you need to assume +* `assumeRoleExternalId`: the external ID for the assume role operation. Optional, but recommended. It helps you to prevent the confused deputy problem ( https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html ) ## Sample YAML @@ -125,10 +125,9 @@ apiVersion: elbv2.k8s.aws/v1beta1 kind: TargetGroupBinding metadata: name: my-tgb - annotations: - alb.ingress.kubernetes.io/IamRoleArnToAssume: "arn:aws:iam::999999999999:role/alb-controller-policy-to-assume" - alb.ingress.kubernetes.io/AssumeRoleExternalId: "some-magic-string" spec: + iamRoleArnToAssume: "arn:aws:iam::999999999999:role/alb-controller-policy-to-assume" + assumeRoleExternalId: "some-magic-string" ... ``` diff --git a/go.sum b/go.sum index 292ee96ce..6676d7940 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,7 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 h1:ta62lid9JkIpKZtZZXSj6rP2AqY github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1 h1:L9Wt9zgtoYKIlaeFTy+EztGjL4oaXBBGtVXA+jaeYko= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1/go.mod h1:yxzLdxt7bVGvIOPYIKFtiaJCJnx2ChlIIvlhW4QgI6M= +github.com/aws/aws-sdk-go-v2/service/iam v1.36.3/go.mod h1:HSvujsK8xeEHMIB18oMXjSfqaN9cVqpo/MtHJIksQRk= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= diff --git a/main.go b/main.go index de2a3edc9..c4641fb10 100644 --- a/main.go +++ b/main.go @@ -90,7 +90,7 @@ func main() { awsMetricsCollector = awsmetrics.NewCollector(metrics.Registry) } - cloud, err := aws.NewCloud(controllerCFG.AWSConfig, awsMetricsCollector, ctrl.Log, nil) + cloud, err := aws.NewCloud(controllerCFG.AWSConfig, controllerCFG.ClusterName, awsMetricsCollector, ctrl.Log, nil) if err != nil { setupLog.Error(err, "unable to initialize AWS cloud") os.Exit(1) diff --git a/pkg/aws/cloud.go b/pkg/aws/cloud.go index 1e9d50f87..d13c6dd05 100644 --- a/pkg/aws/cloud.go +++ b/pkg/aws/cloud.go @@ -3,10 +3,13 @@ package aws import ( "context" "fmt" - "log" + "k8s.io/apimachinery/pkg/util/cache" "net" "os" + "regexp" "strings" + "sync" + "time" awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/aws-sdk-go-v2/aws/ratelimit" @@ -32,10 +35,15 @@ import ( aws_metrics "sigs.k8s.io/aws-load-balancer-controller/pkg/metrics/aws" ) -const userAgent = "elbv2.k8s.aws" +const ( + userAgent = "elbv2.k8s.aws" + cacheTTLBufferTime = 30 * time.Second +) + +var illegalValuesInSessionName = regexp.MustCompile(`[^a-zA-Z0-9=,.@-]+`) // NewCloud constructs new Cloud implementation. -func NewCloud(cfg CloudConfig, metricsCollector *aws_metrics.Collector, logger logr.Logger, awsClientsProvider provider.AWSClientsProvider) (services.Cloud, error) { +func NewCloud(cfg CloudConfig, clusterName string, metricsCollector *aws_metrics.Collector, logger logr.Logger, awsClientsProvider provider.AWSClientsProvider) (services.Cloud, error) { hasIPv4 := true addrs, err := net.InterfaceAddrs() if err == nil { @@ -119,6 +127,7 @@ func NewCloud(cfg CloudConfig, metricsCollector *aws_metrics.Collector, logger l thisObj := &defaultCloud{ cfg: cfg, + clusterName: clusterName, ec2: ec2Service, acm: services.NewACM(awsClientsProvider), wafv2: services.NewWAFv2(awsClientsProvider), @@ -126,7 +135,8 @@ func NewCloud(cfg CloudConfig, metricsCollector *aws_metrics.Collector, logger l shield: services.NewShield(awsClientsProvider), rgt: services.NewRGT(awsClientsProvider), - assumeRoleElbV2: make(map[string]services.ELBV2), + assumeRoleElbV2Cache: cache.NewExpiring(), + awsClientsProvider: awsClientsProvider, logger: logger, } @@ -220,77 +230,65 @@ type defaultCloud struct { shield services.Shield rgt services.RGT - assumeRoleElbV2 map[string]services.ELBV2 + clusterName string + + // A cache holding elbv2 clients that are assuming a role. + assumeRoleElbV2Cache *cache.Expiring + // assumeRoleElbV2CacheMutex protects assumeRoleElbV2Cache + assumeRoleElbV2CacheMutex sync.RWMutex + awsClientsProvider provider.AWSClientsProvider logger logr.Logger } -// returns ELBV2 client for the given assumeRoleArn, or the default ELBV2 client if assumeRoleArn is empty -func (c *defaultCloud) GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn string, externalId string) services.ELBV2 { - +// GetAssumedRoleELBV2 returns ELBV2 client for the given assumeRoleArn, or the default ELBV2 client if assumeRoleArn is empty +func (c *defaultCloud) GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn string, externalId string) (services.ELBV2, error) { if assumeRoleArn == "" { - return c.elbv2 + return c.elbv2, nil } - assumedRoleELBV2, exists := c.assumeRoleElbV2[assumeRoleArn] + c.assumeRoleElbV2CacheMutex.RLock() + assumedRoleELBV2, exists := c.assumeRoleElbV2Cache.Get(assumeRoleArn) + c.assumeRoleElbV2CacheMutex.RUnlock() + if exists { - return assumedRoleELBV2 + return assumedRoleELBV2.(services.ELBV2), nil } c.logger.Info("awsCloud", "method", "GetAssumedRoleELBV2", "AssumeRoleArn", assumeRoleArn, "externalId", externalId) - //////////////// existingAwsConfig, _ := c.awsClientsProvider.GetAWSConfig(ctx, "GetAWSConfigForIAMRoleImpersonation") sourceAccount := sts.NewFromConfig(*existingAwsConfig) response, err := sourceAccount.AssumeRole(ctx, &sts.AssumeRoleInput{ RoleArn: aws.String(assumeRoleArn), - RoleSessionName: aws.String("aws-load-balancer-controller"), + RoleSessionName: aws.String(c.makeClusterNameSessionNameSafe()), ExternalId: aws.String(externalId), }) if err != nil { - log.Fatalf("Unable to assume target role, %v. Attempting to use default client", err) - return c.elbv2 + c.logger.Error(err, "Unable to assume target role, %v") + return nil, err } assumedRoleCreds := response.Credentials newCreds := credentials.NewStaticCredentialsProvider(*assumedRoleCreds.AccessKeyId, *assumedRoleCreds.SecretAccessKey, *assumedRoleCreds.SessionToken) newAwsConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(c.cfg.Region), config.WithCredentialsProvider(newCreds)) if err != nil { - log.Fatalf("Unable to load static credentials for service client config, %v. Attempting to use default client", err) - return c.elbv2 + c.logger.Error(err, "Unable to load static credentials for service client config, %v. Attempting to use default client") + return nil, err } - existingAwsConfig.Credentials = newAwsConfig.Credentials // response.Credentials + cacheTTL := assumedRoleCreds.Expiration.Sub(time.Now()) + existingAwsConfig.Credentials = newAwsConfig.Credentials + elbv2WithAssumedRole := services.NewELBV2(c.awsClientsProvider, c) - // // var assumedRoleCreds *stsTypes.Credentials = response.Credentials - - // // Create config with target service client, using assumed role - // cfg, err = config.LoadDefaultConfig(ctx, config.WithRegion(region), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(*assumedRoleCreds.AccessKeyId, *assumedRoleCreds.SecretAccessKey, *assumedRoleCreds.SessionToken))) - // if err != nil { - // log.Fatalf("unable to load static credentials for service client config, %v", err) - // } - - // //////////////// - // appCreds := stscreds.NewAssumeRoleProvider(client, assumeRoleArn) - // value, err := appCreds.Retrieve(context.TODO()) - // if err != nil { - // // handle error - // } - // ///////// - - // ///////////// OLD - // creds := stscreds.NewCredentials(c.session, assumeRoleArn, func(p *stscreds.AssumeRoleProvider) { - // p.ExternalID = &externalId - // }) - // ////////////// - - // c.awsConfig.Credentials = creds - // // newObj := services.NewELBV2(c.session, c, c.awsCFG) - // newObj := services.NewELBV2(*c.awsConfig, c.endpointsResolver, c) - - newObj := services.NewELBV2(c.awsClientsProvider, c) - c.assumeRoleElbV2[assumeRoleArn] = newObj + c.assumeRoleElbV2CacheMutex.Lock() + defer c.assumeRoleElbV2CacheMutex.Unlock() + c.assumeRoleElbV2Cache.Set(assumeRoleArn, elbv2WithAssumedRole, cacheTTL-cacheTTLBufferTime) + return elbv2WithAssumedRole, nil +} - return newObj +func (c *defaultCloud) makeClusterNameSessionNameSafe() string { + safeClusterName := illegalValuesInSessionName.ReplaceAllString(c.clusterName, "") + return fmt.Sprintf("AWS-LBC-%s", safeClusterName) } func (c *defaultCloud) EC2() services.EC2 { diff --git a/pkg/aws/services/cloudInterface.go b/pkg/aws/services/cloudInterface.go index 3e0ff558e..8b11eaeb1 100644 --- a/pkg/aws/services/cloudInterface.go +++ b/pkg/aws/services/cloudInterface.go @@ -30,5 +30,5 @@ type Cloud interface { // VpcID for the LoadBalancer resources. VpcID() string - GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn string, externalId string) ELBV2 + GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn string, externalId string) (ELBV2, error) } diff --git a/pkg/aws/services/elbv2.go b/pkg/aws/services/elbv2.go index 877cdd5f3..ca34f491f 100644 --- a/pkg/aws/services/elbv2.go +++ b/pkg/aws/services/elbv2.go @@ -60,7 +60,7 @@ type ELBV2 interface { ModifyListenerAttributesWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyListenerAttributesInput) (*elasticloadbalancingv2.ModifyListenerAttributesOutput, error) ModifyCapacityReservationWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyCapacityReservationInput) (*elasticloadbalancingv2.ModifyCapacityReservationOutput, error) DescribeCapacityReservationWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeCapacityReservationInput) (*elasticloadbalancingv2.DescribeCapacityReservationOutput, error) - AssumeRole(ctx context.Context, assumeRoleArn string, externalId string) ELBV2 + AssumeRole(ctx context.Context, assumeRoleArn string, externalId string) (ELBV2, error) } func NewELBV2(awsClientsProvider provider.AWSClientsProvider, cloud Cloud) ELBV2 { @@ -76,9 +76,9 @@ type elbv2Client struct { cloud Cloud } -func (c *elbv2Client) AssumeRole(ctx context.Context, assumeRoleArn string, externalId string) ELBV2 { +func (c *elbv2Client) AssumeRole(ctx context.Context, assumeRoleArn string, externalId string) (ELBV2, error) { if assumeRoleArn == "" { - return c + return c, nil } return c.cloud.GetAssumedRoleELBV2(ctx, assumeRoleArn, externalId) } diff --git a/pkg/aws/services/elbv2_mocks.go b/pkg/aws/services/elbv2_mocks.go index 8d5b540a7..1f427dd56 100644 --- a/pkg/aws/services/elbv2_mocks.go +++ b/pkg/aws/services/elbv2_mocks.go @@ -67,11 +67,12 @@ func (mr *MockELBV2MockRecorder) AddTagsWithContext(arg0, arg1 interface{}) *gom } // AssumeRole mocks base method. -func (m *MockELBV2) AssumeRole(arg0 context.Context, arg1, arg2 string) ELBV2 { +func (m *MockELBV2) AssumeRole(arg0 context.Context, arg1, arg2 string) (ELBV2, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AssumeRole", arg0, arg1, arg2) ret0, _ := ret[0].(ELBV2) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // AssumeRole indicates an expected call of AssumeRole. diff --git a/pkg/targetgroupbinding/resource_manager.go b/pkg/targetgroupbinding/resource_manager.go index 666d25b1c..1b4035f06 100644 --- a/pkg/targetgroupbinding/resource_manager.go +++ b/pkg/targetgroupbinding/resource_manager.go @@ -96,7 +96,6 @@ func (m *defaultResourceManager) Reconcile(ctx context.Context, tgb *elbv2api.Ta var oldCheckPoint string var isDeferred bool var err error - AnnotationsToFields(tgb) if *tgb.Spec.TargetType == elbv2api.TargetTypeIP { newCheckPoint, oldCheckPoint, isDeferred, err = m.reconcileWithIPTargetType(ctx, tgb) @@ -116,7 +115,6 @@ func (m *defaultResourceManager) Reconcile(ctx context.Context, tgb *elbv2api.Ta } func (m *defaultResourceManager) Cleanup(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { - AnnotationsToFields(tgb) if err := m.cleanupTargets(ctx, tgb); err != nil { return err } @@ -546,42 +544,45 @@ func (m *defaultResourceManager) deregisterTargets(ctx context.Context, tgb *elb func (m *defaultResourceManager) registerPodEndpoints(ctx context.Context, tgb *elbv2api.TargetGroupBinding, endpoints []backend.PodEndpoint) error { vpcID := m.vpcID - // Target group is in a different VPC from the cluster's VPC if tgb.Spec.VpcID != "" && tgb.Spec.VpcID != m.vpcID { vpcID = tgb.Spec.VpcID m.logger.Info(fmt.Sprintf( "registering endpoints using the targetGroup's vpcID %s which is different from the cluster's vpcID %s", tgb.Spec.VpcID, m.vpcID)) + } - if tgb.Spec.IamRoleArnToAssume != "" { - // since we need to assume a role for this TGB, - // it is from a different account - // so the packets will need to leave the VPC and therefore - // target.AvailabilityZone = awssdk.String("all") must be set - // or else nothing will work - sdkTargets := make([]elbv2types.TargetDescription, 0, len(endpoints)) - for _, endpoint := range endpoints { - target := elbv2types.TargetDescription{ - Id: awssdk.String(endpoint.IP), - Port: awssdk.Int32(endpoint.Port), - } - target.AvailabilityZone = awssdk.String("all") - sdkTargets = append(sdkTargets, target) - } - return m.targetsManager.RegisterTargets(ctx, tgb, sdkTargets) + var overrideAzFn func(addr netip.Addr) bool + if tgb.Spec.IamRoleArnToAssume != "" { + // If we're interacting with another account, then we should always be sitting "all" AZ to allow this + // target to get registered by the ELB API. + overrideAzFn = func(_ netip.Addr) bool { + return true + } + } else { + vpcInfo, err := m.vpcInfoProvider.FetchVPCInfo(ctx, vpcID) + if err != nil { + return err + } + var vpcRawCIDRs []string + vpcRawCIDRs = append(vpcRawCIDRs, vpcInfo.AssociatedIPv4CIDRs()...) + vpcRawCIDRs = append(vpcRawCIDRs, vpcInfo.AssociatedIPv6CIDRs()...) + vpcCIDRs, err := networking.ParseCIDRs(vpcRawCIDRs) + if err != nil { + return err + } + // If the pod ip resides out of all the VPC CIDRs, then the only way to force the ELB API is to use "all" AZ. + overrideAzFn = func(addr netip.Addr) bool { + return !networking.IsIPWithinCIDRs(addr, vpcCIDRs) } } - vpcInfo, err := m.vpcInfoProvider.FetchVPCInfo(ctx, vpcID) - if err != nil { - return err - } - var vpcRawCIDRs []string - vpcRawCIDRs = append(vpcRawCIDRs, vpcInfo.AssociatedIPv4CIDRs()...) - vpcRawCIDRs = append(vpcRawCIDRs, vpcInfo.AssociatedIPv6CIDRs()...) - vpcCIDRs, err := networking.ParseCIDRs(vpcRawCIDRs) + + sdkTargets, err := m.prepareRegistrationCall(endpoints, overrideAzFn) if err != nil { return err } + return m.targetsManager.RegisterTargets(ctx, tgb, sdkTargets) +} +func (m *defaultResourceManager) prepareRegistrationCall(endpoints []backend.PodEndpoint, doAzOverride func(addr netip.Addr) bool) ([]elbv2types.TargetDescription, error) { sdkTargets := make([]elbv2types.TargetDescription, 0, len(endpoints)) for _, endpoint := range endpoints { target := elbv2types.TargetDescription{ @@ -590,14 +591,14 @@ func (m *defaultResourceManager) registerPodEndpoints(ctx context.Context, tgb * } podIP, err := netip.ParseAddr(endpoint.IP) if err != nil { - return err + return sdkTargets, err } - if !networking.IsIPWithinCIDRs(podIP, vpcCIDRs) { + if doAzOverride(podIP) { target.AvailabilityZone = awssdk.String("all") } sdkTargets = append(sdkTargets, target) } - return m.targetsManager.RegisterTargets(ctx, tgb, sdkTargets) + return sdkTargets, nil } func (m *defaultResourceManager) registerNodePortEndpoints(ctx context.Context, tgb *elbv2api.TargetGroupBinding, endpoints []backend.NodePortEndpoint) error { diff --git a/pkg/targetgroupbinding/targets_manager.go b/pkg/targetgroupbinding/targets_manager.go index b00f95a9b..13e6117a8 100644 --- a/pkg/targetgroupbinding/targets_manager.go +++ b/pkg/targetgroupbinding/targets_manager.go @@ -89,7 +89,13 @@ func (m *cachedTargetsManager) RegisterTargets(ctx context.Context, tgb *elbv2ap m.logger.Info("registering targets", "arn", tgARN, "targets", targetsChunk) - _, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).RegisterTargetsWithContext(ctx, req) + + clientToUse, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) + if err != nil { + return err + } + + _, err = clientToUse.RegisterTargetsWithContext(ctx, req) if err != nil { return err } @@ -111,7 +117,11 @@ func (m *cachedTargetsManager) DeregisterTargets(ctx context.Context, tgb *elbv2 m.logger.Info("deRegistering targets", "arn", tgARN, "targets", targetsChunk) - _, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).DeregisterTargetsWithContext(ctx, req) + clientToUse, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) + if err != nil { + return err + } + _, err = clientToUse.DeregisterTargetsWithContext(ctx, req) if err != nil { return err } @@ -199,7 +209,11 @@ func (m *cachedTargetsManager) listTargetsFromAWS(ctx context.Context, tgb *elbv TargetGroupArn: aws.String(tgARN), Targets: pointerizeTargetDescriptions(targets), } - resp, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).DescribeTargetHealthWithContext(ctx, req) + clientToUse, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) + if err != nil { + return nil, err + } + resp, err := clientToUse.DescribeTargetHealthWithContext(ctx, req) if err != nil { return nil, err } diff --git a/pkg/targetgroupbinding/targets_manager_test.go b/pkg/targetgroupbinding/targets_manager_test.go index f4f476fe0..5b85a6b7c 100644 --- a/pkg/targetgroupbinding/targets_manager_test.go +++ b/pkg/targetgroupbinding/targets_manager_test.go @@ -280,7 +280,7 @@ func Test_cachedTargetsManager_RegisterTargets(t *testing.T) { ctx := context.Background() for _, call := range tt.fields.registerTargetsWithContextCalls { elbv2Client.EXPECT().RegisterTargetsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil) } targetsCache := cache.NewExpiring() @@ -526,7 +526,7 @@ func Test_cachedTargetsManager_DeregisterTargets(t *testing.T) { ctx := context.Background() for _, call := range tt.fields.deregisterTargetsWithContextCalls { elbv2Client.EXPECT().DeregisterTargetsWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil) } targetsCache := cache.NewExpiring() @@ -792,7 +792,7 @@ func Test_cachedTargetsManager_ListTargets(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetHealthWithContextCalls { elbv2Client.EXPECT().DescribeTargetHealthWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil) } targetsCache := cache.NewExpiring() targetsCacheTTL := 1 * time.Minute @@ -1203,7 +1203,7 @@ func Test_cachedTargetsManager_refreshUnhealthyTargets(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetHealthWithContextCalls { elbv2Client.EXPECT().DescribeTargetHealthWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil) } m := &cachedTargetsManager{ elbv2Client: elbv2Client, @@ -1367,7 +1367,7 @@ func Test_cachedTargetsManager_listTargetsFromAWS(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetHealthWithContextCalls { elbv2Client.EXPECT().DescribeTargetHealthWithContext(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client) + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil) } m := &cachedTargetsManager{ diff --git a/pkg/targetgroupbinding/utils.go b/pkg/targetgroupbinding/utils.go index 013c94081..057e08a77 100644 --- a/pkg/targetgroupbinding/utils.go +++ b/pkg/targetgroupbinding/utils.go @@ -25,27 +25,8 @@ const ( // Index Key for "ServiceReference" index. IndexKeyServiceRefName = "spec.serviceRef.name" - - // Annotation for IAM Role ARN to assume when calling AWS APIs. - AnnotationIamRoleArnToAssume = "alb.ingress.kubernetes.io/IamRoleArnToAssume" - - // Annotation for IAM Role External ID to use when calling AWS APIs. - AnnotationAssumeRoleExternalId = "alb.ingress.kubernetes.io/AssumeRoleExternalId" ) -// AnnotationsToFields converts annotations to fields. Currently it's tgb.Spec.IamRoleArnToAssume and tgb.Spec.AssumeRoleExternalId -func AnnotationsToFields(tgb *elbv2api.TargetGroupBinding) { - for key, value := range tgb.Annotations { - if key == AnnotationIamRoleArnToAssume { - tgb.Spec.IamRoleArnToAssume = value - } else { - if key == AnnotationAssumeRoleExternalId { - tgb.Spec.AssumeRoleExternalId = value - } - } - } -} - // BuildTargetHealthPodConditionType constructs the condition type for TargetHealth pod condition. func BuildTargetHealthPodConditionType(tgb *elbv2api.TargetGroupBinding) corev1.PodConditionType { return corev1.PodConditionType(fmt.Sprintf("%s/%s", TargetHealthPodConditionTypePrefix, tgb.Name)) diff --git a/test/framework/framework.go b/test/framework/framework.go index 9830647bb..eb99b67b8 100644 --- a/test/framework/framework.go +++ b/test/framework/framework.go @@ -63,7 +63,7 @@ func InitFramework() (*Framework, error) { VpcID: globalOptions.AWSVPCID, MaxRetries: 3, ThrottleConfig: throttle.NewDefaultServiceOperationsThrottleConfig(), - }, nil, logger, nil) + }, "clusterName", nil, logger, nil) if err != nil { return nil, err } diff --git a/webhooks/elbv2/targetgroupbinding_mutator.go b/webhooks/elbv2/targetgroupbinding_mutator.go index 7818ce39d..2a826bfb3 100644 --- a/webhooks/elbv2/targetgroupbinding_mutator.go +++ b/webhooks/elbv2/targetgroupbinding_mutator.go @@ -13,7 +13,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" - "sigs.k8s.io/aws-load-balancer-controller/pkg/targetgroupbinding" "sigs.k8s.io/aws-load-balancer-controller/pkg/webhook" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -48,7 +47,6 @@ func (m *targetGroupBindingMutator) MutateCreate(ctx context.Context, obj runtim if err := m.getArnFromNameIfNeeded(ctx, tgb); err != nil { return nil, err } - targetgroupbinding.AnnotationsToFields(tgb) if err := m.defaultingTargetType(ctx, tgb); err != nil { return nil, err } @@ -63,7 +61,7 @@ func (m *targetGroupBindingMutator) MutateCreate(ctx context.Context, obj runtim func (m *targetGroupBindingMutator) getArnFromNameIfNeeded(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { if tgb.Spec.TargetGroupARN == "" && tgb.Spec.TargetGroupName != "" { - tgObj, err := m.getTargetGroupsByNameFromAWS(ctx, tgb.Spec.TargetGroupName) + tgObj, err := m.getTargetGroupsByNameFromAWS(ctx, tgb) if err != nil { return err } @@ -153,7 +151,14 @@ func (m *targetGroupBindingMutator) getTargetGroupFromAWS(ctx context.Context, t req := &elbv2sdk.DescribeTargetGroupsInput{ TargetGroupArns: []string{tgARN}, } - tgList, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).DescribeTargetGroupsAsList(ctx, req) + + clientToUse, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) + + if err != nil { + return nil, err + } + + tgList, err := clientToUse.DescribeTargetGroupsAsList(ctx, req) if err != nil { return nil, err } @@ -163,16 +168,22 @@ func (m *targetGroupBindingMutator) getTargetGroupFromAWS(ctx context.Context, t return &tgList[0], nil } -func (m *targetGroupBindingMutator) getTargetGroupsByNameFromAWS(ctx context.Context, tgName string) (*elbv2types.TargetGroup, error) { +func (m *targetGroupBindingMutator) getTargetGroupsByNameFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (*elbv2types.TargetGroup, error) { req := &elbv2sdk.DescribeTargetGroupsInput{ - Names: []string{tgName}, + Names: []string{tgb.Spec.TargetGroupName}, } - tgList, err := m.elbv2Client.DescribeTargetGroupsAsList(ctx, req) + clientToUse, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) + + if err != nil { + return nil, err + } + + tgList, err := clientToUse.DescribeTargetGroupsAsList(ctx, req) if err != nil { return nil, err } if len(tgList) != 1 { - return nil, errors.Errorf("expecting a single targetGroup with name [%s] but got %v", tgName, len(tgList)) + return nil, errors.Errorf("expecting a single targetGroup with name [%s] but got %v", tgb.Spec.TargetGroupName, len(tgList)) } return &tgList[0], nil } diff --git a/webhooks/elbv2/targetgroupbinding_mutator_test.go b/webhooks/elbv2/targetgroupbinding_mutator_test.go index 032440ad1..0348d21de 100644 --- a/webhooks/elbv2/targetgroupbinding_mutator_test.go +++ b/webhooks/elbv2/targetgroupbinding_mutator_test.go @@ -308,7 +308,7 @@ func Test_targetGroupBindingMutator_MutateCreate(t *testing.T) { ctx := context.Background() for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err).AnyTimes() - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil).AnyTimes() } m := &targetGroupBindingMutator{ @@ -415,7 +415,7 @@ func Test_targetGroupBindingMutator_obtainSDKTargetTypeFromAWS(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil).AnyTimes() } m := &targetGroupBindingMutator{ @@ -545,7 +545,7 @@ func Test_targetGroupBindingMutator_getIPAddressTypeFromAWS(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil).AnyTimes() } m := &targetGroupBindingMutator{ @@ -631,7 +631,7 @@ func Test_targetGroupBindingMutator_obtainSDKVpcIDFromAWS(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil).AnyTimes() } m := &targetGroupBindingMutator{ diff --git a/webhooks/elbv2/targetgroupbinding_validator.go b/webhooks/elbv2/targetgroupbinding_validator.go index bb6ee770a..f7d5e7587 100644 --- a/webhooks/elbv2/targetgroupbinding_validator.go +++ b/webhooks/elbv2/targetgroupbinding_validator.go @@ -16,7 +16,6 @@ import ( elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" - "sigs.k8s.io/aws-load-balancer-controller/pkg/targetgroupbinding" "sigs.k8s.io/aws-load-balancer-controller/pkg/webhook" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -56,7 +55,6 @@ func (v *targetGroupBindingValidator) Prototype(_ admission.Request) (runtime.Ob func (v *targetGroupBindingValidator) ValidateCreate(ctx context.Context, obj runtime.Object) error { tgb := obj.(*elbv2api.TargetGroupBinding) - targetgroupbinding.AnnotationsToFields(tgb) if err := v.checkRequiredFields(ctx, tgb); err != nil { return err } @@ -78,7 +76,6 @@ func (v *targetGroupBindingValidator) ValidateCreate(ctx context.Context, obj ru func (v *targetGroupBindingValidator) ValidateUpdate(ctx context.Context, obj runtime.Object, oldObj runtime.Object) error { tgb := obj.(*elbv2api.TargetGroupBinding) oldTgb := oldObj.(*elbv2api.TargetGroupBinding) - targetgroupbinding.AnnotationsToFields(tgb) if err := v.checkRequiredFields(ctx, tgb); err != nil { return err } @@ -237,7 +234,13 @@ func (v *targetGroupBindingValidator) getTargetGroupFromAWS(ctx context.Context, req := &elbv2sdk.DescribeTargetGroupsInput{ TargetGroupArns: []string{tgARN}, } - tgList, err := v.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId).DescribeTargetGroupsAsList(ctx, req) + + clientToUse, err := v.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) + if err != nil { + return nil, err + } + + tgList, err := clientToUse.DescribeTargetGroupsAsList(ctx, req) if err != nil { return nil, err } diff --git a/webhooks/elbv2/targetgroupbinding_validator_test.go b/webhooks/elbv2/targetgroupbinding_validator_test.go index 488960494..14906bd41 100644 --- a/webhooks/elbv2/targetgroupbinding_validator_test.go +++ b/webhooks/elbv2/targetgroupbinding_validator_test.go @@ -333,7 +333,7 @@ func Test_targetGroupBindingValidator_ValidateCreate(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil).AnyTimes() } v := &targetGroupBindingValidator{ k8sClient: k8sClient, @@ -1395,7 +1395,7 @@ func Test_targetGroupBindingValidator_checkTargetGroupVpcID(t *testing.T) { elbv2Client := services.NewMockELBV2(ctrl) for _, call := range tt.fields.describeTargetGroupsAsListCalls { elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err) - elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client).AnyTimes() + elbv2Client.EXPECT().AssumeRole(ctx, gomock.Any(), gomock.Any()).Return(elbv2Client, nil).AnyTimes() } v := &targetGroupBindingValidator{ k8sClient: k8sClient, From 577e1b6d0df54cc2563d46c2077d6189b5be8639 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Fri, 17 Jan 2025 11:51:27 -0800 Subject: [PATCH 15/30] refactor target group lookup in webhooks to reduce code duplication --- webhooks/elbv2/targetgroup_helper.go | 45 ++++++++++++++++ webhooks/elbv2/targetgroupbinding_mutator.go | 52 ++----------------- .../elbv2/targetgroupbinding_validator.go | 44 ++-------------- 3 files changed, 52 insertions(+), 89 deletions(-) create mode 100644 webhooks/elbv2/targetgroup_helper.go diff --git a/webhooks/elbv2/targetgroup_helper.go b/webhooks/elbv2/targetgroup_helper.go new file mode 100644 index 000000000..dc847dc6f --- /dev/null +++ b/webhooks/elbv2/targetgroup_helper.go @@ -0,0 +1,45 @@ +package elbv2 + +import ( + "context" + elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/pkg/errors" + elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" + "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" +) + +// getTargetGroupFromAWS returns the AWS target group corresponding to the arn +func getTargetGroupFromAWS(ctx context.Context, elbv2Client services.ELBV2, tgb *elbv2api.TargetGroupBinding) (*elbv2types.TargetGroup, error) { + tgARN := tgb.Spec.TargetGroupARN + req := &elbv2sdk.DescribeTargetGroupsInput{ + TargetGroupArns: []string{tgARN}, + } + return getTargetGroupHelper(ctx, elbv2Client, tgb, tgARN, req) +} + +// getTargetGroupsByNameFromAWS returns the AWS target group corresponding to the name +func getTargetGroupsByNameFromAWS(ctx context.Context, elbv2Client services.ELBV2, tgb *elbv2api.TargetGroupBinding) (*elbv2types.TargetGroup, error) { + req := &elbv2sdk.DescribeTargetGroupsInput{ + Names: []string{tgb.Spec.TargetGroupName}, + } + + return getTargetGroupHelper(ctx, elbv2Client, tgb, tgb.Spec.TargetGroupName, req) +} + +func getTargetGroupHelper(ctx context.Context, elbv2Client services.ELBV2, tgb *elbv2api.TargetGroupBinding, tgIdentifier string, req *elbv2sdk.DescribeTargetGroupsInput) (*elbv2types.TargetGroup, error) { + clientToUse, err := elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) + + if err != nil { + return nil, err + } + + tgList, err := clientToUse.DescribeTargetGroupsAsList(ctx, req) + if err != nil { + return nil, err + } + if len(tgList) != 1 { + return nil, errors.Errorf("expecting a single targetGroup with query [%s] but got %v", tgIdentifier, len(tgList)) + } + return &tgList[0], nil +} diff --git a/webhooks/elbv2/targetgroupbinding_mutator.go b/webhooks/elbv2/targetgroupbinding_mutator.go index 2a826bfb3..aed2f6dd6 100644 --- a/webhooks/elbv2/targetgroupbinding_mutator.go +++ b/webhooks/elbv2/targetgroupbinding_mutator.go @@ -6,8 +6,6 @@ import ( elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" awssdk "github.com/aws/aws-sdk-go-v2/aws" - elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" - "github.com/go-logr/logr" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" @@ -61,7 +59,7 @@ func (m *targetGroupBindingMutator) MutateCreate(ctx context.Context, obj runtim func (m *targetGroupBindingMutator) getArnFromNameIfNeeded(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error { if tgb.Spec.TargetGroupARN == "" && tgb.Spec.TargetGroupName != "" { - tgObj, err := m.getTargetGroupsByNameFromAWS(ctx, tgb) + tgObj, err := getTargetGroupsByNameFromAWS(ctx, m.elbv2Client, tgb) if err != nil { return err } @@ -121,7 +119,7 @@ func (m *targetGroupBindingMutator) defaultingVpcID(ctx context.Context, tgb *el } func (m *targetGroupBindingMutator) obtainSDKTargetTypeFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (string, error) { - targetGroup, err := m.getTargetGroupFromAWS(ctx, tgb) + targetGroup, err := getTargetGroupFromAWS(ctx, m.elbv2Client, tgb) if err != nil { return "", err } @@ -130,7 +128,7 @@ func (m *targetGroupBindingMutator) obtainSDKTargetTypeFromAWS(ctx context.Conte // getTargetGroupIPAddressTypeFromAWS returns the target group IP address type of AWS target group func (m *targetGroupBindingMutator) getTargetGroupIPAddressTypeFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (elbv2api.TargetGroupIPAddressType, error) { - targetGroup, err := m.getTargetGroupFromAWS(ctx, tgb) + targetGroup, err := getTargetGroupFromAWS(ctx, m.elbv2Client, tgb) if err != nil { return "", err } @@ -146,50 +144,8 @@ func (m *targetGroupBindingMutator) getTargetGroupIPAddressTypeFromAWS(ctx conte return ipAddressType, nil } -func (m *targetGroupBindingMutator) getTargetGroupFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (*elbv2types.TargetGroup, error) { - tgARN := tgb.Spec.TargetGroupARN - req := &elbv2sdk.DescribeTargetGroupsInput{ - TargetGroupArns: []string{tgARN}, - } - - clientToUse, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) - - if err != nil { - return nil, err - } - - tgList, err := clientToUse.DescribeTargetGroupsAsList(ctx, req) - if err != nil { - return nil, err - } - if len(tgList) != 1 { - return nil, errors.Errorf("expecting a single targetGroup but got %v", len(tgList)) - } - return &tgList[0], nil -} - -func (m *targetGroupBindingMutator) getTargetGroupsByNameFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (*elbv2types.TargetGroup, error) { - req := &elbv2sdk.DescribeTargetGroupsInput{ - Names: []string{tgb.Spec.TargetGroupName}, - } - clientToUse, err := m.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) - - if err != nil { - return nil, err - } - - tgList, err := clientToUse.DescribeTargetGroupsAsList(ctx, req) - if err != nil { - return nil, err - } - if len(tgList) != 1 { - return nil, errors.Errorf("expecting a single targetGroup with name [%s] but got %v", tgb.Spec.TargetGroupName, len(tgList)) - } - return &tgList[0], nil -} - func (m *targetGroupBindingMutator) getVpcIDFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (string, error) { - targetGroup, err := m.getTargetGroupFromAWS(ctx, tgb) + targetGroup, err := getTargetGroupFromAWS(ctx, m.elbv2Client, tgb) if err != nil { return "", err } diff --git a/webhooks/elbv2/targetgroupbinding_validator.go b/webhooks/elbv2/targetgroupbinding_validator.go index f7d5e7587..1af07a2f3 100644 --- a/webhooks/elbv2/targetgroupbinding_validator.go +++ b/webhooks/elbv2/targetgroupbinding_validator.go @@ -9,7 +9,6 @@ import ( elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" awssdk "github.com/aws/aws-sdk-go-v2/aws" - elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" "github.com/go-logr/logr" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" @@ -110,7 +109,7 @@ func (v *targetGroupBindingValidator) checkRequiredFields(ctx context.Context, t By changing the object here I guarantee as early as possible that that assumption is true. */ - tgObj, err := v.getTargetGroupsByNameFromAWS(ctx, tgb.Spec.TargetGroupName) + tgObj, err := getTargetGroupsByNameFromAWS(ctx, v.elbv2Client, tgb) if err != nil { return fmt.Errorf("searching TargetGroup with name %s: %w", tgb.Spec.TargetGroupName, err) } @@ -212,7 +211,7 @@ func (v *targetGroupBindingValidator) checkTargetGroupVpcID(ctx context.Context, // getTargetGroupIPAddressTypeFromAWS returns the target group IP address type of AWS target group func (v *targetGroupBindingValidator) getTargetGroupIPAddressTypeFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (elbv2api.TargetGroupIPAddressType, error) { - targetGroup, err := v.getTargetGroupFromAWS(ctx, tgb) + targetGroup, err := getTargetGroupFromAWS(ctx, v.elbv2Client, tgb) if err != nil { return "", err } @@ -228,51 +227,14 @@ func (v *targetGroupBindingValidator) getTargetGroupIPAddressTypeFromAWS(ctx con return ipAddressType, nil } -// getTargetGroupFromAWS returns the AWS target group corresponding to the ARN -func (v *targetGroupBindingValidator) getTargetGroupFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (*elbv2types.TargetGroup, error) { - tgARN := tgb.Spec.TargetGroupARN - req := &elbv2sdk.DescribeTargetGroupsInput{ - TargetGroupArns: []string{tgARN}, - } - - clientToUse, err := v.elbv2Client.AssumeRole(ctx, tgb.Spec.IamRoleArnToAssume, tgb.Spec.AssumeRoleExternalId) - if err != nil { - return nil, err - } - - tgList, err := clientToUse.DescribeTargetGroupsAsList(ctx, req) - if err != nil { - return nil, err - } - if len(tgList) != 1 { - return nil, errors.Errorf("expecting a single targetGroup but got %v", len(tgList)) - } - return &tgList[0], nil -} - func (v *targetGroupBindingValidator) getVpcIDFromAWS(ctx context.Context, tgb *elbv2api.TargetGroupBinding) (string, error) { - targetGroup, err := v.getTargetGroupFromAWS(ctx, tgb) + targetGroup, err := getTargetGroupFromAWS(ctx, v.elbv2Client, tgb) if err != nil { return "", err } return awssdk.ToString(targetGroup.VpcId), nil } -// getTargetGroupFromAWS returns the AWS target group corresponding to the tgName -func (v *targetGroupBindingValidator) getTargetGroupsByNameFromAWS(ctx context.Context, tgName string) (*elbv2types.TargetGroup, error) { - req := &elbv2sdk.DescribeTargetGroupsInput{ - Names: []string{tgName}, - } - tgList, err := v.elbv2Client.DescribeTargetGroupsAsList(ctx, req) - if err != nil { - return nil, err - } - if len(tgList) != 1 { - return nil, errors.Errorf("expecting a single targetGroup with name [%s] but got %v", tgName, len(tgList)) - } - return &tgList[0], nil -} - // +kubebuilder:webhook:path=/validate-elbv2-k8s-aws-v1beta1-targetgroupbinding,mutating=false,failurePolicy=fail,groups=elbv2.k8s.aws,resources=targetgroupbindings,verbs=create;update,versions=v1beta1,name=vtargetgroupbinding.elbv2.k8s.aws,sideEffects=None,webhookVersions=v1,admissionReviewVersions=v1beta1 func (v *targetGroupBindingValidator) SetupWithManager(mgr ctrl.Manager) { From db09cb9d010dea1c853d4e9e859675617f95d188 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Fri, 17 Jan 2025 12:28:29 -0800 Subject: [PATCH 16/30] add tests and correct regex for assume role session name --- pkg/aws/cloud.go | 10 +-------- pkg/aws/cloud_util.go | 25 +++++++++++++++++++++++ pkg/aws/cloud_util_test.go | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 pkg/aws/cloud_util.go create mode 100644 pkg/aws/cloud_util_test.go diff --git a/pkg/aws/cloud.go b/pkg/aws/cloud.go index d13c6dd05..af355497d 100644 --- a/pkg/aws/cloud.go +++ b/pkg/aws/cloud.go @@ -6,7 +6,6 @@ import ( "k8s.io/apimachinery/pkg/util/cache" "net" "os" - "regexp" "strings" "sync" "time" @@ -40,8 +39,6 @@ const ( cacheTTLBufferTime = 30 * time.Second ) -var illegalValuesInSessionName = regexp.MustCompile(`[^a-zA-Z0-9=,.@-]+`) - // NewCloud constructs new Cloud implementation. func NewCloud(cfg CloudConfig, clusterName string, metricsCollector *aws_metrics.Collector, logger logr.Logger, awsClientsProvider provider.AWSClientsProvider) (services.Cloud, error) { hasIPv4 := true @@ -261,7 +258,7 @@ func (c *defaultCloud) GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn st sourceAccount := sts.NewFromConfig(*existingAwsConfig) response, err := sourceAccount.AssumeRole(ctx, &sts.AssumeRoleInput{ RoleArn: aws.String(assumeRoleArn), - RoleSessionName: aws.String(c.makeClusterNameSessionNameSafe()), + RoleSessionName: aws.String(generateAssumeRoleSessionName(c.clusterName)), ExternalId: aws.String(externalId), }) if err != nil { @@ -286,11 +283,6 @@ func (c *defaultCloud) GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn st return elbv2WithAssumedRole, nil } -func (c *defaultCloud) makeClusterNameSessionNameSafe() string { - safeClusterName := illegalValuesInSessionName.ReplaceAllString(c.clusterName, "") - return fmt.Sprintf("AWS-LBC-%s", safeClusterName) -} - func (c *defaultCloud) EC2() services.EC2 { return c.ec2 } diff --git a/pkg/aws/cloud_util.go b/pkg/aws/cloud_util.go new file mode 100644 index 000000000..6da13b8f0 --- /dev/null +++ b/pkg/aws/cloud_util.go @@ -0,0 +1,25 @@ +package aws + +import ( + "fmt" + "regexp" +) + +const ( + sessionNamePrefix = "AWS-LBC-" + maxSessionNameLength = 2047 +) + +var illegalValuesInSessionName = regexp.MustCompile(`[^a-zA-Z0-9=,.@\-_]+`) + +func generateAssumeRoleSessionName(clusterName string) string { + safeClusterName := illegalValuesInSessionName.ReplaceAllString(clusterName, "") + + sessionName := fmt.Sprintf("%s%s", sessionNamePrefix, safeClusterName) + + if len(sessionName) > maxSessionNameLength { + return sessionName[:maxSessionNameLength] + } + + return sessionName +} diff --git a/pkg/aws/cloud_util_test.go b/pkg/aws/cloud_util_test.go new file mode 100644 index 000000000..83e37b612 --- /dev/null +++ b/pkg/aws/cloud_util_test.go @@ -0,0 +1,42 @@ +package aws + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestUpdateTrackedTargets(t *testing.T) { + testCases := []struct { + name string + clusterName string + expectedSessionName string + }{ + { + name: "no mods", + clusterName: "my-cluster-name", + expectedSessionName: "AWS-LBC-my-cluster-name", + }, + { + name: "mix lower and upper case", + clusterName: "My-ClUsTeR-name", + expectedSessionName: "AWS-LBC-My-ClUsTeR-name", + }, + { + name: "with legal characters", + clusterName: "my_cluster-name=foo,something@here.", + expectedSessionName: "AWS-LBC-my_cluster-name=foo,something@here.", + }, + { + name: "with illegal characters", + clusterName: "my&*&*cluster()!(&name", + expectedSessionName: "AWS-LBC-myclustername", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := generateAssumeRoleSessionName(tc.clusterName) + assert.Equal(t, tc.expectedSessionName, result) + }) + } +} From 7e0269a183151fd6e65c54994a08c5815080507f Mon Sep 17 00:00:00 2001 From: Senthil Kumaran Date: Wed, 22 Jan 2025 11:37:39 -0800 Subject: [PATCH 17/30] Added ISO Policy for iso-e and iso-f; updated tests based on testing done for the region. --- docs/install/iam_policy_isoe.json | 242 ++++ docs/install/iam_policy_isof.json | 242 ++++ scripts/run-e2e-test.sh | 18 +- test/prow/v2_6_0_adc.yaml | 1083 ---------------- test/prow/v2_8_2_adc.yaml | 1122 +++++++++++++++++ ...6_0_ingclass.yaml => v2_8_2_ingclass.yaml} | 0 6 files changed, 1618 insertions(+), 1089 deletions(-) create mode 100644 docs/install/iam_policy_isoe.json create mode 100644 docs/install/iam_policy_isof.json delete mode 100644 test/prow/v2_6_0_adc.yaml create mode 100644 test/prow/v2_8_2_adc.yaml rename test/prow/{v2_6_0_ingclass.yaml => v2_8_2_ingclass.yaml} (100%) diff --git a/docs/install/iam_policy_isoe.json b/docs/install/iam_policy_isoe.json new file mode 100644 index 000000000..9afb8d4fa --- /dev/null +++ b/docs/install/iam_policy_isoe.json @@ -0,0 +1,242 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:CreateServiceLinkedRole" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeVpcs", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeTags", + "ec2:GetCoipPoolUsage", + "ec2:DescribeCoipPools", + "ec2:GetSecurityGroupsForVpc", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeListenerCertificates", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeRules", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeTags" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "cognito-idp:DescribeUserPoolClient", + "acm:ListCertificates", + "acm:DescribeCertificate", + "iam:ListServerCertificates", + "iam:GetServerCertificate", + "waf-regional:GetWebACL", + "waf-regional:GetWebACLForResource", + "waf-regional:AssociateWebACL", + "waf-regional:DisassociateWebACL", + "wafv2:GetWebACL", + "wafv2:GetWebACLForResource", + "wafv2:AssociateWebACL", + "wafv2:DisassociateWebACL", + "shield:GetSubscriptionState", + "shield:DescribeProtection", + "shield:CreateProtection", + "shield:DeleteProtection" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateSecurityGroup" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags" + ], + "Resource": "arn:aws-iso-e:ec2:*:*:security-group/*", + "Condition": { + "StringEquals": { + "ec2:CreateAction": "CreateSecurityGroup" + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags", + "ec2:DeleteTags" + ], + "Resource": "arn:aws-iso-e:ec2:*:*:security-group/*", + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "true", + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:DeleteSecurityGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateTargetGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:CreateRule", + "elasticloadbalancing:DeleteRule" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": [ + "arn:aws-iso-e:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws-iso-e:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws-iso-e:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "true", + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": [ + "arn:aws-iso-e:elasticloadbalancing:*:*:listener/net/*/*/*", + "arn:aws-iso-e:elasticloadbalancing:*:*:listener/app/*/*/*", + "arn:aws-iso-e:elasticloadbalancing:*:*:listener-rule/net/*/*/*", + "arn:aws-iso-e:elasticloadbalancing:*:*:listener-rule/app/*/*/*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags" + ], + "Resource": [ + "arn:aws-iso-e:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws-iso-e:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws-iso-e:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "StringEquals": { + "elasticloadbalancing:CreateAction": [ + "CreateTargetGroup", + "CreateLoadBalancer" + ] + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:SetIpAddressType", + "elasticloadbalancing:SetSecurityGroups", + "elasticloadbalancing:SetSubnets", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:DeleteTargetGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets" + ], + "Resource": "arn:aws-iso-e:elasticloadbalancing:*:*:targetgroup/*/*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:SetWebAcl", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:AddListenerCertificates", + "elasticloadbalancing:RemoveListenerCertificates", + "elasticloadbalancing:ModifyRule" + ], + "Resource": "*" + } + ] +} diff --git a/docs/install/iam_policy_isof.json b/docs/install/iam_policy_isof.json new file mode 100644 index 000000000..2c5054393 --- /dev/null +++ b/docs/install/iam_policy_isof.json @@ -0,0 +1,242 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:CreateServiceLinkedRole" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeVpcs", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeTags", + "ec2:GetCoipPoolUsage", + "ec2:DescribeCoipPools", + "ec2:GetSecurityGroupsForVpc", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeListenerCertificates", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeRules", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeTags" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "cognito-idp:DescribeUserPoolClient", + "acm:ListCertificates", + "acm:DescribeCertificate", + "iam:ListServerCertificates", + "iam:GetServerCertificate", + "waf-regional:GetWebACL", + "waf-regional:GetWebACLForResource", + "waf-regional:AssociateWebACL", + "waf-regional:DisassociateWebACL", + "wafv2:GetWebACL", + "wafv2:GetWebACLForResource", + "wafv2:AssociateWebACL", + "wafv2:DisassociateWebACL", + "shield:GetSubscriptionState", + "shield:DescribeProtection", + "shield:CreateProtection", + "shield:DeleteProtection" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateSecurityGroup" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags" + ], + "Resource": "arn:aws-iso-f:ec2:*:*:security-group/*", + "Condition": { + "StringEquals": { + "ec2:CreateAction": "CreateSecurityGroup" + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags", + "ec2:DeleteTags" + ], + "Resource": "arn:aws-iso-f:ec2:*:*:security-group/*", + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "true", + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:DeleteSecurityGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateTargetGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:CreateRule", + "elasticloadbalancing:DeleteRule" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": [ + "arn:aws-iso-f:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws-iso-f:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws-iso-f:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "true", + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": [ + "arn:aws-iso-f:elasticloadbalancing:*:*:listener/net/*/*/*", + "arn:aws-iso-f:elasticloadbalancing:*:*:listener/app/*/*/*", + "arn:aws-iso-f:elasticloadbalancing:*:*:listener-rule/net/*/*/*", + "arn:aws-iso-f:elasticloadbalancing:*:*:listener-rule/app/*/*/*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags" + ], + "Resource": [ + "arn:aws-iso-f:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws-iso-f:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws-iso-f:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "StringEquals": { + "elasticloadbalancing:CreateAction": [ + "CreateTargetGroup", + "CreateLoadBalancer" + ] + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:SetIpAddressType", + "elasticloadbalancing:SetSecurityGroups", + "elasticloadbalancing:SetSubnets", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:DeleteTargetGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets" + ], + "Resource": "arn:aws-iso-f:elasticloadbalancing:*:*:targetgroup/*/*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:SetWebAcl", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:AddListenerCertificates", + "elasticloadbalancing:RemoveListenerCertificates", + "elasticloadbalancing:ModifyRule" + ], + "Resource": "*" + } + ] +} diff --git a/scripts/run-e2e-test.sh b/scripts/run-e2e-test.sh index a5060855f..84de62544 100755 --- a/scripts/run-e2e-test.sh +++ b/scripts/run-e2e-test.sh @@ -14,7 +14,7 @@ TEST_IMAGE_REGISTRY=${TEST_IMAGE_REGISTRY:-"617930562442.dkr.ecr.us-west-2.amazo # PROD_IMAGE_REGISTRY is the registry in build-prod-* accounts where prod LBC images are stored PROD_IMAGE_REGISTRY=${PROD_IMAGE_REGISTRY:-"602401143452.dkr.ecr.us-west-2.amazonaws.com"} -ADC_REGIONS="us-iso-east-1 us-isob-east-1 us-iso-west-1" +ADC_REGIONS="us-iso-east-1 us-isob-east-1 us-iso-west-1 us-isof-south-1 us-isof-east-1 eu-isoe-west-1" CONTAINER_NAME="aws-load-balancer-controller" : "${DISABLE_WAFV2:=false}" DISABLE_WAFV2_FLAGS="" @@ -75,6 +75,12 @@ elif [[ $ADC_REGIONS == *"$REGION"* ]]; then if [[ $REGION == "us-isob-east-1" ]]; then AWS_PARTITION="aws-iso-b" IAM_POLCIY_FILE="iam_policy_isob.json" + elif [[ $REGION == "us-isof-east-1" || $REGION == "us-isof-south-1" ]]; then + AWS_PARTITION="aws-iso-f" + IAM_POLCIY_FILE="iam_policy_isof.json" + elif [[ $REGION == "eu-isoe-west-1" ]]; then + AWS_PARTITION="aws-iso-e" + IAM_POLCIY_FILE="iam_policy_isoe.json" else AWS_PARTITION="aws-iso" IAM_POLCIY_FILE="iam_policy_iso.json" @@ -143,7 +149,7 @@ function install_controller_for_adc_regions() { kubectl apply -f $cert_manager_yaml || true sleep 60s echo "install the controller via yaml" - controller_yaml="$SCRIPT_DIR"/../test/prow/v2_6_0_adc.yaml + controller_yaml="$SCRIPT_DIR"/../test/prow/v2_8_2_adc.yaml default_controller_image="public.ecr.aws/eks/aws-load-balancer-controller" sed -i "s#$default_controller_image#$IMAGE#g" "$controller_yaml" echo "Image URL in $controller_yaml has been updated to $IMAGE" @@ -153,7 +159,7 @@ function install_controller_for_adc_regions() { kubectl rollout status -n kube-system deployment aws-load-balancer-controller || true echo "apply the manifest for ingressclass and ingressclassparam" - ingclass_yaml="$SCRIPT_DIR"/../test/prow/v2_6_0_ingclass.yaml + ingclass_yaml="$SCRIPT_DIR"/../test/prow/v2_8_2_ingclass.yaml kubectl apply -f $ingclass_yaml || true } @@ -217,8 +223,8 @@ function run_ginkgo_test() { local focus=$1 echo "Starting the ginkgo tests from generated ginkgo test binaries with focus: $focus" if [ "$IP_FAMILY" == "IPv4" ] || [ "$IP_FAMILY" == "IPv6" ]; then - CGO_ENABLED=0 GOOS=$OS_OVERRIDE ginkgo --no-color $EXTRA_GINKGO_FLAGS --focus="$focus" -v --timeout 3h --fail-on-pending $GINKGO_TEST_BUILD/ingress.test -- --kubeconfig=$KUBE_CONFIG_PATH --cluster-name=$CLUSTER_NAME --aws-region=$REGION --aws-vpc-id=$VPC_ID --test-image-registry=$TEST_IMAGE_REGISTRY --ip-family=$IP_FAMILY || TEST_RESULT=fail - CGO_ENABLED=0 GOOS=$OS_OVERRIDE ginkgo --no-color $EXTRA_GINKGO_FLAGS --focus="$focus" -v --timeout 3h --fail-on-pending $GINKGO_TEST_BUILD/service.test -- --kubeconfig=$KUBE_CONFIG_PATH --cluster-name=$CLUSTER_NAME --aws-region=$REGION --aws-vpc-id=$VPC_ID --test-image-registry=$TEST_IMAGE_REGISTRY --ip-family=$IP_FAMILY || TEST_RESULT=fail + CGO_ENABLED=0 GOOS=$OS_OVERRIDE ginkgo --no-color $EXTRA_GINKGO_FLAGS --focus="$focus" -v --timeout 2h --fail-on-pending $GINKGO_TEST_BUILD/ingress.test -- --kubeconfig=$KUBE_CONFIG_PATH --cluster-name=$CLUSTER_NAME --aws-region=$REGION --aws-vpc-id=$VPC_ID --test-image-registry=$TEST_IMAGE_REGISTRY --ip-family=$IP_FAMILY || TEST_RESULT=fail + CGO_ENABLED=0 GOOS=$OS_OVERRIDE ginkgo --no-color $EXTRA_GINKGO_FLAGS --focus="$focus" -v --timeout 2h --fail-on-pending $GINKGO_TEST_BUILD/service.test -- --kubeconfig=$KUBE_CONFIG_PATH --cluster-name=$CLUSTER_NAME --aws-region=$REGION --aws-vpc-id=$VPC_ID --test-image-registry=$TEST_IMAGE_REGISTRY --ip-family=$IP_FAMILY || TEST_RESULT=fail else echo "Invalid IP_FAMILY input, choose from IPv4 or IPv6 only" fi @@ -253,4 +259,4 @@ if [[ "$TEST_RESULT" == fail ]]; then exit 1 fi -echo "Successfully finished the test suite $(($SECONDS / 60)) minutes and $(($SECONDS % 60)) seconds" +echo "Successfully finished the test suite $(($SECONDS / 60)) minutes and $(($SECONDS % 60)) seconds" \ No newline at end of file diff --git a/test/prow/v2_6_0_adc.yaml b/test/prow/v2_6_0_adc.yaml deleted file mode 100644 index 53f41d75f..000000000 --- a/test/prow/v2_6_0_adc.yaml +++ /dev/null @@ -1,1083 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.11.1 - creationTimestamp: null - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: ingressclassparams.elbv2.k8s.aws -spec: - group: elbv2.k8s.aws - names: - kind: IngressClassParams - listKind: IngressClassParamsList - plural: ingressclassparams - singular: ingressclassparams - scope: Cluster - versions: - - additionalPrinterColumns: - - description: The Ingress Group name - jsonPath: .spec.group.name - name: GROUP-NAME - type: string - - description: The AWS Load Balancer scheme - jsonPath: .spec.scheme - name: SCHEME - type: string - - description: The AWS Load Balancer ipAddressType - jsonPath: .spec.ipAddressType - name: IP-ADDRESS-TYPE - type: string - - jsonPath: .metadata.creationTimestamp - name: AGE - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: IngressClassParams is the Schema for the IngressClassParams API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: IngressClassParamsSpec defines the desired state of IngressClassParams - properties: - group: - description: Group defines the IngressGroup for all Ingresses that - belong to IngressClass with this IngressClassParams. - properties: - name: - description: Name is the name of IngressGroup. - type: string - required: - - name - type: object - inboundCIDRs: - description: InboundCIDRs specifies the CIDRs that are allowed to - access the Ingresses that belong to IngressClass with this IngressClassParams. - items: - type: string - type: array - ipAddressType: - description: IPAddressType defines the ip address type for all Ingresses - that belong to IngressClass with this IngressClassParams. - enum: - - ipv4 - - dualstack - type: string - loadBalancerAttributes: - description: LoadBalancerAttributes define the custom attributes to - LoadBalancers for all Ingress that that belong to IngressClass with - this IngressClassParams. - items: - description: Attributes defines custom attributes on resources. - properties: - key: - description: The key of the attribute. - type: string - value: - description: The value of the attribute. - type: string - required: - - key - - value - type: object - type: array - namespaceSelector: - description: NamespaceSelector restrict the namespaces of Ingresses - that are allowed to specify the IngressClass with this IngressClassParams. - * if absent or present but empty, it selects all namespaces. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - scheme: - description: Scheme defines the scheme for all Ingresses that belong - to IngressClass with this IngressClassParams. - enum: - - internal - - internet-facing - type: string - sslPolicy: - description: SSLPolicy specifies the SSL Policy for all Ingresses - that belong to IngressClass with this IngressClassParams. - type: string - subnets: - description: Subnets defines the subnets for all Ingresses that belong - to IngressClass with this IngressClassParams. - properties: - ids: - description: IDs specify the resource IDs of subnets. Exactly - one of this or `tags` must be specified. - items: - description: SubnetID specifies a subnet ID. - pattern: subnet-[0-9a-f]+ - type: string - minItems: 1 - type: array - tags: - additionalProperties: - items: - type: string - type: array - description: Tags specifies subnets in the load balancer's VPC - where each tag specified in the map key contains one of the - values in the corresponding value list. Exactly one of this - or `ids` must be specified. - type: object - type: object - tags: - description: Tags defines list of Tags on AWS resources provisioned - for Ingresses that belong to IngressClass with this IngressClassParams. - items: - description: Tag defines a AWS Tag on resources. - properties: - key: - description: The key of the tag. - type: string - value: - description: The value of the tag. - type: string - required: - - key - - value - type: object - type: array - type: object - type: object - served: true - storage: true - subresources: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.11.1 - creationTimestamp: null - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: targetgroupbindings.elbv2.k8s.aws -spec: - group: elbv2.k8s.aws - names: - kind: TargetGroupBinding - listKind: TargetGroupBindingList - plural: targetgroupbindings - singular: targetgroupbinding - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The Kubernetes Service's name - jsonPath: .spec.serviceRef.name - name: SERVICE-NAME - type: string - - description: The Kubernetes Service's port - jsonPath: .spec.serviceRef.port - name: SERVICE-PORT - type: string - - description: The AWS TargetGroup's TargetType - jsonPath: .spec.targetType - name: TARGET-TYPE - type: string - - description: The AWS TargetGroup's Amazon Resource Name - jsonPath: .spec.targetGroupARN - name: ARN - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - name: AGE - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: TargetGroupBinding is the Schema for the TargetGroupBinding API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding - properties: - networking: - description: networking provides the networking setup for ELBV2 LoadBalancer - to access targets in TargetGroup. - properties: - ingress: - description: List of ingress rules to allow ELBV2 LoadBalancer - to access targets in TargetGroup. - items: - properties: - from: - description: List of peers which should be able to access - the targets in TargetGroup. At least one NetworkingPeer - should be specified. - items: - description: NetworkingPeer defines the source/destination - peer for networking rules. - properties: - ipBlock: - description: IPBlock defines an IPBlock peer. If specified, - none of the other fields can be set. - properties: - cidr: - description: CIDR is the network CIDR. Both IPV4 - or IPV6 CIDR are accepted. - type: string - required: - - cidr - type: object - securityGroup: - description: SecurityGroup defines a SecurityGroup - peer. If specified, none of the other fields can - be set. - properties: - groupID: - description: GroupID is the EC2 SecurityGroupID. - type: string - required: - - groupID - type: object - type: object - type: array - ports: - description: List of ports which should be made accessible - on the targets in TargetGroup. If ports is empty or unspecified, - it defaults to all ports with TCP. - items: - properties: - port: - anyOf: - - type: integer - - type: string - description: The port which traffic must match. When - NodePort endpoints(instance TargetType) is used, - this must be a numerical port. When Port endpoints(ip - TargetType) is used, this can be either numerical - or named port on pods. if port is unspecified, it - defaults to all ports. - x-kubernetes-int-or-string: true - protocol: - description: The protocol which traffic must match. - If protocol is unspecified, it defaults to TCP. - enum: - - TCP - - UDP - type: string - type: object - type: array - required: - - from - - ports - type: object - type: array - type: object - serviceRef: - description: serviceRef is a reference to a Kubernetes Service and - ServicePort. - properties: - name: - description: Name is the name of the Service. - type: string - port: - anyOf: - - type: integer - - type: string - description: Port is the port of the ServicePort. - x-kubernetes-int-or-string: true - required: - - name - - port - type: object - targetGroupARN: - description: targetGroupARN is the Amazon Resource Name (ARN) for - the TargetGroup. - type: string - targetType: - description: targetType is the TargetType of TargetGroup. If unspecified, - it will be automatically inferred. - enum: - - instance - - ip - type: string - required: - - serviceRef - - targetGroupARN - type: object - status: - description: TargetGroupBindingStatus defines the observed state of TargetGroupBinding - properties: - observedGeneration: - description: The generation observed by the TargetGroupBinding controller. - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: The Kubernetes Service's name - jsonPath: .spec.serviceRef.name - name: SERVICE-NAME - type: string - - description: The Kubernetes Service's port - jsonPath: .spec.serviceRef.port - name: SERVICE-PORT - type: string - - description: The AWS TargetGroup's TargetType - jsonPath: .spec.targetType - name: TARGET-TYPE - type: string - - description: The AWS TargetGroup's Amazon Resource Name - jsonPath: .spec.targetGroupARN - name: ARN - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - name: AGE - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: TargetGroupBinding is the Schema for the TargetGroupBinding API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding - properties: - ipAddressType: - description: ipAddressType specifies whether the target group is of - type IPv4 or IPv6. If unspecified, it will be automatically inferred. - enum: - - ipv4 - - ipv6 - type: string - networking: - description: networking defines the networking rules to allow ELBV2 - LoadBalancer to access targets in TargetGroup. - properties: - ingress: - description: List of ingress rules to allow ELBV2 LoadBalancer - to access targets in TargetGroup. - items: - description: NetworkingIngressRule defines a particular set - of traffic that is allowed to access TargetGroup's targets. - properties: - from: - description: List of peers which should be able to access - the targets in TargetGroup. At least one NetworkingPeer - should be specified. - items: - description: NetworkingPeer defines the source/destination - peer for networking rules. - properties: - ipBlock: - description: IPBlock defines an IPBlock peer. If specified, - none of the other fields can be set. - properties: - cidr: - description: CIDR is the network CIDR. Both IPV4 - or IPV6 CIDR are accepted. - type: string - required: - - cidr - type: object - securityGroup: - description: SecurityGroup defines a SecurityGroup - peer. If specified, none of the other fields can - be set. - properties: - groupID: - description: GroupID is the EC2 SecurityGroupID. - type: string - required: - - groupID - type: object - type: object - type: array - ports: - description: List of ports which should be made accessible - on the targets in TargetGroup. If ports is empty or unspecified, - it defaults to all ports with TCP. - items: - description: NetworkingPort defines the port and protocol - for networking rules. - properties: - port: - anyOf: - - type: integer - - type: string - description: The port which traffic must match. When - NodePort endpoints(instance TargetType) is used, - this must be a numerical port. When Port endpoints(ip - TargetType) is used, this can be either numerical - or named port on pods. if port is unspecified, it - defaults to all ports. - x-kubernetes-int-or-string: true - protocol: - description: The protocol which traffic must match. - If protocol is unspecified, it defaults to TCP. - enum: - - TCP - - UDP - type: string - type: object - type: array - required: - - from - - ports - type: object - type: array - type: object - nodeSelector: - description: node selector for instance type target groups to only - register certain nodes - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - serviceRef: - description: serviceRef is a reference to a Kubernetes Service and - ServicePort. - properties: - name: - description: Name is the name of the Service. - type: string - port: - anyOf: - - type: integer - - type: string - description: Port is the port of the ServicePort. - x-kubernetes-int-or-string: true - required: - - name - - port - type: object - targetGroupARN: - description: targetGroupARN is the Amazon Resource Name (ARN) for - the TargetGroup. - minLength: 1 - type: string - targetType: - description: targetType is the TargetType of TargetGroup. If unspecified, - it will be automatically inferred. - enum: - - instance - - ip - type: string - required: - - serviceRef - - targetGroupARN - type: object - status: - description: TargetGroupBindingStatus defines the observed state of TargetGroupBinding - properties: - observedGeneration: - description: The generation observed by the TargetGroupBinding controller. - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-controller-leader-election-role - namespace: kube-system -rules: - - apiGroups: - - "" - resources: - - configmaps - verbs: - - create - - apiGroups: - - "" - resourceNames: - - aws-load-balancer-controller-leader - resources: - - configmaps - verbs: - - get - - update - - patch - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - create - - apiGroups: - - coordination.k8s.io - resourceNames: - - aws-load-balancer-controller-leader - resources: - - leases - verbs: - - get - - update - - patch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - creationTimestamp: null - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-controller-role -rules: - - apiGroups: - - "" - resources: - - endpoints - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - events - verbs: - - create - - patch - - apiGroups: - - "" - resources: - - namespaces - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - nodes - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - pods - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - pods/status - verbs: - - patch - - update - - apiGroups: - - "" - resources: - - services - verbs: - - get - - list - - patch - - update - - watch - - apiGroups: - - "" - resources: - - services/status - verbs: - - patch - - update - - apiGroups: - - discovery.k8s.io - resources: - - endpointslices - verbs: - - get - - list - - watch - - apiGroups: - - elbv2.k8s.aws - resources: - - ingressclassparams - verbs: - - get - - list - - watch - - apiGroups: - - elbv2.k8s.aws - resources: - - targetgroupbindings - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - - apiGroups: - - elbv2.k8s.aws - resources: - - targetgroupbindings/status - verbs: - - patch - - update - - apiGroups: - - extensions - resources: - - ingresses - verbs: - - get - - list - - patch - - update - - watch - - apiGroups: - - extensions - resources: - - ingresses/status - verbs: - - patch - - update - - apiGroups: - - networking.k8s.io - resources: - - ingressclasses - verbs: - - get - - list - - watch - - apiGroups: - - networking.k8s.io - resources: - - ingresses - verbs: - - get - - list - - patch - - update - - watch - - apiGroups: - - networking.k8s.io - resources: - - ingresses/status - verbs: - - patch - - update ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-controller-leader-election-rolebinding - namespace: kube-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: aws-load-balancer-controller-leader-election-role -subjects: - - kind: ServiceAccount - name: aws-load-balancer-controller - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-controller-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: aws-load-balancer-controller-role -subjects: - - kind: ServiceAccount - name: aws-load-balancer-controller - namespace: kube-system ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-webhook-service - namespace: kube-system -spec: - ports: - - port: 443 - targetPort: 9443 - selector: - app.kubernetes.io/component: controller - app.kubernetes.io/name: aws-load-balancer-controller ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/component: controller - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-controller - namespace: kube-system -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/component: controller - app.kubernetes.io/name: aws-load-balancer-controller - template: - metadata: - labels: - app.kubernetes.io/component: controller - app.kubernetes.io/name: aws-load-balancer-controller - spec: - containers: - - args: - - --cluster-name=your-cluster-name - - --ingress-class=alb - image: public.ecr.aws/eks/aws-load-balancer-controller:v2.6.0 - livenessProbe: - failureThreshold: 2 - httpGet: - path: /healthz - port: 61779 - scheme: HTTP - initialDelaySeconds: 30 - timeoutSeconds: 10 - name: controller - ports: - - containerPort: 9443 - name: webhook-server - protocol: TCP - resources: - limits: - cpu: 200m - memory: 500Mi - requests: - cpu: 100m - memory: 200Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - runAsNonRoot: true - volumeMounts: - - mountPath: /tmp/k8s-webhook-server/serving-certs - name: cert - readOnly: true - priorityClassName: system-cluster-critical - securityContext: - fsGroup: 1337 - serviceAccountName: aws-load-balancer-controller - terminationGracePeriodSeconds: 10 - volumes: - - name: cert - secret: - defaultMode: 420 - secretName: aws-load-balancer-webhook-tls ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-serving-cert - namespace: kube-system -spec: - dnsNames: - - aws-load-balancer-webhook-service.kube-system.svc - - aws-load-balancer-webhook-service.kube-system.svc.cluster.local - issuerRef: - kind: Issuer - name: aws-load-balancer-selfsigned-issuer - secretName: aws-load-balancer-webhook-tls ---- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-selfsigned-issuer - namespace: kube-system -spec: - selfSigned: {} ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - annotations: - cert-manager.io/inject-ca-from: kube-system/aws-load-balancer-serving-cert - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-webhook -webhooks: - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: aws-load-balancer-webhook-service - namespace: kube-system - path: /mutate-v1-service - failurePolicy: Fail - name: mservice.elbv2.k8s.aws - objectSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: NotIn - values: - - aws-load-balancer-controller - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - CREATE - resources: - - services - sideEffects: None - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: aws-load-balancer-webhook-service - namespace: kube-system - path: /mutate-v1-pod - failurePolicy: Fail - name: mpod.elbv2.k8s.aws - namespaceSelector: - matchExpressions: - - key: elbv2.k8s.aws/pod-readiness-gate-inject - operator: In - values: - - enabled - objectSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: NotIn - values: - - aws-load-balancer-controller - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - CREATE - resources: - - pods - sideEffects: None - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: aws-load-balancer-webhook-service - namespace: kube-system - path: /mutate-elbv2-k8s-aws-v1beta1-targetgroupbinding - failurePolicy: Fail - name: mtargetgroupbinding.elbv2.k8s.aws - rules: - - apiGroups: - - elbv2.k8s.aws - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - targetgroupbindings - sideEffects: None ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - annotations: - cert-manager.io/inject-ca-from: kube-system/aws-load-balancer-serving-cert - labels: - app.kubernetes.io/name: aws-load-balancer-controller - name: aws-load-balancer-webhook -webhooks: - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: aws-load-balancer-webhook-service - namespace: kube-system - path: /validate-elbv2-k8s-aws-v1beta1-ingressclassparams - failurePolicy: Fail - name: vingressclassparams.elbv2.k8s.aws - objectSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: NotIn - values: - - aws-load-balancer-controller - rules: - - apiGroups: - - elbv2.k8s.aws - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - ingressclassparams - sideEffects: None - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: aws-load-balancer-webhook-service - namespace: kube-system - path: /validate-elbv2-k8s-aws-v1beta1-targetgroupbinding - failurePolicy: Fail - name: vtargetgroupbinding.elbv2.k8s.aws - rules: - - apiGroups: - - elbv2.k8s.aws - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - targetgroupbindings - sideEffects: None - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: aws-load-balancer-webhook-service - namespace: kube-system - path: /validate-networking-v1-ingress - failurePolicy: Fail - matchPolicy: Equivalent - name: vingress.elbv2.k8s.aws - rules: - - apiGroups: - - networking.k8s.io - apiVersions: - - v1 - operations: - - CREATE - - UPDATE - resources: - - ingresses - sideEffects: None diff --git a/test/prow/v2_8_2_adc.yaml b/test/prow/v2_8_2_adc.yaml new file mode 100644 index 000000000..1927690e0 --- /dev/null +++ b/test/prow/v2_8_2_adc.yaml @@ -0,0 +1,1122 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: ingressclassparams.elbv2.k8s.aws +spec: + group: elbv2.k8s.aws + names: + kind: IngressClassParams + listKind: IngressClassParamsList + plural: ingressclassparams + singular: ingressclassparams + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The Ingress Group name + jsonPath: .spec.group.name + name: GROUP-NAME + type: string + - description: The AWS Load Balancer scheme + jsonPath: .spec.scheme + name: SCHEME + type: string + - description: The AWS Load Balancer ipAddressType + jsonPath: .spec.ipAddressType + name: IP-ADDRESS-TYPE + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: IngressClassParams is the Schema for the IngressClassParams API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IngressClassParamsSpec defines the desired state of IngressClassParams + properties: + certificateArn: + description: CertificateArn specifies the ARN of the certificates + for all Ingresses that belong to IngressClass with this IngressClassParams. + items: + type: string + type: array + group: + description: Group defines the IngressGroup for all Ingresses that + belong to IngressClass with this IngressClassParams. + properties: + name: + description: Name is the name of IngressGroup. + type: string + required: + - name + type: object + inboundCIDRs: + description: InboundCIDRs specifies the CIDRs that are allowed to + access the Ingresses that belong to IngressClass with this IngressClassParams. + items: + type: string + type: array + ipAddressType: + description: IPAddressType defines the ip address type for all Ingresses + that belong to IngressClass with this IngressClassParams. + enum: + - ipv4 + - dualstack + - dualstack-without-public-ipv4 + type: string + loadBalancerAttributes: + description: LoadBalancerAttributes define the custom attributes to + LoadBalancers for all Ingress that that belong to IngressClass with + this IngressClassParams. + items: + description: Attributes defines custom attributes on resources. + properties: + key: + description: The key of the attribute. + type: string + value: + description: The value of the attribute. + type: string + required: + - key + - value + type: object + type: array + namespaceSelector: + description: |- + NamespaceSelector restrict the namespaces of Ingresses that are allowed to specify the IngressClass with this IngressClassParams. + * if absent or present but empty, it selects all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + scheme: + description: Scheme defines the scheme for all Ingresses that belong + to IngressClass with this IngressClassParams. + enum: + - internal + - internet-facing + type: string + sslPolicy: + description: SSLPolicy specifies the SSL Policy for all Ingresses + that belong to IngressClass with this IngressClassParams. + type: string + subnets: + description: Subnets defines the subnets for all Ingresses that belong + to IngressClass with this IngressClassParams. + properties: + ids: + description: IDs specify the resource IDs of subnets. Exactly + one of this or `tags` must be specified. + items: + description: SubnetID specifies a subnet ID. + pattern: subnet-[0-9a-f]+ + type: string + minItems: 1 + type: array + tags: + additionalProperties: + items: + type: string + type: array + description: |- + Tags specifies subnets in the load balancer's VPC where each + tag specified in the map key contains one of the values in the corresponding + value list. + Exactly one of this or `ids` must be specified. + type: object + type: object + tags: + description: Tags defines list of Tags on AWS resources provisioned + for Ingresses that belong to IngressClass with this IngressClassParams. + items: + description: Tag defines a AWS Tag on resources. + properties: + key: + description: The key of the tag. + type: string + value: + description: The value of the tag. + type: string + required: + - key + - value + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: targetgroupbindings.elbv2.k8s.aws +spec: + group: elbv2.k8s.aws + names: + kind: TargetGroupBinding + listKind: TargetGroupBindingList + plural: targetgroupbindings + singular: targetgroupbinding + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The Kubernetes Service's name + jsonPath: .spec.serviceRef.name + name: SERVICE-NAME + type: string + - description: The Kubernetes Service's port + jsonPath: .spec.serviceRef.port + name: SERVICE-PORT + type: string + - description: The AWS TargetGroup's TargetType + jsonPath: .spec.targetType + name: TARGET-TYPE + type: string + - description: The AWS TargetGroup's Amazon Resource Name + jsonPath: .spec.targetGroupARN + name: ARN + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: TargetGroupBinding is the Schema for the TargetGroupBinding API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding + properties: + networking: + description: networking provides the networking setup for ELBV2 LoadBalancer + to access targets in TargetGroup. + properties: + ingress: + description: List of ingress rules to allow ELBV2 LoadBalancer + to access targets in TargetGroup. + items: + properties: + from: + description: |- + List of peers which should be able to access the targets in TargetGroup. + At least one NetworkingPeer should be specified. + items: + description: NetworkingPeer defines the source/destination + peer for networking rules. + properties: + ipBlock: + description: |- + IPBlock defines an IPBlock peer. + If specified, none of the other fields can be set. + properties: + cidr: + description: |- + CIDR is the network CIDR. + Both IPV4 or IPV6 CIDR are accepted. + type: string + required: + - cidr + type: object + securityGroup: + description: |- + SecurityGroup defines a SecurityGroup peer. + If specified, none of the other fields can be set. + properties: + groupID: + description: GroupID is the EC2 SecurityGroupID. + type: string + required: + - groupID + type: object + type: object + type: array + ports: + description: |- + List of ports which should be made accessible on the targets in TargetGroup. + If ports is empty or unspecified, it defaults to all ports with TCP. + items: + properties: + port: + anyOf: + - type: integer + - type: string + description: |- + The port which traffic must match. + When NodePort endpoints(instance TargetType) is used, this must be a numerical port. + When Port endpoints(ip TargetType) is used, this can be either numerical or named port on pods. + if port is unspecified, it defaults to all ports. + x-kubernetes-int-or-string: true + protocol: + description: |- + The protocol which traffic must match. + If protocol is unspecified, it defaults to TCP. + enum: + - TCP + - UDP + type: string + type: object + type: array + required: + - from + - ports + type: object + type: array + type: object + serviceRef: + description: serviceRef is a reference to a Kubernetes Service and + ServicePort. + properties: + name: + description: Name is the name of the Service. + type: string + port: + anyOf: + - type: integer + - type: string + description: Port is the port of the ServicePort. + x-kubernetes-int-or-string: true + required: + - name + - port + type: object + targetGroupARN: + description: targetGroupARN is the Amazon Resource Name (ARN) for + the TargetGroup. + type: string + targetType: + description: targetType is the TargetType of TargetGroup. If unspecified, + it will be automatically inferred. + enum: + - instance + - ip + type: string + required: + - serviceRef + - targetGroupARN + type: object + status: + description: TargetGroupBindingStatus defines the observed state of TargetGroupBinding + properties: + observedGeneration: + description: The generation observed by the TargetGroupBinding controller. + format: int64 + type: integer + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The Kubernetes Service's name + jsonPath: .spec.serviceRef.name + name: SERVICE-NAME + type: string + - description: The Kubernetes Service's port + jsonPath: .spec.serviceRef.port + name: SERVICE-PORT + type: string + - description: The AWS TargetGroup's TargetType + jsonPath: .spec.targetType + name: TARGET-TYPE + type: string + - description: The AWS TargetGroup's Amazon Resource Name + jsonPath: .spec.targetGroupARN + name: ARN + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: TargetGroupBinding is the Schema for the TargetGroupBinding API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding + properties: + ipAddressType: + description: ipAddressType specifies whether the target group is of + type IPv4 or IPv6. If unspecified, it will be automatically inferred. + enum: + - ipv4 + - ipv6 + type: string + networking: + description: networking defines the networking rules to allow ELBV2 + LoadBalancer to access targets in TargetGroup. + properties: + ingress: + description: List of ingress rules to allow ELBV2 LoadBalancer + to access targets in TargetGroup. + items: + description: NetworkingIngressRule defines a particular set + of traffic that is allowed to access TargetGroup's targets. + properties: + from: + description: |- + List of peers which should be able to access the targets in TargetGroup. + At least one NetworkingPeer should be specified. + items: + description: NetworkingPeer defines the source/destination + peer for networking rules. + properties: + ipBlock: + description: |- + IPBlock defines an IPBlock peer. + If specified, none of the other fields can be set. + properties: + cidr: + description: |- + CIDR is the network CIDR. + Both IPV4 or IPV6 CIDR are accepted. + type: string + required: + - cidr + type: object + securityGroup: + description: |- + SecurityGroup defines a SecurityGroup peer. + If specified, none of the other fields can be set. + properties: + groupID: + description: GroupID is the EC2 SecurityGroupID. + type: string + required: + - groupID + type: object + type: object + type: array + ports: + description: |- + List of ports which should be made accessible on the targets in TargetGroup. + If ports is empty or unspecified, it defaults to all ports with TCP. + items: + description: NetworkingPort defines the port and protocol + for networking rules. + properties: + port: + anyOf: + - type: integer + - type: string + description: |- + The port which traffic must match. + When NodePort endpoints(instance TargetType) is used, this must be a numerical port. + When Port endpoints(ip TargetType) is used, this can be either numerical or named port on pods. + if port is unspecified, it defaults to all ports. + x-kubernetes-int-or-string: true + protocol: + description: |- + The protocol which traffic must match. + If protocol is unspecified, it defaults to TCP. + enum: + - TCP + - UDP + type: string + type: object + type: array + required: + - from + - ports + type: object + type: array + type: object + nodeSelector: + description: node selector for instance type target groups to only + register certain nodes + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + serviceRef: + description: serviceRef is a reference to a Kubernetes Service and + ServicePort. + properties: + name: + description: Name is the name of the Service. + type: string + port: + anyOf: + - type: integer + - type: string + description: Port is the port of the ServicePort. + x-kubernetes-int-or-string: true + required: + - name + - port + type: object + targetGroupARN: + description: targetGroupARN is the Amazon Resource Name (ARN) for + the TargetGroup. + minLength: 1 + type: string + targetType: + description: targetType is the TargetType of TargetGroup. If unspecified, + it will be automatically inferred. + enum: + - instance + - ip + type: string + vpcID: + description: VpcID is the VPC of the TargetGroup. If unspecified, + it will be automatically inferred. + type: string + required: + - serviceRef + - targetGroupARN + type: object + status: + description: TargetGroupBindingStatus defines the observed state of TargetGroupBinding + properties: + observedGeneration: + description: The generation observed by the TargetGroupBinding controller. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-controller + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-controller-leader-election-role + namespace: kube-system +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create +- apiGroups: + - "" + resourceNames: + - aws-load-balancer-controller-leader + resources: + - configmaps + verbs: + - get + - update + - patch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create +- apiGroups: + - coordination.k8s.io + resourceNames: + - aws-load-balancer-controller-leader + resources: + - leases + verbs: + - get + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-controller-role +rules: +- apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods/status + verbs: + - patch + - update +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - services/status + verbs: + - patch + - update +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - elbv2.k8s.aws + resources: + - ingressclassparams + verbs: + - get + - list + - watch +- apiGroups: + - elbv2.k8s.aws + resources: + - targetgroupbindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - elbv2.k8s.aws + resources: + - targetgroupbindings/status + verbs: + - patch + - update +- apiGroups: + - extensions + resources: + - ingresses + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - extensions + resources: + - ingresses/status + verbs: + - patch + - update +- apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-controller-leader-election-rolebinding + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: aws-load-balancer-controller-leader-election-role +subjects: +- kind: ServiceAccount + name: aws-load-balancer-controller + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-controller-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: aws-load-balancer-controller-role +subjects: +- kind: ServiceAccount + name: aws-load-balancer-controller + namespace: kube-system +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-webhook-service + namespace: kube-system +spec: + ports: + - port: 443 + targetPort: 9443 + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/name: aws-load-balancer-controller +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-controller + namespace: kube-system +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: aws-load-balancer-controller + template: + metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: aws-load-balancer-controller + spec: + containers: + - args: + - --cluster-name=your-cluster-name + - --ingress-class=alb + image: public.ecr.aws/eks/aws-load-balancer-controller:v2.8.2 + livenessProbe: + failureThreshold: 2 + httpGet: + path: /healthz + port: 61779 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 10 + name: controller + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + resources: + limits: + cpu: 200m + memory: 500Mi + requests: + cpu: 100m + memory: 200Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + priorityClassName: system-cluster-critical + securityContext: + fsGroup: 1337 + serviceAccountName: aws-load-balancer-controller + terminationGracePeriodSeconds: 10 + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: aws-load-balancer-webhook-tls +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-serving-cert + namespace: kube-system +spec: + dnsNames: + - aws-load-balancer-webhook-service.kube-system.svc + - aws-load-balancer-webhook-service.kube-system.svc.cluster.local + issuerRef: + kind: Issuer + name: aws-load-balancer-selfsigned-issuer + secretName: aws-load-balancer-webhook-tls +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-selfsigned-issuer + namespace: kube-system +spec: + selfSigned: {} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + annotations: + cert-manager.io/inject-ca-from: kube-system/aws-load-balancer-serving-cert + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-webhook +webhooks: +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: aws-load-balancer-webhook-service + namespace: kube-system + path: /mutate-v1-service + failurePolicy: Fail + name: mservice.elbv2.k8s.aws + objectSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: NotIn + values: + - aws-load-balancer-controller + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - services + sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: aws-load-balancer-webhook-service + namespace: kube-system + path: /mutate-v1-pod + failurePolicy: Fail + name: mpod.elbv2.k8s.aws + namespaceSelector: + matchExpressions: + - key: elbv2.k8s.aws/pod-readiness-gate-inject + operator: In + values: + - enabled + objectSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: NotIn + values: + - aws-load-balancer-controller + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - pods + sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: aws-load-balancer-webhook-service + namespace: kube-system + path: /mutate-elbv2-k8s-aws-v1beta1-targetgroupbinding + failurePolicy: Fail + name: mtargetgroupbinding.elbv2.k8s.aws + rules: + - apiGroups: + - elbv2.k8s.aws + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - targetgroupbindings + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + annotations: + cert-manager.io/inject-ca-from: kube-system/aws-load-balancer-serving-cert + labels: + app.kubernetes.io/name: aws-load-balancer-controller + name: aws-load-balancer-webhook +webhooks: +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: aws-load-balancer-webhook-service + namespace: kube-system + path: /validate-elbv2-k8s-aws-v1beta1-ingressclassparams + failurePolicy: Fail + name: vingressclassparams.elbv2.k8s.aws + objectSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: NotIn + values: + - aws-load-balancer-controller + rules: + - apiGroups: + - elbv2.k8s.aws + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - ingressclassparams + sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: aws-load-balancer-webhook-service + namespace: kube-system + path: /validate-elbv2-k8s-aws-v1beta1-targetgroupbinding + failurePolicy: Fail + name: vtargetgroupbinding.elbv2.k8s.aws + rules: + - apiGroups: + - elbv2.k8s.aws + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - targetgroupbindings + sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: aws-load-balancer-webhook-service + namespace: kube-system + path: /validate-networking-v1-ingress + failurePolicy: Fail + matchPolicy: Equivalent + name: vingress.elbv2.k8s.aws + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + sideEffects: None diff --git a/test/prow/v2_6_0_ingclass.yaml b/test/prow/v2_8_2_ingclass.yaml similarity index 100% rename from test/prow/v2_6_0_ingclass.yaml rename to test/prow/v2_8_2_ingclass.yaml From 6b500678ff39325898b6d9a5682308a8cb08a878 Mon Sep 17 00:00:00 2001 From: kahirokunn Date: Mon, 20 Jan 2025 10:51:53 +0900 Subject: [PATCH 18/30] Issue 4023. Checking for taint karpenter.sh/disrupted:NoSchedule while checking if node is suitable to handle traffic. Signed-off-by: kahirokunn --- pkg/backend/endpoint_utils.go | 12 +++++++++--- pkg/backend/endpoint_utils_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/pkg/backend/endpoint_utils.go b/pkg/backend/endpoint_utils.go index 57bea8ac0..dda86c02f 100644 --- a/pkg/backend/endpoint_utils.go +++ b/pkg/backend/endpoint_utils.go @@ -13,7 +13,8 @@ const ( labelAlphaNodeRoleExcludeBalancer = "alpha.service-controller.kubernetes.io/exclude-balancer" labelEKSComputeType = "eks.amazonaws.com/compute-type" - toBeDeletedByCATaint = "ToBeDeletedByClusterAutoscaler" + toBeDeletedByCATaint = "ToBeDeletedByClusterAutoscaler" + toBeDeletedByKarpenterTaint = "karpenter.sh/disrupted" ) var ( @@ -61,12 +62,17 @@ func GetTrafficProxyNodeSelector(tgb *elbv2api.TargetGroupBinding) (labels.Selec // IsNodeSuitableAsTrafficProxy check whether node is suitable as a traffic proxy. // This should be checked in additional to the nodeSelector defined in TargetGroupBinding. func IsNodeSuitableAsTrafficProxy(node *corev1.Node) bool { - // ToBeDeletedByClusterAutoscaler taint is added by cluster autoscaler before removing node from cluster - // Marking the node as unsuitable for traffic once the taint is observed on the node for _, taint := range node.Spec.Taints { + // ToBeDeletedByClusterAutoscaler taint is added by cluster autoscaler before removing node from cluster + // Marking the node as unsuitable for traffic once the taint is observed on the node if taint.Key == toBeDeletedByCATaint { return false } + // karpenter.sh/disrupted:NoSchedule taint is added by karpenter before removing node from cluster + // Marking the node as unsuitable for traffic once the taint is observed on the node + if taint.Key == toBeDeletedByKarpenterTaint && taint.Effect == corev1.TaintEffectNoSchedule { + return false + } } return true diff --git a/pkg/backend/endpoint_utils_test.go b/pkg/backend/endpoint_utils_test.go index 21cd59e72..fb376ff93 100644 --- a/pkg/backend/endpoint_utils_test.go +++ b/pkg/backend/endpoint_utils_test.go @@ -131,6 +131,31 @@ func TestIsNodeSuitableAsTrafficProxy(t *testing.T) { }, want: false, }, + { + name: "node is ready but tainted with karpenter.sh/disrupted", + args: args{ + node: &corev1.Node{ + Status: corev1.NodeStatus{ + Conditions: []corev1.NodeCondition{ + { + Type: corev1.NodeReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Spec: corev1.NodeSpec{ + Unschedulable: false, + Taints: []corev1.Taint{ + { + Key: toBeDeletedByKarpenterTaint, + Effect: corev1.TaintEffectNoSchedule, + }, + }, + }, + }, + }, + want: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From dfae145dac87eb18641cbc45f33f069082973a6f Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Fri, 17 Jan 2025 16:59:55 -0800 Subject: [PATCH 19/30] fix assume role tgb --- .../targetgroupbinding/targetgroupbinding.md | 100 +++++++++++++++++- .../crds/crds.yaml | 18 ++++ pkg/aws/aws_config.go | 81 ++++++++++++++ pkg/aws/cloud.go | 58 ++++------ .../provider/default_aws_clients_provider.go | 41 +++++-- pkg/aws/provider/provider.go | 5 +- pkg/aws/services/elbv2.go | 97 ++++++++++------- pkg/targetgroupbinding/resource_manager.go | 2 +- .../elbv2/targetgroupbinding_validator.go | 19 ++++ .../targetgroupbinding_validator_test.go | 61 +++++++++++ 10 files changed, 388 insertions(+), 94 deletions(-) create mode 100644 pkg/aws/aws_config.go diff --git a/docs/guide/targetgroupbinding/targetgroupbinding.md b/docs/guide/targetgroupbinding/targetgroupbinding.md index b534136a8..e7d3ccf4a 100644 --- a/docs/guide/targetgroupbinding/targetgroupbinding.md +++ b/docs/guide/targetgroupbinding/targetgroupbinding.md @@ -112,12 +112,110 @@ spec: ### AssumeRole Sometimes the AWS LoadBalancer controller needs to manipulate target groups from different AWS accounts. -The way to do that is assuming a role from such account. The following spec fields help you with that. +The way to do that is assuming a role from such an account. The following spec fields help you with that. * `iamRoleArnToAssume`: the ARN that you need to assume * `assumeRoleExternalId`: the external ID for the assume role operation. Optional, but recommended. It helps you to prevent the confused deputy problem ( https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html ) +```yaml +apiVersion: elbv2.k8s.aws/v1beta1 +kind: TargetGroupBinding +metadata: + name: peered-tg + namespace: nlb-game-2048-1 +spec: + assumeRoleExternalId: very-secret-string-2 + iamRoleArnToAssume: arn:aws:iam::155642222660:role/tg-management-role + networking: + ingress: + - from: + - securityGroup: + groupID: sg-0b6a41a2fd959623f + ports: + - port: 80 + protocol: TCP + serviceRef: + name: service-2048 + port: 80 + targetGroupARN: arn:aws:elasticloadbalancing:us-west-2:155642222660:targetgroup/peered-tg/6a4ecf7bfae473c1 +``` + +In the following examples, we will refer to Cluster Owner (CO) and Target Group Owner (TGO) accounts. + +First, in the TGO account creates a role that will allow the AWS LBC in the CO account to assume it. +For improved security, we only allow the AWS LBC role in CO account to assume the role. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::565768096483:role/eksctl-awslbc-loadtest-addon-iamserviceaccoun-Role1-13RdJCMqV6p2" + }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "very-secret-string" + } + } + } + ] +} +``` + +Next, still in the TGO account we need to add the following permissions to the Role created in the first step. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets" + ], + "Resource": [ + "arn:aws:elasticloadbalancing:us-west-2:155642222660:targetgroup/tg1/*", + "arn:aws:elasticloadbalancing:us-west-2:155642222660:targetgroup/tg2/*" + // add more here // + ] + }, + { + "Sid": "VisualEditor1", + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetHealth" + ], + "Resource": "*" + } + ] +} +``` + + +Next, in the CO account, we need to allow the AWS LBC to perform the AssumeRole call. +By default, this permission is not a part of the standard IAM policy that is vended with the LBC installation scripts. +For improved security, it is possible to scope the AssumeRole permissions down to only roles that you know ahead of time the +LBC will need to Assume. + +```json + { + "Effect": "Allow", + "Action": [ + "sts:AssumeRole" + ], + "Resource": "*" + } +``` + + ## Sample YAML ```yaml diff --git a/helm/aws-load-balancer-controller/crds/crds.yaml b/helm/aws-load-balancer-controller/crds/crds.yaml index b72e68789..ea31acc11 100644 --- a/helm/aws-load-balancer-controller/crds/crds.yaml +++ b/helm/aws-load-balancer-controller/crds/crds.yaml @@ -317,6 +317,15 @@ spec: spec: description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding properties: + assumeRoleExternalId: + description: IAM Role ARN to assume when calling AWS APIs. Needed + to assume a role in another account and prevent the confused deputy + problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html + type: string + iamRoleArnToAssume: + description: IAM Role ARN to assume when calling AWS APIs. Useful + if the target group is in a different AWS account + type: string multiClusterTargetGroup: description: MultiClusterTargetGroup Denotes if the TargetGroup is shared among multiple clusters @@ -494,6 +503,15 @@ spec: spec: description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding properties: + assumeRoleExternalId: + description: IAM Role ARN to assume when calling AWS APIs. Needed + to assume a role in another account and prevent the confused deputy + problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html + type: string + iamRoleArnToAssume: + description: IAM Role ARN to assume when calling AWS APIs. Useful + if the target group is in a different AWS account + type: string ipAddressType: description: ipAddressType specifies whether the target group is of type IPv4 or IPv6. If unspecified, it will be automatically inferred. diff --git a/pkg/aws/aws_config.go b/pkg/aws/aws_config.go new file mode 100644 index 000000000..4172f34da --- /dev/null +++ b/pkg/aws/aws_config.go @@ -0,0 +1,81 @@ +package aws + +import ( + "context" + "github.com/aws/aws-sdk-go-v2/aws" + awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" + "github.com/aws/aws-sdk-go-v2/aws/ratelimit" + "github.com/aws/aws-sdk-go-v2/aws/retry" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + smithymiddleware "github.com/aws/smithy-go/middleware" + "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle" + awsmetrics "sigs.k8s.io/aws-load-balancer-controller/pkg/metrics/aws" + "sigs.k8s.io/aws-load-balancer-controller/pkg/version" +) + +const ( + userAgent = "elbv2.k8s.aws" +) + +func NewAWSConfigGenerator(cfg CloudConfig, ec2IMDSEndpointMode imds.EndpointModeState, metricsCollector *awsmetrics.Collector) AWSConfigGenerator { + return &awsConfigGeneratorImpl{ + cfg: cfg, + ec2IMDSEndpointMode: ec2IMDSEndpointMode, + metricsCollector: metricsCollector, + } + +} + +// AWSConfigGenerator is responsible for generating an aws config based on the running environment +type AWSConfigGenerator interface { + GenerateAWSConfig(optFns ...func(*config.LoadOptions) error) (aws.Config, error) +} + +type awsConfigGeneratorImpl struct { + cfg CloudConfig + ec2IMDSEndpointMode imds.EndpointModeState + metricsCollector *awsmetrics.Collector +} + +func (gen *awsConfigGeneratorImpl) GenerateAWSConfig(optFns ...func(*config.LoadOptions) error) (aws.Config, error) { + + defaultOpts := []func(*config.LoadOptions) error{ + config.WithRegion(gen.cfg.Region), + config.WithRetryer(func() aws.Retryer { + return retry.NewStandard(func(o *retry.StandardOptions) { + o.RateLimiter = ratelimit.None + o.MaxAttempts = gen.cfg.MaxRetries + }) + }), + config.WithEC2IMDSEndpointMode(gen.ec2IMDSEndpointMode), + config.WithAPIOptions([]func(stack *smithymiddleware.Stack) error{ + awsmiddleware.AddUserAgentKeyValue(userAgent, version.GitVersion), + }), + } + + defaultOpts = append(defaultOpts, optFns...) + + awsConfig, err := config.LoadDefaultConfig(context.TODO(), + defaultOpts..., + ) + + if err != nil { + return aws.Config{}, err + } + + if gen.cfg.ThrottleConfig != nil { + throttler := throttle.NewThrottler(gen.cfg.ThrottleConfig) + awsConfig.APIOptions = append(awsConfig.APIOptions, func(stack *smithymiddleware.Stack) error { + return throttle.WithSDKRequestThrottleMiddleware(throttler)(stack) + }) + } + + if gen.metricsCollector != nil { + awsConfig.APIOptions = awsmetrics.WithSDKMetricCollector(gen.metricsCollector, awsConfig.APIOptions) + } + + return awsConfig, nil +} + +var _ AWSConfigGenerator = &awsConfigGeneratorImpl{} diff --git a/pkg/aws/cloud.go b/pkg/aws/cloud.go index af355497d..bd733a657 100644 --- a/pkg/aws/cloud.go +++ b/pkg/aws/cloud.go @@ -10,18 +10,11 @@ import ( "sync" "time" - awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" - "github.com/aws/aws-sdk-go-v2/aws/ratelimit" - "github.com/aws/aws-sdk-go-v2/aws/retry" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/aws-sdk-go-v2/service/sts" - smithymiddleware "github.com/aws/smithy-go/middleware" - "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle" - "sigs.k8s.io/aws-load-balancer-controller/pkg/version" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/service/ec2" @@ -35,7 +28,6 @@ import ( ) const ( - userAgent = "elbv2.k8s.aws" cacheTTLBufferTime = 30 * time.Second ) @@ -81,29 +73,11 @@ func NewCloud(cfg CloudConfig, clusterName string, metricsCollector *aws_metrics } cfg.Region = region } - awsConfig, err := config.LoadDefaultConfig(context.TODO(), - config.WithRegion(cfg.Region), - config.WithRetryer(func() aws.Retryer { - return retry.NewStandard(func(o *retry.StandardOptions) { - o.RateLimiter = ratelimit.None - o.MaxAttempts = cfg.MaxRetries - }) - }), - config.WithEC2IMDSEndpointMode(ec2IMDSEndpointMode), - config.WithAPIOptions([]func(stack *smithymiddleware.Stack) error{ - awsmiddleware.AddUserAgentKeyValue(userAgent, version.GitVersion), - }), - ) - - if cfg.ThrottleConfig != nil { - throttler := throttle.NewThrottler(cfg.ThrottleConfig) - awsConfig.APIOptions = append(awsConfig.APIOptions, func(stack *smithymiddleware.Stack) error { - return throttle.WithSDKRequestThrottleMiddleware(throttler)(stack) - }) - } - if metricsCollector != nil { - awsConfig.APIOptions = aws_metrics.WithSDKMetricCollector(metricsCollector, awsConfig.APIOptions) + awsConfigGenerator := NewAWSConfigGenerator(cfg, ec2IMDSEndpointMode, metricsCollector) + awsConfig, err := awsConfigGenerator.GenerateAWSConfig() + if err != nil { + return nil, errors.Wrap(err, "Unable to generate AWS config") } if awsClientsProvider == nil { @@ -132,6 +106,8 @@ func NewCloud(cfg CloudConfig, clusterName string, metricsCollector *aws_metrics shield: services.NewShield(awsClientsProvider), rgt: services.NewRGT(awsClientsProvider), + awsConfigGenerator: awsConfigGenerator, + assumeRoleElbV2Cache: cache.NewExpiring(), awsClientsProvider: awsClientsProvider, @@ -229,6 +205,8 @@ type defaultCloud struct { clusterName string + awsConfigGenerator AWSConfigGenerator + // A cache holding elbv2 clients that are assuming a role. assumeRoleElbV2Cache *cache.Expiring // assumeRoleElbV2CacheMutex protects assumeRoleElbV2Cache @@ -251,31 +229,33 @@ func (c *defaultCloud) GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn st if exists { return assumedRoleELBV2.(services.ELBV2), nil } - c.logger.Info("awsCloud", "method", "GetAssumedRoleELBV2", "AssumeRoleArn", assumeRoleArn, "externalId", externalId) + c.logger.Info("Constructing new elbv2 client", "AssumeRoleArn", assumeRoleArn, "externalId", externalId) - existingAwsConfig, _ := c.awsClientsProvider.GetAWSConfig(ctx, "GetAWSConfigForIAMRoleImpersonation") + stsClient, err := c.awsClientsProvider.GetSTSClient(ctx, "AssumeRole") + if err != nil { + // This should never happen, but let's be forward-looking. + return nil, err + } - sourceAccount := sts.NewFromConfig(*existingAwsConfig) - response, err := sourceAccount.AssumeRole(ctx, &sts.AssumeRoleInput{ + response, err := stsClient.AssumeRole(ctx, &sts.AssumeRoleInput{ RoleArn: aws.String(assumeRoleArn), RoleSessionName: aws.String(generateAssumeRoleSessionName(c.clusterName)), ExternalId: aws.String(externalId), }) if err != nil { - c.logger.Error(err, "Unable to assume target role, %v") + c.logger.Error(err, "Unable to assume target role", "roleArn", assumeRoleArn) return nil, err } assumedRoleCreds := response.Credentials newCreds := credentials.NewStaticCredentialsProvider(*assumedRoleCreds.AccessKeyId, *assumedRoleCreds.SecretAccessKey, *assumedRoleCreds.SessionToken) - newAwsConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(c.cfg.Region), config.WithCredentialsProvider(newCreds)) + newAwsConfig, err := c.awsConfigGenerator.GenerateAWSConfig(config.WithCredentialsProvider(newCreds)) if err != nil { - c.logger.Error(err, "Unable to load static credentials for service client config, %v. Attempting to use default client") + c.logger.Error(err, "Create new service client config service client config", "roleArn", assumeRoleArn) return nil, err } cacheTTL := assumedRoleCreds.Expiration.Sub(time.Now()) - existingAwsConfig.Credentials = newAwsConfig.Credentials - elbv2WithAssumedRole := services.NewELBV2(c.awsClientsProvider, c) + elbv2WithAssumedRole := services.NewELBV2FromStaticClient(c.awsClientsProvider.GenerateNewELBv2Client(newAwsConfig), c) c.assumeRoleElbV2CacheMutex.Lock() defer c.assumeRoleElbV2CacheMutex.Unlock() diff --git a/pkg/aws/provider/default_aws_clients_provider.go b/pkg/aws/provider/default_aws_clients_provider.go index 41cd78055..1d1a2b713 100644 --- a/pkg/aws/provider/default_aws_clients_provider.go +++ b/pkg/aws/provider/default_aws_clients_provider.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" "github.com/aws/aws-sdk-go-v2/service/shield" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/service/wafregional" "github.com/aws/aws-sdk-go-v2/service/wafv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/endpoints" @@ -21,11 +22,13 @@ type defaultAWSClientsProvider struct { wafRegionClient *wafregional.Client shieldClient *shield.Client rgtClient *resourcegroupstaggingapi.Client + stsClient *sts.Client - awsConfig *aws.Config + // used for dynamic creation of ELBv2 client + elbv2CustomEndpoint *string } -func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.Resolver) (*defaultAWSClientsProvider, error) { +func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.Resolver) (AWSClientsProvider, error) { ec2CustomEndpoint := endpointsResolver.EndpointFor(ec2.ServiceID) elbv2CustomEndpoint := endpointsResolver.EndpointFor(elasticloadbalancingv2.ServiceID) acmCustomEndpoint := endpointsResolver.EndpointFor(acm.ServiceID) @@ -33,17 +36,16 @@ func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.R wafregionalCustomEndpoint := endpointsResolver.EndpointFor(wafregional.ServiceID) shieldCustomEndpoint := endpointsResolver.EndpointFor(shield.ServiceID) rgtCustomEndpoint := endpointsResolver.EndpointFor(resourcegroupstaggingapi.ServiceID) + stsCustomEndpoint := endpointsResolver.EndpointFor(sts.ServiceID) ec2Client := ec2.NewFromConfig(cfg, func(o *ec2.Options) { if ec2CustomEndpoint != nil { o.BaseEndpoint = ec2CustomEndpoint } }) - elbv2Client := elasticloadbalancingv2.NewFromConfig(cfg, func(o *elasticloadbalancingv2.Options) { - if elbv2CustomEndpoint != nil { - o.BaseEndpoint = elbv2CustomEndpoint - } - }) + + elbv2Client := generateNewELBv2ClientHelper(cfg, elbv2CustomEndpoint) + acmClient := acm.NewFromConfig(cfg, func(o *acm.Options) { if acmCustomEndpoint != nil { o.BaseEndpoint = acmCustomEndpoint @@ -68,6 +70,12 @@ func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.R } }) + stsClient := sts.NewFromConfig(cfg, func(o *sts.Options) { + if stsCustomEndpoint != nil { + o.BaseEndpoint = stsCustomEndpoint + } + }) + return &defaultAWSClientsProvider{ ec2Client: ec2Client, elbv2Client: elbv2Client, @@ -76,8 +84,9 @@ func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.R wafRegionClient: wafregionalClient, shieldClient: shieldClient, rgtClient: rgtClient, + stsClient: stsClient, - awsConfig: &cfg, + elbv2CustomEndpoint: elbv2CustomEndpoint, }, nil } @@ -112,6 +121,18 @@ func (p *defaultAWSClientsProvider) GetRGTClient(ctx context.Context, operationN return p.rgtClient, nil } -func (p *defaultAWSClientsProvider) GetAWSConfig(ctx context.Context, operationName string) (*aws.Config, error) { - return p.awsConfig, nil +func (p *defaultAWSClientsProvider) GetSTSClient(ctx context.Context, operationName string) (*sts.Client, error) { + return p.stsClient, nil +} + +func (p *defaultAWSClientsProvider) GenerateNewELBv2Client(cfg aws.Config) *elasticloadbalancingv2.Client { + return generateNewELBv2ClientHelper(cfg, p.elbv2CustomEndpoint) +} + +func generateNewELBv2ClientHelper(cfg aws.Config, elbv2CustomEndpoint *string) *elasticloadbalancingv2.Client { + return elasticloadbalancingv2.NewFromConfig(cfg, func(o *elasticloadbalancingv2.Options) { + if elbv2CustomEndpoint != nil { + o.BaseEndpoint = elbv2CustomEndpoint + } + }) } diff --git a/pkg/aws/provider/provider.go b/pkg/aws/provider/provider.go index 2cdff4574..66bb16828 100644 --- a/pkg/aws/provider/provider.go +++ b/pkg/aws/provider/provider.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" "github.com/aws/aws-sdk-go-v2/service/shield" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/service/wafregional" "github.com/aws/aws-sdk-go-v2/service/wafv2" ) @@ -20,6 +21,6 @@ type AWSClientsProvider interface { GetWAFRegionClient(ctx context.Context, operationName string) (*wafregional.Client, error) GetShieldClient(ctx context.Context, operationName string) (*shield.Client, error) GetRGTClient(ctx context.Context, operationName string) (*resourcegroupstaggingapi.Client, error) - - GetAWSConfig(ctx context.Context, operationName string) (*aws.Config, error) + GetSTSClient(ctx context.Context, operationName string) (*sts.Client, error) + GenerateNewELBv2Client(cfg aws.Config) *elasticloadbalancingv2.Client } diff --git a/pkg/aws/services/elbv2.go b/pkg/aws/services/elbv2.go index ca34f491f..293f61f3b 100644 --- a/pkg/aws/services/elbv2.go +++ b/pkg/aws/services/elbv2.go @@ -70,9 +70,17 @@ func NewELBV2(awsClientsProvider provider.AWSClientsProvider, cloud Cloud) ELBV2 } } +func NewELBV2FromStaticClient(staticELBClient *elasticloadbalancingv2.Client, cloud Cloud) ELBV2 { + return &elbv2Client{ + staticELBClient: staticELBClient, + cloud: cloud, + } +} + // default implementation for ELBV2. type elbv2Client struct { awsClientsProvider provider.AWSClientsProvider + staticELBClient *elasticloadbalancingv2.Client cloud Cloud } @@ -84,7 +92,7 @@ func (c *elbv2Client) AssumeRole(ctx context.Context, assumeRoleArn string, exte } func (c *elbv2Client) AddListenerCertificatesWithContext(ctx context.Context, input *elasticloadbalancingv2.AddListenerCertificatesInput) (*elasticloadbalancingv2.AddListenerCertificatesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "AddListenerCertificates") + client, err := c.getClient(ctx, "AddListenerCertificates") if err != nil { return nil, err } @@ -92,7 +100,7 @@ func (c *elbv2Client) AddListenerCertificatesWithContext(ctx context.Context, in } func (c *elbv2Client) RemoveListenerCertificatesWithContext(ctx context.Context, input *elasticloadbalancingv2.RemoveListenerCertificatesInput) (*elasticloadbalancingv2.RemoveListenerCertificatesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "RemoveListenerCertificates") + client, err := c.getClient(ctx, "RemoveListenerCertificates") if err != nil { return nil, err } @@ -100,7 +108,7 @@ func (c *elbv2Client) RemoveListenerCertificatesWithContext(ctx context.Context, } func (c *elbv2Client) DescribeListenersWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeListenersInput) (*elasticloadbalancingv2.DescribeListenersOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeListeners") + client, err := c.getClient(ctx, "DescribeListeners") if err != nil { return nil, err } @@ -108,7 +116,7 @@ func (c *elbv2Client) DescribeListenersWithContext(ctx context.Context, input *e } func (c *elbv2Client) DescribeRulesWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeRulesInput) (*elasticloadbalancingv2.DescribeRulesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeRules") + client, err := c.getClient(ctx, "DescribeRules") if err != nil { return nil, err } @@ -116,7 +124,7 @@ func (c *elbv2Client) DescribeRulesWithContext(ctx context.Context, input *elast } func (c *elbv2Client) RegisterTargetsWithContext(ctx context.Context, input *elasticloadbalancingv2.RegisterTargetsInput) (*elasticloadbalancingv2.RegisterTargetsOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "RegisterTargets") + client, err := c.getClient(ctx, "RegisterTargets") if err != nil { return nil, err } @@ -124,7 +132,7 @@ func (c *elbv2Client) RegisterTargetsWithContext(ctx context.Context, input *ela } func (c *elbv2Client) DeregisterTargetsWithContext(ctx context.Context, input *elasticloadbalancingv2.DeregisterTargetsInput) (*elasticloadbalancingv2.DeregisterTargetsOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DeregisterTargets") + client, err := c.getClient(ctx, "DeregisterTargets") if err != nil { return nil, err } @@ -132,7 +140,7 @@ func (c *elbv2Client) DeregisterTargetsWithContext(ctx context.Context, input *e } func (c *elbv2Client) DescribeTrustStoresWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeTrustStoresInput) (*elasticloadbalancingv2.DescribeTrustStoresOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeTrustStores") + client, err := c.getClient(ctx, "DescribeTrustStores") if err != nil { return nil, err } @@ -140,7 +148,7 @@ func (c *elbv2Client) DescribeTrustStoresWithContext(ctx context.Context, input } func (c *elbv2Client) ModifyRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyRuleInput) (*elasticloadbalancingv2.ModifyRuleOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "ModifyRule") + client, err := c.getClient(ctx, "ModifyRule") if err != nil { return nil, err } @@ -148,7 +156,7 @@ func (c *elbv2Client) ModifyRuleWithContext(ctx context.Context, input *elasticl } func (c *elbv2Client) DeleteRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.DeleteRuleInput) (*elasticloadbalancingv2.DeleteRuleOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DeleteRule") + client, err := c.getClient(ctx, "DeleteRule") if err != nil { return nil, err } @@ -156,7 +164,7 @@ func (c *elbv2Client) DeleteRuleWithContext(ctx context.Context, input *elasticl } func (c *elbv2Client) CreateRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.CreateRuleInput) (*elasticloadbalancingv2.CreateRuleOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "CreateRule") + client, err := c.getClient(ctx, "CreateRule") if err != nil { return nil, err } @@ -164,7 +172,7 @@ func (c *elbv2Client) CreateRuleWithContext(ctx context.Context, input *elasticl } func (c *elbv2Client) WaitUntilLoadBalancerAvailableWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeLoadBalancersInput) error { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeLoadBalancers") + client, err := c.getClient(ctx, "DescribeLoadBalancers") if err != nil { return err } @@ -174,7 +182,7 @@ func (c *elbv2Client) WaitUntilLoadBalancerAvailableWithContext(ctx context.Cont } func (c *elbv2Client) DescribeLoadBalancersWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeLoadBalancersInput) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeLoadBalancers") + client, err := c.getClient(ctx, "DescribeLoadBalancers") if err != nil { return nil, err } @@ -182,7 +190,7 @@ func (c *elbv2Client) DescribeLoadBalancersWithContext(ctx context.Context, inpu } func (c *elbv2Client) DescribeTargetHealthWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeTargetHealthInput) (*elasticloadbalancingv2.DescribeTargetHealthOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeTargetHealth") + client, err := c.getClient(ctx, "DescribeTargetHealth") if err != nil { return nil, err } @@ -190,7 +198,7 @@ func (c *elbv2Client) DescribeTargetHealthWithContext(ctx context.Context, input } func (c *elbv2Client) DescribeTargetGroupsWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeTargetGroupsInput) (*elasticloadbalancingv2.DescribeTargetGroupsOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeTargetGroups") + client, err := c.getClient(ctx, "DescribeTargetGroups") if err != nil { return nil, err } @@ -198,7 +206,7 @@ func (c *elbv2Client) DescribeTargetGroupsWithContext(ctx context.Context, input } func (c *elbv2Client) DeleteTargetGroupWithContext(ctx context.Context, input *elasticloadbalancingv2.DeleteTargetGroupInput) (*elasticloadbalancingv2.DeleteTargetGroupOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DeleteTargetGroup") + client, err := c.getClient(ctx, "DeleteTargetGroup") if err != nil { return nil, err } @@ -206,7 +214,7 @@ func (c *elbv2Client) DeleteTargetGroupWithContext(ctx context.Context, input *e } func (c *elbv2Client) ModifyTargetGroupWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyTargetGroupInput) (*elasticloadbalancingv2.ModifyTargetGroupOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "ModifyTargetGroup") + client, err := c.getClient(ctx, "ModifyTargetGroup") if err != nil { return nil, err } @@ -214,7 +222,7 @@ func (c *elbv2Client) ModifyTargetGroupWithContext(ctx context.Context, input *e } func (c *elbv2Client) CreateTargetGroupWithContext(ctx context.Context, input *elasticloadbalancingv2.CreateTargetGroupInput) (*elasticloadbalancingv2.CreateTargetGroupOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "CreateTargetGroup") + client, err := c.getClient(ctx, "CreateTargetGroup") if err != nil { return nil, err } @@ -222,7 +230,7 @@ func (c *elbv2Client) CreateTargetGroupWithContext(ctx context.Context, input *e } func (c *elbv2Client) DescribeTargetGroupAttributesWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeTargetGroupAttributesInput) (*elasticloadbalancingv2.DescribeTargetGroupAttributesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeTargetGroupAttributes") + client, err := c.getClient(ctx, "DescribeTargetGroupAttributes") if err != nil { return nil, err } @@ -230,7 +238,7 @@ func (c *elbv2Client) DescribeTargetGroupAttributesWithContext(ctx context.Conte } func (c *elbv2Client) ModifyTargetGroupAttributesWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyTargetGroupAttributesInput) (*elasticloadbalancingv2.ModifyTargetGroupAttributesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "ModifyTargetGroupAttributes") + client, err := c.getClient(ctx, "ModifyTargetGroupAttributes") if err != nil { return nil, err } @@ -238,7 +246,7 @@ func (c *elbv2Client) ModifyTargetGroupAttributesWithContext(ctx context.Context } func (c *elbv2Client) SetSecurityGroupsWithContext(ctx context.Context, input *elasticloadbalancingv2.SetSecurityGroupsInput) (*elasticloadbalancingv2.SetSecurityGroupsOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "SetSecurityGroups") + client, err := c.getClient(ctx, "SetSecurityGroups") if err != nil { return nil, err } @@ -246,7 +254,7 @@ func (c *elbv2Client) SetSecurityGroupsWithContext(ctx context.Context, input *e } func (c *elbv2Client) SetSubnetsWithContext(ctx context.Context, input *elasticloadbalancingv2.SetSubnetsInput) (*elasticloadbalancingv2.SetSubnetsOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "SetSubnets") + client, err := c.getClient(ctx, "SetSubnets") if err != nil { return nil, err } @@ -254,7 +262,7 @@ func (c *elbv2Client) SetSubnetsWithContext(ctx context.Context, input *elasticl } func (c *elbv2Client) SetIpAddressTypeWithContext(ctx context.Context, input *elasticloadbalancingv2.SetIpAddressTypeInput) (*elasticloadbalancingv2.SetIpAddressTypeOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "SetIpAddressType") + client, err := c.getClient(ctx, "SetIpAddressType") if err != nil { return nil, err } @@ -262,7 +270,7 @@ func (c *elbv2Client) SetIpAddressTypeWithContext(ctx context.Context, input *el } func (c *elbv2Client) DeleteLoadBalancerWithContext(ctx context.Context, input *elasticloadbalancingv2.DeleteLoadBalancerInput) (*elasticloadbalancingv2.DeleteLoadBalancerOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DeleteLoadBalancer") + client, err := c.getClient(ctx, "DeleteLoadBalancer") if err != nil { return nil, err } @@ -270,7 +278,7 @@ func (c *elbv2Client) DeleteLoadBalancerWithContext(ctx context.Context, input * } func (c *elbv2Client) CreateLoadBalancerWithContext(ctx context.Context, input *elasticloadbalancingv2.CreateLoadBalancerInput) (*elasticloadbalancingv2.CreateLoadBalancerOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "CreateLoadBalancer") + client, err := c.getClient(ctx, "CreateLoadBalancer") if err != nil { return nil, err } @@ -278,7 +286,7 @@ func (c *elbv2Client) CreateLoadBalancerWithContext(ctx context.Context, input * } func (c *elbv2Client) DescribeLoadBalancerAttributesWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeLoadBalancerAttributesInput) (*elasticloadbalancingv2.DescribeLoadBalancerAttributesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeLoadBalancerAttributes") + client, err := c.getClient(ctx, "DescribeLoadBalancerAttributes") if err != nil { return nil, err } @@ -286,7 +294,7 @@ func (c *elbv2Client) DescribeLoadBalancerAttributesWithContext(ctx context.Cont } func (c *elbv2Client) ModifyLoadBalancerAttributesWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyLoadBalancerAttributesInput) (*elasticloadbalancingv2.ModifyLoadBalancerAttributesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "ModifyLoadBalancerAttributes") + client, err := c.getClient(ctx, "ModifyLoadBalancerAttributes") if err != nil { return nil, err } @@ -294,7 +302,7 @@ func (c *elbv2Client) ModifyLoadBalancerAttributesWithContext(ctx context.Contex } func (c *elbv2Client) ModifyListenerWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyListenerInput) (*elasticloadbalancingv2.ModifyListenerOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "ModifyListener") + client, err := c.getClient(ctx, "ModifyListener") if err != nil { return nil, err } @@ -302,7 +310,7 @@ func (c *elbv2Client) ModifyListenerWithContext(ctx context.Context, input *elas } func (c *elbv2Client) DeleteListenerWithContext(ctx context.Context, input *elasticloadbalancingv2.DeleteListenerInput) (*elasticloadbalancingv2.DeleteListenerOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DeleteListener") + client, err := c.getClient(ctx, "DeleteListener") if err != nil { return nil, err } @@ -310,7 +318,7 @@ func (c *elbv2Client) DeleteListenerWithContext(ctx context.Context, input *elas } func (c *elbv2Client) CreateListenerWithContext(ctx context.Context, input *elasticloadbalancingv2.CreateListenerInput) (*elasticloadbalancingv2.CreateListenerOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "CreateListener") + client, err := c.getClient(ctx, "CreateListener") if err != nil { return nil, err } @@ -318,7 +326,7 @@ func (c *elbv2Client) CreateListenerWithContext(ctx context.Context, input *elas } func (c *elbv2Client) DescribeTagsWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeTagsInput) (*elasticloadbalancingv2.DescribeTagsOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeTags") + client, err := c.getClient(ctx, "DescribeTags") if err != nil { return nil, err } @@ -326,7 +334,7 @@ func (c *elbv2Client) DescribeTagsWithContext(ctx context.Context, input *elasti } func (c *elbv2Client) AddTagsWithContext(ctx context.Context, input *elasticloadbalancingv2.AddTagsInput) (*elasticloadbalancingv2.AddTagsOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "AddTags") + client, err := c.getClient(ctx, "AddTags") if err != nil { return nil, err } @@ -334,7 +342,7 @@ func (c *elbv2Client) AddTagsWithContext(ctx context.Context, input *elasticload } func (c *elbv2Client) RemoveTagsWithContext(ctx context.Context, input *elasticloadbalancingv2.RemoveTagsInput) (*elasticloadbalancingv2.RemoveTagsOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "RemoveTags") + client, err := c.getClient(ctx, "RemoveTags") if err != nil { return nil, err } @@ -345,7 +353,7 @@ func (c *elbv2Client) DescribeLoadBalancersAsList(ctx context.Context, input *el var result []types.LoadBalancer var client *elasticloadbalancingv2.Client var err error - client, err = c.awsClientsProvider.GetELBv2Client(ctx, "DescribeLoadBalancers") + client, err = c.getClient(ctx, "DescribeLoadBalancers") if err != nil { return nil, err } @@ -364,7 +372,7 @@ func (c *elbv2Client) DescribeTargetGroupsAsList(ctx context.Context, input *ela var result []types.TargetGroup var client *elasticloadbalancingv2.Client var err error - client, err = c.awsClientsProvider.GetELBv2Client(ctx, "DescribeTargetGroups") + client, err = c.getClient(ctx, "DescribeTargetGroups") if err != nil { return nil, err } @@ -383,7 +391,7 @@ func (c *elbv2Client) DescribeListenersAsList(ctx context.Context, input *elasti var result []types.Listener var client *elasticloadbalancingv2.Client var err error - client, err = c.awsClientsProvider.GetELBv2Client(ctx, "DescribeListeners") + client, err = c.getClient(ctx, "DescribeListeners") if err != nil { return nil, err } @@ -402,7 +410,7 @@ func (c *elbv2Client) DescribeListenerCertificatesAsList(ctx context.Context, in var result []types.Certificate var client *elasticloadbalancingv2.Client var err error - client, err = c.awsClientsProvider.GetELBv2Client(ctx, "DescribeListenerCertificates") + client, err = c.getClient(ctx, "DescribeListenerCertificates") if err != nil { return nil, err } @@ -421,7 +429,7 @@ func (c *elbv2Client) DescribeRulesAsList(ctx context.Context, input *elasticloa var result []types.Rule var client *elasticloadbalancingv2.Client var err error - client, err = c.awsClientsProvider.GetELBv2Client(ctx, "DescribeRules") + client, err = c.getClient(ctx, "DescribeRules") if err != nil { return nil, err } @@ -437,7 +445,7 @@ func (c *elbv2Client) DescribeRulesAsList(ctx context.Context, input *elasticloa } func (c *elbv2Client) DescribeListenerAttributesWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeListenerAttributesInput) (*elasticloadbalancingv2.DescribeListenerAttributesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeListenerAttributes") + client, err := c.getClient(ctx, "DescribeListenerAttributes") if err != nil { return nil, err } @@ -445,7 +453,7 @@ func (c *elbv2Client) DescribeListenerAttributesWithContext(ctx context.Context, } func (c *elbv2Client) ModifyListenerAttributesWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyListenerAttributesInput) (*elasticloadbalancingv2.ModifyListenerAttributesOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "ModifyListenerAttributes") + client, err := c.getClient(ctx, "ModifyListenerAttributes") if err != nil { return nil, err } @@ -453,7 +461,7 @@ func (c *elbv2Client) ModifyListenerAttributesWithContext(ctx context.Context, i } func (c *elbv2Client) ModifyCapacityReservationWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyCapacityReservationInput) (*elasticloadbalancingv2.ModifyCapacityReservationOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "ModifyCapacityReservation") + client, err := c.getClient(ctx, "ModifyCapacityReservation") if err != nil { return nil, err } @@ -461,9 +469,16 @@ func (c *elbv2Client) ModifyCapacityReservationWithContext(ctx context.Context, } func (c *elbv2Client) DescribeCapacityReservationWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeCapacityReservationInput) (*elasticloadbalancingv2.DescribeCapacityReservationOutput, error) { - client, err := c.awsClientsProvider.GetELBv2Client(ctx, "DescribeCapacityReservation") + client, err := c.getClient(ctx, "DescribeCapacityReservation") if err != nil { return nil, err } return client.DescribeCapacityReservation(ctx, input) } + +func (c *elbv2Client) getClient(ctx context.Context, operation string) (*elasticloadbalancingv2.Client, error) { + if c.staticELBClient != nil { + return c.staticELBClient, nil + } + return c.awsClientsProvider.GetELBv2Client(ctx, operation) +} diff --git a/pkg/targetgroupbinding/resource_manager.go b/pkg/targetgroupbinding/resource_manager.go index 1b4035f06..be3dfd81d 100644 --- a/pkg/targetgroupbinding/resource_manager.go +++ b/pkg/targetgroupbinding/resource_manager.go @@ -552,7 +552,7 @@ func (m *defaultResourceManager) registerPodEndpoints(ctx context.Context, tgb * var overrideAzFn func(addr netip.Addr) bool if tgb.Spec.IamRoleArnToAssume != "" { - // If we're interacting with another account, then we should always be sitting "all" AZ to allow this + // If we're interacting with another account, then we should always be setting "all" AZ to allow this // target to get registered by the ELB API. overrideAzFn = func(_ netip.Addr) bool { return true diff --git a/webhooks/elbv2/targetgroupbinding_validator.go b/webhooks/elbv2/targetgroupbinding_validator.go index 1af07a2f3..2e3e63bfe 100644 --- a/webhooks/elbv2/targetgroupbinding_validator.go +++ b/webhooks/elbv2/targetgroupbinding_validator.go @@ -69,6 +69,9 @@ func (v *targetGroupBindingValidator) ValidateCreate(ctx context.Context, obj ru if err := v.checkTargetGroupVpcID(ctx, tgb); err != nil { return err } + if err := v.checkAssumeRoleConfig(tgb); err != nil { + return err + } return nil } @@ -84,6 +87,9 @@ func (v *targetGroupBindingValidator) ValidateUpdate(ctx context.Context, obj ru if err := v.checkNodeSelector(tgb); err != nil { return err } + if err := v.checkAssumeRoleConfig(tgb); err != nil { + return err + } return nil } @@ -235,6 +241,19 @@ func (v *targetGroupBindingValidator) getVpcIDFromAWS(ctx context.Context, tgb * return awssdk.ToString(targetGroup.VpcId), nil } +// checkAssumeRoleConfig various checks for using cross account target group bindings. +func (v *targetGroupBindingValidator) checkAssumeRoleConfig(tgb *elbv2api.TargetGroupBinding) error { + if tgb.Spec.IamRoleArnToAssume == "" { + return nil + } + + if tgb.Spec.TargetType != nil && *tgb.Spec.TargetType == elbv2api.TargetTypeInstance { + return errors.New("Unable to use instance target type while using assume role") + } + + return nil +} + // +kubebuilder:webhook:path=/validate-elbv2-k8s-aws-v1beta1-targetgroupbinding,mutating=false,failurePolicy=fail,groups=elbv2.k8s.aws,resources=targetgroupbindings,verbs=create;update,versions=v1beta1,name=vtargetgroupbinding.elbv2.k8s.aws,sideEffects=None,webhookVersions=v1,admissionReviewVersions=v1beta1 func (v *targetGroupBindingValidator) SetupWithManager(mgr ctrl.Manager) { diff --git a/webhooks/elbv2/targetgroupbinding_validator_test.go b/webhooks/elbv2/targetgroupbinding_validator_test.go index 14906bd41..33fe3607a 100644 --- a/webhooks/elbv2/targetgroupbinding_validator_test.go +++ b/webhooks/elbv2/targetgroupbinding_validator_test.go @@ -1412,6 +1412,67 @@ func Test_targetGroupBindingValidator_checkTargetGroupVpcID(t *testing.T) { } } +func TestCheckAssumeRoleConfig(t *testing.T) { + instance := elbv2api.TargetTypeInstance + ip := elbv2api.TargetTypeIP + testCases := []struct { + name string + tgb *elbv2api.TargetGroupBinding + err error + }{ + { + name: "ip no assume role", + tgb: &elbv2api.TargetGroupBinding{ + Spec: elbv2api.TargetGroupBindingSpec{ + TargetType: &ip, + }, + }, + }, + { + name: "instance no assume role", + tgb: &elbv2api.TargetGroupBinding{ + Spec: elbv2api.TargetGroupBindingSpec{ + TargetType: &instance, + }, + }, + }, + { + name: "ip with assume role", + tgb: &elbv2api.TargetGroupBinding{ + Spec: elbv2api.TargetGroupBindingSpec{ + TargetType: &ip, + IamRoleArnToAssume: "foo", + }, + }, + }, + { + name: "instance with assume role", + tgb: &elbv2api.TargetGroupBinding{ + Spec: elbv2api.TargetGroupBindingSpec{ + TargetType: &instance, + IamRoleArnToAssume: "foo", + }, + }, + err: errors.New("Unable to use instance target type while using assume role"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + v := &targetGroupBindingValidator{ + logger: logr.New(&log.NullLogSink{}), + } + + err := v.checkAssumeRoleConfig(tc.tgb) + if tc.err == nil { + assert.Nil(t, err) + } else { + assert.EqualError(t, err, tc.err.Error()) + } + }) + } +} + func generateRandomString(n int, addChars ...rune) string { const letters = "0123456789abcdef" From 92ae3dc797c33be7ea7a1f4207cc6b2ad750b5d0 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Thu, 16 Jan 2025 13:16:59 -0800 Subject: [PATCH 20/30] allow multiple targetgroupbindings to reference same tg arn if using multi cluster mode --- .../targetgroupbinding/targetgroupbinding.md | 2 +- .../elbv2/targetgroupbinding_validator.go | 22 ++- .../targetgroupbinding_validator_test.go | 127 +++++++++++++++++- 3 files changed, 144 insertions(+), 7 deletions(-) diff --git a/docs/guide/targetgroupbinding/targetgroupbinding.md b/docs/guide/targetgroupbinding/targetgroupbinding.md index e7d3ccf4a..fadb001eb 100644 --- a/docs/guide/targetgroupbinding/targetgroupbinding.md +++ b/docs/guide/targetgroupbinding/targetgroupbinding.md @@ -257,7 +257,7 @@ spec: name: awesome-service # route traffic to the awesome-service port: 80 targetGroupARN: - multiClusterTargetGroup: "true" + multiClusterTargetGroup: true ``` diff --git a/webhooks/elbv2/targetgroupbinding_validator.go b/webhooks/elbv2/targetgroupbinding_validator.go index 2e3e63bfe..ad355d6cf 100644 --- a/webhooks/elbv2/targetgroupbinding_validator.go +++ b/webhooks/elbv2/targetgroupbinding_validator.go @@ -3,6 +3,7 @@ package elbv2 import ( "context" "fmt" + "k8s.io/apimachinery/pkg/types" "regexp" "strings" @@ -90,6 +91,9 @@ func (v *targetGroupBindingValidator) ValidateUpdate(ctx context.Context, obj ru if err := v.checkAssumeRoleConfig(tgb); err != nil { return err } + if err := v.checkExistingTargetGroups(tgb); err != nil { + return err + } return nil } @@ -162,17 +166,29 @@ func (v *targetGroupBindingValidator) checkImmutableFields(tgb *elbv2api.TargetG } // checkExistingTargetGroups will check for unique TargetGroup per TargetGroupBinding -func (v *targetGroupBindingValidator) checkExistingTargetGroups(tgb *elbv2api.TargetGroupBinding) error { +func (v *targetGroupBindingValidator) checkExistingTargetGroups(updatedTgb *elbv2api.TargetGroupBinding) error { ctx := context.Background() tgbList := elbv2api.TargetGroupBindingList{} if err := v.k8sClient.List(ctx, &tgbList); err != nil { return errors.Wrap(err, "failed to list TargetGroupBindings in the cluster") } + + duplicateTGBs := make([]types.NamespacedName, 0) + multiClusterSupported := updatedTgb.Spec.MultiClusterTargetGroup + for _, tgbObj := range tgbList.Items { - if tgbObj.Spec.TargetGroupARN == tgb.Spec.TargetGroupARN { - return errors.Errorf("TargetGroup %v is already bound to TargetGroupBinding %v", tgb.Spec.TargetGroupARN, k8s.NamespacedName(&tgbObj).String()) + if tgbObj.UID != updatedTgb.UID && tgbObj.Spec.TargetGroupARN == updatedTgb.Spec.TargetGroupARN { + if !tgbObj.Spec.MultiClusterTargetGroup { + multiClusterSupported = false + } + duplicateTGBs = append(duplicateTGBs, k8s.NamespacedName(&tgbObj)) } } + + if len(duplicateTGBs) != 0 && !multiClusterSupported { + return errors.Errorf("TargetGroup %v is already bound to following TargetGroupBindings %v. Please enable MultiCluster mode on all TargetGroupBindings referencing %v or choose a different Target Group ARN.", updatedTgb.Spec.TargetGroupARN, duplicateTGBs, updatedTgb.Spec.TargetGroupARN) + } + return nil } diff --git a/webhooks/elbv2/targetgroupbinding_validator_test.go b/webhooks/elbv2/targetgroupbinding_validator_test.go index 33fe3607a..e74dd6793 100644 --- a/webhooks/elbv2/targetgroupbinding_validator_test.go +++ b/webhooks/elbv2/targetgroupbinding_validator_test.go @@ -462,8 +462,14 @@ func Test_targetGroupBindingValidator_ValidateUpdate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + k8sSchema := runtime.NewScheme() + clientgoscheme.AddToScheme(k8sSchema) + elbv2api.AddToScheme(k8sSchema) + k8sClient := testclient.NewClientBuilder().WithScheme(k8sSchema).Build() + v := &targetGroupBindingValidator{ - logger: logr.New(&log.NullLogSink{}), + logger: logr.New(&log.NullLogSink{}), + k8sClient: k8sClient, } err := v.ValidateUpdate(context.Background(), tt.args.obj, tt.args.oldObj) if tt.wantErr != nil { @@ -954,6 +960,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb1", Namespace: "ns1", + UID: "tgb1", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-1", @@ -971,6 +978,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb1", Namespace: "ns1", + UID: "tgb1", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-1", @@ -984,6 +992,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb2", Namespace: "ns2", + UID: "tgb2", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-2", @@ -1001,6 +1010,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb1", Namespace: "ns1", + UID: "tgb1", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-1", @@ -1011,6 +1021,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb2", Namespace: "ns2", + UID: "tgb2", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-2", @@ -1021,12 +1032,24 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb3", Namespace: "ns3", + UID: "tgb3", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-3", TargetType: nil, }, }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "tgb22", + Namespace: "ns1", + UID: "tgb22", + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-22", + TargetType: nil, + }, + }, }, }, args: args{ @@ -1034,6 +1057,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb22", Namespace: "ns1", + UID: "tgb22", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-22", @@ -1051,6 +1075,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb1", Namespace: "ns1", + UID: "tgb1", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-1", @@ -1064,6 +1089,98 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb2", Namespace: "ns1", + UID: "tgb2", + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-1", + TargetType: nil, + }, + }, + }, + wantErr: errors.New("TargetGroup tg-1 is already bound to following TargetGroupBindings [ns1/tgb1]. Please enable MultiCluster mode on all TargetGroupBindings referencing tg-1 or choose a different Target Group ARN."), + }, + { + name: "[ok] duplicate target groups with multi cluster support", + env: env{ + existingTGBs: []elbv2api.TargetGroupBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "tgb1", + Namespace: "ns1", + UID: "tgb1", + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-1", + TargetType: nil, + MultiClusterTargetGroup: true, + }, + }, + }, + }, + args: args{ + tgb: &elbv2api.TargetGroupBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tgb2", + Namespace: "ns1", + UID: "tgb2", + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-1", + TargetType: nil, + MultiClusterTargetGroup: true, + }, + }, + }, + wantErr: nil, + }, + { + name: "[err] try to add binding without multicluster support while multiple bindings are using the same tg arn", + env: env{ + existingTGBs: []elbv2api.TargetGroupBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "tgb1", + Namespace: "ns1", + UID: "tgb1", + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-1", + TargetType: nil, + MultiClusterTargetGroup: true, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "tgb3", + Namespace: "ns1", + UID: "tgb3", + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-1", + TargetType: nil, + MultiClusterTargetGroup: true, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "tgb4", + Namespace: "ns1", + UID: "tgb4", + }, + Spec: elbv2api.TargetGroupBindingSpec{ + TargetGroupARN: "tg-1", + TargetType: nil, + MultiClusterTargetGroup: true, + }, + }, + }, + }, + args: args{ + tgb: &elbv2api.TargetGroupBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tgb2", + Namespace: "ns1", + UID: "tgb2", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-1", @@ -1071,7 +1188,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroup tg-1 is already bound to TargetGroupBinding ns1/tgb1"), + wantErr: errors.New("TargetGroup tg-1 is already bound to following TargetGroupBindings [ns1/tgb1 ns1/tgb3 ns1/tgb4]. Please enable MultiCluster mode on all TargetGroupBindings referencing tg-1 or choose a different Target Group ARN."), }, { name: "[err] duplicate target groups - one target group binding", @@ -1081,6 +1198,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb1", Namespace: "ns1", + UID: "tgb1", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-1", @@ -1091,6 +1209,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb2", Namespace: "ns2", + UID: "tgb2", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-111", @@ -1101,6 +1220,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb3", Namespace: "ns3", + UID: "tgb3", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-3", @@ -1114,6 +1234,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "tgb111", Namespace: "ns1", + UID: "tgb111", }, Spec: elbv2api.TargetGroupBindingSpec{ TargetGroupARN: "tg-111", @@ -1121,7 +1242,7 @@ func Test_targetGroupBindingValidator_checkExistingTargetGroups(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroup tg-111 is already bound to TargetGroupBinding ns2/tgb2"), + wantErr: errors.New("TargetGroup tg-111 is already bound to following TargetGroupBindings [ns2/tgb2]. Please enable MultiCluster mode on all TargetGroupBindings referencing tg-111 or choose a different Target Group ARN."), }, } for _, tt := range tests { From 57472c44170f0f75df67bb5b6e83b75a2db03231 Mon Sep 17 00:00:00 2001 From: wweiwei-li <79778352+wweiwei-li@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:17:54 -0800 Subject: [PATCH 21/30] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 70 +++++++++++++++++++---- .github/ISSUE_TEMPLATE/feature_request.md | 25 ++++++-- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7d18f5a41..9dc542aa2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,23 +2,73 @@ name: Bug report about: Create a report to help us improve title: '' -labels: bug +labels: '' assignees: '' --- -**Describe the bug** -A concise description of what the bug is. + -**Steps to reproduce** + -**Expected outcome** -A concise description of what you expected to happen. +**Bug Description** + + +**Steps to Reproduce** + +- Step-by-step guide to reproduce the bug: +- Manifests applied while reproducing the issue: +- Controller logs/error messages while reproducing the issue: + +**Expected Behavior** + + +**Actual Behavior** + + +**Regression** +Was the functionality working correctly in a previous version ? [Yes / No] +If yes, specify the last version where it worked as expected + +**Current Workarounds** + **Environment** +- AWS Load Balancer controller version: +- Kubernetes version: +- Using EKS (yes/no), if so version?: +- Using Service or Ingress: +- AWS region: +- How was the aws-load-balancer-controller installed: + - If helm was used then please show output of `helm ls -A | grep -i aws-load-balancer-controller` + - If helm was used then please show output of `helm -n get values ` + - If helm was not used, then copy/paste the exact command used to install the controller, including flags and options. +- Current state of the Controller configuration: + - `kubectl -n describe deployment aws-load-balancer-controller` +- Current state of the Ingress/Service configuration: + - `kubectl describe ingressclasses` + - `kubectl -n describe ingress ` + - `kubectl -n describe svc ` + +**Possible Solution (Optional)** + -* AWS Load Balancer controller version -* Kubernetes version -* Using EKS (yes/no), if so version? +**Contribution Intention (Optional)** + +- [ ] Yes, I'm willing to submit a PR to fix this issue +- [ ] No, I cannot work on a PR at this time -**Additional Context**: +**Additional Context** + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 7fb2e8486..c23f236ea 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,16 +2,29 @@ name: Feature request about: Suggest an idea for this project title: '' -labels: enhancement +labels: '' assignees: '' --- -**Is your feature request related to a problem?** -A description of what the problem is. For example: I'm frustrated when [...] + -**Describe the solution you'd like** -A description of what you want to happen. +**Describe the feature you are requesting** + + +**Motivation** + + +**Describe the proposed solution you'd like** + **Describe alternatives you've considered** -A description of any alternative solutions or features you've considered. + + +**Contribution Intention (Optional)** + +-[ ] Yes, I am willing to contribute a PR to implement this feature +-[ ] No, I cannot work on a PR at this time From 60bb639442bc4516971ba82f4d7944e86949a009 Mon Sep 17 00:00:00 2001 From: shraddha bang Date: Mon, 17 Feb 2025 12:41:37 -0800 Subject: [PATCH 22/30] Fix docs for source ranges for internal NLB --- docs/guide/service/annotations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/service/annotations.md b/docs/guide/service/annotations.md index 702107e78..a9d01de4d 100644 --- a/docs/guide/service/annotations.md +++ b/docs/guide/service/annotations.md @@ -498,12 +498,12 @@ Load balancer access can be controlled via following annotations: - `service.beta.kubernetes.io/load-balancer-source-ranges` specifies the CIDRs that are allowed to access the NLB. !!!tip - we recommend specifying CIDRs in the service `spec.loadBalancerSourceRanges` instead + - We recommend specifying CIDRs in the service `spec.loadBalancerSourceRanges` instead + - For enhanced security with `internal` network load balancers, we recommend limiting access by specifying allowed source IP ranges. This can be done using either the `service.beta.kubernetes.io/load-balancer-source-ranges` annotation or the `spec.loadBalancerSourceRanges` field. !!!note "Default" - `0.0.0.0/0` will be used if the IPAddressType is "ipv4" - `0.0.0.0/0` and `::/0` will be used if the IPAddressType is "dualstack" - - The VPC CIDR will be used if `service.beta.kubernetes.io/aws-load-balancer-scheme` is `internal` !!!warning "" This annotation will be ignored in case preserve client IP is not enabled. From e8c32fdb3fb6895f951a8c0c9767b00b67c32e61 Mon Sep 17 00:00:00 2001 From: shraddha bang Date: Mon, 17 Feb 2025 12:49:21 -0800 Subject: [PATCH 23/30] Update docs for supporting removal of availability zone for NLB --- docs/guide/service/annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/service/annotations.md b/docs/guide/service/annotations.md index 702107e78..69ebd9dc1 100644 --- a/docs/guide/service/annotations.md +++ b/docs/guide/service/annotations.md @@ -125,7 +125,7 @@ the NLB will route traffic to. See [Network Load Balancers](https://docs.aws.ama !!!warning "limitations" - Each subnets must be from a different Availability Zone - - AWS has restrictions on disabling existing subnets for NLB. As a result, you might not be able to edit this annotation once the NLB gets provisioned. + - Similar to any delete operation, removing an Availability Zone can be a potentially disruptive operation. We recommend you evaluate for any potential impact on existing connections, traffic flows, or production workloads. Refer to [product documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/availability-zones.html) for prescriptive guidance on how to use this capability in a safe manner. !!!example ``` From 4b4c94d3029f019f56318bf9fcb18a553b05a922 Mon Sep 17 00:00:00 2001 From: shraddha bang Date: Mon, 17 Feb 2025 12:49:21 -0800 Subject: [PATCH 24/30] Update docs for supporting removal of availability zone for NLB --- .go-version | 2 +- go.mod | 7 +++---- go.sum | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.go-version b/.go-version index 229a27c6f..fda2152c8 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.22.8 +1.22.12 \ No newline at end of file diff --git a/go.mod b/go.mod index 2346312e9..a37edbc7e 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ module sigs.k8s.io/aws-load-balancer-controller -go 1.22.8 +go 1.22.12 require ( github.com/aws/aws-sdk-go v1.55.5 github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.27.27 + github.com/aws/aws-sdk-go-v2/credentials v1.17.27 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 github.com/aws/aws-sdk-go-v2/service/acm v1.28.4 github.com/aws/aws-sdk-go-v2/service/appmesh v1.27.7 @@ -14,6 +15,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.23.3 github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.31.7 github.com/aws/aws-sdk-go-v2/service/shield v1.27.3 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 github.com/aws/aws-sdk-go-v2/service/wafregional v1.23.3 github.com/aws/aws-sdk-go-v2/service/wafv2 v1.51.4 github.com/aws/smithy-go v1.22.1 @@ -56,16 +58,13 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/iam v1.36.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect diff --git a/go.sum b/go.sum index 6676d7940..292ee96ce 100644 --- a/go.sum +++ b/go.sum @@ -60,7 +60,6 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 h1:ta62lid9JkIpKZtZZXSj6rP2AqY github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1 h1:L9Wt9zgtoYKIlaeFTy+EztGjL4oaXBBGtVXA+jaeYko= github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1/go.mod h1:yxzLdxt7bVGvIOPYIKFtiaJCJnx2ChlIIvlhW4QgI6M= -github.com/aws/aws-sdk-go-v2/service/iam v1.36.3/go.mod h1:HSvujsK8xeEHMIB18oMXjSfqaN9cVqpo/MtHJIksQRk= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= From 9ab7f9dca57fbab0e3863fc07c5a4bfd4f05093f Mon Sep 17 00:00:00 2001 From: M00nF1sh Date: Tue, 18 Feb 2025 10:27:22 -0800 Subject: [PATCH 25/30] change pod mutator webhook to fail-open (#4062) --- helm/aws-load-balancer-controller/templates/webhook.yaml | 2 +- webhooks/core/pod_mutator.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/aws-load-balancer-controller/templates/webhook.yaml b/helm/aws-load-balancer-controller/templates/webhook.yaml index 504f08ccb..5d27a8083 100644 --- a/helm/aws-load-balancer-controller/templates/webhook.yaml +++ b/helm/aws-load-balancer-controller/templates/webhook.yaml @@ -19,7 +19,7 @@ webhooks: name: {{ template "aws-load-balancer-controller.webhookService" . }} namespace: {{ $.Release.Namespace }} path: /mutate-v1-pod - failurePolicy: Fail + failurePolicy: Ignore name: mpod.elbv2.k8s.aws admissionReviewVersions: - v1beta1 diff --git a/webhooks/core/pod_mutator.go b/webhooks/core/pod_mutator.go index dfb33c05a..1d8974953 100644 --- a/webhooks/core/pod_mutator.go +++ b/webhooks/core/pod_mutator.go @@ -44,7 +44,7 @@ func (m *podMutator) MutateUpdate(ctx context.Context, obj runtime.Object, oldOb return obj, nil } -// +kubebuilder:webhook:path=/mutate-v1-pod,mutating=true,failurePolicy=fail,groups="",resources=pods,verbs=create,versions=v1,name=mpod.elbv2.k8s.aws,sideEffects=None,webhookVersions=v1,admissionReviewVersions=v1beta1 +// +kubebuilder:webhook:path=/mutate-v1-pod,mutating=true,failurePolicy=ignore,groups="",resources=pods,verbs=create,versions=v1,name=mpod.elbv2.k8s.aws,sideEffects=None,webhookVersions=v1,admissionReviewVersions=v1beta1 func (m *podMutator) SetupWithManager(mgr ctrl.Manager) { mgr.GetWebhookServer().Register(apiPathMutatePod, webhook.MutatingWebhookForMutator(m, mgr.GetScheme())) From 526b830b93b0e7f78c1808ced4482f75440abccc Mon Sep 17 00:00:00 2001 From: M00nF1sh Date: Tue, 18 Feb 2025 16:28:35 -0800 Subject: [PATCH 26/30] add toggle to adjust failurePolicy of pod webhook and documentations (#4063) the vulcheck failed due to other reasons. --- config/webhook/manifests.yaml | 2 +- docs/deploy/pod_readiness_gate.md | 15 +++++++++++++++ .../templates/webhook.yaml | 2 +- helm/aws-load-balancer-controller/test.yaml | 4 ++++ helm/aws-load-balancer-controller/values.yaml | 5 +++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 0b1a24bfe..00793b470 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -11,7 +11,7 @@ webhooks: name: webhook-service namespace: system path: /mutate-v1-pod - failurePolicy: Fail + failurePolicy: Ignore name: mpod.elbv2.k8s.aws rules: - apiGroups: diff --git a/docs/deploy/pod_readiness_gate.md b/docs/deploy/pod_readiness_gate.md index 60ef17a62..c376895f5 100644 --- a/docs/deploy/pod_readiness_gate.md +++ b/docs/deploy/pod_readiness_gate.md @@ -47,6 +47,21 @@ The readiness gates have the prefix `target-health.elbv2.k8s.aws` and the contro !!!tip "create ingress or service before pod" To ensure all of your pods in a namespace get the readiness gate config, you need create your Ingress or Service and label the namespace before creating the pods +## FailurePolicy +The `failurePolicy` of a webhook determines how errors, such as unrecognized or timeout errors, are handled by the admission webhook. + +* `failurePolicy: Fail`: When applied to a pod mutation webhook, this setting will prevent the launch of any pods in labeled namespaces if the AWSLoadBalancerController pods are unavailable. While this can help avoid incomplete or faulty deployments, it could also delay the cluster's recovery in extreme scenarios, such as an API Server outage. +* `failurePolicy: Ignore`: Setting this policy allows Kubernetes to proceed with pod deployments even if the AWSLoadBalancerController pods are unavailable. This can lead to availability risks for applications since Kubernetes may terminate application pods before the new pods have become healthy in the TargetGroups + +To strike a balance between reliability and availability, the default failurePolicy for pod mutation webhooks that inject readiness gates is configured as follows: + +* `failurePolicy: Ignore` (for versions > v2.11.0) +* `failurePolicy: Fail` (for versions <= v2.11.0) +You can customize the behavior using Helm chart settings, e.g. `--set podMutatorWebhookConfig.failurePolicy=Fail` + +!!!note "Recommended settings" + For optimal reliability & availability, it is recommended to use `failurePolicy: Fail` combined with an explicit [Object Selector](#object-selector) + ## Object Selector The default webhook configuration matches all pods in the namespaces containing the label `elbv2.k8s.aws/pod-readiness-gate-inject=enabled`. You can modify the webhook configuration further to select specific pods from the labeled namespace by specifying the `objectSelector`. For example, in order to select resources with `elbv2.k8s.aws/pod-readiness-gate-inject: enabled` label, diff --git a/helm/aws-load-balancer-controller/templates/webhook.yaml b/helm/aws-load-balancer-controller/templates/webhook.yaml index 5d27a8083..7d5fcae0f 100644 --- a/helm/aws-load-balancer-controller/templates/webhook.yaml +++ b/helm/aws-load-balancer-controller/templates/webhook.yaml @@ -19,7 +19,7 @@ webhooks: name: {{ template "aws-load-balancer-controller.webhookService" . }} namespace: {{ $.Release.Namespace }} path: /mutate-v1-pod - failurePolicy: Ignore + failurePolicy: {{ .Values.podMutatorWebhookConfig.failurePolicy }} name: mpod.elbv2.k8s.aws admissionReviewVersions: - v1beta1 diff --git a/helm/aws-load-balancer-controller/test.yaml b/helm/aws-load-balancer-controller/test.yaml index f5074c857..266ebd14d 100644 --- a/helm/aws-load-balancer-controller/test.yaml +++ b/helm/aws-load-balancer-controller/test.yaml @@ -353,3 +353,7 @@ serviceMutatorWebhookConfig: operations: - CREATE # - UPDATE + +podMutatorWebhookConfig: + # whether or not to fail the pod creation if the webhook fails + failurePolicy: Ignore \ No newline at end of file diff --git a/helm/aws-load-balancer-controller/values.yaml b/helm/aws-load-balancer-controller/values.yaml index eb28380b2..f3a60f4d3 100644 --- a/helm/aws-load-balancer-controller/values.yaml +++ b/helm/aws-load-balancer-controller/values.yaml @@ -430,6 +430,11 @@ serviceMutatorWebhookConfig: - CREATE # - UPDATE +# podMutatorWebhookConfig contains configurations specific to the service mutator webhook +podMutatorWebhookConfig: + # whether or not to fail the pod creation if the webhook fails + failurePolicy: Ignore + # serviceTargetENISGTags specifies AWS tags, in addition to the cluster tags, for finding the target ENI SG to which to add inbound rules from NLBs. serviceTargetENISGTags: From dc64725dc9100d0ff01fb2c578739e971e32d4df Mon Sep 17 00:00:00 2001 From: Sherif Abdel-Naby Date: Mon, 17 Feb 2025 20:22:05 -0600 Subject: [PATCH 27/30] Do not render .spec.replicas if HPA Is enabled --- helm/aws-load-balancer-controller/Chart.yaml | 2 +- helm/aws-load-balancer-controller/templates/deployment.yaml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/helm/aws-load-balancer-controller/Chart.yaml b/helm/aws-load-balancer-controller/Chart.yaml index 1f1b4e9ba..48f0a7eca 100644 --- a/helm/aws-load-balancer-controller/Chart.yaml +++ b/helm/aws-load-balancer-controller/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: aws-load-balancer-controller description: AWS Load Balancer Controller Helm chart for Kubernetes -version: 1.11.0 +version: 1.11.1 appVersion: v2.11.0 home: https://github.com/aws/eks-charts icon: https://raw.githubusercontent.com/aws/eks-charts/master/docs/logo/aws.png diff --git a/helm/aws-load-balancer-controller/templates/deployment.yaml b/helm/aws-load-balancer-controller/templates/deployment.yaml index 4ec8aa662..130ca6158 100644 --- a/helm/aws-load-balancer-controller/templates/deployment.yaml +++ b/helm/aws-load-balancer-controller/templates/deployment.yaml @@ -10,7 +10,9 @@ metadata: labels: {{- include "aws-load-balancer-controller.labels" . | nindent 4 }} spec: + {{ if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} + {{ end }} revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} selector: matchLabels: From d435b881d43f6ca54007651e7498bc00c9a65a15 Mon Sep 17 00:00:00 2001 From: Zachary Nixon Date: Mon, 24 Feb 2025 15:23:21 -0800 Subject: [PATCH 28/30] bump go version to 1.23.6 for clean vuln report --- .go-version | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.go-version b/.go-version index fda2152c8..d8c40e539 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.22.12 \ No newline at end of file +1.23.6 diff --git a/go.mod b/go.mod index a37edbc7e..a2186f227 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module sigs.k8s.io/aws-load-balancer-controller -go 1.22.12 +go 1.23.6 require ( github.com/aws/aws-sdk-go v1.55.5 From e8fef67cba259d5c7c4a0e7de714d5ddc42c96ba Mon Sep 17 00:00:00 2001 From: Matteo Ruina Date: Tue, 25 Feb 2025 17:20:28 +0100 Subject: [PATCH 29/30] Fix webhook error message --- .../elbv2/targetgroupbinding_validator.go | 5 ++-- .../targetgroupbinding_validator_test.go | 28 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/webhooks/elbv2/targetgroupbinding_validator.go b/webhooks/elbv2/targetgroupbinding_validator.go index ad355d6cf..7b0e6450c 100644 --- a/webhooks/elbv2/targetgroupbinding_validator.go +++ b/webhooks/elbv2/targetgroupbinding_validator.go @@ -3,10 +3,11 @@ package elbv2 import ( "context" "fmt" - "k8s.io/apimachinery/pkg/types" "regexp" "strings" + "k8s.io/apimachinery/pkg/types" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" awssdk "github.com/aws/aws-sdk-go-v2/aws" @@ -160,7 +161,7 @@ func (v *targetGroupBindingValidator) checkImmutableFields(tgb *elbv2api.TargetG changedImmutableFields = append(changedImmutableFields, "spec.vpcID") } if len(changedImmutableFields) != 0 { - return errors.Errorf("%s update may not change these fields: %s", "TargetGroupBinding", strings.Join(changedImmutableFields, ",")) + return errors.Errorf("%s update may not change these immutable fields: %s", "TargetGroupBinding", strings.Join(changedImmutableFields, ",")) } return nil } diff --git a/webhooks/elbv2/targetgroupbinding_validator_test.go b/webhooks/elbv2/targetgroupbinding_validator_test.go index e74dd6793..6fd0cc7b3 100644 --- a/webhooks/elbv2/targetgroupbinding_validator_test.go +++ b/webhooks/elbv2/targetgroupbinding_validator_test.go @@ -399,7 +399,7 @@ func Test_targetGroupBindingValidator_ValidateUpdate(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.targetGroupARN"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.targetGroupARN"), }, { name: "[err] targetType is ip, nodeSelector is set", @@ -439,7 +439,7 @@ func Test_targetGroupBindingValidator_ValidateUpdate(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.ipAddressType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.ipAddressType"), }, { name: "[ok] no update to spec", @@ -576,7 +576,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.targetGroupARN"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.targetGroupARN"), }, { name: "targetType is changed", @@ -594,7 +594,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.targetType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.targetType"), }, { name: "targetType is changed from unset to set", @@ -612,7 +612,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.targetType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.targetType"), }, { name: "targetType is changed from set to unset", @@ -630,7 +630,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.targetType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.targetType"), }, { name: "both targetGroupARN and targetType are changed", @@ -648,7 +648,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.targetGroupARN,spec.targetType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.targetGroupARN,spec.targetType"), }, { name: "both targetGroupARN and targetType are not changed", @@ -686,7 +686,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.ipAddressType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.ipAddressType"), }, { name: "ipAddressType modified, old value nil", @@ -705,7 +705,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.ipAddressType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.ipAddressType"), }, { name: "ipAddressType modified from nil to ipv4", @@ -743,7 +743,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.ipAddressType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.ipAddressType"), }, { name: "ipAddressType modified from nil to ipv6", @@ -762,7 +762,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.ipAddressType"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.ipAddressType"), }, { name: "VpcID modified from vpc-0aaaaaaa to vpc-0bbbbbbb", @@ -782,7 +782,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.vpcID"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.vpcID"), }, { name: "VpcID modified from vpc-0aaaaaaa to nil", @@ -801,7 +801,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.vpcID"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.vpcID"), }, { name: "VpcID modified from nil to vpc-0aaaaaaa", @@ -820,7 +820,7 @@ func Test_targetGroupBindingValidator_checkImmutableFields(t *testing.T) { }, }, }, - wantErr: errors.New("TargetGroupBinding update may not change these fields: spec.vpcID"), + wantErr: errors.New("TargetGroupBinding update may not change these immutable fields: spec.vpcID"), }, { name: "VpcID modified from nil to cluster vpc-id is allowed", From e942a0f6a9459a2524b0fdca05d13343fb308d41 Mon Sep 17 00:00:00 2001 From: Shraddha Bang <18206078+shraddhabang@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:32:46 -0800 Subject: [PATCH 30/30] Update rule management to avoid sporadic 503 errors (#4039) * Update rule management to avoid sporadic 503 errors * Update rule management to avoid sporadic 503 errors * Update rule management to avoid sporadic 503 errors * Update rule management to avoid sporadic 503 errors * Update rule management to avoid sporadic 503 errors * Update rule management to avoid sporadic 503 errors * Update rule management to avoid sporadic 503 errors --- docs/install/iam_policy.json | 3 +- docs/install/iam_policy_cn.json | 3 +- docs/install/iam_policy_iso.json | 3 +- docs/install/iam_policy_isob.json | 3 +- docs/install/iam_policy_isoe.json | 3 +- docs/install/iam_policy_isof.json | 3 +- docs/install/iam_policy_us-gov.json | 3 +- pkg/aws/services/elbv2.go | 11 +- pkg/aws/services/elbv2_mocks.go | 15 + pkg/deploy/elbv2/listener_rule_manager.go | 127 +- .../elbv2/listener_rule_manager_test.go | 422 ++++++ pkg/deploy/elbv2/listener_rule_synthesizer.go | 107 +- .../elbv2/listener_rule_synthesizer_test.go | 1280 +++++++++++++++++ pkg/deploy/elbv2/listener_utils.go | 12 + pkg/deploy/stack_deployer.go | 2 +- 15 files changed, 1929 insertions(+), 68 deletions(-) create mode 100644 pkg/deploy/elbv2/listener_rule_manager_test.go create mode 100644 pkg/deploy/elbv2/listener_rule_synthesizer_test.go diff --git a/docs/install/iam_policy.json b/docs/install/iam_policy.json index 1a5b4d614..0da4ee564 100644 --- a/docs/install/iam_policy.json +++ b/docs/install/iam_policy.json @@ -239,7 +239,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_cn.json b/docs/install/iam_policy_cn.json index ba8a39fa7..bb475f6ec 100644 --- a/docs/install/iam_policy_cn.json +++ b/docs/install/iam_policy_cn.json @@ -239,7 +239,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_iso.json b/docs/install/iam_policy_iso.json index bd87af7f5..46187d30e 100644 --- a/docs/install/iam_policy_iso.json +++ b/docs/install/iam_policy_iso.json @@ -234,7 +234,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_isob.json b/docs/install/iam_policy_isob.json index e552c458f..c7107d710 100644 --- a/docs/install/iam_policy_isob.json +++ b/docs/install/iam_policy_isob.json @@ -234,7 +234,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_isoe.json b/docs/install/iam_policy_isoe.json index 9afb8d4fa..6db25833e 100644 --- a/docs/install/iam_policy_isoe.json +++ b/docs/install/iam_policy_isoe.json @@ -234,7 +234,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_isof.json b/docs/install/iam_policy_isof.json index 2c5054393..7a43d3e40 100644 --- a/docs/install/iam_policy_isof.json +++ b/docs/install/iam_policy_isof.json @@ -234,7 +234,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/docs/install/iam_policy_us-gov.json b/docs/install/iam_policy_us-gov.json index 828f77f4d..790304851 100644 --- a/docs/install/iam_policy_us-gov.json +++ b/docs/install/iam_policy_us-gov.json @@ -239,7 +239,8 @@ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", - "elasticloadbalancing:ModifyRule" + "elasticloadbalancing:ModifyRule", + "elasticloadbalancing:SetRulePriorities" ], "Resource": "*" } diff --git a/pkg/aws/services/elbv2.go b/pkg/aws/services/elbv2.go index 293f61f3b..c2eb0d401 100644 --- a/pkg/aws/services/elbv2.go +++ b/pkg/aws/services/elbv2.go @@ -50,7 +50,8 @@ type ELBV2 interface { DescribeRulesWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeRulesInput) (*elasticloadbalancingv2.DescribeRulesOutput, error) CreateRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.CreateRuleInput) (*elasticloadbalancingv2.CreateRuleOutput, error) DeleteRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.DeleteRuleInput) (*elasticloadbalancingv2.DeleteRuleOutput, error) - ModifyRuleWithContext(ctx context.Context, inout *elasticloadbalancingv2.ModifyRuleInput) (*elasticloadbalancingv2.ModifyRuleOutput, error) + ModifyRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.ModifyRuleInput) (*elasticloadbalancingv2.ModifyRuleOutput, error) + SetRulePrioritiesWithContext(ctx context.Context, input *elasticloadbalancingv2.SetRulePrioritiesInput) (*elasticloadbalancingv2.SetRulePrioritiesOutput, error) RegisterTargetsWithContext(ctx context.Context, input *elasticloadbalancingv2.RegisterTargetsInput) (*elasticloadbalancingv2.RegisterTargetsOutput, error) DeregisterTargetsWithContext(ctx context.Context, input *elasticloadbalancingv2.DeregisterTargetsInput) (*elasticloadbalancingv2.DeregisterTargetsOutput, error) DescribeTrustStoresWithContext(ctx context.Context, input *elasticloadbalancingv2.DescribeTrustStoresInput) (*elasticloadbalancingv2.DescribeTrustStoresOutput, error) @@ -163,6 +164,14 @@ func (c *elbv2Client) DeleteRuleWithContext(ctx context.Context, input *elasticl return client.DeleteRule(ctx, input) } +func (c *elbv2Client) SetRulePrioritiesWithContext(ctx context.Context, input *elasticloadbalancingv2.SetRulePrioritiesInput) (*elasticloadbalancingv2.SetRulePrioritiesOutput, error) { + client, err := c.awsClientsProvider.GetELBv2Client(ctx, "SetRulePriorities") + if err != nil { + return nil, err + } + return client.SetRulePriorities(ctx, input) +} + func (c *elbv2Client) CreateRuleWithContext(ctx context.Context, input *elasticloadbalancingv2.CreateRuleInput) (*elasticloadbalancingv2.CreateRuleOutput, error) { client, err := c.getClient(ctx, "CreateRule") if err != nil { diff --git a/pkg/aws/services/elbv2_mocks.go b/pkg/aws/services/elbv2_mocks.go index 1f427dd56..e04343b2a 100644 --- a/pkg/aws/services/elbv2_mocks.go +++ b/pkg/aws/services/elbv2_mocks.go @@ -621,6 +621,21 @@ func (mr *MockELBV2MockRecorder) SetIpAddressTypeWithContext(arg0, arg1 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetIpAddressTypeWithContext", reflect.TypeOf((*MockELBV2)(nil).SetIpAddressTypeWithContext), arg0, arg1) } +// SetRulePrioritiesWithContext mocks base method. +func (m *MockELBV2) SetRulePrioritiesWithContext(arg0 context.Context, arg1 *elasticloadbalancingv2.SetRulePrioritiesInput) (*elasticloadbalancingv2.SetRulePrioritiesOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetRulePrioritiesWithContext", arg0, arg1) + ret0, _ := ret[0].(*elasticloadbalancingv2.SetRulePrioritiesOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetRulePrioritiesWithContext indicates an expected call of SetRulePrioritiesWithContext. +func (mr *MockELBV2MockRecorder) SetRulePrioritiesWithContext(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRulePrioritiesWithContext", reflect.TypeOf((*MockELBV2)(nil).SetRulePrioritiesWithContext), arg0, arg1) +} + // SetSecurityGroupsWithContext mocks base method. func (m *MockELBV2) SetSecurityGroupsWithContext(arg0 context.Context, arg1 *elasticloadbalancingv2.SetSecurityGroupsInput) (*elasticloadbalancingv2.SetSecurityGroupsOutput, error) { m.ctrl.T.Helper() diff --git a/pkg/deploy/elbv2/listener_rule_manager.go b/pkg/deploy/elbv2/listener_rule_manager.go index 4efe82d72..45803b88f 100644 --- a/pkg/deploy/elbv2/listener_rule_manager.go +++ b/pkg/deploy/elbv2/listener_rule_manager.go @@ -6,24 +6,29 @@ import ( elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/go-logr/logr" - "github.com/google/go-cmp/cmp" "github.com/pkg/errors" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" "sigs.k8s.io/aws-load-balancer-controller/pkg/config" "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking" - elbv2equality "sigs.k8s.io/aws-load-balancer-controller/pkg/equality/elbv2" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/runtime" + "slices" + "sort" + "strconv" "time" ) // ListenerRuleManager is responsible for create/update/delete ListenerRule resources. type ListenerRuleManager interface { - Create(ctx context.Context, resLR *elbv2model.ListenerRule) (elbv2model.ListenerRuleStatus, error) + Create(ctx context.Context, resLR *elbv2model.ListenerRule, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) - Update(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) + UpdateRules(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) + + UpdateRulesTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) Delete(ctx context.Context, sdkLR ListenerRuleWithTags) error + + SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair, unmatchedSDKLRs []ListenerRuleWithTags) error } // NewDefaultListenerRuleManager constructs new defaultListenerRuleManager. @@ -54,8 +59,8 @@ type defaultListenerRuleManager struct { waitLSExistenceTimeout time.Duration } -func (m *defaultListenerRuleManager) Create(ctx context.Context, resLR *elbv2model.ListenerRule) (elbv2model.ListenerRuleStatus, error) { - req, err := buildSDKCreateListenerRuleInput(resLR.Spec, m.featureGates) +func (m *defaultListenerRuleManager) Create(ctx context.Context, resLR *elbv2model.ListenerRule, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) { + req, err := buildSDKCreateListenerRuleInput(resLR.Spec, desiredActionsAndConditions, m.featureGates) if err != nil { return elbv2model.ListenerRuleStatus{}, err } @@ -90,13 +95,17 @@ func (m *defaultListenerRuleManager) Create(ctx context.Context, resLR *elbv2mod return buildResListenerRuleStatus(sdkLR), nil } -func (m *defaultListenerRuleManager) Update(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) { +func (m *defaultListenerRuleManager) UpdateRulesTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) (elbv2model.ListenerRuleStatus, error) { if m.featureGates.Enabled(config.ListenerRulesTagging) { if err := m.updateSDKListenerRuleWithTags(ctx, resLR, sdkLR); err != nil { return elbv2model.ListenerRuleStatus{}, err } } - if err := m.updateSDKListenerRuleWithSettings(ctx, resLR, sdkLR); err != nil { + return buildResListenerRuleStatus(sdkLR), nil +} + +func (m *defaultListenerRuleManager) UpdateRules(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) (elbv2model.ListenerRuleStatus, error) { + if err := m.updateSDKListenerRuleWithSettings(ctx, resLR, sdkLR, desiredActionsAndConditions); err != nil { return elbv2model.ListenerRuleStatus{}, err } return buildResListenerRuleStatus(sdkLR), nil @@ -116,15 +125,28 @@ func (m *defaultListenerRuleManager) Delete(ctx context.Context, sdkLR ListenerR return nil } -func (m *defaultListenerRuleManager) updateSDKListenerRuleWithSettings(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) error { - desiredActions, err := buildSDKActions(resLR.Spec.Actions, m.featureGates) - if err != nil { +func (m *defaultListenerRuleManager) SetRulePriorities(ctx context.Context, matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair, unmatchedSDKLRs []ListenerRuleWithTags) error { + req := buildSDKSetRulePrioritiesInput(matchedResAndSDKLRsBySettings, unmatchedSDKLRs) + m.logger.Info("setting listener rule priorities", + "rule priority pairs", req.RulePriorities) + if _, err := m.elbv2Client.SetRulePrioritiesWithContext(ctx, req); err != nil { return err } - desiredConditions := buildSDKRuleConditions(resLR.Spec.Conditions) - if !isSDKListenerRuleSettingsDrifted(resLR.Spec, sdkLR, desiredActions, desiredConditions) { - return nil - } + m.logger.Info("setting listener rule priorities complete", + "rule priority pairs", req.RulePriorities) + return nil +} + +func (m *defaultListenerRuleManager) updateSDKListenerRuleWithTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) error { + desiredTags := m.trackingProvider.ResourceTags(resLR.Stack(), resLR, resLR.Spec.Tags) + return m.taggingManager.ReconcileTags(ctx, awssdk.ToString(sdkLR.ListenerRule.RuleArn), desiredTags, + WithCurrentTags(sdkLR.Tags), + WithIgnoredTagKeys(m.externalManagedTags)) +} + +func (m *defaultListenerRuleManager) updateSDKListenerRuleWithSettings(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair) error { + desiredActions := desiredActionsAndConditions.desiredActions + desiredConditions := desiredActionsAndConditions.desiredConditions req := buildSDKModifyListenerRuleInput(resLR.Spec, desiredActions, desiredConditions) req.RuleArn = sdkLR.ListenerRule.RuleArn @@ -142,27 +164,7 @@ func (m *defaultListenerRuleManager) updateSDKListenerRuleWithSettings(ctx conte return nil } -func (m *defaultListenerRuleManager) updateSDKListenerRuleWithTags(ctx context.Context, resLR *elbv2model.ListenerRule, sdkLR ListenerRuleWithTags) error { - desiredTags := m.trackingProvider.ResourceTags(resLR.Stack(), resLR, resLR.Spec.Tags) - return m.taggingManager.ReconcileTags(ctx, awssdk.ToString(sdkLR.ListenerRule.RuleArn), desiredTags, - WithCurrentTags(sdkLR.Tags), - WithIgnoredTagKeys(m.externalManagedTags)) -} - -func isSDKListenerRuleSettingsDrifted(lrSpec elbv2model.ListenerRuleSpec, sdkLR ListenerRuleWithTags, - desiredActions []elbv2types.Action, desiredConditions []elbv2types.RuleCondition) bool { - - if !cmp.Equal(desiredActions, sdkLR.ListenerRule.Actions, elbv2equality.CompareOptionForActions()) { - return true - } - if !cmp.Equal(desiredConditions, sdkLR.ListenerRule.Conditions, elbv2equality.CompareOptionForRuleConditions()) { - return true - } - - return false -} - -func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, featureGates config.FeatureGates) (*elbv2sdk.CreateRuleInput, error) { +func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, desiredActionsAndConditions *resLRDesiredActionsAndConditionsPair, featureGates config.FeatureGates) (*elbv2sdk.CreateRuleInput, error) { ctx := context.Background() lsARN, err := lrSpec.ListenerARN.Resolve(ctx) if err != nil { @@ -171,12 +173,20 @@ func buildSDKCreateListenerRuleInput(lrSpec elbv2model.ListenerRuleSpec, feature sdkObj := &elbv2sdk.CreateRuleInput{} sdkObj.ListenerArn = awssdk.String(lsARN) sdkObj.Priority = awssdk.Int32(lrSpec.Priority) - actions, err := buildSDKActions(lrSpec.Actions, featureGates) - if err != nil { - return nil, err + if desiredActionsAndConditions != nil && desiredActionsAndConditions.desiredActions != nil { + sdkObj.Actions = desiredActionsAndConditions.desiredActions + } else { + actions, err := buildSDKActions(lrSpec.Actions, featureGates) + if err != nil { + return nil, err + } + sdkObj.Actions = actions + } + if desiredActionsAndConditions != nil && desiredActionsAndConditions.desiredConditions != nil { + sdkObj.Conditions = desiredActionsAndConditions.desiredConditions + } else { + sdkObj.Conditions = buildSDKRuleConditions(lrSpec.Conditions) } - sdkObj.Actions = actions - sdkObj.Conditions = buildSDKRuleConditions(lrSpec.Conditions) return sdkObj, nil } @@ -187,6 +197,41 @@ func buildSDKModifyListenerRuleInput(_ elbv2model.ListenerRuleSpec, desiredActio return sdkObj } +func buildSDKSetRulePrioritiesInput(matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair, unmatchedSDKLRs []ListenerRuleWithTags) *elbv2sdk.SetRulePrioritiesInput { + var rulePriorities []elbv2types.RulePriorityPair + var lastAvailablePriority int32 = 50000 + var sdkLRs []ListenerRuleWithTags + + // Sort the unmatched existing SDK rules based on their priority to be pushed down in same order + sort.Slice(unmatchedSDKLRs, func(i, j int) bool { + priorityI, _ := strconv.Atoi(awssdk.ToString(unmatchedSDKLRs[i].ListenerRule.Priority)) + priorityJ, _ := strconv.Atoi(awssdk.ToString(unmatchedSDKLRs[j].ListenerRule.Priority)) + return priorityI < priorityJ + }) + // Push down all the unmatched existing SDK rules on load balancer so that updated rules can take their place + for _, sdkLR := range slices.Backward(unmatchedSDKLRs) { + sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(lastAvailablePriority))) + sdkLRs = append(sdkLRs, sdkLR) + lastAvailablePriority-- + } + //Re-Prioritize matched rules by settings + for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { + resAndSDKLR.sdkLR.ListenerRule.Priority = awssdk.String(strconv.Itoa(int(resAndSDKLR.resLR.Spec.Priority))) + sdkLRs = append(sdkLRs, resAndSDKLR.sdkLR) + } + for _, sdkLR := range sdkLRs { + p, _ := strconv.ParseInt(awssdk.ToString(sdkLR.ListenerRule.Priority), 10, 32) + rulePriorityPair := elbv2types.RulePriorityPair{ + RuleArn: sdkLR.ListenerRule.RuleArn, + Priority: awssdk.Int32(int32(p)), + } + rulePriorities = append(rulePriorities, rulePriorityPair) + } + sdkObj := &elbv2sdk.SetRulePrioritiesInput{ + RulePriorities: rulePriorities, + } + return sdkObj +} func buildResListenerRuleStatus(sdkLR ListenerRuleWithTags) elbv2model.ListenerRuleStatus { return elbv2model.ListenerRuleStatus{ RuleARN: awssdk.ToString(sdkLR.ListenerRule.RuleArn), diff --git a/pkg/deploy/elbv2/listener_rule_manager_test.go b/pkg/deploy/elbv2/listener_rule_manager_test.go new file mode 100644 index 000000000..c8b0dcc74 --- /dev/null +++ b/pkg/deploy/elbv2/listener_rule_manager_test.go @@ -0,0 +1,422 @@ +package elbv2 + +import ( + awssdk "github.com/aws/aws-sdk-go-v2/aws" + elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + coremodel "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" + "testing" +) + +func Test_buildSDKSetRulePrioritiesInput(t *testing.T) { + stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) + type args struct { + matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair + unmatchedSDKLRs []ListenerRuleWithTags + } + tests := []struct { + name string + args args + want *elbv2sdk.SetRulePrioritiesInput + }{ + { + name: "Only re-prioritize matched rules by settings", + args: args{ + matchedResAndSDKLRsBySettings: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + unmatchedSDKLRs: []ListenerRuleWithTags{}, + }, + want: &elbv2sdk.SetRulePrioritiesInput{ + RulePriorities: []elbv2types.RulePriorityPair{ + { + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.Int32(3), + }, + { + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.Int32(1), + }, + }, + }, + }, + { + name: "push down unmatched sdk rules in order", + args: args{ + matchedResAndSDKLRsBySettings: []resAndSDKListenerRulePair{}, + unmatchedSDKLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + }, + want: &elbv2sdk.SetRulePrioritiesInput{ + RulePriorities: []elbv2types.RulePriorityPair{ + { + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.Int32(50000), + }, + { + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.Int32(49999), + }, + }, + }, + }, + { + name: "Re-prioritize matched rules by settings and also push down unmatched sdk rules in order", + args: args{ + matchedResAndSDKLRsBySettings: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + unmatchedSDKLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-35"), + Priority: awssdk.String("35"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo35-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo35"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-16"), + Priority: awssdk.String("16"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo16-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo16"}, + }, + }, + }, + }, + }, + }, + }, + want: &elbv2sdk.SetRulePrioritiesInput{ + RulePriorities: []elbv2types.RulePriorityPair{ + { + RuleArn: awssdk.String("arn-35"), + Priority: awssdk.Int32(50000), + }, + { + RuleArn: awssdk.String("arn-16"), + Priority: awssdk.Int32(49999), + }, + { + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.Int32(3), + }, + { + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.Int32(1), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + got := buildSDKSetRulePrioritiesInput(tt.args.matchedResAndSDKLRsBySettings, tt.args.unmatchedSDKLRs) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/deploy/elbv2/listener_rule_synthesizer.go b/pkg/deploy/elbv2/listener_rule_synthesizer.go index 99a940936..4c6eef109 100644 --- a/pkg/deploy/elbv2/listener_rule_synthesizer.go +++ b/pkg/deploy/elbv2/listener_rule_synthesizer.go @@ -3,9 +3,13 @@ package elbv2 import ( "context" awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/go-logr/logr" + "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" + "sigs.k8s.io/aws-load-balancer-controller/pkg/config" + elbv2equality "sigs.k8s.io/aws-load-balancer-controller/pkg/equality/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "strconv" @@ -13,11 +17,12 @@ import ( // NewListenerRuleSynthesizer constructs new listenerRuleSynthesizer. func NewListenerRuleSynthesizer(elbv2Client services.ELBV2, taggingManager TaggingManager, - lrManager ListenerRuleManager, logger logr.Logger, stack core.Stack) *listenerRuleSynthesizer { + lrManager ListenerRuleManager, logger logr.Logger, featureGates config.FeatureGates, stack core.Stack) *listenerRuleSynthesizer { return &listenerRuleSynthesizer{ elbv2Client: elbv2Client, lrManager: lrManager, logger: logger, + featureGates: featureGates, taggingManager: taggingManager, stack: stack, } @@ -26,6 +31,7 @@ func NewListenerRuleSynthesizer(elbv2Client services.ELBV2, taggingManager Taggi type listenerRuleSynthesizer struct { elbv2Client services.ELBV2 lrManager ListenerRuleManager + featureGates config.FeatureGates logger logr.Logger taggingManager TaggingManager @@ -61,26 +67,60 @@ func (s *listenerRuleSynthesizer) PostSynthesize(ctx context.Context) error { } func (s *listenerRuleSynthesizer) synthesizeListenerRulesOnListener(ctx context.Context, lsARN string, resLRs []*elbv2model.ListenerRule) error { + // Find existing listener rules on the load balancer sdkLRs, err := s.findSDKListenersRulesOnLS(ctx, lsARN) if err != nil { return err } - - matchedResAndSDKLRs, unmatchedResLRs, unmatchedSDKLRs := matchResAndSDKListenerRules(resLRs, sdkLRs) - for _, sdkLR := range unmatchedSDKLRs { - if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + // Build desired actions and conditions pairs for resource listener rules. + resLRDesiredActionsAndConditionsPairs := make(map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair, len(resLRs)) + for _, resLR := range resLRs { + resLRDesiredActionsAndConditionsPair, err := buildResLRDesiredActionsAndConditionsPair(resLR, s.featureGates) + if err != nil { + return err + } + resLRDesiredActionsAndConditionsPairs[resLR] = resLRDesiredActionsAndConditionsPair + } + // matchedResAndSDKLRsBySettings : A slice of matched resLR and SDKLR rule pairs that have matching settings like actions and conditions. These needs to be only reprioratized to their corresponding priorities + // matchedResAndSDKLRsByPriority : A slice of matched resLR and SDKLR rule pairs that have matching priorities but not settings like actions and conditions. These needs to be modified in place to avoid any 503 errors + // unmatchedResLRs : A slice of resLR that do not have a corresponding match in the sdkLRs. These rules need to be created on the load balancer. + // unmatchedSDKLRs : A slice of sdkLRs that do not have a corresponding match in the resLRs. These rules need to be first pushed down in the priority so that the new rules are created/modified at higher priority first and then deleted from the load balancer to avoid any 503 errors. + matchedResAndSDKLRsBySettings, matchedResAndSDKLRsByPriority, unmatchedResLRs, unmatchedSDKLRs, err := s.matchResAndSDKListenerRules(resLRs, sdkLRs, resLRDesiredActionsAndConditionsPairs) + if err != nil { + return err + } + // Re-prioritize matched listener rules. + if len(matchedResAndSDKLRsBySettings) > 0 { + err := s.lrManager.SetRulePriorities(ctx, matchedResAndSDKLRsBySettings, unmatchedSDKLRs) + if err != nil { + return err + } + } + // Modify rules in place which are matching priorities + for _, resAndSDKLR := range matchedResAndSDKLRsByPriority { + lsStatus, err := s.lrManager.UpdateRules(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR, resLRDesiredActionsAndConditionsPairs[resAndSDKLR.resLR]) + if err != nil { return err } + resAndSDKLR.resLR.SetStatus(lsStatus) } + // Create all the new rules on the LB for _, resLR := range unmatchedResLRs { - lrStatus, err := s.lrManager.Create(ctx, resLR) + lrStatus, err := s.lrManager.Create(ctx, resLR, resLRDesiredActionsAndConditionsPairs[resLR]) if err != nil { return err } resLR.SetStatus(lrStatus) } - for _, resAndSDKLR := range matchedResAndSDKLRs { - lsStatus, err := s.lrManager.Update(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR) + // Delete all unmatched sdk LRs which were pushed down as new rules are either modified or created at higher priority + for _, sdkLR := range unmatchedSDKLRs { + if err := s.lrManager.Delete(ctx, sdkLR); err != nil { + return err + } + } + // Update existing listener rules on the load balancer for their tags + for _, resAndSDKLR := range matchedResAndSDKLRsBySettings { + lsStatus, err := s.lrManager.UpdateRulesTags(ctx, resAndSDKLR.resLR, resAndSDKLR.sdkLR) if err != nil { return err } @@ -110,31 +150,62 @@ type resAndSDKListenerRulePair struct { sdkLR ListenerRuleWithTags } -func matchResAndSDKListenerRules(resLRs []*elbv2model.ListenerRule, sdkLRs []ListenerRuleWithTags) ([]resAndSDKListenerRulePair, []*elbv2model.ListenerRule, []ListenerRuleWithTags) { - var matchedResAndSDKLRs []resAndSDKListenerRulePair +type resLRDesiredActionsAndConditionsPair struct { + desiredActions []types.Action + desiredConditions []types.RuleCondition +} + +func (s *listenerRuleSynthesizer) matchResAndSDKListenerRules(resLRs []*elbv2model.ListenerRule, unmatchedSDKLRs []ListenerRuleWithTags, resLRDesiredActionsAndConditionsPairs map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair) ([]resAndSDKListenerRulePair, []resAndSDKListenerRulePair, []*elbv2model.ListenerRule, []ListenerRuleWithTags, error) { + var matchedResAndSDKLRsBySettings []resAndSDKListenerRulePair + var matchedResAndSDKLRsByPriority []resAndSDKListenerRulePair var unmatchedResLRs []*elbv2model.ListenerRule - var unmatchedSDKLRs []ListenerRuleWithTags + var resLRsToCreate []*elbv2model.ListenerRule + var sdkLRsToDelete []ListenerRuleWithTags - resLRByPriority := mapResListenerRuleByPriority(resLRs) - sdkLRByPriority := mapSDKListenerRuleByPriority(sdkLRs) + for _, resLR := range resLRs { + resLRDesiredActionsAndConditionsPair := resLRDesiredActionsAndConditionsPairs[resLR] + found := false + for i := 0; i < len(unmatchedSDKLRs); i++ { + sdkLR := unmatchedSDKLRs[i] + if cmp.Equal(resLRDesiredActionsAndConditionsPair.desiredActions, sdkLR.ListenerRule.Actions, elbv2equality.CompareOptionForActions()) && + cmp.Equal(resLRDesiredActionsAndConditionsPair.desiredConditions, sdkLR.ListenerRule.Conditions, elbv2equality.CompareOptionForRuleConditions()) { + sdkLRPriority, _ := strconv.ParseInt(awssdk.ToString(sdkLR.ListenerRule.Priority), 10, 64) + if resLR.Spec.Priority != int32(sdkLRPriority) { + matchedResAndSDKLRsBySettings = append(matchedResAndSDKLRsBySettings, resAndSDKListenerRulePair{ + resLR: resLR, + sdkLR: sdkLR, + }) + } + unmatchedSDKLRs = append(unmatchedSDKLRs[:i], unmatchedSDKLRs[i+1:]...) + i-- + found = true + break + } + } + if !found { + unmatchedResLRs = append(unmatchedResLRs, resLR) + } + } + + resLRByPriority := mapResListenerRuleByPriority(unmatchedResLRs) + sdkLRByPriority := mapSDKListenerRuleByPriority(unmatchedSDKLRs) resLRPriorities := sets.Int32KeySet(resLRByPriority) sdkLRPriorities := sets.Int32KeySet(sdkLRByPriority) for _, priority := range resLRPriorities.Intersection(sdkLRPriorities).List() { resLR := resLRByPriority[priority] sdkLR := sdkLRByPriority[priority] - matchedResAndSDKLRs = append(matchedResAndSDKLRs, resAndSDKListenerRulePair{ + matchedResAndSDKLRsByPriority = append(matchedResAndSDKLRsByPriority, resAndSDKListenerRulePair{ resLR: resLR, sdkLR: sdkLR, }) } for _, priority := range resLRPriorities.Difference(sdkLRPriorities).List() { - unmatchedResLRs = append(unmatchedResLRs, resLRByPriority[priority]) + resLRsToCreate = append(resLRsToCreate, resLRByPriority[priority]) } for _, priority := range sdkLRPriorities.Difference(resLRPriorities).List() { - unmatchedSDKLRs = append(unmatchedSDKLRs, sdkLRByPriority[priority]) + sdkLRsToDelete = append(sdkLRsToDelete, sdkLRByPriority[priority]) } - - return matchedResAndSDKLRs, unmatchedResLRs, unmatchedSDKLRs + return matchedResAndSDKLRsBySettings, matchedResAndSDKLRsByPriority, resLRsToCreate, sdkLRsToDelete, nil } func mapResListenerRuleByPriority(resLRs []*elbv2model.ListenerRule) map[int32]*elbv2model.ListenerRule { diff --git a/pkg/deploy/elbv2/listener_rule_synthesizer_test.go b/pkg/deploy/elbv2/listener_rule_synthesizer_test.go new file mode 100644 index 000000000..3a654fb21 --- /dev/null +++ b/pkg/deploy/elbv2/listener_rule_synthesizer_test.go @@ -0,0 +1,1280 @@ +package elbv2 + +import ( + awssdk "github.com/aws/aws-sdk-go-v2/aws" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services" + "sigs.k8s.io/aws-load-balancer-controller/pkg/config" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + coremodel "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" + "testing" +) + +func Test_matchResAndSDKListenerRules(t *testing.T) { + stack := coremodel.NewDefaultStack(coremodel.StackID{Namespace: "namespace", Name: "name"}) + type args struct { + resLRs []*elbv2model.ListenerRule + sdkLRs []ListenerRuleWithTags + } + tests := []struct { + name string + args args + want []resAndSDKListenerRulePair + want1 []resAndSDKListenerRulePair + want2 []*elbv2model.ListenerRule + want3 []ListenerRuleWithTags + wantErr error + }{ + { + name: "all listener rules has match", + args: args{ + resLRs: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + sdkLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want: nil, + want1: nil, + want2: nil, + want3: nil, + wantErr: nil, + }, + { + name: "all listener rules settings has match but not priority", + args: args{ + resLRs: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + sdkLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want1: nil, + want2: nil, + want3: nil, + wantErr: nil, + }, + { + name: "some listener rules settings has match but not priority, some listener rules priority match but not settings", + args: args{ + resLRs: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + sdkLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + }, + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want1: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + }, + }, + want2: nil, + want3: nil, + wantErr: nil, + }, + { + name: "some listener rules settings has match but not priority, some listener rules priority match but not settings(requires modification), some needs to be created and some sdk needs to be deleted", + args: args{ + resLRs: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 1, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 2, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo5-updated-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo5"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 4, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo6-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo6"}, + }, + }, + }, + }, + }, + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 5, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + }, + sdkLRs: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-1"), + Priority: awssdk.String("1"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo1-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo1"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-2"), + Priority: awssdk.String("2"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo2-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo2"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-4"), + Priority: awssdk.String("4"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-5"), + Priority: awssdk.String("5"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo5-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo5"}, + }, + }, + }, + }, + }, + }, + }, + want: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 5, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-4"), + Priority: awssdk.String("4"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo4-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo4"}, + }, + }, + }, + }, + }, + }, + }, + want1: []resAndSDKListenerRulePair{ + { + resLR: &elbv2model.ListenerRule{ + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 3, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo5-updated-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo5"}, + }, + }, + }, + }, + }, + sdkLR: ListenerRuleWithTags{ + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-3"), + Priority: awssdk.String("3"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo3-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo3"}, + }, + }, + }, + }, + }, + }, + }, + want2: []*elbv2model.ListenerRule{ + { + ResourceMeta: coremodel.NewResourceMeta(stack, "AWS::ElasticLoadBalancingV2::ListenerRule", "id-1"), + Spec: elbv2model.ListenerRuleSpec{ + Priority: 4, + Actions: []elbv2model.Action{ + { + Type: "forward", + ForwardConfig: &elbv2model.ForwardActionConfig{ + TargetGroups: []elbv2model.TargetGroupTuple{ + { + TargetGroupARN: core.LiteralStringToken("foo6-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2model.RuleCondition{ + { + Field: "path-pattern", + PathPatternConfig: &elbv2model.PathPatternConditionConfig{ + Values: []string{"/foo6"}, + }, + }, + }, + }, + }, + }, + want3: []ListenerRuleWithTags{ + { + ListenerRule: &elbv2types.Rule{ + RuleArn: awssdk.String("arn-5"), + Priority: awssdk.String("5"), + Actions: []elbv2types.Action{ + { + Type: elbv2types.ActionTypeEnumForward, + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ + { + TargetGroupArn: awssdk.String("foo5-tg"), + }, + }, + }, + }, + }, + Conditions: []elbv2types.RuleCondition{ + { + Field: awssdk.String("path-pattern"), + PathPatternConfig: &elbv2types.PathPatternConditionConfig{ + Values: []string{"/foo5"}, + }, + }, + }, + }, + }, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + elbv2Client := services.NewMockELBV2(ctrl) + featureGates := config.NewFeatureGates() + s := &listenerRuleSynthesizer{ + elbv2Client: elbv2Client, + featureGates: featureGates, + } + resLRDesiredActionsAndConditionsPairs := make(map[*elbv2model.ListenerRule]*resLRDesiredActionsAndConditionsPair, len(tt.args.resLRs)) + for _, resLR := range tt.args.resLRs { + resLRDesiredActionsAndConditionsPair, _ := buildResLRDesiredActionsAndConditionsPair(resLR, featureGates) + resLRDesiredActionsAndConditionsPairs[resLR] = resLRDesiredActionsAndConditionsPair + } + got, got1, got2, got3, err := s.matchResAndSDKListenerRules(tt.args.resLRs, tt.args.sdkLRs, resLRDesiredActionsAndConditionsPairs) + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.want1, got1) + assert.Equal(t, tt.want2, got2) + assert.Equal(t, tt.want3, got3) + } + }) + } +} diff --git a/pkg/deploy/elbv2/listener_utils.go b/pkg/deploy/elbv2/listener_utils.go index 8a1d8df1f..5a2f8bd2f 100644 --- a/pkg/deploy/elbv2/listener_utils.go +++ b/pkg/deploy/elbv2/listener_utils.go @@ -16,6 +16,18 @@ const ( defaultWaitLSExistenceTimeout = 20 * time.Second ) +func buildResLRDesiredActionsAndConditionsPair(resLR *elbv2model.ListenerRule, featureGates config.FeatureGates) (*resLRDesiredActionsAndConditionsPair, error) { + desiredActions, err := buildSDKActions(resLR.Spec.Actions, featureGates) + if err != nil { + return nil, err + } + desiredConditions := buildSDKRuleConditions(resLR.Spec.Conditions) + return &resLRDesiredActionsAndConditionsPair{ + desiredActions: desiredActions, + desiredConditions: desiredConditions, + }, err +} + func buildSDKActions(modelActions []elbv2model.Action, featureGates config.FeatureGates) ([]elbv2types.Action, error) { var sdkActions []elbv2types.Action if len(modelActions) != 0 { diff --git a/pkg/deploy/stack_deployer.go b/pkg/deploy/stack_deployer.go index eddba59a9..b7787200d 100644 --- a/pkg/deploy/stack_deployer.go +++ b/pkg/deploy/stack_deployer.go @@ -93,7 +93,7 @@ func (d *defaultStackDeployer) Deploy(ctx context.Context, stack core.Stack) err elbv2.NewTargetGroupSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2TGManager, d.logger, d.featureGates, stack), elbv2.NewLoadBalancerSynthesizer(d.cloud.ELBV2(), d.trackingProvider, d.elbv2TaggingManager, d.elbv2LBManager, d.logger, d.featureGates, d.controllerConfig, stack), elbv2.NewListenerSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LSManager, d.logger, stack), - elbv2.NewListenerRuleSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LRManager, d.logger, stack), + elbv2.NewListenerRuleSynthesizer(d.cloud.ELBV2(), d.elbv2TaggingManager, d.elbv2LRManager, d.logger, d.featureGates, stack), elbv2.NewTargetGroupBindingSynthesizer(d.k8sClient, d.trackingProvider, d.elbv2TGBManager, d.logger, stack), }