@@ -22,57 +22,90 @@ import (
22
22
"github.com/go-logr/logr"
23
23
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
24
24
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
25
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
25
26
controllerruntime "sigs.k8s.io/controller-runtime"
26
- "sigs.k8s.io/controller-runtime/pkg/builder"
27
- "sigs.k8s.io/controller-runtime/pkg/client"
28
- "sigs.k8s.io/controller-runtime/pkg/event"
29
- "sigs.k8s.io/controller-runtime/pkg/predicate"
27
+ "sigs.k8s.io/controller-runtime/pkg/cache"
30
28
"sigs.k8s.io/controller-runtime/pkg/reconcile"
31
29
)
32
30
33
31
// CRDHandler is called by the CRDReconciler to handle establishment of a CRD.
34
- type CRDHandler func () error
32
+ type CRDHandler func (ctx context. Context , established bool ) error
35
33
36
- var _ reconcile.Reconciler = & CRDReconciler {}
34
+ // CRDWatcher allows callers to add CRDHandlers for specific CRDs by name.
35
+ type CRDWatcher interface {
36
+ SetCRDHandler (crdName string , handler CRDHandler )
37
+ RemoveCRDHandler (crdName string )
38
+ }
39
+
40
+ var _ reconcile.Reconciler = & CRDController {}
41
+ var _ CRDWatcher = & CRDController {}
37
42
38
- // CRDReconciler watches CRDs and calls handlers once they are established.
39
- type CRDReconciler struct {
43
+ // CRDController watches CRDs and calls handlers once they are established.
44
+ type CRDController struct {
40
45
loggingController
46
+ cache cache.Cache
41
47
42
- registerLock sync.Mutex
43
- handlers map [string ]CRDHandler
44
- handledCRDs map [string ]struct {}
48
+ registerLock sync.Mutex
49
+ handlers map [string ]CRDHandler
50
+ handledCRDState map [string ]bool
45
51
}
46
52
47
- // NewCRDReconciler constructs a new CRDReconciler.
48
- func NewCRDReconciler ( log logr.Logger ) * CRDReconciler {
49
- return & CRDReconciler {
53
+ // NewCRDController constructs a new CRDReconciler.
54
+ func NewCRDController ( cache cache. Cache , log logr.Logger ) * CRDController {
55
+ return & CRDController {
50
56
loggingController : loggingController {
51
57
log : log ,
52
58
},
53
- handlers : make (map [string ]CRDHandler ),
54
- handledCRDs : make (map [string ]struct {}),
59
+ cache : cache ,
60
+ handlers : make (map [string ]CRDHandler ),
61
+ handledCRDState : make (map [string ]bool ),
55
62
}
56
63
}
57
64
58
65
// SetCRDHandler adds an handler for the specified CRD.
59
66
// The handler will be called when the CRD becomes established.
60
67
// If the handler errors, it will be retried with backoff until success.
61
- // One the handler succeeds, it will not be called again, unless SetCRDHandler
62
- // is called again.
63
- func (r * CRDReconciler ) SetCRDHandler (crdName string , crdHandler CRDHandler ) {
68
+ // One the handler succeeds as established , it will not be called again, unless
69
+ // SetCRDHandler is called again or the CRD becomes unestablished or is deleted .
70
+ func (r * CRDController ) SetCRDHandler (crdName string , crdHandler CRDHandler ) {
64
71
r .registerLock .Lock ()
65
72
defer r .registerLock .Unlock ()
66
73
67
74
r .handlers [crdName ] = crdHandler
68
- delete (r .handledCRDs , crdName )
75
+ delete (r .handledCRDState , crdName )
76
+ }
77
+
78
+ // RemoveCRDHandler removes the handler for the specified CRD.
79
+ func (r * CRDController ) RemoveCRDHandler (crdName string ) {
80
+ r .registerLock .Lock ()
81
+ defer r .registerLock .Unlock ()
82
+
83
+ delete (r .handlers , crdName )
84
+ delete (r .handledCRDState , crdName )
69
85
}
70
86
71
87
// Reconcile the otel ConfigMap and update the Deployment annotation.
72
- func (r * CRDReconciler ) Reconcile (ctx context.Context , req reconcile.Request ) (reconcile.Result , error ) {
88
+ func (r * CRDController ) Reconcile (ctx context.Context , req reconcile.Request ) (reconcile.Result , error ) {
73
89
crdName := req .Name
74
90
ctx = r .setLoggerValues (ctx , "crd" , crdName )
75
91
92
+ // Established if CRD exists and .status.conditions[type="Established"].status = "True"
93
+ var established bool
94
+ crdObj := & apiextensionsv1.CustomResourceDefinition {}
95
+ if err := r .cache .Get (ctx , req .NamespacedName , crdObj ); err != nil {
96
+ switch {
97
+ // Should never run into NoMatchFound, since CRD is a built-in resource.
98
+ case apierrors .IsNotFound (err ):
99
+ established = false
100
+ default :
101
+ // Retry with backoff
102
+ return reconcile.Result {},
103
+ fmt .Errorf ("getting CRD from cache: %s: %w" , crdName , err )
104
+ }
105
+ } else {
106
+ established = crdIsEstablished (crdObj )
107
+ }
108
+
76
109
r .registerLock .Lock ()
77
110
defer r .registerLock .Unlock ()
78
111
@@ -81,57 +114,35 @@ func (r *CRDReconciler) Reconcile(ctx context.Context, req reconcile.Request) (r
81
114
// No handler for this CRD
82
115
return reconcile.Result {}, nil
83
116
}
84
- if _ , handled := r .handledCRDs [crdName ]; handled {
85
- // Already handled
86
- return reconcile.Result {}, nil
117
+ if lastHandledState , found := r .handledCRDState [crdName ]; found {
118
+ if lastHandledState == established {
119
+ // Already handled latest state
120
+ return reconcile.Result {}, nil
121
+ }
87
122
}
88
123
89
- r .logger (ctx ).V (3 ).Info ("reconciling CRD" , "crd" , crdName )
124
+ r .logger (ctx ).V (3 ).Info ("reconciling CRD" , "crd" , crdName , "established" , established )
90
125
91
- if err := handler (); err != nil {
126
+ if err := handler (ctx , established ); err != nil {
92
127
// Retry with backoff
93
128
return reconcile.Result {},
94
129
fmt .Errorf ("reconciling CRD %s: %w" , crdName , err )
95
130
}
96
- // Mark CRD as handled
97
- r .handledCRDs [crdName ] = struct {}{}
131
+ // Record latest handled CRD state
132
+ r .handledCRDState [crdName ] = established
98
133
99
- r .logger (ctx ).V (3 ).Info ("reconciling CRD successful" , "crd" , crdName )
134
+ r .logger (ctx ).V (3 ).Info ("reconciling CRD successful" , "crd" , crdName , "established" , established )
100
135
101
136
return controllerruntime.Result {}, nil
102
137
}
103
138
104
139
// Register the CRD controller with reconciler-manager.
105
- func (r * CRDReconciler ) Register (mgr controllerruntime.Manager ) error {
140
+ func (r * CRDController ) Register (mgr controllerruntime.Manager ) error {
106
141
return controllerruntime .NewControllerManagedBy (mgr ).
107
- For (& apiextensionsv1.CustomResourceDefinition {},
108
- builder .WithPredicates (
109
- ignoreDeletesPredicate (),
110
- crdIsEstablishedPredicate ())).
142
+ For (& apiextensionsv1.CustomResourceDefinition {}).
111
143
Complete (r )
112
144
}
113
145
114
- // ignoreDeletesPredicate returns a predicate that handles CREATE, UPDATE, and
115
- // GENERIC events, but not DELETE events.
116
- func ignoreDeletesPredicate () predicate.Predicate {
117
- return predicate.Funcs {
118
- DeleteFunc : func (_ event.DeleteEvent ) bool {
119
- return false
120
- },
121
- }
122
- }
123
-
124
- // crdIsEstablishedPredicate returns a predicate that only processes events for
125
- // established CRDs.
126
- func crdIsEstablishedPredicate () predicate.Predicate {
127
- return predicate .NewPredicateFuncs (func (obj client.Object ) bool {
128
- if crd , ok := obj .(* apiextensionsv1.CustomResourceDefinition ); ok {
129
- return crdIsEstablished (crd )
130
- }
131
- return false
132
- })
133
- }
134
-
135
146
// crdIsEstablished returns true if the given CRD is established on the cluster,
136
147
// which indicates if discovery knows about it yet. For more info see
137
148
// https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#create-a-customresourcedefinition
0 commit comments