@@ -8,18 +8,13 @@ import (
88 "k8s.io/apimachinery/pkg/util/validation/field"
99 "sigs.k8s.io/controller-runtime/pkg/client"
1010 v1 "sigs.k8s.io/gateway-api/apis/v1"
11+ "sigs.k8s.io/gateway-api/pkg/consts"
12+ "sigs.k8s.io/gateway-api/pkg/features"
1113
1214 "github.com/nginx/nginx-gateway-fabric/v2/internal/controller/state/conditions"
1315 "github.com/nginx/nginx-gateway-fabric/v2/internal/framework/kinds"
1416)
1517
16- const (
17- // BundleVersionAnnotation is the annotation on Gateway API CRDs that contains the installed version.
18- BundleVersionAnnotation = "gateway.networking.k8s.io/bundle-version"
19- // SupportedVersion is the supported version of the Gateway API CRDs.
20- SupportedVersion = "v1.4.0"
21- )
22-
2318var gatewayCRDs = map [string ]apiVersion {
2419 "gatewayclasses.gateway.networking.k8s.io" : {},
2520 "gateways.gateway.networking.k8s.io" : {},
@@ -40,6 +35,10 @@ type GatewayClass struct {
4035 Conditions []conditions.Condition
4136 // Valid shows whether the GatewayClass is valid.
4237 Valid bool
38+ // Experimental indicates whether experimental features are supported.
39+ // This is true only when BOTH the ExperimentalFeatures config flag is enabled
40+ // AND the Gateway API CRDs have the experimental channel annotation.
41+ Experimental bool
4342}
4443
4544// processedGatewayClasses holds the resources that belong to NGF.
@@ -82,6 +81,7 @@ func buildGatewayClass(
8281 gc * v1.GatewayClass ,
8382 nps map [types.NamespacedName ]* NginxProxy ,
8483 crdVersions map [types.NamespacedName ]* metav1.PartialObjectMetadata ,
84+ experimentalEnabled bool ,
8585) * GatewayClass {
8686 if gc == nil {
8787 return nil
@@ -92,13 +92,17 @@ func buildGatewayClass(
9292 np = getNginxProxyForGatewayClass (* gc .Spec .ParametersRef , nps )
9393 }
9494
95- conds , valid := validateGatewayClass (gc , np , crdVersions )
95+ conds , valid , crdExperimental := validateGatewayClass (gc , np , crdVersions )
96+
97+ // Experimental features are enabled only if both the config flag AND CRD channel are experimental
98+ experimental := experimentalEnabled && crdExperimental
9699
97100 return & GatewayClass {
98- Source : gc ,
99- NginxProxy : np ,
100- Valid : valid ,
101- Conditions : conds ,
101+ Source : gc ,
102+ NginxProxy : np ,
103+ Valid : valid ,
104+ Conditions : conds ,
105+ Experimental : experimental ,
102106 }
103107}
104108
@@ -144,14 +148,14 @@ func validateGatewayClass(
144148 gc * v1.GatewayClass ,
145149 npCfg * NginxProxy ,
146150 crdVersions map [types.NamespacedName ]* metav1.PartialObjectMetadata ,
147- ) ([]conditions.Condition , bool ) {
151+ ) ([]conditions.Condition , bool , bool ) {
148152 var conds []conditions.Condition
149153
150- supportedVersionConds , versionsValid := validateCRDVersions (crdVersions )
154+ supportedVersionConds , versionsValid , experimental := validateCRDVersions (crdVersions )
151155 conds = append (conds , supportedVersionConds ... )
152156
153157 if gc .Spec .ParametersRef == nil {
154- return conds , versionsValid
158+ return conds , versionsValid , experimental
155159 }
156160
157161 path := field .NewPath ("spec" ).Child ("parametersRef" )
@@ -160,7 +164,7 @@ func validateGatewayClass(
160164 // return early since parametersRef isn't valid
161165 if len (refConds ) > 0 {
162166 conds = append (conds , refConds ... )
163- return conds , versionsValid
167+ return conds , versionsValid , experimental
164168 }
165169
166170 if npCfg == nil {
@@ -171,7 +175,7 @@ func validateGatewayClass(
171175 field .NotFound (path .Child ("name" ), gc .Spec .ParametersRef .Name ).Error (),
172176 ),
173177 )
174- return conds , versionsValid
178+ return conds , versionsValid , experimental
175179 }
176180
177181 if ! npCfg .Valid {
@@ -181,10 +185,10 @@ func validateGatewayClass(
181185 conditions .NewGatewayClassRefInvalid (msg ),
182186 conditions .NewGatewayClassInvalidParameters (msg ),
183187 )
184- return conds , versionsValid
188+ return conds , versionsValid , experimental
185189 }
186190
187- return append (conds , conditions .NewGatewayClassResolvedRefs ()), versionsValid
191+ return append (conds , conditions .NewGatewayClassResolvedRefs ()), versionsValid , experimental
188192}
189193
190194var supportedParamKinds = map [string ]struct {}{
@@ -198,9 +202,9 @@ type apiVersion struct {
198202
199203func validateCRDVersions (
200204 crdMetadata map [types.NamespacedName ]* metav1.PartialObjectMetadata ,
201- ) (conds []conditions.Condition , valid bool ) {
202- installedAPIVersions := getBundleVersions (crdMetadata )
203- supportedAPIVersion := parseVersionString (SupportedVersion )
205+ ) (conds []conditions.Condition , valid bool , experimental bool ) {
206+ installedAPIVersions , channels := getBundleVersions (crdMetadata )
207+ supportedAPIVersion := parseVersionString (consts . BundleVersion )
204208
205209 var unsupported , bestEffort bool
206210
@@ -212,15 +216,23 @@ func validateCRDVersions(
212216 }
213217 }
214218
219+ // Check if any CRD is using experimental channel
220+ for _ , ch := range channels {
221+ if ch == features .FeatureChannelExperimental {
222+ experimental = true
223+ break
224+ }
225+ }
226+
215227 if unsupported {
216- return conditions .NewGatewayClassUnsupportedVersion (SupportedVersion ), false
228+ return conditions .NewGatewayClassUnsupportedVersion (consts . BundleVersion ), false , experimental
217229 }
218230
219231 if bestEffort {
220- return conditions .NewGatewayClassSupportedVersionBestEffort (SupportedVersion ), true
232+ return conditions .NewGatewayClassSupportedVersionBestEffort (consts . BundleVersion ), true , experimental
221233 }
222234
223- return nil , true
235+ return nil , true , experimental
224236}
225237
226238func parseVersionString (version string ) apiVersion {
@@ -245,15 +257,25 @@ func parseVersionString(version string) apiVersion {
245257 }
246258}
247259
248- func getBundleVersions (crdMetadata map [types.NamespacedName ]* metav1.PartialObjectMetadata ) []apiVersion {
260+ func getBundleVersions (
261+ crdMetadata map [types.NamespacedName ]* metav1.PartialObjectMetadata ,
262+ ) ([]apiVersion , []features.FeatureChannel ) {
249263 versions := make ([]apiVersion , 0 , len (gatewayCRDs ))
264+ channels := make ([]features.FeatureChannel , 0 , len (gatewayCRDs ))
250265
251266 for nsname , md := range crdMetadata {
252267 if _ , ok := gatewayCRDs [nsname .Name ]; ok {
253- bundleVersion := md .Annotations [BundleVersionAnnotation ]
268+ bundleVersion := md .Annotations [consts . BundleVersionAnnotation ]
254269 versions = append (versions , parseVersionString (bundleVersion ))
270+
271+ // Default to standard channel if annotation is missing
272+ ch := md .Annotations [consts .ChannelAnnotation ]
273+ if ch == "" {
274+ ch = string (features .FeatureChannelStandard )
275+ }
276+ channels = append (channels , features .FeatureChannel (ch ))
255277 }
256278 }
257279
258- return versions
280+ return versions , channels
259281}
0 commit comments