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  }