@@ -55,7 +55,7 @@ type marshaler interface {
55
55
// resource exists in our cache. If it does then there is no need to bombard
56
56
// the APIserver with our request and we should write the response from the
57
57
// proxy.
58
- func CacheResponseHandler (h http.Handler , informerCache cache.Cache , restMapper meta.RESTMapper , watchedNamespaces map [string ]interface {}) http.Handler {
58
+ func CacheResponseHandler (h http.Handler , informerCache cache.Cache , restMapper meta.RESTMapper , watchedNamespaces map [string ]interface {}, cMap * controllermap. ControllerMap , injectOwnerRef bool ) http.Handler {
59
59
return http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
60
60
switch req .Method {
61
61
case http .MethodGet :
@@ -138,17 +138,39 @@ func CacheResponseHandler(h http.Handler, informerCache cache.Cache, restMapper
138
138
}
139
139
m = & un
140
140
} else {
141
- un := unstructured.Unstructured {}
141
+ un := & unstructured.Unstructured {}
142
142
un .SetGroupVersionKind (k )
143
143
obj := client.ObjectKey {Namespace : r .Namespace , Name : r .Name }
144
- err = informerCache .Get (context .Background (), obj , & un )
144
+ err = informerCache .Get (context .Background (), obj , un )
145
145
if err != nil {
146
146
// break here in case resource doesn't exist in cache but exists on APIserver
147
147
// This is very unlikely but provides user with expected 404
148
148
log .Info (fmt .Sprintf ("Cache miss: %v, %v" , k , obj ))
149
149
break
150
150
}
151
- m = & un
151
+ m = un
152
+ // Once we get the resource, we are going to attempt to recover the dependent watches here,
153
+ // This will happen in the background, and log errors.
154
+ if injectOwnerRef {
155
+ go func () {
156
+ ownerRef , err := getRequestOwnerRef (req )
157
+ if err != nil {
158
+ log .Error (err , "Could not get ownerRef from proxy" )
159
+ return
160
+ }
161
+
162
+ for _ , oRef := range un .GetOwnerReferences () {
163
+ if oRef .APIVersion == ownerRef .APIVersion && oRef .Kind == ownerRef .Kind {
164
+ err := addWatchToController (ownerRef , cMap , un , restMapper )
165
+ if err != nil {
166
+ log .Error (err , "Could not recover dependent resource watch" , "owner" , ownerRef )
167
+ return
168
+ }
169
+ }
170
+ }
171
+ }()
172
+ }
173
+
152
174
}
153
175
154
176
i := bytes.Buffer {}
@@ -184,7 +206,7 @@ func CacheResponseHandler(h http.Handler, informerCache cache.Cache, restMapper
184
206
// InjectOwnerReferenceHandler will handle proxied requests and inject the
185
207
// owner refernece found in the authorization header. The Authorization is
186
208
// then deleted so that the proxy can re-set with the correct authorization.
187
- func InjectOwnerReferenceHandler (h http.Handler , cMap * controllermap.ControllerMap , restMapper meta.RESTMapper ) http.Handler {
209
+ func InjectOwnerReferenceHandler (h http.Handler , cMap * controllermap.ControllerMap , restMapper meta.RESTMapper , watchedNamespaces map [ string ] interface {} ) http.Handler {
188
210
return http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
189
211
switch req .Method {
190
212
case http .MethodPost :
@@ -203,29 +225,9 @@ func InjectOwnerReferenceHandler(h http.Handler, cMap *controllermap.ControllerM
203
225
break
204
226
}
205
227
log .Info ("Injecting owner reference" )
206
-
207
- user , _ , ok := req .BasicAuth ()
208
- if ! ok {
209
- log .Error (errors .New ("basic auth header not found" ), "" )
210
- w .Header ().Set ("WWW-Authenticate" , "Basic realm=\" Operator Proxy\" " )
211
- http .Error (w , "" , http .StatusUnauthorized )
212
- return
213
- }
214
- authString , err := base64 .StdEncoding .DecodeString (user )
228
+ owner , err := getRequestOwnerRef (req )
215
229
if err != nil {
216
- m := "Could not base64 decode username"
217
- log .Error (err , m )
218
- http .Error (w , m , http .StatusBadRequest )
219
- return
220
- }
221
- // Set owner to NamespacedOwnerReference, which has metav1.OwnerReference
222
- // as a subset along with the Namespace of the owner. Please see the
223
- // kubeconfig.NamespacedOwnerReference type for more information. The
224
- // namespace is required when creating the reconcile requests.
225
- owner := kubeconfig.NamespacedOwnerReference {}
226
- json .Unmarshal (authString , & owner )
227
- if err := json .Unmarshal (authString , & owner ); err != nil {
228
- m := "Could not unmarshal auth string"
230
+ m := "Could not get owner reference"
229
231
log .Error (err , m )
230
232
http .Error (w , m , http .StatusInternalServerError )
231
233
return
@@ -257,35 +259,23 @@ func InjectOwnerReferenceHandler(h http.Handler, cMap *controllermap.ControllerM
257
259
log .V (1 ).Info ("Serialized body" , "Body" , string (newBody ))
258
260
req .Body = ioutil .NopCloser (bytes .NewBuffer (newBody ))
259
261
req .ContentLength = int64 (len (newBody ))
260
- dataMapping , err := restMapper .RESTMapping (data .GroupVersionKind ().GroupKind (), data .GroupVersionKind ().Version )
261
- if err != nil {
262
- m := fmt .Sprintf ("Could not get rest mapping for: %v" , data .GroupVersionKind ())
263
- log .Error (err , m )
264
- http .Error (w , m , http .StatusInternalServerError )
265
- return
266
- }
267
- // We need to determine whether or not the owner is a cluster-scoped
268
- // resource because enqueue based on an owner reference does not work if
269
- // a namespaced resource owns a cluster-scoped resource
270
- ownerGV , err := schema .ParseGroupVersion (owner .APIVersion )
271
- ownerMapping , err := restMapper .RESTMapping (schema.GroupKind {Kind : owner .Kind , Group : ownerGV .Group }, ownerGV .Version )
272
- if err != nil {
273
- m := fmt .Sprintf ("could not get rest mapping for: %v" , owner )
274
- log .Error (err , m )
275
- http .Error (w , m , http .StatusInternalServerError )
276
- return
277
- }
278
262
279
- dataNamespaceScoped := dataMapping .Scope .Name () != meta .RESTScopeNameRoot
280
- ownerNamespaceScoped := ownerMapping .Scope .Name () != meta .RESTScopeNameRoot
281
- useOwnerReference := ! ownerNamespaceScoped || dataNamespaceScoped
282
263
// add watch for resource
283
- err = addWatchToController (owner , cMap , data , useOwnerReference )
284
- if err != nil {
285
- m := "could not add watch to controller"
286
- log .Error (err , m )
287
- http .Error (w , m , http .StatusInternalServerError )
288
- return
264
+ // check if resource doesn't exist in watched namespaces
265
+ // if watchedNamespaces[""] exists then we are watching all namespaces
266
+ // and want to continue
267
+ // This is making sure we are not attempting to watch a resource outside of the
268
+ // namespaces that the cache can watch.
269
+ _ , allNsPresent := watchedNamespaces [metav1 .NamespaceAll ]
270
+ _ , reqNsPresent := watchedNamespaces [r .Namespace ]
271
+ if allNsPresent || reqNsPresent {
272
+ err = addWatchToController (owner , cMap , data , restMapper )
273
+ if err != nil {
274
+ m := "could not add watch to controller"
275
+ log .Error (err , m )
276
+ http .Error (w , m , http .StatusInternalServerError )
277
+ return
278
+ }
289
279
}
290
280
}
291
281
// Removing the authorization so that the proxy can set the correct authorization.
@@ -294,6 +284,7 @@ func InjectOwnerReferenceHandler(h http.Handler, cMap *controllermap.ControllerM
294
284
})
295
285
}
296
286
287
+ // RequestLogHandler - log the requests that come through the proxy.
297
288
func RequestLogHandler (h http.Handler ) http.Handler {
298
289
return http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
299
290
// read body
@@ -379,13 +370,13 @@ func Run(done chan error, o Options) error {
379
370
}
380
371
381
372
if ! o .NoOwnerInjection {
382
- server .Handler = InjectOwnerReferenceHandler (server .Handler , o .ControllerMap , o .RESTMapper )
373
+ server .Handler = InjectOwnerReferenceHandler (server .Handler , o .ControllerMap , o .RESTMapper , watchedNamespaceMap )
383
374
}
384
375
if o .LogRequests {
385
376
server .Handler = RequestLogHandler (server .Handler )
386
377
}
387
378
if ! o .DisableCache {
388
- server .Handler = CacheResponseHandler (server .Handler , o .Cache , o .RESTMapper , watchedNamespaceMap )
379
+ server .Handler = CacheResponseHandler (server .Handler , o .Cache , o .RESTMapper , watchedNamespaceMap , o . ControllerMap , ! o . NoOwnerInjection )
389
380
}
390
381
391
382
l , err := server .Listen (o .Address , o .Port )
@@ -399,57 +390,71 @@ func Run(done chan error, o Options) error {
399
390
return nil
400
391
}
401
392
402
- func addWatchToController (owner kubeconfig.NamespacedOwnerReference , cMap * controllermap.ControllerMap , resource * unstructured.Unstructured , useOwnerReference bool ) error {
403
- gv , err := schema .ParseGroupVersion (owner .APIVersion )
393
+ func addWatchToController (owner kubeconfig.NamespacedOwnerReference , cMap * controllermap.ControllerMap , resource * unstructured.Unstructured , restMapper meta.RESTMapper ) error {
394
+ dataMapping , err := restMapper .RESTMapping (resource .GroupVersionKind ().GroupKind (), resource .GroupVersionKind ().Version )
395
+ if err != nil {
396
+ m := fmt .Sprintf ("Could not get rest mapping for: %v" , resource .GroupVersionKind ())
397
+ log .Error (err , m )
398
+ return err
399
+
400
+ }
401
+ // We need to determine whether or not the owner is a cluster-scoped
402
+ // resource because enqueue based on an owner reference does not work if
403
+ // a namespaced resource owns a cluster-scoped resource
404
+ ownerGV , err := schema .ParseGroupVersion (owner .APIVersion )
404
405
if err != nil {
406
+ m := fmt .Sprintf ("could not get broup version for: %v" , owner )
407
+ log .Error (err , m )
405
408
return err
406
409
}
407
- gvk := schema.GroupVersionKind {
408
- Group : gv .Group ,
409
- Version : gv .Version ,
410
- Kind : owner .Kind ,
410
+ ownerMapping , err := restMapper .RESTMapping (schema.GroupKind {Kind : owner .Kind , Group : ownerGV .Group }, ownerGV .Version )
411
+ if err != nil {
412
+ m := fmt .Sprintf ("could not get rest mapping for: %v" , owner )
413
+ log .Error (err , m )
414
+ return err
411
415
}
412
- contents , ok := cMap .Get (gvk )
416
+
417
+ dataNamespaceScoped := dataMapping .Scope .Name () != meta .RESTScopeNameRoot
418
+ ownerNamespaceScoped := ownerMapping .Scope .Name () != meta .RESTScopeNameRoot
419
+ useOwnerReference := ! ownerNamespaceScoped || dataNamespaceScoped
420
+ contents , ok := cMap .Get (ownerMapping .GroupVersionKind )
413
421
if ! ok {
414
422
return errors .New ("failed to find controller in map" )
415
423
}
416
424
wMap := contents .WatchMap
417
425
uMap := contents .UIDMap
418
- // Store UID
419
- uMap .Store (owner .UID , types.NamespacedName {
420
- Name : owner .Name ,
421
- Namespace : owner .Namespace ,
422
- })
423
426
u := & unstructured.Unstructured {}
424
- u .SetGroupVersionKind (gvk )
427
+ u .SetGroupVersionKind (ownerMapping . GroupVersionKind )
425
428
// Add a watch to controller
426
429
if contents .WatchDependentResources {
427
- // Use EnqueueRequestForOwner unless user has configured watching cluster scoped resources
428
- if useOwnerReference && ! contents .WatchClusterScopedResources {
429
- _ , exists := wMap .Get (resource .GroupVersionKind ())
430
- // If already watching resource no need to add a new watch
431
- if exists {
432
- return nil
433
- }
430
+ // Store UID
431
+ uMap .Store (owner .UID , types.NamespacedName {
432
+ Name : owner .Name ,
433
+ Namespace : owner .Namespace ,
434
+ })
435
+ _ , exists := wMap .Get (resource .GroupVersionKind ())
436
+ // If already watching resource no need to add a new watch
437
+ if exists {
438
+ return nil
439
+ }
440
+ // Store watch in map
441
+ wMap .Store (resource .GroupVersionKind ())
442
+ // Use EnqueueRequestForOwner unless user has configured watching cluster scoped resources and we have to
443
+ if useOwnerReference {
434
444
log .Info ("Watching child resource" , "kind" , resource .GroupVersionKind (), "enqueue_kind" , u .GroupVersionKind ())
435
445
// Store watch in map
436
- wMap .Store (resource .GroupVersionKind ())
437
446
err = contents .Controller .Watch (& source.Kind {Type : resource }, & handler.EnqueueRequestForOwner {OwnerType : u })
438
- } else if contents .WatchClusterScopedResources {
439
- // Use Map func since EnqueuRequestForOwner won't work
440
- // Check if resource is already watched
441
- _ , exists := wMap .Get (resource .GroupVersionKind ())
442
- if exists {
443
- return nil
447
+ if err != nil {
448
+ return err
444
449
}
450
+ } else if contents .WatchClusterScopedResources {
445
451
log .Info ("Watching child resource which can be cluster-scoped" , "kind" , resource .GroupVersionKind (), "enqueue_kind" , u .GroupVersionKind ())
446
- // Store watch in map
447
- wMap .Store (resource .GroupVersionKind ())
448
452
// Add watch
449
453
err = contents .Controller .Watch (
450
454
& source.Kind {Type : resource },
455
+ // Use Map func since EnqueuRequestForOwner won't work
451
456
& handler.EnqueueRequestsFromMapFunc {ToRequests : handler .ToRequestsFunc (func (a handler.MapObject ) []reconcile.Request {
452
- log .V (2 ).Info ("Creating reconcile request from object" , "gvk" , gvk , "name" , a .Meta .GetName ())
457
+ log .V (2 ).Info ("Creating reconcile request from object" , "gvk" , ownerMapping . GroupVersionKind , "name" , a .Meta .GetName ())
453
458
ownRefs := a .Meta .GetOwnerReferences ()
454
459
for _ , ref := range ownRefs {
455
460
nn , exists := uMap .Get (ref .UID )
@@ -470,3 +475,28 @@ func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *contr
470
475
}
471
476
return nil
472
477
}
478
+
479
+ func getRequestOwnerRef (req * http.Request ) (kubeconfig.NamespacedOwnerReference , error ) {
480
+ owner := kubeconfig.NamespacedOwnerReference {}
481
+ user , _ , ok := req .BasicAuth ()
482
+ if ! ok {
483
+ return owner , errors .New ("basic auth header not found" )
484
+ }
485
+ authString , err := base64 .StdEncoding .DecodeString (user )
486
+ if err != nil {
487
+ m := "Could not base64 decode username"
488
+ log .Error (err , m )
489
+ return owner , err
490
+ }
491
+ // Set owner to NamespacedOwnerReference, which has metav1.OwnerReference
492
+ // as a subset along with the Namespace of the owner. Please see the
493
+ // kubeconfig.NamespacedOwnerReference type for more information. The
494
+ // namespace is required when creating the reconcile requests.
495
+ json .Unmarshal (authString , & owner )
496
+ if err := json .Unmarshal (authString , & owner ); err != nil {
497
+ m := "Could not unmarshal auth string"
498
+ log .Error (err , m )
499
+ return owner , err
500
+ }
501
+ return owner , err
502
+ }
0 commit comments