knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/typed.go (about) 1 /* 2 Copyright 2018 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package duck 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "time" 24 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/watch" 30 "k8s.io/client-go/dynamic" 31 "k8s.io/client-go/tools/cache" 32 33 "knative.dev/pkg/apis" 34 ) 35 36 // TypedInformerFactory implements InformerFactory such that the elements 37 // tracked by the informer/lister have the type of the canonical "obj". 38 type TypedInformerFactory struct { 39 Client dynamic.Interface 40 Type apis.Listable 41 ResyncPeriod time.Duration 42 StopChannel <-chan struct{} 43 } 44 45 // Check that TypedInformerFactory implements InformerFactory. 46 var _ InformerFactory = (*TypedInformerFactory)(nil) 47 48 // Get implements InformerFactory. 49 func (dif *TypedInformerFactory) Get(ctx context.Context, gvr schema.GroupVersionResource) (cache.SharedIndexInformer, cache.GenericLister, error) { 50 // Avoid error cases, like the GVR does not exist. 51 // It is not a full check. Some RBACs might sneak by, but the window is very small. 52 if _, err := dif.Client.Resource(gvr).List(ctx, metav1.ListOptions{}); err != nil { 53 return nil, nil, err 54 } 55 56 listObj := dif.Type.GetListType() 57 rawLW := &cache.ListWatch{ 58 ListWithContextFunc: asStructuredLister(dif.Client.Resource(gvr).List, listObj), 59 WatchFuncWithContext: AsStructuredWatcher(dif.Client.Resource(gvr).Watch, dif.Type), 60 } 61 // Wrap with standard K8s function to properly handle WatchList semantics detection 62 lw := cache.ToListWatcherWithWatchListSemantics(rawLW, dif.Client) 63 inf := cache.NewSharedIndexInformer(lw, dif.Type, dif.ResyncPeriod, cache.Indexers{ 64 cache.NamespaceIndex: cache.MetaNamespaceIndexFunc, 65 }) 66 67 lister := cache.NewGenericLister(inf.GetIndexer(), gvr.GroupResource()) 68 69 go inf.Run(dif.StopChannel) 70 71 if ok := cache.WaitForCacheSync(dif.StopChannel, inf.HasSynced); !ok { 72 return nil, nil, fmt.Errorf("failed starting shared index informer for %v with type %T", gvr, dif.Type) 73 } 74 75 return inf, lister, nil 76 } 77 78 type unstructuredLister func(context.Context, metav1.ListOptions) (*unstructured.UnstructuredList, error) 79 80 func asStructuredLister(ulist unstructuredLister, listObj runtime.Object) cache.ListWithContextFunc { 81 return func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { 82 ul, err := ulist(ctx, opts) 83 if err != nil { 84 return nil, err 85 } 86 res := listObj.DeepCopyObject() 87 if err := FromUnstructured(ul, res); err != nil { 88 return nil, err 89 } 90 return res, nil 91 } 92 } 93 94 type structuredWatcher func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) 95 96 // AsStructuredWatcher is public for testing only. 97 // TODO(mattmoor): Move tests for this to `package duck` and make private. 98 func AsStructuredWatcher(wf structuredWatcher, obj runtime.Object) cache.WatchFuncWithContext { 99 return func(ctx context.Context, lo metav1.ListOptions) (watch.Interface, error) { 100 uw, err := wf(ctx, lo) 101 if err != nil { 102 return nil, err 103 } 104 structuredCh := make(chan watch.Event) 105 go func() { 106 defer close(structuredCh) 107 unstructuredCh := uw.ResultChan() 108 for ue := range unstructuredCh { 109 unstructuredObj, ok := ue.Object.(*unstructured.Unstructured) 110 if !ok { 111 // If it isn't an unstructured object, then forward the 112 // event as-is. This is likely to happen when the event's 113 // Type is an Error. 114 structuredCh <- ue 115 continue 116 } 117 structuredObj := obj.DeepCopyObject() 118 119 err := FromUnstructured(unstructuredObj, structuredObj) 120 if err != nil { 121 // Pass back an error indicating that the object we got 122 // was invalid. 123 structuredCh <- watch.Event{ 124 Type: watch.Error, 125 Object: &metav1.Status{ 126 Status: metav1.StatusFailure, 127 Code: http.StatusUnprocessableEntity, 128 Reason: metav1.StatusReasonInvalid, 129 Message: err.Error(), 130 }, 131 } 132 continue 133 } 134 // Send the structured event. 135 structuredCh <- watch.Event{ 136 Type: ue.Type, 137 Object: structuredObj, 138 } 139 } 140 }() 141 142 return watch.NewProxyWatcher(structuredCh), nil 143 } 144 }