github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/lib/controller-runtime/client/ssa.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  
     8  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
     9  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    11  	"k8s.io/apimachinery/pkg/runtime"
    12  	kscheme "k8s.io/client-go/kubernetes/scheme"
    13  	"k8s.io/client-go/rest"
    14  	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
    15  	k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client"
    16  )
    17  
    18  const (
    19  	defaultOwner = "olm.registry"
    20  )
    21  
    22  type Object interface {
    23  	runtime.Object
    24  	metav1.Object
    25  }
    26  
    27  func NewForConfig(c *rest.Config, scheme *runtime.Scheme, owner string) (*ServerSideApplier, error) {
    28  	if scheme == nil {
    29  		scheme = runtime.NewScheme()
    30  		localSchemeBuilder := runtime.NewSchemeBuilder(
    31  			kscheme.AddToScheme,
    32  			apiextensionsv1.AddToScheme,
    33  			apiregistrationv1.AddToScheme,
    34  		)
    35  		if err := localSchemeBuilder.AddToScheme(scheme); err != nil {
    36  			return nil, err
    37  		}
    38  	}
    39  	restClient, err := k8scontrollerclient.New(c, k8scontrollerclient.Options{
    40  		Scheme: scheme,
    41  	})
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	if len(owner) == 0 {
    47  		owner = defaultOwner
    48  	}
    49  
    50  	return &ServerSideApplier{
    51  		client: restClient,
    52  		Scheme: scheme,
    53  		Owner:  k8scontrollerclient.FieldOwner(owner),
    54  	}, nil
    55  }
    56  
    57  func SetDefaultGroupVersionKind(obj Object, s *runtime.Scheme) {
    58  	gvk := obj.GetObjectKind().GroupVersionKind()
    59  	if gvk.Empty() && s != nil {
    60  		// Best-effort guess the GVK
    61  		gvks, _, err := s.ObjectKinds(obj)
    62  		if err != nil {
    63  			panic(fmt.Sprintf("unable to get gvks for object %T: %s", obj, err))
    64  		}
    65  		if len(gvks) == 0 || gvks[0].Empty() {
    66  			panic(fmt.Sprintf("unexpected gvks registered for object %T: %v", obj, gvks))
    67  		}
    68  		// TODO: The same object can be registered for multiple group versions
    69  		// (although in practise this doesn't seem to be used).
    70  		// In such case, the version set may not be correct.
    71  		gvk = gvks[0]
    72  	}
    73  	obj.GetObjectKind().SetGroupVersionKind(gvk)
    74  }
    75  
    76  type ServerSideApplier struct {
    77  	client k8scontrollerclient.Client
    78  	Scheme *runtime.Scheme
    79  	// Owner becomes the Field Manager for whatever field the Server-Side apply acts on
    80  	Owner k8scontrollerclient.FieldOwner
    81  }
    82  
    83  // Apply returns a function that invokes a change func on an object and performs a server-side apply patch with the result and its status subresource.
    84  // The given resource must be a pointer to a struct that specifies its Name, Namespace, APIVersion, and Kind at minimum.
    85  // The given change function must be unary, matching the signature: "func(<obj type>) error".
    86  // The returned function is suitable for use w/ asyncronous assertions.
    87  // The underlying value of the given resource pointer is updated to reflect the latest cluster state each time the closure is successfully invoked.
    88  // Ex. Change the spec of an existing InstallPlan
    89  //
    90  //	plan := &InstallPlan{}
    91  //	plan.SetNamespace("ns")
    92  //	plan.SetName("install-123def")
    93  //	Eventually(c.Apply(plan, func(p *v1alpha1.InstallPlan) error {
    94  //		p.Spec.Approved = true
    95  //		return nil
    96  //	})).Should(Succeed())
    97  func (c *ServerSideApplier) Apply(ctx context.Context, obj Object, changeFunc interface{}) func() error {
    98  	// Ensure given object is a pointer
    99  	objType := reflect.TypeOf(obj)
   100  	if objType.Kind() != reflect.Ptr {
   101  		panic(fmt.Sprintf("argument object must be a pointer"))
   102  	}
   103  
   104  	// Ensure given function matches expected signature
   105  	var (
   106  		change     = reflect.ValueOf(changeFunc)
   107  		changeType = change.Type()
   108  	)
   109  	if n := changeType.NumIn(); n != 1 {
   110  		panic(fmt.Sprintf("unexpected number of formal parameters in change function signature: expected 1, present %d", n))
   111  	}
   112  	if pt := changeType.In(0); pt.Kind() != reflect.Interface {
   113  		if objType != pt {
   114  			panic(fmt.Sprintf("argument object type does not match the change function parameter type: argument %s, parameter: %s", objType, pt))
   115  		}
   116  	} else if !objType.Implements(pt) {
   117  		panic(fmt.Sprintf("argument object type does not implement the change function parameter type: argument %s, parameter: %s", objType, pt))
   118  	}
   119  	if n := changeType.NumOut(); n != 1 {
   120  		panic(fmt.Sprintf("unexpected number of return values in change function signature: expected 1, present %d", n))
   121  	}
   122  	var err error
   123  	if rt := changeType.Out(0); !rt.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
   124  		panic(fmt.Sprintf("unexpected return type in change function signature: expected %t, present %s", err, rt))
   125  	}
   126  
   127  	// Determine if we need to apply a status subresource
   128  	_, applyStatus := objType.Elem().FieldByName("Status")
   129  
   130  	if unstructuredObj, ok := obj.(*unstructured.Unstructured); ok {
   131  		_, applyStatus = unstructuredObj.Object["status"]
   132  	}
   133  
   134  	key := k8scontrollerclient.ObjectKeyFromObject(obj)
   135  
   136  	// Ensure the GVK is set before patching
   137  	SetDefaultGroupVersionKind(obj, c.Scheme)
   138  
   139  	return func() error {
   140  		changed := func(obj Object) (Object, error) {
   141  			cp := obj.DeepCopyObject().(Object)
   142  			if err := c.client.Get(ctx, key, cp); err != nil {
   143  				return nil, err
   144  			}
   145  			// Reset the GVK after the client call strips it
   146  			SetDefaultGroupVersionKind(cp, c.Scheme)
   147  			cp.SetManagedFields(nil)
   148  
   149  			out := change.Call([]reflect.Value{reflect.ValueOf(cp)})
   150  			if len(out) != 1 {
   151  				panic(fmt.Sprintf("unexpected number of return values from apply mutation func: expected 1, returned %d", len(out)))
   152  			}
   153  
   154  			if err := out[0].Interface(); err != nil {
   155  				return nil, err.(error)
   156  			}
   157  
   158  			return cp, nil
   159  		}
   160  
   161  		cp, err := changed(obj)
   162  		if err != nil {
   163  			return err
   164  		}
   165  
   166  		if len(c.Owner) == 0 {
   167  			c.Owner = defaultOwner
   168  		}
   169  
   170  		if err := c.client.Patch(ctx, cp, k8scontrollerclient.Apply, k8scontrollerclient.ForceOwnership, c.Owner); err != nil {
   171  			fmt.Printf("first patch error: %s\n", err)
   172  			return err
   173  		}
   174  
   175  		if !applyStatus {
   176  			reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(cp).Elem())
   177  			return nil
   178  		}
   179  
   180  		cp, err = changed(cp)
   181  		if err != nil {
   182  			return err
   183  		}
   184  
   185  		pos := &k8scontrollerclient.SubResourcePatchOptions{}
   186  		k8scontrollerclient.ForceOwnership.ApplyToPatch(&pos.PatchOptions)
   187  
   188  		if err := c.client.Status().Patch(ctx, cp, k8scontrollerclient.Apply, pos, c.Owner); err != nil {
   189  			fmt.Printf("second patch error: %s\n", err)
   190  			return err
   191  		}
   192  
   193  		reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(cp).Elem())
   194  
   195  		return nil
   196  	}
   197  }