Skip to content

Commit 276eff3

Browse files
Merge branch 'kubernetes-sigs:main' into main
2 parents eee972d + 6c555ad commit 276eff3

File tree

9 files changed

+401
-32
lines changed

9 files changed

+401
-32
lines changed

docs/guide/gateway/gateway.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@ The LBC is built for Gateway API version v1.3.0.
2424

2525
## Configuration
2626

27-
By default, the LBC will _not_ listen to Gateway API CRDs. To enable support, specify the following feature flag(s) in the LBC deployment:
27+
The Load Balancer Controller (LBC) will attempt to detect Gateway CRDs.
28+
If they are present, the respective controller will be enabled.
29+
To explicitly disable these controllers, use the following feature gates:
2830

29-
* `NLBGatewayAPI`: For enabling L4 Routing
30-
* `ALBGatewayAPI`: For enabling L7 Routing
31+
```--feature-gates=NLBGatewayAPI=false,ALBGatewayAPI=false```
3132

32-
```
33-
- --feature-gates=NLBGatewayAPI=true,ALBGatewayAPI=true
34-
```
33+
For the NLB Gateway controller (Layer 4) to be enabled, ensure the following CRDs are installed:
34+
`Gateway`, `GatewayClass`, `TCPRoute`, `UDPRoute`, and `TLSRoute`
35+
36+
For the ALB Gateway controller (Layer 7) to be enabled, ensure the following CRDs are installed:
37+
`Gateway`, `GatewayClass`, `HTTPRoute`, and `GRPCRoute`
3538

3639
## Subnet tagging requirements
3740
See [Subnet Discovery](../../deploy/subnet_discovery.md) for details on configuring Elastic Load Balancing for public or private placement.

