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 }