@@ -3,8 +3,11 @@ package controller
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "strings"
6
7
7
8
corev1 "k8s.io/api/core/v1"
9
+ apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10
+ "k8s.io/apimachinery/pkg/api/errors"
8
11
"k8s.io/apimachinery/pkg/api/meta"
9
12
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10
13
"k8s.io/apimachinery/pkg/fields"
@@ -21,9 +24,11 @@ import (
21
24
"sigs.k8s.io/controller-runtime/pkg/reconcile"
22
25
apiv1 "sigs.k8s.io/gateway-api/apis/v1"
23
26
apiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
27
+ "sigs.k8s.io/gateway-api/pkg/consts"
24
28
25
29
sologatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1/kube/apis/gateway.solo.io/v1"
26
30
"github.com/solo-io/gloo/projects/gateway2/api/v1alpha1"
31
+ "github.com/solo-io/gloo/projects/gateway2/crds"
27
32
"github.com/solo-io/gloo/projects/gateway2/deployer"
28
33
"github.com/solo-io/gloo/projects/gateway2/extensions"
29
34
"github.com/solo-io/gloo/projects/gateway2/query"
@@ -62,13 +67,15 @@ func NewBaseGatewayController(ctx context.Context, cfg GatewayConfig) error {
62
67
controllerBuilder := & controllerBuilder {
63
68
cfg : cfg ,
64
69
reconciler : & controllerReconciler {
65
- cli : cfg .Mgr .GetClient (),
66
- scheme : cfg .Mgr .GetScheme (),
67
- kick : cfg .Kick ,
70
+ controllerName : cfg .ControllerName ,
71
+ cli : cfg .Mgr .GetClient (),
72
+ scheme : cfg .Mgr .GetScheme (),
73
+ kick : cfg .Kick ,
68
74
},
69
75
}
70
76
71
77
return run (ctx ,
78
+ controllerBuilder .watchCRDs ,
72
79
controllerBuilder .watchGwClass ,
73
80
controllerBuilder .watchGw ,
74
81
controllerBuilder .watchHttpRoute ,
@@ -244,6 +251,26 @@ func shouldIgnoreStatusChild(gvk schema.GroupVersionKind) bool {
244
251
return gvk .Kind == "Deployment"
245
252
}
246
253
254
+ // watchCRDs sets up a controller to watch for changes to specific Gateway API CRDs and triggers
255
+ // GatewayClass reconciliation if generation or annotations change.
256
+ func (c * controllerBuilder ) watchCRDs (ctx context.Context ) error {
257
+ return ctrl .NewControllerManagedBy (c .cfg .Mgr ).
258
+ For (& apiextv1.CustomResourceDefinition {}).
259
+ WithEventFilter (predicate .NewPredicateFuncs (func (object client.Object ) bool {
260
+ // We care about changes to the specific CRDs
261
+ crd , ok := object .(* apiextv1.CustomResourceDefinition )
262
+ if ! ok {
263
+ return false
264
+ }
265
+ return crds .IsSupported (crd .Name )
266
+ })).
267
+ WithEventFilter (predicate .Or (
268
+ predicate.AnnotationChangedPredicate {},
269
+ predicate.GenerationChangedPredicate {},
270
+ )).
271
+ Complete (reconcile .Func (c .reconciler .ReconcileCRDs ))
272
+ }
273
+
247
274
func (c * controllerBuilder ) watchGwClass (_ context.Context ) error {
248
275
return ctrl .NewControllerManagedBy (c .cfg .Mgr ).
249
276
WithEventFilter (predicate.GenerationChangedPredicate {}).
@@ -330,9 +357,10 @@ func (c *controllerBuilder) watchDirectResponses(_ context.Context) error {
330
357
}
331
358
332
359
type controllerReconciler struct {
333
- cli client.Client
334
- scheme * runtime.Scheme
335
- kick func (ctx context.Context )
360
+ controllerName string
361
+ cli client.Client
362
+ scheme * runtime.Scheme
363
+ kick func (ctx context.Context )
336
364
}
337
365
338
366
func (r * controllerReconciler ) ReconcileHttpListenerOptions (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
@@ -408,8 +436,39 @@ func (r *controllerReconciler) ReconcileReferenceGrants(ctx context.Context, req
408
436
return ctrl.Result {}, nil
409
437
}
410
438
439
+ func (r * controllerReconciler ) ReconcileCRDs (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
440
+ log := log .FromContext (ctx ).WithValues ("CustomResourceDefinition" , req .Name )
441
+
442
+ log .Info ("reconciling CustomResourceDefinition" )
443
+
444
+ var gatewayClassList apiv1.GatewayClassList
445
+ if err := r .cli .List (ctx , & gatewayClassList ); err != nil {
446
+ log .Error (err , "Failed to list GatewayClasses" )
447
+ return ctrl.Result {}, err
448
+ }
449
+
450
+ for _ , gatewayClass := range gatewayClassList .Items {
451
+ if gatewayClass .Spec .ControllerName == apiv1 .GatewayController (r .controllerName ) {
452
+ req := ctrl.Request {
453
+ NamespacedName : client.ObjectKey {
454
+ Name : gatewayClass .Name ,
455
+ },
456
+ }
457
+
458
+ if _ , err := r .ReconcileGatewayClasses (ctx , req ); err != nil {
459
+ log .Error (err , "Failed to reconcile GatewayClass" , "name" , gatewayClass .Name )
460
+ return ctrl.Result {}, err
461
+ }
462
+ }
463
+ }
464
+
465
+ log .Info ("Reconciled CustomResourceDefinition" )
466
+
467
+ return ctrl.Result {}, nil
468
+ }
469
+
411
470
func (r * controllerReconciler ) ReconcileGatewayClasses (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
412
- log := log .FromContext (ctx ).WithValues ("gwclass " , req .NamespacedName )
471
+ log := log .FromContext (ctx ).WithValues ("GatewayClass " , req .Name )
413
472
414
473
gwclass := & apiv1.GatewayClass {}
415
474
if err := r .cli .Get (ctx , req .NamespacedName , gwclass ); err != nil {
@@ -420,31 +479,74 @@ func (r *controllerReconciler) ReconcileGatewayClasses(ctx context.Context, req
420
479
return ctrl.Result {}, client .IgnoreNotFound (err )
421
480
}
422
481
423
- log .Info ("reconciling gateway class " )
482
+ log .Info ("Reconciling GatewayClass " )
424
483
425
- // mark it as accepted:
484
+ // Initialize the status conditions. No need to set LastTransitionTime since it's handled by SetStatusCondition.
485
+ msg := "Gateway API CRDs are a supported version"
426
486
acceptedCondition := metav1.Condition {
427
487
Type : string (apiv1 .GatewayClassConditionStatusAccepted ),
428
488
Status : metav1 .ConditionTrue ,
429
489
Reason : string (apiv1 .GatewayClassReasonAccepted ),
490
+ Message : msg ,
430
491
ObservedGeneration : gwclass .Generation ,
431
- // no need to set LastTransitionTime, it will be set automatically by SetStatusCondition
432
492
}
433
- meta .SetStatusCondition (& gwclass .Status .Conditions , acceptedCondition )
434
-
435
- // TODO: This should actually check the version of the CRDs in the cluster to be 100% sure
436
- supportedVersionCondition := metav1.Condition {
493
+ supportedCondition := metav1.Condition {
437
494
Type : string (apiv1 .GatewayClassConditionStatusSupportedVersion ),
438
495
Status : metav1 .ConditionTrue ,
439
- ObservedGeneration : gwclass .Generation ,
440
496
Reason : string (apiv1 .GatewayClassReasonSupportedVersion ),
497
+ Message : msg ,
498
+ ObservedGeneration : gwclass .Generation ,
499
+ }
500
+
501
+ // Check CRD versions
502
+ supported , err := r .checkCRDVersions (ctx )
503
+ if err != nil {
504
+ log .Error (err , "Failed to check CRD versions" )
505
+ return ctrl.Result {}, err
506
+ }
507
+
508
+ if ! supported {
509
+ // Update the values of status conditions
510
+ msg = fmt .Sprintf ("Unsupported Gateway API CRDs detected. Supported versions are: %s" ,
511
+ strings .Join (wellknown .SupportedVersions , ", " ))
512
+ acceptedCondition .Status = metav1 .ConditionFalse
513
+ acceptedCondition .Reason = string (apiv1 .GatewayClassReasonUnsupportedVersion )
514
+ acceptedCondition .Message = msg
515
+ supportedCondition .Status = metav1 .ConditionFalse
516
+ supportedCondition .Reason = string (apiv1 .GatewayClassReasonUnsupportedVersion )
517
+ supportedCondition .Message = msg
441
518
}
442
- meta .SetStatusCondition (& gwclass .Status .Conditions , supportedVersionCondition )
519
+
520
+ // Set the status conditions
521
+ meta .SetStatusCondition (& gwclass .Status .Conditions , acceptedCondition )
522
+ meta .SetStatusCondition (& gwclass .Status .Conditions , supportedCondition )
443
523
444
524
if err := r .cli .Status ().Update (ctx , gwclass ); err != nil {
525
+ log .Error (err , "Failed to update GatewayClass status" )
445
526
return ctrl.Result {}, err
446
527
}
447
- log .Info ("updated gateway class status" )
528
+
529
+ log .Info ("Reconciled GatewayClass" )
448
530
449
531
return ctrl.Result {}, nil
450
532
}
533
+
534
+ // checkCRDVersions checks that the "gateway.networking.k8s.io/bundle-version" annotation key is set
535
+ // to a supported version for each required Gateway API CRD.
536
+ func (r * controllerReconciler ) checkCRDVersions (ctx context.Context ) (bool , error ) {
537
+ for _ , crdName := range crds .Required {
538
+ crd := & apiextv1.CustomResourceDefinition {}
539
+ if err := r .cli .Get (ctx , client.ObjectKey {Name : crdName }, crd ); err != nil {
540
+ if errors .IsNotFound (err ) {
541
+ return false , nil
542
+ }
543
+ return false , err
544
+ }
545
+
546
+ bundleVersion , exists := crd .Annotations [consts .BundleVersionAnnotation ]
547
+ if ! exists || ! crds .IsSupportedVersion (bundleVersion ) {
548
+ return false , nil
549
+ }
550
+ }
551
+ return true , nil
552
+ }
0 commit comments