go.mod

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ require (
3333
github.com/spf13/pflag v1.0.7
3434
github.com/stretchr/testify v1.10.0
3535
go.uber.org/zap v1.27.0
36-
golang.org/x/net v0.42.0
36+
golang.org/x/net v0.51.0
3737
golang.org/x/time v0.12.0
3838
gomodules.xyz/jsonpatch/v2 v2.4.0
3939
google.golang.org/grpc v1.71.1
@@ -163,14 +163,14 @@ require (
163163
go.uber.org/multierr v1.11.0 // indirect
164164
go.yaml.in/yaml/v2 v2.4.2 // indirect
165165
go.yaml.in/yaml/v3 v3.0.3 // indirect
166-
golang.org/x/crypto v0.40.0 // indirect
167-
golang.org/x/mod v0.26.0 // indirect
166+
golang.org/x/crypto v0.48.0 // indirect
167+
golang.org/x/mod v0.32.0 // indirect
168168
golang.org/x/oauth2 v0.30.0 // indirect
169-
golang.org/x/sync v0.16.0 // indirect
170-
golang.org/x/sys v0.34.0 // indirect
171-
golang.org/x/term v0.33.0 // indirect
172-
golang.org/x/text v0.27.0 // indirect
173-
golang.org/x/tools v0.34.0 // indirect
169+
golang.org/x/sync v0.19.0 // indirect
170+
golang.org/x/sys v0.41.0 // indirect
171+
golang.org/x/term v0.40.0 // indirect
172+
golang.org/x/text v0.34.0 // indirect
173+
golang.org/x/tools v0.41.0 // indirect
174174
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
175175
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
176176
gopkg.in/inf.v0 v0.9.1 // indirect

go.sum

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -485,14 +485,14 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
485485
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
486486
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
487487
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
488-
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
489-
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
488+
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
489+
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
490490
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
491491
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
492492
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
493493
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
494-
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
495-
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
494+
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
495+
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
496496
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
497497
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
498498
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -504,17 +504,17 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
504504
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
505505
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
506506
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
507-
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
508-
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
507+
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
508+
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
509509
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
510510
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
511511
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
512512
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
513513
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
514514
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
515515
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
516-
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
517-
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
516+
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
517+
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
518518
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
519519
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
520520
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -536,18 +536,18 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
536536
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
537537
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
538538
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
539-
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
540-
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
539+
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
540+
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
541541
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
542542
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
543-
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
544-
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
543+
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
544+
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
545545
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
546546
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
547547
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
548548
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
549-
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
550-
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
549+
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
550+
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
551551
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
552552
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
553553
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -557,8 +557,8 @@ golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4f
557557
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
558558
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
559559
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
560-
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
561-
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
560+
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
561+
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
562562
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
563563
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
564564
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"os"
23+
2324
"sigs.k8s.io/aws-load-balancer-controller/pkg/aga"
2425
"sigs.k8s.io/aws-load-balancer-controller/pkg/certs"
2526
"sigs.k8s.io/aws-load-balancer-controller/pkg/shared_utils"
@@ -33,6 +34,7 @@ import (
3334
"sigs.k8s.io/aws-load-balancer-controller/pkg/deploy"
3435
gatewaypkg "sigs.k8s.io/aws-load-balancer-controller/pkg/gateway"
3536
gateway_constants "sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/constants"
37+
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/crddetect"
3638
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/referencecounter"
3739
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/routeutils"
3840
"sigs.k8s.io/aws-load-balancer-controller/pkg/inject/pod_readiness"
@@ -180,6 +182,10 @@ func main() {
180182
os.Exit(1)
181183
}
182184

185+
// Gateway API CRD auto-detection: check which CRDs are installed and disable
186+
// feature flags for missing CRDs before any controller setup reads them.
187+
crddetect.ApplyGatewayCRDDetection(clientSet.Discovery(), controllerCFG.FeatureGates, setupLog)
188+
183189
nlbGatewayEnabled := controllerCFG.FeatureGates.Enabled(config.NLBGatewayAPI)
184190
albGatewayEnabled := controllerCFG.FeatureGates.Enabled(config.ALBGatewayAPI)
185191
podInfoRepo := k8s.NewDefaultPodInfoRepo(clientSet.CoreV1().RESTClient(), controllerCFG.RuntimeConfig.WatchNamespace, controllerCFG.ServerIDInjectionConfig.EnvironmentVariableName, ctrl.Log)

pkg/config/feature_gates.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ func NewFeatureGates() FeatureGates {
7171
ALBSingleSubnet: false,
7272
SubnetDiscoveryByReachability: true,
7373
LBCapacityReservation: true,
74-
NLBGatewayAPI: false,
75-
ALBGatewayAPI: false,
74+
NLBGatewayAPI: true,
75+
ALBGatewayAPI: true,
7676
GlobalAcceleratorController: false,
7777
EnableTCPUDPListenerType: false,
7878
EnhancedDefaultBehavior: false,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package crddetect
2+
3+
import (
4+
"github.com/go-logr/logr"
5+
"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
6+
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
7+
)
8+
9+
const (
10+
// GatewayV1GroupVersion is the stable Gateway API group version.
11+
GatewayV1GroupVersion = "gateway.networking.k8s.io/v1"
12+
// GatewayV1Alpha2GroupVersion is the experimental Gateway API group version.
13+
GatewayV1Alpha2GroupVersion = "gateway.networking.k8s.io/v1alpha2"
14+
)
15+
16+
var (
17+
// StandardCRDKinds lists the CRD kinds required for ALB Gateway API support.
18+
StandardCRDKinds = []string{"Gateway", "GatewayClass", "HTTPRoute", "GRPCRoute"}
19+
// ExperimentalCRDKinds lists the CRD kinds required for NLB Gateway API support.
20+
ExperimentalCRDKinds = []string{"TCPRoute", "UDPRoute", "TLSRoute"}
21+
)
22+
23+
// ApplyGatewayCRDDetection checks for the presence of Gateway API CRDs and
24+
// disables the corresponding feature flags when required CRDs are missing.
25+
// It is called from main() after the k8s client is ready and before any
26+
// controller reads the feature flags.
27+
func ApplyGatewayCRDDetection(client k8s.DiscoveryClient, featureGates config.FeatureGates, logger logr.Logger) {
28+
standardResult := k8s.DetectCRDs(client, GatewayV1GroupVersion, StandardCRDKinds)
29+
experimentalResult := k8s.DetectCRDs(client, GatewayV1Alpha2GroupVersion, ExperimentalCRDKinds)
30+
31+
ApplyGatewayFeatureFlags(standardResult, experimentalResult, featureGates, logger)
32+
}
33+
34+
// ApplyGatewayFeatureFlags applies the Gateway CRD detection results to the
35+
// feature gates. Extracted for testability — accepts pre-computed results.
36+
func ApplyGatewayFeatureFlags(standardResult, experimentalResult k8s.CRDGroupResult, featureGates config.FeatureGates, logger logr.Logger) {
37+
if !standardResult.AllPresent {
38+
logger.Info("Disabling ALBGatewayAPI: missing standard Gateway API CRDs",
39+
"groupVersion", standardResult.GroupVersion,
40+
"missing", standardResult.MissingKinds)
41+
featureGates.Disable(config.ALBGatewayAPI)
42+
} else {
43+
logger.Info("All standard Gateway API CRDs detected, ALBGatewayAPI remains enabled",
44+
"groupVersion", standardResult.GroupVersion)
45+
}
46+
47+
if !standardResult.AllPresent || !experimentalResult.AllPresent {
48+
logger.Info("Disabling NLBGatewayAPI: missing required Gateway API CRDs",
49+
"missingStandard", standardResult.MissingKinds,
50+
"missingExperimental", experimentalResult.MissingKinds)
51+
featureGates.Disable(config.NLBGatewayAPI)
52+
} else {
53+
logger.Info("All required Gateway API CRDs detected, NLBGatewayAPI remains enabled",
54+
"standardGroupVersion", standardResult.GroupVersion,
55+
"experimentalGroupVersion", experimentalResult.GroupVersion)
56+
}
57+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package crddetect
2+
3+
import (
4+
"testing"
5+
6+
"github.com/go-logr/logr"
7+
"github.com/stretchr/testify/assert"
8+
"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
9+
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
10+
)
11+
12+
func TestApplyGatewayFeatureFlags_StandardMissing(t *testing.T) {
13+
fg := config.NewFeatureGates()
14+
assert.True(t, fg.Enabled(config.ALBGatewayAPI), "precondition: ALBGatewayAPI should default to true")
15+
assert.True(t, fg.Enabled(config.NLBGatewayAPI), "precondition: NLBGatewayAPI should default to true")
16+
17+
standardResult := k8s.CRDGroupResult{
18+
GroupVersion: "gateway.networking.k8s.io/v1",
19+
AllPresent: false,
20+
MissingKinds: []string{"HTTPRoute"},
21+
}
22+
experimentalResult := k8s.CRDGroupResult{
23+
GroupVersion: "gateway.networking.k8s.io/v1alpha2",
24+
AllPresent: true,
25+
}
26+
27+
ApplyGatewayFeatureFlags(standardResult, experimentalResult, fg, logr.Discard())
28+
29+
assert.False(t, fg.Enabled(config.ALBGatewayAPI), "ALBGatewayAPI should be disabled when standard CRDs are missing")
30+
assert.False(t, fg.Enabled(config.NLBGatewayAPI), "NLBGatewayAPI should be disabled when standard CRDs are missing")
31+
}
32+
33+
func TestApplyGatewayFeatureFlags_ExperimentalMissing_StandardPresent(t *testing.T) {
34+
fg := config.NewFeatureGates()
35+
36+
standardResult := k8s.CRDGroupResult{
37+
GroupVersion: "gateway.networking.k8s.io/v1",
38+
AllPresent: true,
39+
}
40+
experimentalResult := k8s.CRDGroupResult{
41+
GroupVersion: "gateway.networking.k8s.io/v1alpha2",
42+
AllPresent: false,
43+
MissingKinds: []string{"TCPRoute"},
44+
}
45+
46+
ApplyGatewayFeatureFlags(standardResult, experimentalResult, fg, logr.Discard())
47+
48+
assert.True(t, fg.Enabled(config.ALBGatewayAPI), "ALBGatewayAPI should remain enabled when standard CRDs are present")
49+
assert.False(t, fg.Enabled(config.NLBGatewayAPI), "NLBGatewayAPI should be disabled when experimental CRDs are missing")
50+
}
51+
52+
func TestApplyGatewayFeatureFlags_BothPresent(t *testing.T) {
53+
fg := config.NewFeatureGates()
54+
55+
standardResult := k8s.CRDGroupResult{
56+
GroupVersion: "gateway.networking.k8s.io/v1",
57+
AllPresent: true,
58+
}
59+
experimentalResult := k8s.CRDGroupResult{
60+
GroupVersion: "gateway.networking.k8s.io/v1alpha2",
61+
AllPresent: true,
62+
}
63+
64+
ApplyGatewayFeatureFlags(standardResult, experimentalResult, fg, logr.Discard())
65+
66+
assert.True(t, fg.Enabled(config.ALBGatewayAPI), "ALBGatewayAPI should remain enabled")
67+
assert.True(t, fg.Enabled(config.NLBGatewayAPI), "NLBGatewayAPI should remain enabled")
68+
}
69+
70+
func TestApplyGatewayFeatureFlags_BothMissing(t *testing.T) {
71+
fg := config.NewFeatureGates()
72+
73+
standardResult := k8s.CRDGroupResult{
74+
GroupVersion: "gateway.networking.k8s.io/v1",
75+
AllPresent: false,
76+
MissingKinds: []string{"Gateway", "GatewayClass"},
77+
}
78+
experimentalResult := k8s.CRDGroupResult{
79+
GroupVersion: "gateway.networking.k8s.io/v1alpha2",
80+
AllPresent: false,
81+
MissingKinds: []string{"TCPRoute", "UDPRoute"},
82+
}
83+
84+
ApplyGatewayFeatureFlags(standardResult, experimentalResult, fg, logr.Discard())
85+
86+
assert.False(t, fg.Enabled(config.ALBGatewayAPI), "ALBGatewayAPI should be disabled")
87+
assert.False(t, fg.Enabled(config.NLBGatewayAPI), "NLBGatewayAPI should be disabled")
88+
}
89+
90+
func TestApplyGatewayFeatureFlags_ExplicitlyEnabledStillDisabledWhenCRDsMissing(t *testing.T) {
91+
fg := config.NewFeatureGates()
92+
// Simulate explicit --feature-gates ALBGatewayAPI=true,NLBGatewayAPI=true
93+
fg.Enable(config.ALBGatewayAPI)
94+
fg.Enable(config.NLBGatewayAPI)
95+
96+
standardResult := k8s.CRDGroupResult{
97+
GroupVersion: "gateway.networking.k8s.io/v1",
98+
AllPresent: false,
99+
MissingKinds: []string{"GRPCRoute"},
100+
}
101+
experimentalResult := k8s.CRDGroupResult{
102+
GroupVersion: "gateway.networking.k8s.io/v1alpha2",
103+
AllPresent: true,
104+
}
105+
106+
ApplyGatewayFeatureFlags(standardResult, experimentalResult, fg, logr.Discard())
107+
108+
assert.False(t, fg.Enabled(config.ALBGatewayAPI), "ALBGatewayAPI should be force-disabled even when explicitly enabled")
109+
assert.False(t, fg.Enabled(config.NLBGatewayAPI), "NLBGatewayAPI should be force-disabled even when explicitly enabled")
110+
}

pkg/k8s/crd_detector.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package k8s
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
)
6+
7+
// DiscoveryClient is the interface for querying available API resources.
8+
// Satisfied by kubernetes.Clientset (via its Discovery().ServerResourcesForGroupVersion method).
9+
type DiscoveryClient interface {
10+
ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error)
11+
}
12+
13+
// CRDGroupResult holds the detection result for a single API group version.
14+
type CRDGroupResult struct {
15+
// GroupVersion is the API group version that was queried (e.g. "gateway.networking.k8s.io/v1").
16+
GroupVersion string
17+
// AllPresent is true when all required kinds were found.
18+
AllPresent bool
19+
// MissingKinds lists the resource kinds that were expected but not found.
20+
MissingKinds []string
21+
}
22+
23+
// DetectCRDs queries the Kubernetes API server for the specified resource kinds
24+
// in the given group version and reports which are present and which are missing.
25+
// On API error, it returns AllPresent: false with all required kinds as missing (fail-closed).
26+
func DetectCRDs(client DiscoveryClient, groupVersion string, requiredKinds []string) CRDGroupResult {
27+
result := CRDGroupResult{
28+
GroupVersion: groupVersion,
29+
}
30+
31+
resList, err := client.ServerResourcesForGroupVersion(groupVersion)
32+
if err != nil {
33+
result.AllPresent = false
34+
result.MissingKinds = make([]string, len(requiredKinds))
35+
copy(result.MissingKinds, requiredKinds)
36+
return result
37+
}
38+
39+
presentKinds := make(map[string]bool, len(resList.APIResources))
40+
for _, res := range resList.APIResources {
41+
presentKinds[res.Kind] = true
42+
}
43+
44+
for _, kind := range requiredKinds {
45+
if !presentKinds[kind] {
46+
result.MissingKinds = append(result.MissingKinds, kind)
47+
}
48+
}
49+
50+
result.AllPresent = len(result.MissingKinds) == 0
51+
return result
52+
}

0 commit comments

Comments
 (0)