github.com/kubewharf/katalyst-core@v0.5.3/pkg/client/control/unstructured.go (about) 1 /* 2 Copyright 2022 The Katalyst 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 control 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 24 jsonpatch "github.com/evanphx/json-patch" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/client-go/dynamic" 30 31 "github.com/kubewharf/katalyst-core/pkg/util/native" 32 ) 33 34 // UnstructuredControl is used to update Unstructured obj 35 // todo: use patch instead of update to avoid conflict 36 type UnstructuredControl interface { 37 // CreateUnstructured is used to create unstructured obj 38 CreateUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 39 obj *unstructured.Unstructured, opts metav1.CreateOptions) (*unstructured.Unstructured, error) 40 41 // GetUnstructured is used to get unstructured obj 42 GetUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 43 namespace, name string, opts metav1.GetOptions) (*unstructured.Unstructured, error) 44 45 // PatchUnstructured is to patch unstructured object spec and metadata 46 PatchUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 47 oldObj, newObj *unstructured.Unstructured) (*unstructured.Unstructured, error) 48 49 // UpdateUnstructured is used to update unstructured obj spec and metadata 50 UpdateUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 51 obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) 52 53 // UpdateUnstructuredStatus is used to update unstructured obj status 54 UpdateUnstructuredStatus(ctx context.Context, gvr metav1.GroupVersionResource, 55 obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) 56 57 // PatchUnstructuredStatus is to patch unstructured object status 58 PatchUnstructuredStatus(ctx context.Context, gvr metav1.GroupVersionResource, 59 oldObj, newObj *unstructured.Unstructured) (*unstructured.Unstructured, error) 60 61 // DeleteUnstructured is used to delete unstructured obj 62 DeleteUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 63 obj *unstructured.Unstructured, opts metav1.DeleteOptions) error 64 } 65 66 type DummyUnstructuredControl struct{} 67 68 func (d DummyUnstructuredControl) CreateUnstructured(_ context.Context, _ metav1.GroupVersionResource, 69 obj *unstructured.Unstructured, _ metav1.CreateOptions, 70 ) (*unstructured.Unstructured, error) { 71 return obj, nil 72 } 73 74 func (d DummyUnstructuredControl) GetUnstructured(_ context.Context, _ metav1.GroupVersionResource, 75 _, _ string, _ metav1.GetOptions, 76 ) (*unstructured.Unstructured, error) { 77 return nil, nil 78 } 79 80 func (d DummyUnstructuredControl) UpdateUnstructured(_ context.Context, _ metav1.GroupVersionResource, 81 obj *unstructured.Unstructured, _ metav1.UpdateOptions, 82 ) (*unstructured.Unstructured, error) { 83 return obj, nil 84 } 85 86 func (d DummyUnstructuredControl) PatchUnstructured(_ context.Context, _ metav1.GroupVersionResource, 87 _, newObj *unstructured.Unstructured, 88 ) (*unstructured.Unstructured, error) { 89 return newObj, nil 90 } 91 92 func (d DummyUnstructuredControl) UpdateUnstructuredStatus(_ context.Context, _ metav1.GroupVersionResource, 93 obj *unstructured.Unstructured, _ metav1.UpdateOptions, 94 ) (*unstructured.Unstructured, error) { 95 return obj, nil 96 } 97 98 func (d DummyUnstructuredControl) PatchUnstructuredStatus(_ context.Context, _ metav1.GroupVersionResource, 99 _, newObj *unstructured.Unstructured, 100 ) (*unstructured.Unstructured, error) { 101 return newObj, nil 102 } 103 104 func (d DummyUnstructuredControl) DeleteUnstructured(_ context.Context, _ metav1.GroupVersionResource, 105 _ *unstructured.Unstructured, _ metav1.DeleteOptions, 106 ) error { 107 return nil 108 } 109 110 type RealUnstructuredControl struct { 111 client dynamic.Interface 112 } 113 114 func (r *RealUnstructuredControl) CreateUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 115 obj *unstructured.Unstructured, opts metav1.CreateOptions, 116 ) (*unstructured.Unstructured, error) { 117 schemaGVR := schema.GroupVersionResource(gvr) 118 119 if obj.GetNamespace() != "" { 120 return r.client.Resource(schemaGVR).Namespace(obj.GetNamespace()).Create(ctx, obj, opts) 121 } 122 123 return r.client.Resource(schemaGVR).Create(ctx, obj, opts) 124 } 125 126 func (r *RealUnstructuredControl) GetUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 127 namespace, name string, opts metav1.GetOptions, 128 ) (*unstructured.Unstructured, error) { 129 schemaGVR := schema.GroupVersionResource(gvr) 130 131 if namespace != "" { 132 return r.client.Resource(schemaGVR).Namespace(namespace).Get(ctx, name, opts) 133 } 134 135 return r.client.Resource(schemaGVR).Get(ctx, name, opts) 136 } 137 138 func (r *RealUnstructuredControl) UpdateUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 139 obj *unstructured.Unstructured, opts metav1.UpdateOptions, 140 ) (*unstructured.Unstructured, error) { 141 if obj == nil { 142 return nil, fmt.Errorf("can't update a nil Unstructured obj") 143 } 144 145 schemaGVR := schema.GroupVersionResource(gvr) 146 if obj.GetNamespace() != "" { 147 return r.client.Resource(schemaGVR).Namespace(obj.GetNamespace()).Update(ctx, obj, opts) 148 } 149 150 return r.client.Resource(schemaGVR).Update(ctx, obj, opts) 151 } 152 153 func (r *RealUnstructuredControl) PatchUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, 154 oldObj, newObj *unstructured.Unstructured, 155 ) (*unstructured.Unstructured, error) { 156 patchBytes, err := prepareUnstructuredPatchBytes(oldObj, newObj) 157 if err != nil { 158 return nil, err 159 } 160 161 schemaGVR := schema.GroupVersionResource(gvr) 162 if newObj.GetNamespace() == "" { 163 return r.client.Resource(schemaGVR).Patch(ctx, newObj.GetName(), types.MergePatchType, patchBytes, metav1.PatchOptions{}) 164 } 165 166 return r.client.Resource(schemaGVR).Namespace(newObj.GetNamespace()).Patch(ctx, newObj.GetName(), types.MergePatchType, patchBytes, metav1.PatchOptions{}) 167 } 168 169 func (r *RealUnstructuredControl) UpdateUnstructuredStatus(ctx context.Context, gvr metav1.GroupVersionResource, 170 obj *unstructured.Unstructured, opts metav1.UpdateOptions, 171 ) (*unstructured.Unstructured, error) { 172 if obj == nil { 173 return nil, fmt.Errorf("can't update a nil Unstructured obj's status") 174 } 175 176 schemaGVR := schema.GroupVersionResource(gvr) 177 if obj.GetNamespace() != "" { 178 return r.client.Resource(schemaGVR).Namespace(obj.GetNamespace()).UpdateStatus(ctx, obj, opts) 179 } 180 181 return r.client.Resource(schemaGVR).UpdateStatus(ctx, obj, opts) 182 } 183 184 func (r *RealUnstructuredControl) PatchUnstructuredStatus(ctx context.Context, gvr metav1.GroupVersionResource, 185 oldObj, newObj *unstructured.Unstructured, 186 ) (*unstructured.Unstructured, error) { 187 patchBytes, err := prepareUnstructuredStatusPatchBytes(oldObj, newObj) 188 if err != nil { 189 return nil, err 190 } 191 192 schemaGVR := schema.GroupVersionResource(gvr) 193 if newObj.GetNamespace() == "" { 194 return r.client.Resource(schemaGVR).Patch(ctx, newObj.GetName(), types.MergePatchType, patchBytes, metav1.PatchOptions{}, "status") 195 } 196 197 return r.client.Resource(schemaGVR).Namespace(newObj.GetNamespace()).Patch(ctx, newObj.GetName(), types.MergePatchType, patchBytes, metav1.PatchOptions{}, "status") 198 } 199 200 func (r *RealUnstructuredControl) DeleteUnstructured(ctx context.Context, gvr metav1.GroupVersionResource, obj *unstructured.Unstructured, opts metav1.DeleteOptions) error { 201 if obj == nil { 202 return fmt.Errorf("can't delete a nil Unstructured obj") 203 } 204 205 schemaGVR := native.ToSchemaGVR(gvr.Group, gvr.Version, gvr.Resource) 206 if obj.GetNamespace() != "" { 207 return r.client.Resource(schemaGVR).Namespace(obj.GetNamespace()).Delete(ctx, obj.GetName(), opts) 208 } 209 210 return r.client.Resource(schemaGVR).Delete(ctx, obj.GetName(), opts) 211 } 212 213 func NewRealUnstructuredControl(client dynamic.Interface) *RealUnstructuredControl { 214 return &RealUnstructuredControl{ 215 client: client, 216 } 217 } 218 219 func prepareUnstructuredPatchBytes(oldObj, newObj *unstructured.Unstructured) ([]byte, error) { 220 oldData, err := json.Marshal(oldObj) 221 if err != nil { 222 return nil, fmt.Errorf("failed to marshal oldData %q: %v", newObj.GetName(), err) 223 } 224 225 diff := oldObj.DeepCopy() 226 metadata, _, err := unstructured.NestedFieldNoCopy(newObj.Object, "metadata") 227 if err != nil { 228 return nil, err 229 } 230 err = unstructured.SetNestedField(diff.Object, metadata, "metadata") 231 if err != nil { 232 return nil, err 233 } 234 235 spec, _, err := unstructured.NestedFieldNoCopy(newObj.Object, "spec") 236 if err != nil { 237 return nil, err 238 } 239 err = unstructured.SetNestedField(diff.Object, spec, "spec") 240 if err != nil { 241 return nil, err 242 } 243 244 newData, err := json.Marshal(diff) 245 if err != nil { 246 return nil, fmt.Errorf("failed to marshal newData %q: %v", newObj.GetName(), err) 247 } 248 249 patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData) 250 if err != nil { 251 return nil, fmt.Errorf("failed to CreateTwoWayMergePatch %q: %v", newObj.GetName(), err) 252 } 253 254 return patchBytes, nil 255 } 256 257 func prepareUnstructuredStatusPatchBytes(oldObj, newObj *unstructured.Unstructured) ([]byte, error) { 258 oldData, err := json.Marshal(oldObj) 259 if err != nil { 260 return nil, fmt.Errorf("failed to marshal oldData %q: %v", newObj.GetName(), err) 261 } 262 263 diff := oldObj.DeepCopy() 264 spec, _, err := unstructured.NestedFieldNoCopy(newObj.Object, "status") 265 if err != nil { 266 return nil, err 267 } 268 err = unstructured.SetNestedField(diff.Object, spec, "status") 269 if err != nil { 270 return nil, err 271 } 272 273 newData, err := json.Marshal(diff) 274 if err != nil { 275 return nil, fmt.Errorf("failed to marshal newData %q: %v", newObj.GetName(), err) 276 } 277 278 patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData) 279 if err != nil { 280 return nil, fmt.Errorf("failed to CreateTwoWayMergePatch %q: %v", newObj.GetName(), err) 281 } 282 283 return patchBytes, nil 284 }