k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/rest_test.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes 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 handlers
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"reflect"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/google/go-cmp/cmp"
    31  	fuzz "github.com/google/gofuzz"
    32  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
    33  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    34  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    37  	testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
    38  	"k8s.io/apimachinery/pkg/runtime"
    39  	"k8s.io/apimachinery/pkg/runtime/schema"
    40  	"k8s.io/apimachinery/pkg/runtime/serializer"
    41  	"k8s.io/apimachinery/pkg/types"
    42  	"k8s.io/apimachinery/pkg/util/json"
    43  	"k8s.io/apimachinery/pkg/util/managedfields"
    44  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    45  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    46  	"k8s.io/apimachinery/pkg/util/yaml"
    47  	"k8s.io/apiserver/pkg/admission"
    48  	"k8s.io/apiserver/pkg/apis/example"
    49  	examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
    50  	"k8s.io/apiserver/pkg/endpoints/handlers/metrics"
    51  	"k8s.io/apiserver/pkg/endpoints/request"
    52  	"k8s.io/apiserver/pkg/registry/rest"
    53  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    54  	"k8s.io/component-base/metrics/legacyregistry"
    55  	"k8s.io/component-base/metrics/testutil"
    56  )
    57  
    58  var (
    59  	scheme = runtime.NewScheme()
    60  	codecs = serializer.NewCodecFactory(scheme)
    61  )
    62  
    63  func init() {
    64  	metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)
    65  	utilruntime.Must(example.AddToScheme(scheme))
    66  	utilruntime.Must(examplev1.AddToScheme(scheme))
    67  }
    68  
    69  type testPatchType struct {
    70  	metav1.TypeMeta `json:",inline"`
    71  
    72  	TestPatchSubType `json:",inline"`
    73  }
    74  
    75  // We explicitly make it public as private types doesn't
    76  // work correctly with json inlined types.
    77  type TestPatchSubType struct {
    78  	StringField string `json:"theField"`
    79  }
    80  
    81  func (obj *testPatchType) DeepCopyObject() runtime.Object {
    82  	if obj == nil {
    83  		return nil
    84  	}
    85  	clone := *obj
    86  	return &clone
    87  }
    88  
    89  func TestPatchAnonymousField(t *testing.T) {
    90  	testGV := schema.GroupVersion{Group: "", Version: "v"}
    91  	scheme.AddKnownTypes(testGV, &testPatchType{})
    92  	defaulter := runtime.ObjectDefaulter(scheme)
    93  
    94  	original := &testPatchType{
    95  		TypeMeta:         metav1.TypeMeta{Kind: "testPatchType", APIVersion: "v"},
    96  		TestPatchSubType: TestPatchSubType{StringField: "my-value"},
    97  	}
    98  	patch := `{"theField": "changed!"}`
    99  	expected := &testPatchType{
   100  		TypeMeta:         metav1.TypeMeta{Kind: "testPatchType", APIVersion: "v"},
   101  		TestPatchSubType: TestPatchSubType{StringField: "changed!"},
   102  	}
   103  
   104  	actual := &testPatchType{}
   105  	err := strategicPatchObject(context.TODO(), defaulter, original, []byte(patch), actual, &testPatchType{}, "")
   106  	if err != nil {
   107  		t.Fatalf("unexpected error: %v", err)
   108  	}
   109  	if !apiequality.Semantic.DeepEqual(actual, expected) {
   110  		t.Errorf("expected %#v, got %#v", expected, actual)
   111  	}
   112  }
   113  
   114  func TestLimitedReadBody(t *testing.T) {
   115  	defer legacyregistry.Reset()
   116  	legacyregistry.Register(metrics.RequestBodySizes)
   117  
   118  	testcases := []struct {
   119  		desc            string
   120  		requestBody     io.Reader
   121  		limit           int64
   122  		expectedMetrics string
   123  		expectedErr     bool
   124  	}{
   125  		{
   126  			desc:            "aaaa with limit 1",
   127  			requestBody:     strings.NewReader("aaaa"),
   128  			limit:           1,
   129  			expectedMetrics: "",
   130  			expectedErr:     true,
   131  		},
   132  		{
   133  			desc:        "aaaa with limit 5",
   134  			requestBody: strings.NewReader("aaaa"),
   135  			limit:       5,
   136  			expectedMetrics: `
   137          # HELP apiserver_request_body_size_bytes [ALPHA] Apiserver request body size in bytes broken out by resource and verb.
   138          # TYPE apiserver_request_body_size_bytes histogram
   139          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="50000"} 1
   140          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="150000"} 1
   141          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="250000"} 1
   142          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="350000"} 1
   143          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="450000"} 1
   144          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="550000"} 1
   145          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="650000"} 1
   146          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="750000"} 1
   147          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="850000"} 1
   148          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="950000"} 1
   149          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.05e+06"} 1
   150          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.15e+06"} 1
   151          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.25e+06"} 1
   152          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.35e+06"} 1
   153          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.45e+06"} 1
   154          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.55e+06"} 1
   155          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.65e+06"} 1
   156          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.75e+06"} 1
   157          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.85e+06"} 1
   158          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="1.95e+06"} 1
   159          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.05e+06"} 1
   160          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.15e+06"} 1
   161          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.25e+06"} 1
   162          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.35e+06"} 1
   163          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.45e+06"} 1
   164          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.55e+06"} 1
   165          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.65e+06"} 1
   166          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.75e+06"} 1
   167          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.85e+06"} 1
   168          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="2.95e+06"} 1
   169          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="3.05e+06"} 1
   170          apiserver_request_body_size_bytes_bucket{resource="resource.group",verb="create",le="+Inf"} 1
   171          apiserver_request_body_size_bytes_sum{resource="resource.group",verb="create"} 4
   172          apiserver_request_body_size_bytes_count{resource="resource.group",verb="create"} 1
   173  `,
   174  			expectedErr: false,
   175  		},
   176  	}
   177  
   178  	for _, tc := range testcases {
   179  		t.Run(tc.desc, func(t *testing.T) {
   180  			// reset metrics
   181  			defer metrics.RequestBodySizes.Reset()
   182  			defer legacyregistry.Reset()
   183  
   184  			req, err := http.NewRequest("POST", "/", tc.requestBody)
   185  			if err != nil {
   186  				t.Errorf("err not expected: got %v", err)
   187  			}
   188  			_, err = limitedReadBodyWithRecordMetric(context.Background(), req, tc.limit, "resource.group", metrics.Create)
   189  			if tc.expectedErr {
   190  				if err == nil {
   191  					t.Errorf("err expected: got nil")
   192  				}
   193  				return
   194  			}
   195  			if err = testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.expectedMetrics), "apiserver_request_body_size_bytes"); err != nil {
   196  				t.Errorf("unexpected err: %v", err)
   197  			}
   198  		})
   199  	}
   200  }
   201  
   202  func TestStrategicMergePatchInvalid(t *testing.T) {
   203  	testGV := schema.GroupVersion{Group: "", Version: "v"}
   204  	scheme.AddKnownTypes(testGV, &testPatchType{})
   205  	defaulter := runtime.ObjectDefaulter(scheme)
   206  
   207  	original := &testPatchType{
   208  		TypeMeta:         metav1.TypeMeta{Kind: "testPatchType", APIVersion: "v"},
   209  		TestPatchSubType: TestPatchSubType{StringField: "my-value"},
   210  	}
   211  	patch := `barbaz`
   212  	expectedError := "invalid character 'b' looking for beginning of value"
   213  
   214  	actual := &testPatchType{}
   215  	err := strategicPatchObject(context.TODO(), defaulter, original, []byte(patch), actual, &testPatchType{}, "")
   216  	if !apierrors.IsBadRequest(err) {
   217  		t.Errorf("expected HTTP status: BadRequest, got: %#v", apierrors.ReasonForError(err))
   218  	}
   219  	if !strings.Contains(err.Error(), expectedError) {
   220  		t.Errorf("expected %#v, got %#v", expectedError, err.Error())
   221  	}
   222  }
   223  
   224  func TestJSONPatch(t *testing.T) {
   225  	for _, test := range []struct {
   226  		name              string
   227  		patch             string
   228  		expectedError     string
   229  		expectedErrorType metav1.StatusReason
   230  	}{
   231  		{
   232  			name:  "valid",
   233  			patch: `[{"op": "test", "value": "podA", "path": "/metadata/name"}]`,
   234  		},
   235  		{
   236  			name:              "invalid-syntax",
   237  			patch:             `invalid json patch`,
   238  			expectedError:     "invalid character 'i' looking for beginning of value",
   239  			expectedErrorType: metav1.StatusReasonBadRequest,
   240  		},
   241  		{
   242  			name:              "invalid-semantics",
   243  			patch:             `[{"op": "test", "value": "podA", "path": "/invalid/path"}]`,
   244  			expectedError:     "the server rejected our request due to an error in our request",
   245  			expectedErrorType: metav1.StatusReasonInvalid,
   246  		},
   247  		{
   248  			name:  "valid-negative-index-patch",
   249  			patch: `[{"op": "test", "value": "foo", "path": "/metadata/finalizers/-1"}]`,
   250  		},
   251  	} {
   252  		p := &patcher{
   253  			patchType:  types.JSONPatchType,
   254  			patchBytes: []byte(test.patch),
   255  		}
   256  		jp := jsonPatcher{patcher: p}
   257  		codec := codecs.LegacyCodec(examplev1.SchemeGroupVersion)
   258  		pod := &examplev1.Pod{}
   259  		pod.Name = "podA"
   260  		pod.ObjectMeta.Finalizers = []string{"foo"}
   261  		versionedJS, err := runtime.Encode(codec, pod)
   262  		if err != nil {
   263  			t.Errorf("%s: unexpected error: %v", test.name, err)
   264  			continue
   265  		}
   266  		_, _, err = jp.applyJSPatch(versionedJS)
   267  		if err != nil {
   268  			if len(test.expectedError) == 0 {
   269  				t.Errorf("%s: expect no error when applying json patch, but got %v", test.name, err)
   270  				continue
   271  			}
   272  			if !strings.Contains(err.Error(), test.expectedError) {
   273  				t.Errorf("%s: expected error %v, but got %v", test.name, test.expectedError, err)
   274  			}
   275  			if test.expectedErrorType != apierrors.ReasonForError(err) {
   276  				t.Errorf("%s: expected error type %v, but got %v", test.name, test.expectedErrorType, apierrors.ReasonForError(err))
   277  			}
   278  		} else if len(test.expectedError) > 0 {
   279  			t.Errorf("%s: expected err %s", test.name, test.expectedError)
   280  		}
   281  	}
   282  }
   283  
   284  func TestPatchCustomResource(t *testing.T) {
   285  	testGV := schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"}
   286  	scheme.AddKnownTypes(testGV, &unstructured.Unstructured{})
   287  	defaulter := runtime.ObjectDefaulter(scheme)
   288  
   289  	original := &unstructured.Unstructured{
   290  		Object: map[string]interface{}{
   291  			"apiVersion": "mygroup.example.com/v1beta1",
   292  			"kind":       "Noxu",
   293  			"metadata": map[string]interface{}{
   294  				"namespace": "Namespaced",
   295  				"name":      "foo",
   296  			},
   297  			"spec": map[string]interface{}{
   298  				"num": "10",
   299  			},
   300  		},
   301  	}
   302  	patch := `{"spec":{"num":"20"}}`
   303  	expectedError := "strategic merge patch format is not supported"
   304  
   305  	actual := &unstructured.Unstructured{}
   306  	err := strategicPatchObject(context.TODO(), defaulter, original, []byte(patch), actual, &unstructured.Unstructured{}, "")
   307  	if !apierrors.IsBadRequest(err) {
   308  		t.Errorf("expected HTTP status: BadRequest, got: %#v", apierrors.ReasonForError(err))
   309  	}
   310  	if err.Error() != expectedError {
   311  		t.Errorf("expected %#v, got %#v", expectedError, err.Error())
   312  	}
   313  }
   314  
   315  type testPatcher struct {
   316  	t *testing.T
   317  
   318  	// startingPod is used for the first Update
   319  	startingPod *example.Pod
   320  
   321  	// updatePod is the pod that is used for conflict comparison and used for subsequent Update calls
   322  	updatePod *example.Pod
   323  
   324  	numUpdates int
   325  }
   326  
   327  func (p *testPatcher) New() runtime.Object {
   328  	return &example.Pod{}
   329  }
   330  
   331  func (p *testPatcher) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
   332  	// Simulate GuaranteedUpdate behavior (retries internally on etcd changes if the incoming resource doesn't pin resourceVersion)
   333  	for {
   334  		currentPod := p.startingPod
   335  		if p.numUpdates > 0 {
   336  			currentPod = p.updatePod
   337  		}
   338  		p.numUpdates++
   339  
   340  		// Remember the current resource version
   341  		currentResourceVersion := currentPod.ResourceVersion
   342  
   343  		obj, err := objInfo.UpdatedObject(ctx, currentPod)
   344  		if err != nil {
   345  			return nil, false, err
   346  		}
   347  		inPod := obj.(*example.Pod)
   348  		if inPod.ResourceVersion == "" || inPod.ResourceVersion == "0" {
   349  			inPod.ResourceVersion = p.updatePod.ResourceVersion
   350  		}
   351  		if inPod.ResourceVersion != p.updatePod.ResourceVersion {
   352  			// If the patch didn't have an opinion on the resource version, retry like GuaranteedUpdate does
   353  			if inPod.ResourceVersion == currentResourceVersion {
   354  				continue
   355  			}
   356  			// If the patch changed the resource version and it mismatches, conflict
   357  			return nil, false, apierrors.NewConflict(example.Resource("pods"), inPod.Name, fmt.Errorf("existing %v, new %v", p.updatePod.ResourceVersion, inPod.ResourceVersion))
   358  		}
   359  
   360  		if currentPod == nil {
   361  			if err := createValidation(ctx, currentPod); err != nil {
   362  				return nil, false, err
   363  			}
   364  		} else {
   365  			if err := updateValidation(ctx, currentPod, inPod); err != nil {
   366  				return nil, false, err
   367  			}
   368  		}
   369  
   370  		return inPod, false, nil
   371  	}
   372  }
   373  
   374  func (p *testPatcher) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
   375  	p.t.Fatal("Unexpected call to testPatcher.Get")
   376  	return nil, errors.New("Unexpected call to testPatcher.Get")
   377  }
   378  
   379  type testNamer struct {
   380  	namespace string
   381  	name      string
   382  }
   383  
   384  func (p *testNamer) Namespace(req *http.Request) (namespace string, err error) {
   385  	return p.namespace, nil
   386  }
   387  
   388  // Name returns the name from the request, and an optional namespace value if this is a namespace
   389  // scoped call. An error is returned if the name is not available.
   390  func (p *testNamer) Name(req *http.Request) (namespace, name string, err error) {
   391  	return p.namespace, p.name, nil
   392  }
   393  
   394  // ObjectName returns the namespace and name from an object if they exist, or an error if the object
   395  // does not support names.
   396  func (p *testNamer) ObjectName(obj runtime.Object) (namespace, name string, err error) {
   397  	return p.namespace, p.name, nil
   398  }
   399  
   400  type patchTestCase struct {
   401  	name string
   402  
   403  	// admission chain to use, nil is fine
   404  	admissionMutation   mutateObjectUpdateFunc
   405  	admissionValidation rest.ValidateObjectUpdateFunc
   406  
   407  	// startingPod is used as the starting point for the first Update
   408  	startingPod *example.Pod
   409  	// changedPod can be set as the "destination" pod for the patch, and the test will compute a patch from the startingPod to the changedPod,
   410  	// or patches can be set directly using strategicMergePatch, mergePatch, and jsonPatch
   411  	changedPod          *example.Pod
   412  	strategicMergePatch string
   413  	mergePatch          string
   414  	jsonPatch           string
   415  
   416  	// updatePod is the pod that is used for conflict comparison and as the starting point for the second Update
   417  	updatePod *example.Pod
   418  
   419  	// expectedPod is the pod that you expect to get back after the patch is complete
   420  	expectedPod   *example.Pod
   421  	expectedError string
   422  	// if set, indicates the number of times patching was expected to be attempted
   423  	expectedTries int
   424  }
   425  
   426  func (tc *patchTestCase) Run(t *testing.T) {
   427  	t.Logf("Starting test %s", tc.name)
   428  
   429  	namespace := tc.startingPod.Namespace
   430  	name := tc.startingPod.Name
   431  
   432  	codec := codecs.LegacyCodec(examplev1.SchemeGroupVersion)
   433  
   434  	admissionMutation := tc.admissionMutation
   435  	if admissionMutation == nil {
   436  		admissionMutation = func(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object) error {
   437  			return nil
   438  		}
   439  	}
   440  	admissionValidation := tc.admissionValidation
   441  	if admissionValidation == nil {
   442  		admissionValidation = func(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object) error {
   443  			return nil
   444  		}
   445  	}
   446  
   447  	ctx := request.NewDefaultContext()
   448  	ctx = request.WithNamespace(ctx, namespace)
   449  
   450  	namer := &testNamer{namespace, name}
   451  	creater := runtime.ObjectCreater(scheme)
   452  	defaulter := runtime.ObjectDefaulter(scheme)
   453  	convertor := runtime.UnsafeObjectConvertor(scheme)
   454  	objectInterfaces := admission.NewObjectInterfacesFromScheme(scheme)
   455  	kind := examplev1.SchemeGroupVersion.WithKind("Pod")
   456  	resource := examplev1.SchemeGroupVersion.WithResource("pods")
   457  	schemaReferenceObj := &examplev1.Pod{}
   458  	hubVersion := example.SchemeGroupVersion
   459  
   460  	fieldmanager, err := managedfields.NewDefaultFieldManager(
   461  		managedfields.NewDeducedTypeConverter(),
   462  		convertor, defaulter, creater, kind, hubVersion, "", nil)
   463  
   464  	if err != nil {
   465  		t.Fatalf("failed to create field manager: %v", err)
   466  	}
   467  	for _, patchType := range []types.PatchType{types.JSONPatchType, types.MergePatchType, types.StrategicMergePatchType} {
   468  		// This needs to be reset on each iteration.
   469  		testPatcher := &testPatcher{
   470  			t:           t,
   471  			startingPod: tc.startingPod,
   472  			updatePod:   tc.updatePod,
   473  		}
   474  
   475  		t.Logf("Working with patchType %v", patchType)
   476  
   477  		patch := []byte{}
   478  		switch patchType {
   479  		case types.StrategicMergePatchType:
   480  			patch = []byte(tc.strategicMergePatch)
   481  			if len(patch) == 0 {
   482  				originalObjJS, err := runtime.Encode(codec, tc.startingPod)
   483  				if err != nil {
   484  					t.Errorf("%s: unexpected error: %v", tc.name, err)
   485  					continue
   486  				}
   487  				changedJS, err := runtime.Encode(codec, tc.changedPod)
   488  				if err != nil {
   489  					t.Errorf("%s: unexpected error: %v", tc.name, err)
   490  					continue
   491  				}
   492  				patch, err = strategicpatch.CreateTwoWayMergePatch(originalObjJS, changedJS, schemaReferenceObj)
   493  				if err != nil {
   494  					t.Errorf("%s: unexpected error: %v", tc.name, err)
   495  					continue
   496  				}
   497  			}
   498  
   499  		case types.MergePatchType:
   500  			patch = []byte(tc.mergePatch)
   501  			if len(patch) == 0 {
   502  				originalObjJS, err := runtime.Encode(codec, tc.startingPod)
   503  				if err != nil {
   504  					t.Errorf("%s: unexpected error: %v", tc.name, err)
   505  					continue
   506  				}
   507  				changedJS, err := runtime.Encode(codec, tc.changedPod)
   508  				if err != nil {
   509  					t.Errorf("%s: unexpected error: %v", tc.name, err)
   510  					continue
   511  				}
   512  				patch, err = jsonpatch.CreateMergePatch(originalObjJS, changedJS)
   513  				if err != nil {
   514  					t.Errorf("%s: unexpected error: %v", tc.name, err)
   515  					continue
   516  				}
   517  			}
   518  
   519  		case types.JSONPatchType:
   520  			patch = []byte(tc.jsonPatch)
   521  			if len(patch) == 0 {
   522  				// TODO SUPPORT THIS!
   523  				continue
   524  			}
   525  
   526  		default:
   527  			t.Error("unsupported patch type")
   528  		}
   529  
   530  		p := patcher{
   531  			namer:           namer,
   532  			creater:         creater,
   533  			defaulter:       defaulter,
   534  			unsafeConvertor: convertor,
   535  			kind:            kind,
   536  			resource:        resource,
   537  
   538  			objectInterfaces: objectInterfaces,
   539  
   540  			hubGroupVersion: hubVersion,
   541  
   542  			createValidation: rest.ValidateAllObjectFunc,
   543  			updateValidation: admissionValidation,
   544  			admissionCheck:   admissionMutation,
   545  
   546  			codec: codec,
   547  
   548  			restPatcher: testPatcher,
   549  			name:        name,
   550  			patchType:   patchType,
   551  			patchBytes:  patch,
   552  			options: &metav1.PatchOptions{
   553  				FieldManager: "test-manager",
   554  			},
   555  		}
   556  
   557  		ctx, cancel := context.WithTimeout(ctx, time.Second)
   558  		resultObj, _, err := p.patchResource(ctx, &RequestScope{
   559  			FieldManager: fieldmanager,
   560  		})
   561  		cancel()
   562  
   563  		if len(tc.expectedError) != 0 {
   564  			if err == nil || err.Error() != tc.expectedError {
   565  				t.Errorf("%s: expected error %v, but got %v", tc.name, tc.expectedError, err)
   566  				continue
   567  			}
   568  		} else {
   569  			if err != nil {
   570  				t.Errorf("%s: unexpected error: %v", tc.name, err)
   571  				continue
   572  			}
   573  		}
   574  
   575  		if tc.expectedTries > 0 {
   576  			if tc.expectedTries != testPatcher.numUpdates {
   577  				t.Errorf("%s: expected %d tries, got %d", tc.name, tc.expectedTries, testPatcher.numUpdates)
   578  			}
   579  		}
   580  
   581  		if tc.expectedPod == nil {
   582  			if resultObj != nil {
   583  				t.Errorf("%s: unexpected result: %v", tc.name, resultObj)
   584  			}
   585  			continue
   586  		}
   587  
   588  		resultPod := resultObj.(*example.Pod)
   589  
   590  		// roundtrip to get defaulting
   591  		expectedJS, err := runtime.Encode(codec, tc.expectedPod)
   592  		if err != nil {
   593  			t.Errorf("%s: unexpected error: %v", tc.name, err)
   594  			continue
   595  		}
   596  		expectedObj, err := runtime.Decode(codec, expectedJS)
   597  		if err != nil {
   598  			t.Errorf("%s: unexpected error: %v", tc.name, err)
   599  			continue
   600  		}
   601  		reallyExpectedPod := expectedObj.(*example.Pod)
   602  
   603  		if !reflect.DeepEqual(*reallyExpectedPod, *resultPod) {
   604  			t.Errorf("%s mismatch: %v\n", tc.name, cmp.Diff(reallyExpectedPod, resultPod))
   605  			continue
   606  		}
   607  	}
   608  
   609  }
   610  
   611  func TestNumberConversion(t *testing.T) {
   612  	defaulter := runtime.ObjectDefaulter(scheme)
   613  
   614  	terminationGracePeriodSeconds := int64(42)
   615  	activeDeadlineSeconds := int64(42)
   616  	currentVersionedObject := &examplev1.Pod{
   617  		TypeMeta:   metav1.TypeMeta{Kind: "Example", APIVersion: examplev1.SchemeGroupVersion.String()},
   618  		ObjectMeta: metav1.ObjectMeta{Name: "test-example"},
   619  		Spec: examplev1.PodSpec{
   620  			TerminationGracePeriodSeconds: &terminationGracePeriodSeconds,
   621  			ActiveDeadlineSeconds:         &activeDeadlineSeconds,
   622  		},
   623  	}
   624  	versionedObjToUpdate := &examplev1.Pod{}
   625  	schemaReferenceObj := &examplev1.Pod{}
   626  
   627  	patchJS := []byte(`{"spec":{"terminationGracePeriodSeconds":42,"activeDeadlineSeconds":120}}`)
   628  
   629  	err := strategicPatchObject(context.TODO(), defaulter, currentVersionedObject, patchJS, versionedObjToUpdate, schemaReferenceObj, "")
   630  	if err != nil {
   631  		t.Fatal(err)
   632  	}
   633  	if versionedObjToUpdate.Spec.TerminationGracePeriodSeconds == nil || *versionedObjToUpdate.Spec.TerminationGracePeriodSeconds != 42 ||
   634  		versionedObjToUpdate.Spec.ActiveDeadlineSeconds == nil || *versionedObjToUpdate.Spec.ActiveDeadlineSeconds != 120 {
   635  		t.Fatal(errors.New("Ports failed to merge because of number conversion issue"))
   636  	}
   637  }
   638  
   639  func TestPatchResourceNumberConversion(t *testing.T) {
   640  	namespace := "bar"
   641  	name := "foo"
   642  	uid := types.UID("uid")
   643  	fifteen := int64(15)
   644  	thirty := int64(30)
   645  
   646  	tc := &patchTestCase{
   647  		name: "TestPatchResourceNumberConversion",
   648  
   649  		startingPod: &example.Pod{},
   650  		changedPod:  &example.Pod{},
   651  		updatePod:   &example.Pod{},
   652  
   653  		expectedPod: &example.Pod{},
   654  	}
   655  
   656  	setTcPod(tc.startingPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &fifteen, "")
   657  
   658  	// Patch tries to change to 30.
   659  	setTcPod(tc.changedPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &thirty, "")
   660  
   661  	// Someone else already changed it to 30.
   662  	// This should be fine since it's not a "meaningful conflict".
   663  	// Previously this was detected as a meaningful conflict because int64(30) != float64(30).
   664  	setTcPod(tc.updatePod, name, namespace, uid, "2", examplev1.SchemeGroupVersion.String(), &thirty, "anywhere")
   665  
   666  	setTcPod(tc.expectedPod, name, namespace, uid, "2", "", &thirty, "anywhere")
   667  
   668  	tc.Run(t)
   669  }
   670  
   671  func TestPatchResourceWithVersionConflict(t *testing.T) {
   672  	namespace := "bar"
   673  	name := "foo"
   674  	uid := types.UID("uid")
   675  	fifteen := int64(15)
   676  	thirty := int64(30)
   677  
   678  	tc := &patchTestCase{
   679  		name: "TestPatchResourceWithVersionConflict",
   680  
   681  		startingPod: &example.Pod{},
   682  		changedPod:  &example.Pod{},
   683  		updatePod:   &example.Pod{},
   684  
   685  		expectedPod: &example.Pod{},
   686  	}
   687  
   688  	setTcPod(tc.startingPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &fifteen, "")
   689  
   690  	setTcPod(tc.changedPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &thirty, "")
   691  
   692  	setTcPod(tc.updatePod, name, namespace, uid, "2", examplev1.SchemeGroupVersion.String(), &fifteen, "anywhere")
   693  
   694  	setTcPod(tc.expectedPod, name, namespace, uid, "2", "", &thirty, "anywhere")
   695  
   696  	tc.Run(t)
   697  }
   698  
   699  func TestPatchResourceWithStaleVersionConflict(t *testing.T) {
   700  	namespace := "bar"
   701  	name := "foo"
   702  	uid := types.UID("uid")
   703  
   704  	tc := &patchTestCase{
   705  		name: "TestPatchResourceWithStaleVersionConflict",
   706  
   707  		startingPod: &example.Pod{},
   708  		updatePod:   &example.Pod{},
   709  
   710  		expectedError: `Operation cannot be fulfilled on pods.example.apiserver.k8s.io "foo": existing 2, new 1`,
   711  		expectedTries: 1,
   712  	}
   713  
   714  	// starting pod is at rv=2
   715  	tc.startingPod.Name = name
   716  	tc.startingPod.Namespace = namespace
   717  	tc.startingPod.UID = uid
   718  	tc.startingPod.ResourceVersion = "2"
   719  	tc.startingPod.APIVersion = examplev1.SchemeGroupVersion.String()
   720  	// same pod is still in place when attempting to persist the update
   721  	tc.updatePod = tc.startingPod
   722  
   723  	// patches are submitted with a stale rv=1
   724  	tc.mergePatch = `{"metadata":{"resourceVersion":"1"},"spec":{"nodeName":"foo"}}`
   725  	tc.strategicMergePatch = `{"metadata":{"resourceVersion":"1"},"spec":{"nodeName":"foo"}}`
   726  
   727  	tc.Run(t)
   728  }
   729  
   730  func TestPatchResourceWithRacingVersionConflict(t *testing.T) {
   731  	namespace := "bar"
   732  	name := "foo"
   733  	uid := types.UID("uid")
   734  
   735  	tc := &patchTestCase{
   736  		name: "TestPatchResourceWithRacingVersionConflict",
   737  
   738  		startingPod: &example.Pod{},
   739  		updatePod:   &example.Pod{},
   740  
   741  		expectedError: `Operation cannot be fulfilled on pods.example.apiserver.k8s.io "foo": existing 3, new 2`,
   742  		expectedTries: 2,
   743  	}
   744  
   745  	// starting pod is at rv=2
   746  	tc.startingPod.Name = name
   747  	tc.startingPod.Namespace = namespace
   748  	tc.startingPod.UID = uid
   749  	tc.startingPod.ResourceVersion = "2"
   750  	tc.startingPod.APIVersion = examplev1.SchemeGroupVersion.String()
   751  
   752  	// pod with rv=3 is found when attempting to persist the update
   753  	tc.updatePod.Name = name
   754  	tc.updatePod.Namespace = namespace
   755  	tc.updatePod.UID = uid
   756  	tc.updatePod.ResourceVersion = "3"
   757  	tc.updatePod.APIVersion = examplev1.SchemeGroupVersion.String()
   758  
   759  	// patches are submitted with a rv=2
   760  	tc.mergePatch = `{"metadata":{"resourceVersion":"2"},"spec":{"nodeName":"foo"}}`
   761  	tc.strategicMergePatch = `{"metadata":{"resourceVersion":"2"},"spec":{"nodeName":"foo"}}`
   762  
   763  	tc.Run(t)
   764  }
   765  
   766  func TestPatchResourceWithConflict(t *testing.T) {
   767  	namespace := "bar"
   768  	name := "foo"
   769  	uid := types.UID("uid")
   770  
   771  	tc := &patchTestCase{
   772  		name: "TestPatchResourceWithConflict",
   773  
   774  		startingPod: &example.Pod{},
   775  		changedPod:  &example.Pod{},
   776  		updatePod:   &example.Pod{},
   777  		expectedPod: &example.Pod{},
   778  	}
   779  
   780  	// See issue #63104 for discussion of how much sense this makes.
   781  
   782  	setTcPod(tc.startingPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), nil, "here")
   783  
   784  	setTcPod(tc.changedPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), nil, "there")
   785  
   786  	setTcPod(tc.updatePod, name, namespace, uid, "2", examplev1.SchemeGroupVersion.String(), nil, "anywhere")
   787  
   788  	tc.expectedPod.Name = name
   789  	tc.expectedPod.Namespace = namespace
   790  	tc.expectedPod.UID = uid
   791  	tc.expectedPod.ResourceVersion = "2"
   792  	tc.expectedPod.APIVersion = examplev1.SchemeGroupVersion.String()
   793  	tc.expectedPod.Spec.NodeName = "there"
   794  
   795  	tc.Run(t)
   796  }
   797  
   798  func TestPatchWithAdmissionRejection(t *testing.T) {
   799  	namespace := "bar"
   800  	name := "foo"
   801  	uid := types.UID("uid")
   802  	fifteen := int64(15)
   803  	thirty := int64(30)
   804  
   805  	type Test struct {
   806  		name                string
   807  		admissionMutation   mutateObjectUpdateFunc
   808  		admissionValidation rest.ValidateObjectUpdateFunc
   809  		expectedError       string
   810  	}
   811  	for _, test := range []Test{
   812  		{
   813  			name: "TestPatchWithMutatingAdmissionRejection",
   814  			admissionMutation: func(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object) error {
   815  				return errors.New("mutating admission failure")
   816  			},
   817  			admissionValidation: rest.ValidateAllObjectUpdateFunc,
   818  			expectedError:       "mutating admission failure",
   819  		},
   820  		{
   821  			name:              "TestPatchWithValidatingAdmissionRejection",
   822  			admissionMutation: rest.ValidateAllObjectUpdateFunc,
   823  			admissionValidation: func(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object) error {
   824  				return errors.New("validating admission failure")
   825  			},
   826  			expectedError: "validating admission failure",
   827  		},
   828  		{
   829  			name: "TestPatchWithBothAdmissionRejections",
   830  			admissionMutation: func(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object) error {
   831  				return errors.New("mutating admission failure")
   832  			},
   833  			admissionValidation: func(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object) error {
   834  				return errors.New("validating admission failure")
   835  			},
   836  			expectedError: "mutating admission failure",
   837  		},
   838  	} {
   839  		tc := &patchTestCase{
   840  			name: test.name,
   841  
   842  			admissionMutation:   test.admissionMutation,
   843  			admissionValidation: test.admissionValidation,
   844  
   845  			startingPod: &example.Pod{},
   846  			changedPod:  &example.Pod{},
   847  			updatePod:   &example.Pod{},
   848  
   849  			expectedError: test.expectedError,
   850  		}
   851  
   852  		setTcPod(tc.startingPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &fifteen, "")
   853  
   854  		setTcPod(tc.changedPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &thirty, "")
   855  
   856  		setTcPod(tc.updatePod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &fifteen, "")
   857  
   858  		tc.Run(t)
   859  	}
   860  }
   861  
   862  func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.T) {
   863  	namespace := "bar"
   864  	name := "foo"
   865  	uid := types.UID("uid")
   866  	fifteen := int64(15)
   867  	thirty := int64(30)
   868  	seen := false
   869  
   870  	tc := &patchTestCase{
   871  		name: "TestPatchWithVersionConflictThenAdmissionFailure",
   872  
   873  		admissionMutation: func(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object) error {
   874  			if seen {
   875  				return errors.New("admission failure")
   876  			}
   877  
   878  			seen = true
   879  			return nil
   880  		},
   881  
   882  		startingPod: &example.Pod{},
   883  		changedPod:  &example.Pod{},
   884  		updatePod:   &example.Pod{},
   885  
   886  		expectedError: "admission failure",
   887  	}
   888  
   889  	setTcPod(tc.startingPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &fifteen, "")
   890  
   891  	setTcPod(tc.changedPod, name, namespace, uid, "1", examplev1.SchemeGroupVersion.String(), &thirty, "")
   892  
   893  	setTcPod(tc.updatePod, name, namespace, uid, "2", examplev1.SchemeGroupVersion.String(), &fifteen, "anywhere")
   894  
   895  	tc.Run(t)
   896  }
   897  
   898  func TestHasUID(t *testing.T) {
   899  	testcases := []struct {
   900  		obj    runtime.Object
   901  		hasUID bool
   902  	}{
   903  		{obj: nil, hasUID: false},
   904  		{obj: &example.Pod{}, hasUID: false},
   905  		{obj: nil, hasUID: false},
   906  		{obj: runtime.Object(nil), hasUID: false},
   907  		{obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("A")}}, hasUID: true},
   908  	}
   909  	for i, tc := range testcases {
   910  		actual, err := hasUID(tc.obj)
   911  		if err != nil {
   912  			t.Errorf("%d: unexpected error %v", i, err)
   913  			continue
   914  		}
   915  		if tc.hasUID != actual {
   916  			t.Errorf("%d: expected %v, got %v", i, tc.hasUID, actual)
   917  		}
   918  	}
   919  }
   920  
   921  func setTcPod(tcPod *example.Pod, name string, namespace string, uid types.UID, resourceVersion string, apiVersion string, activeDeadlineSeconds *int64, nodeName string) {
   922  	tcPod.Name = name
   923  	tcPod.Namespace = namespace
   924  	tcPod.UID = uid
   925  	tcPod.ResourceVersion = resourceVersion
   926  	if len(apiVersion) != 0 {
   927  		tcPod.APIVersion = apiVersion
   928  	}
   929  	if activeDeadlineSeconds != nil {
   930  		tcPod.Spec.ActiveDeadlineSeconds = activeDeadlineSeconds
   931  	}
   932  	if len(nodeName) != 0 {
   933  		tcPod.Spec.NodeName = nodeName
   934  	}
   935  }
   936  
   937  func (f mutateObjectUpdateFunc) Handles(operation admission.Operation) bool {
   938  	return true
   939  }
   940  
   941  func (f mutateObjectUpdateFunc) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
   942  	return f(ctx, a.GetObject(), a.GetOldObject())
   943  }
   944  
   945  func TestTransformDecodeErrorEnsuresBadRequestError(t *testing.T) {
   946  	testCases := []struct {
   947  		name             string
   948  		typer            runtime.ObjectTyper
   949  		decodedGVK       *schema.GroupVersionKind
   950  		decodeIntoObject runtime.Object
   951  		baseErr          error
   952  		expectedErr      error
   953  	}{
   954  		{
   955  			name:  "decoding normal objects fails and returns a bad-request error",
   956  			typer: clientgoscheme.Scheme,
   957  			decodedGVK: &schema.GroupVersionKind{
   958  				Group:   testapigroupv1.GroupName,
   959  				Version: "v1",
   960  				Kind:    "Carp",
   961  			},
   962  			decodeIntoObject: &testapigroupv1.Carp{}, // which client-go's scheme doesn't recognize
   963  			baseErr:          fmt.Errorf("plain error"),
   964  		},
   965  		{
   966  			name:             "decoding objects with unknown GVK fails and returns a bad-request error",
   967  			typer:            alwaysErrorTyper{},
   968  			decodedGVK:       nil,
   969  			decodeIntoObject: &testapigroupv1.Carp{}, // which client-go's scheme doesn't recognize
   970  			baseErr:          nil,
   971  		},
   972  	}
   973  	for _, testCase := range testCases {
   974  		err := transformDecodeError(testCase.typer, testCase.baseErr, testCase.decodeIntoObject, testCase.decodedGVK, []byte(``))
   975  		if apiStatus, ok := err.(apierrors.APIStatus); !ok || apiStatus.Status().Code != http.StatusBadRequest {
   976  			t.Errorf("expected bad request error but got: %v", err)
   977  		}
   978  	}
   979  }
   980  
   981  var _ runtime.ObjectTyper = alwaysErrorTyper{}
   982  
   983  type alwaysErrorTyper struct{}
   984  
   985  func (alwaysErrorTyper) ObjectKinds(runtime.Object) ([]schema.GroupVersionKind, bool, error) {
   986  	return nil, false, fmt.Errorf("always error")
   987  }
   988  
   989  func (alwaysErrorTyper) Recognizes(gvk schema.GroupVersionKind) bool {
   990  	return false
   991  }
   992  
   993  func TestUpdateToCreateOptions(t *testing.T) {
   994  	f := fuzz.New()
   995  	for i := 0; i < 100; i++ {
   996  		t.Run(fmt.Sprintf("Run %d/100", i), func(t *testing.T) {
   997  			update := &metav1.UpdateOptions{}
   998  			f.Fuzz(update)
   999  			create := updateToCreateOptions(update)
  1000  
  1001  			b, err := json.Marshal(create)
  1002  			if err != nil {
  1003  				t.Fatalf("failed to marshal CreateOptions (%v): %v", err, create)
  1004  			}
  1005  			got := &metav1.UpdateOptions{}
  1006  			err = json.Unmarshal(b, &got)
  1007  			if err != nil {
  1008  				t.Fatalf("failed to unmarshal UpdateOptions: %v", err)
  1009  			}
  1010  			got.TypeMeta = metav1.TypeMeta{}
  1011  			update.TypeMeta = metav1.TypeMeta{}
  1012  			if !reflect.DeepEqual(*update, *got) {
  1013  				t.Fatalf(`updateToCreateOptions round-trip failed:
  1014  got:  %#+v
  1015  want: %#+v`, got, update)
  1016  			}
  1017  
  1018  		})
  1019  	}
  1020  }
  1021  
  1022  func TestPatchToUpdateOptions(t *testing.T) {
  1023  	tests := []struct {
  1024  		name        string
  1025  		converterFn func(po *metav1.PatchOptions) interface{}
  1026  	}{
  1027  		{
  1028  			name: "patchToUpdateOptions",
  1029  			converterFn: func(patch *metav1.PatchOptions) interface{} {
  1030  				return patchToUpdateOptions(patch)
  1031  			},
  1032  		},
  1033  		{
  1034  			name: "patchToCreateOptions",
  1035  			converterFn: func(patch *metav1.PatchOptions) interface{} {
  1036  				return patchToCreateOptions(patch)
  1037  			},
  1038  		},
  1039  	}
  1040  
  1041  	f := fuzz.New()
  1042  	for _, test := range tests {
  1043  		t.Run(test.name, func(t *testing.T) {
  1044  			for i := 0; i < 100; i++ {
  1045  				t.Run(fmt.Sprintf("Run %d/100", i), func(t *testing.T) {
  1046  					patch := &metav1.PatchOptions{}
  1047  					f.Fuzz(patch)
  1048  					converted := test.converterFn(patch)
  1049  
  1050  					b, err := json.Marshal(converted)
  1051  					if err != nil {
  1052  						t.Fatalf("failed to marshal converted object (%v): %v", err, converted)
  1053  					}
  1054  					got := &metav1.PatchOptions{}
  1055  					err = json.Unmarshal(b, &got)
  1056  					if err != nil {
  1057  						t.Fatalf("failed to unmarshal converted object: %v", err)
  1058  					}
  1059  
  1060  					// Clear TypeMeta because we expect it to be different between the original and converted type
  1061  					got.TypeMeta = metav1.TypeMeta{}
  1062  					patch.TypeMeta = metav1.TypeMeta{}
  1063  
  1064  					// clear fields that we know belong in PatchOptions only
  1065  					patch.Force = nil
  1066  
  1067  					if !reflect.DeepEqual(*patch, *got) {
  1068  						t.Fatalf(`round-trip failed:
  1069  got:  %#+v
  1070  want: %#+v`, got, converted)
  1071  					}
  1072  
  1073  				})
  1074  			}
  1075  		})
  1076  	}
  1077  }
  1078  
  1079  func TestDedupOwnerReferences(t *testing.T) {
  1080  	falseA := false
  1081  	falseB := false
  1082  	testCases := []struct {
  1083  		name            string
  1084  		ownerReferences []metav1.OwnerReference
  1085  		expected        []metav1.OwnerReference
  1086  	}{
  1087  		{
  1088  			name: "simple multiple duplicates",
  1089  			ownerReferences: []metav1.OwnerReference{
  1090  				{
  1091  					APIVersion: "customresourceVersion",
  1092  					Kind:       "customresourceKind",
  1093  					Name:       "name",
  1094  					UID:        "1",
  1095  				},
  1096  				{
  1097  					APIVersion: "customresourceVersion",
  1098  					Kind:       "customresourceKind",
  1099  					Name:       "name",
  1100  					UID:        "2",
  1101  				},
  1102  				{
  1103  					APIVersion: "customresourceVersion",
  1104  					Kind:       "customresourceKind",
  1105  					Name:       "name",
  1106  					UID:        "1",
  1107  				},
  1108  				{
  1109  					APIVersion: "customresourceVersion",
  1110  					Kind:       "customresourceKind",
  1111  					Name:       "name",
  1112  					UID:        "1",
  1113  				},
  1114  				{
  1115  					APIVersion: "customresourceVersion",
  1116  					Kind:       "customresourceKind",
  1117  					Name:       "name",
  1118  					UID:        "2",
  1119  				},
  1120  			},
  1121  			expected: []metav1.OwnerReference{
  1122  				{
  1123  					APIVersion: "customresourceVersion",
  1124  					Kind:       "customresourceKind",
  1125  					Name:       "name",
  1126  					UID:        "1",
  1127  				},
  1128  				{
  1129  					APIVersion: "customresourceVersion",
  1130  					Kind:       "customresourceKind",
  1131  					Name:       "name",
  1132  					UID:        "2",
  1133  				},
  1134  			},
  1135  		},
  1136  		{
  1137  			name: "don't dedup same uid different name entries",
  1138  			ownerReferences: []metav1.OwnerReference{
  1139  				{
  1140  					APIVersion: "customresourceVersion",
  1141  					Kind:       "customresourceKind",
  1142  					Name:       "name1",
  1143  					UID:        "1",
  1144  				},
  1145  				{
  1146  					APIVersion: "customresourceVersion",
  1147  					Kind:       "customresourceKind",
  1148  					Name:       "name2",
  1149  					UID:        "1",
  1150  				},
  1151  			},
  1152  			expected: []metav1.OwnerReference{
  1153  				{
  1154  					APIVersion: "customresourceVersion",
  1155  					Kind:       "customresourceKind",
  1156  					Name:       "name1",
  1157  					UID:        "1",
  1158  				},
  1159  				{
  1160  					APIVersion: "customresourceVersion",
  1161  					Kind:       "customresourceKind",
  1162  					Name:       "name2",
  1163  					UID:        "1",
  1164  				},
  1165  			},
  1166  		},
  1167  		{
  1168  			name: "don't dedup same uid different API version entries",
  1169  			ownerReferences: []metav1.OwnerReference{
  1170  				{
  1171  					APIVersion: "customresourceVersion1",
  1172  					Kind:       "customresourceKind",
  1173  					Name:       "name",
  1174  					UID:        "1",
  1175  				},
  1176  				{
  1177  					APIVersion: "customresourceVersion2",
  1178  					Kind:       "customresourceKind",
  1179  					Name:       "name",
  1180  					UID:        "1",
  1181  				},
  1182  			},
  1183  			expected: []metav1.OwnerReference{
  1184  				{
  1185  					APIVersion: "customresourceVersion1",
  1186  					Kind:       "customresourceKind",
  1187  					Name:       "name",
  1188  					UID:        "1",
  1189  				},
  1190  				{
  1191  					APIVersion: "customresourceVersion2",
  1192  					Kind:       "customresourceKind",
  1193  					Name:       "name",
  1194  					UID:        "1",
  1195  				},
  1196  			},
  1197  		},
  1198  		{
  1199  			name: "dedup memory-equal entries",
  1200  			ownerReferences: []metav1.OwnerReference{
  1201  				{
  1202  					APIVersion:         "customresourceVersion",
  1203  					Kind:               "customresourceKind",
  1204  					Name:               "name",
  1205  					UID:                "1",
  1206  					Controller:         &falseA,
  1207  					BlockOwnerDeletion: &falseA,
  1208  				},
  1209  				{
  1210  					APIVersion:         "customresourceVersion",
  1211  					Kind:               "customresourceKind",
  1212  					Name:               "name",
  1213  					UID:                "1",
  1214  					Controller:         &falseA,
  1215  					BlockOwnerDeletion: &falseA,
  1216  				},
  1217  			},
  1218  			expected: []metav1.OwnerReference{
  1219  				{
  1220  					APIVersion:         "customresourceVersion",
  1221  					Kind:               "customresourceKind",
  1222  					Name:               "name",
  1223  					UID:                "1",
  1224  					Controller:         &falseA,
  1225  					BlockOwnerDeletion: &falseA,
  1226  				},
  1227  			},
  1228  		},
  1229  		{
  1230  			name: "dedup semantic-equal entries",
  1231  			ownerReferences: []metav1.OwnerReference{
  1232  				{
  1233  					APIVersion:         "customresourceVersion",
  1234  					Kind:               "customresourceKind",
  1235  					Name:               "name",
  1236  					UID:                "1",
  1237  					Controller:         &falseA,
  1238  					BlockOwnerDeletion: &falseA,
  1239  				},
  1240  				{
  1241  					APIVersion:         "customresourceVersion",
  1242  					Kind:               "customresourceKind",
  1243  					Name:               "name",
  1244  					UID:                "1",
  1245  					Controller:         &falseB,
  1246  					BlockOwnerDeletion: &falseB,
  1247  				},
  1248  			},
  1249  			expected: []metav1.OwnerReference{
  1250  				{
  1251  					APIVersion:         "customresourceVersion",
  1252  					Kind:               "customresourceKind",
  1253  					Name:               "name",
  1254  					UID:                "1",
  1255  					Controller:         &falseA,
  1256  					BlockOwnerDeletion: &falseA,
  1257  				},
  1258  			},
  1259  		},
  1260  		{
  1261  			name: "don't dedup semantic-different entries",
  1262  			ownerReferences: []metav1.OwnerReference{
  1263  				{
  1264  					APIVersion:         "customresourceVersion",
  1265  					Kind:               "customresourceKind",
  1266  					Name:               "name",
  1267  					UID:                "1",
  1268  					Controller:         &falseA,
  1269  					BlockOwnerDeletion: &falseA,
  1270  				},
  1271  				{
  1272  					APIVersion: "customresourceVersion",
  1273  					Kind:       "customresourceKind",
  1274  					Name:       "name",
  1275  					UID:        "1",
  1276  				},
  1277  			},
  1278  			expected: []metav1.OwnerReference{
  1279  				{
  1280  					APIVersion:         "customresourceVersion",
  1281  					Kind:               "customresourceKind",
  1282  					Name:               "name",
  1283  					UID:                "1",
  1284  					Controller:         &falseA,
  1285  					BlockOwnerDeletion: &falseA,
  1286  				},
  1287  				{
  1288  					APIVersion: "customresourceVersion",
  1289  					Kind:       "customresourceKind",
  1290  					Name:       "name",
  1291  					UID:        "1",
  1292  				},
  1293  			},
  1294  		},
  1295  	}
  1296  	for _, tc := range testCases {
  1297  		t.Run(tc.name, func(t *testing.T) {
  1298  			deduped, _ := dedupOwnerReferences(tc.ownerReferences)
  1299  			if !apiequality.Semantic.DeepEqual(deduped, tc.expected) {
  1300  				t.Errorf("diff: %v", cmp.Diff(deduped, tc.expected))
  1301  			}
  1302  		})
  1303  	}
  1304  }
  1305  
  1306  func TestParseYAMLWarnings(t *testing.T) {
  1307  	yamlNoErrs := `---
  1308  apiVersion: foo
  1309  kind: bar
  1310  metadata:
  1311    name: no-errors
  1312  spec:
  1313    field1: val1
  1314    field2: val2
  1315    nested:
  1316    - name: nestedName
  1317      nestedField1: val1`
  1318  	yamlOneErr := `---
  1319  apiVersion: foo
  1320  kind: bar
  1321  metadata:
  1322    name: no-errors
  1323  spec:
  1324    field1: val1
  1325    field2: val2
  1326    field2: val3
  1327    nested:
  1328    - name: nestedName
  1329      nestedField1: val1`
  1330  	yamlManyErrs := `---
  1331  apiVersion: foo
  1332  kind: bar
  1333  metadata:
  1334    name: no-errors
  1335  spec:
  1336    field1: val1
  1337    field2: val2
  1338    field2: val3
  1339    nested:
  1340    - name: nestedName
  1341      nestedField1: val1
  1342      nestedField2: val2
  1343      nestedField2: val3`
  1344  	testCases := []struct {
  1345  		name     string
  1346  		yaml     string
  1347  		expected []string
  1348  	}{
  1349  		{
  1350  			name: "no errors",
  1351  			yaml: yamlNoErrs,
  1352  		},
  1353  		{
  1354  			name:     "one error",
  1355  			yaml:     yamlOneErr,
  1356  			expected: []string{`line 9: key "field2" already set in map`},
  1357  		},
  1358  		{
  1359  			name:     "many errors",
  1360  			yaml:     yamlManyErrs,
  1361  			expected: []string{`line 9: key "field2" already set in map`, `line 14: key "nestedField2" already set in map`},
  1362  		},
  1363  	}
  1364  	for _, tc := range testCases {
  1365  		t.Run(tc.name, func(t *testing.T) {
  1366  			obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
  1367  			if err := yaml.UnmarshalStrict([]byte(tc.yaml), &obj.Object); err != nil {
  1368  				parsedErrs := parseYAMLWarnings(err.Error())
  1369  				if !reflect.DeepEqual(tc.expected, parsedErrs) {
  1370  					t.Fatalf("expected: %v\n, got: %v\n", tc.expected, parsedErrs)
  1371  				}
  1372  			}
  1373  		})
  1374  	}
  1375  }