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  }