k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/apimachinery/apply.go (about)

     1  /*
     2  Copyright 2021 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 apimachinery
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"reflect"
    24  
    25  	appsv1 "k8s.io/api/apps/v1"
    26  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    27  	apiextensionclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    28  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/apimachinery/pkg/api/meta"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/client-go/dynamic"
    35  	clientset "k8s.io/client-go/kubernetes"
    36  	"k8s.io/kubernetes/test/e2e/framework"
    37  	e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
    38  	imageutils "k8s.io/kubernetes/test/utils/image"
    39  	admissionapi "k8s.io/pod-security-admission/api"
    40  
    41  	"github.com/onsi/ginkgo/v2"
    42  
    43  	// ensure libs have a chance to initialize
    44  	_ "github.com/stretchr/testify/assert"
    45  )
    46  
    47  var _ = SIGDescribe("ServerSideApply", func() {
    48  	f := framework.NewDefaultFramework("apply")
    49  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    50  
    51  	var client clientset.Interface
    52  	var ns string
    53  
    54  	ginkgo.BeforeEach(func() {
    55  		client = f.ClientSet
    56  		ns = f.Namespace.Name
    57  	})
    58  
    59  	ginkgo.AfterEach(func(ctx context.Context) {
    60  		_ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment", metav1.DeleteOptions{})
    61  		_ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment-shared-unset", metav1.DeleteOptions{})
    62  		_ = client.AppsV1().Deployments(ns).Delete(ctx, "deployment-shared-map-item-removal", metav1.DeleteOptions{})
    63  		_ = client.CoreV1().Pods(ns).Delete(ctx, "test-pod", metav1.DeleteOptions{})
    64  	})
    65  
    66  	/*
    67  		Release : v1.21
    68  		Testname: Server Side Apply, Create
    69  		Description: Apply an object. An apply on an object that does not exist MUST create the object.
    70  	*/
    71  	ginkgo.It("should create an applied object if it does not already exist", func(ctx context.Context) {
    72  		testCases := []struct {
    73  			resource      string
    74  			name          string
    75  			body          string
    76  			managedFields string
    77  		}{
    78  			{
    79  				resource: "pods",
    80  				name:     "test-pod",
    81  				body: `{
    82  				"apiVersion": "v1",
    83  				"kind": "Pod",
    84  				"metadata": {
    85  					"name": "test-pod"
    86  				},
    87  				"spec": {
    88  					"containers": [{
    89  						"name":  "test-container",
    90  						"image": "test-image"
    91  					}]
    92  				}
    93  			}`,
    94  				managedFields: `{"f:spec":{"f:containers":{"k:{\"name\":\"test-container\"}":{".":{},"f:image":{},"f:name":{}}}}}`,
    95  			}, {
    96  				resource: "services",
    97  				name:     "test-svc",
    98  				body: `{
    99  				"apiVersion": "v1",
   100  				"kind": "Service",
   101  				"metadata": {
   102  					"name": "test-svc"
   103  				},
   104  				"spec": {
   105  					"ports": [{
   106  						"port": 8080,
   107  						"protocol": "UDP"
   108  					}]
   109  				}
   110  			}`,
   111  				managedFields: `{"f:spec":{"f:ports":{"k:{\"port\":8080,\"protocol\":\"UDP\"}":{".":{},"f:port":{},"f:protocol":{}}}}}`,
   112  			},
   113  		}
   114  
   115  		for _, tc := range testCases {
   116  			_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   117  				Namespace(ns).
   118  				Resource(tc.resource).
   119  				Name(tc.name).
   120  				Param("fieldManager", "apply_test").
   121  				Body([]byte(tc.body)).
   122  				Do(ctx).
   123  				Get()
   124  			if err != nil {
   125  				framework.Failf("Failed to create object using Apply patch: %v", err)
   126  			}
   127  
   128  			_, err = client.CoreV1().RESTClient().Get().Namespace(ns).Resource(tc.resource).Name(tc.name).Do(ctx).Get()
   129  			if err != nil {
   130  				framework.Failf("Failed to retrieve object: %v", err)
   131  			}
   132  
   133  			// Test that we can re apply with a different field manager and don't get conflicts
   134  			obj, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   135  				Namespace(ns).
   136  				Resource(tc.resource).
   137  				Name(tc.name).
   138  				Param("fieldManager", "apply_test_2").
   139  				Body([]byte(tc.body)).
   140  				Do(ctx).
   141  				Get()
   142  			if err != nil {
   143  				framework.Failf("Failed to re-apply object using Apply patch: %v", err)
   144  			}
   145  
   146  			// Verify that both appliers own the fields
   147  			accessor, err := meta.Accessor(obj)
   148  			framework.ExpectNoError(err, "getting ObjectMeta")
   149  			managedFields := accessor.GetManagedFields()
   150  			for _, entry := range managedFields {
   151  				if entry.Manager == "apply_test_2" || entry.Manager == "apply_test" {
   152  					if entry.FieldsV1.String() != tc.managedFields {
   153  						framework.Failf("Expected managed fields %s, got %s", tc.managedFields, entry.FieldsV1.String())
   154  					}
   155  				}
   156  			}
   157  		}
   158  	})
   159  
   160  	/*
   161  		Release : v1.21
   162  		Testname: Server Side Apply, Subresource
   163  		Description: Apply a resource and issue a subsequent apply on a subresource. The subresource MUST be updated with the applied object contents.
   164  	*/
   165  	ginkgo.It("should work for subresources", func(ctx context.Context) {
   166  		{
   167  			testCases := []struct {
   168  				resource    string
   169  				name        string
   170  				body        string
   171  				statusPatch string
   172  			}{
   173  				{
   174  					resource: "pods",
   175  					name:     "test-pod",
   176  					body: `{
   177  				"apiVersion": "v1",
   178  				"kind": "Pod",
   179  				"metadata": {
   180  					"name": "test-pod"
   181  				},
   182  				"spec": {
   183  					"containers": [{
   184  						"name":  "nginx",
   185  						"image": "` + imageutils.GetE2EImage(imageutils.NginxNew) + `",
   186  					}]
   187  				}
   188  			}`,
   189  					statusPatch: `{
   190  				"apiVersion": "v1",
   191  				"kind": "Pod",
   192  				"metadata": {
   193  					"name": "test-pod"
   194  				},
   195  				"status": {"conditions": [{"type": "MyStatus", "status":"True"}]}}`,
   196  				},
   197  			}
   198  
   199  			for _, tc := range testCases {
   200  				_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   201  					Namespace(ns).
   202  					Resource(tc.resource).
   203  					Name(tc.name).
   204  					Param("fieldManager", "apply_test").
   205  					Body([]byte(tc.body)).
   206  					Do(ctx).
   207  					Get()
   208  				if err != nil {
   209  					framework.Failf("Failed to create object using Apply patch: %v", err)
   210  				}
   211  
   212  				_, err = client.CoreV1().RESTClient().Get().Namespace(ns).Resource(tc.resource).Name(tc.name).Do(ctx).Get()
   213  				if err != nil {
   214  					framework.Failf("Failed to retrieve object: %v", err)
   215  				}
   216  
   217  				// Test that apply does not update subresources unless directed at a subresource endpoint
   218  				_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   219  					Namespace(ns).
   220  					Resource(tc.resource).
   221  					Name(tc.name).
   222  					Param("fieldManager", "apply_test2").
   223  					Body([]byte(tc.statusPatch)).
   224  					Do(ctx).
   225  					Get()
   226  				if err != nil {
   227  					framework.Failf("Failed to Apply Status using Apply patch: %v", err)
   228  				}
   229  				pod, err := client.CoreV1().Pods(ns).Get(ctx, "test-pod", metav1.GetOptions{})
   230  				framework.ExpectNoError(err, "retrieving test pod")
   231  				for _, c := range pod.Status.Conditions {
   232  					if c.Type == "MyStatus" {
   233  						framework.Failf("Apply should not update subresources unless the endpoint is specifically specified")
   234  					}
   235  				}
   236  
   237  				// Test that apply to subresource updates the subresource
   238  				_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   239  					Namespace(ns).
   240  					Resource(tc.resource).
   241  					SubResource("status").
   242  					Name(tc.name).
   243  					Param("fieldManager", "apply_test2").
   244  					Body([]byte(tc.statusPatch)).
   245  					Do(ctx).
   246  					Get()
   247  				if err != nil {
   248  					framework.Failf("Failed to Apply Status using Apply patch: %v", err)
   249  				}
   250  
   251  				pod, err = client.CoreV1().Pods(ns).Get(ctx, "test-pod", metav1.GetOptions{})
   252  				framework.ExpectNoError(err, "retrieving test pod")
   253  
   254  				myStatusFound := false
   255  				for _, c := range pod.Status.Conditions {
   256  					if c.Type == "MyStatus" {
   257  						myStatusFound = true
   258  						break
   259  					}
   260  				}
   261  				if myStatusFound == false {
   262  					framework.Failf("Expected pod to have applied status")
   263  				}
   264  			}
   265  		}
   266  	})
   267  
   268  	/*
   269  		Release : v1.21
   270  		Testname: Server Side Apply, unset field
   271  		Description: Apply an object. Issue a subsequent apply that removes a field. The particular field MUST be removed.
   272  	*/
   273  	ginkgo.It("should remove a field if it is owned but removed in the apply request", func(ctx context.Context) {
   274  		obj := []byte(`{
   275  		"apiVersion": "apps/v1",
   276  		"kind": "Deployment",
   277  		"metadata": {
   278  			"name": "deployment",
   279  			"labels": {"app": "nginx"}
   280  		},
   281  		"spec": {
   282  			"replicas": 3,
   283  			"selector": {
   284  				"matchLabels": {
   285  					"app": "nginx"
   286  				}
   287  			},
   288  			"template": {
   289  				"metadata": {
   290  					"labels": {
   291  						"app": "nginx"
   292  					}
   293  				},
   294  				"spec": {
   295  					"containers": [{
   296  						"name":  "nginx",
   297  						"image": "` + imageutils.GetE2EImage(imageutils.NginxNew) + `",
   298  						"ports": [{
   299  							"containerPort": 80,
   300  							"protocol": "TCP"
   301  						}]
   302  					}]
   303  				}
   304  			}
   305  		}
   306  	}`)
   307  
   308  		_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   309  			AbsPath("/apis/apps/v1").
   310  			Namespace(ns).
   311  			Resource("deployments").
   312  			Name("deployment").
   313  			Param("fieldManager", "apply_test").
   314  			Body(obj).Do(ctx).Get()
   315  		if err != nil {
   316  			framework.Failf("Failed to create object using Apply patch: %v", err)
   317  		}
   318  
   319  		obj = []byte(`{
   320  		"apiVersion": "apps/v1",
   321  		"kind": "Deployment",
   322  		"metadata": {
   323  			"name": "deployment",
   324  			"labels": {"app": "nginx"}
   325  		},
   326  		"spec": {
   327  			"replicas": 3,
   328  			"selector": {
   329  				"matchLabels": {
   330  					"app": "nginx"
   331  				}
   332  			},
   333  			"template": {
   334  				"metadata": {
   335  					"labels": {
   336  						"app": "nginx"
   337  					}
   338  				},
   339  				"spec": {
   340  					"containers": [{
   341  						"name":  "nginx",
   342  						"image": "` + imageutils.GetE2EImage(imageutils.NginxNew) + `",
   343  					}]
   344  				}
   345  			}
   346  		}
   347  	}`)
   348  
   349  		_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   350  			AbsPath("/apis/apps/v1").
   351  			Namespace(ns).
   352  			Resource("deployments").
   353  			Name("deployment").
   354  			Param("fieldManager", "apply_test").
   355  			Body(obj).Do(ctx).Get()
   356  		if err != nil {
   357  			framework.Failf("Failed to remove container port using Apply patch: %v", err)
   358  		}
   359  
   360  		deployment, err := client.AppsV1().Deployments(ns).Get(ctx, "deployment", metav1.GetOptions{})
   361  		if err != nil {
   362  			framework.Failf("Failed to retrieve object: %v", err)
   363  		}
   364  
   365  		if len(deployment.Spec.Template.Spec.Containers[0].Ports) > 0 {
   366  			framework.Failf("Expected no container ports but got: %v, object: \n%#v", deployment.Spec.Template.Spec.Containers[0].Ports, deployment)
   367  		}
   368  
   369  	})
   370  
   371  	/*
   372  		Release : v1.21
   373  		Testname: Server Side Apply, unset field shared
   374  		Description: Apply an object. Unset ownership of a field that is also owned by other managers and make a subsequent apply request. The unset field MUST not be removed from the object.
   375  	*/
   376  	ginkgo.It("should not remove a field if an owner unsets the field but other managers still have ownership of the field", func(ctx context.Context) {
   377  		// spec.replicas is a optional, defaulted field
   378  		// spec.template.spec.hostname is an optional, non-defaulted field
   379  		apply := []byte(`{
   380  		"apiVersion": "apps/v1",
   381  		"kind": "Deployment",
   382  		"metadata": {
   383  			"name": "deployment-shared-unset",
   384  			"labels": {"app": "nginx"}
   385  		},
   386  		"spec": {
   387  			"replicas": 3,
   388  			"selector": {
   389  				"matchLabels": {
   390  					"app": "nginx"
   391  				}
   392  			},
   393  			"template": {
   394  				"metadata": {
   395  					"labels": {
   396  						"app": "nginx"
   397  					}
   398  				},
   399  				"spec": {
   400  					"hostname": "test-hostname",
   401  					"containers": [{
   402  						"name":  "nginx",
   403  						"image": "` + imageutils.GetE2EImage(imageutils.NginxNew) + `",
   404  					}]
   405  				}
   406  			}
   407  		}
   408  	}`)
   409  
   410  		for _, fieldManager := range []string{"shared_owner_1", "shared_owner_2"} {
   411  			_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   412  				AbsPath("/apis/apps/v1").
   413  				Namespace(ns).
   414  				Resource("deployments").
   415  				Name("deployment-shared-unset").
   416  				Param("fieldManager", fieldManager).
   417  				Body(apply).
   418  				Do(ctx).
   419  				Get()
   420  			if err != nil {
   421  				framework.Failf("Failed to create object using Apply patch: %v", err)
   422  			}
   423  		}
   424  
   425  		// unset spec.replicas and spec.template.spec.hostname
   426  		apply = []byte(`{
   427  		"apiVersion": "apps/v1",
   428  		"kind": "Deployment",
   429  		"metadata": {
   430  			"name": "deployment-shared-unset",
   431  			"labels": {"app": "nginx"}
   432  		},
   433  		"spec": {
   434  			"selector": {
   435  				"matchLabels": {
   436  					"app": "nginx"
   437  				}
   438  			},
   439  			"template": {
   440  				"metadata": {
   441  					"labels": {
   442  						"app": "nginx"
   443  					}
   444  				},
   445  				"spec": {
   446  					"containers": [{
   447  						"name":  "nginx",
   448  						"image": "` + imageutils.GetE2EImage(imageutils.NginxNew) + `",
   449  					}]
   450  				}
   451  			}
   452  		}
   453  	}`)
   454  
   455  		patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   456  			AbsPath("/apis/apps/v1").
   457  			Namespace(ns).
   458  			Resource("deployments").
   459  			Name("deployment-shared-unset").
   460  			Param("fieldManager", "shared_owner_1").
   461  			Body(apply).
   462  			Do(ctx).
   463  			Get()
   464  		if err != nil {
   465  			framework.Failf("Failed to create object using Apply patch: %v", err)
   466  		}
   467  
   468  		deployment, ok := patched.(*appsv1.Deployment)
   469  		if !ok {
   470  			framework.Failf("Failed to convert response object to Deployment")
   471  		}
   472  		if *deployment.Spec.Replicas != 3 {
   473  			framework.Failf("Expected deployment.spec.replicas to be 3, but got %d", deployment.Spec.Replicas)
   474  		}
   475  		if deployment.Spec.Template.Spec.Hostname != "test-hostname" {
   476  			framework.Failf("Expected deployment.spec.template.spec.hostname to be \"test-hostname\", but got %s", deployment.Spec.Template.Spec.Hostname)
   477  		}
   478  	})
   479  
   480  	/*
   481  		Release : v1.21
   482  		Testname: Server Side Apply, Force Apply
   483  		Description: Apply an object. Force apply a modified version of the object such that a conflict will exist in the managed fields. The force apply MUST successfully update the object.
   484  	*/
   485  	ginkgo.It("should ignore conflict errors if force apply is used", func(ctx context.Context) {
   486  		obj := []byte(`{
   487  		"apiVersion": "apps/v1",
   488  		"kind": "Deployment",
   489  		"metadata": {
   490  			"name": "deployment",
   491  			"labels": {"app": "nginx"}
   492  		},
   493  		"spec": {
   494  			"replicas": 3,
   495  			"selector": {
   496  				"matchLabels": {
   497  					 "app": "nginx"
   498  				}
   499  			},
   500  			"template": {
   501  				"metadata": {
   502  					"labels": {
   503  						"app": "nginx"
   504  					}
   505  				},
   506  				"spec": {
   507  					"containers": [{
   508  						"name":  "nginx",
   509  						"image": "` + imageutils.GetE2EImage(imageutils.NginxNew) + `",
   510  					}]
   511  				}
   512  			}
   513  		}
   514  	}`)
   515  		_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   516  			AbsPath("/apis/apps/v1").
   517  			Namespace(ns).
   518  			Resource("deployments").
   519  			Name("deployment").
   520  			Param("fieldManager", "apply_test").
   521  			Body(obj).Do(ctx).Get()
   522  		if err != nil {
   523  			framework.Failf("Failed to create object using Apply patch: %v", err)
   524  		}
   525  
   526  		_, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
   527  			AbsPath("/apis/apps/v1").
   528  			Namespace(ns).
   529  			Resource("deployments").
   530  			Name("deployment").
   531  			Body([]byte(`{"spec":{"replicas": 5}}`)).Do(ctx).Get()
   532  		if err != nil {
   533  			framework.Failf("Failed to patch object: %v", err)
   534  		}
   535  
   536  		_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   537  			AbsPath("/apis/apps/v1").
   538  			Namespace(ns).
   539  			Resource("deployments").
   540  			Name("deployment").
   541  			Param("fieldManager", "apply_test").
   542  			Body(obj).Do(ctx).Get()
   543  		if err == nil {
   544  			framework.Failf("Expecting to get conflicts when applying object")
   545  		}
   546  		status, ok := err.(*apierrors.StatusError)
   547  		if !(ok && apierrors.IsConflict(status)) {
   548  			framework.Failf("Expecting to get conflicts as API error")
   549  		}
   550  		if len(status.Status().Details.Causes) < 1 {
   551  			framework.Failf("Expecting to get at least one conflict when applying object, got: %v", status.Status().Details.Causes)
   552  		}
   553  
   554  		_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
   555  			AbsPath("/apis/apps/v1").
   556  			Namespace(ns).
   557  			Resource("deployments").
   558  			Name("deployment").
   559  			Param("force", "true").
   560  			Param("fieldManager", "apply_test").
   561  			Body(obj).Do(ctx).Get()
   562  		if err != nil {
   563  			framework.Failf("Failed to apply object with force: %v", err)
   564  		}
   565  	})
   566  
   567  	/*
   568  		Release : v1.21
   569  		Testname: Server Side Apply, CRD
   570  		Description: Create a CRD and apply a CRD resource. Subsequent apply requests that do not conflict with the previous ones should update the object. Apply requests that cause conflicts should fail.
   571  	*/
   572  	ginkgo.It("should work for CRDs", func(ctx context.Context) {
   573  		config, err := framework.LoadConfig()
   574  		if err != nil {
   575  			framework.Failf("%s", err)
   576  		}
   577  		apiExtensionClient, err := apiextensionclientset.NewForConfig(config)
   578  		if err != nil {
   579  			framework.Failf("%s", err)
   580  		}
   581  		dynamicClient, err := dynamic.NewForConfig(config)
   582  		if err != nil {
   583  			framework.Failf("%s", err)
   584  		}
   585  
   586  		noxuDefinition := fixtures.NewRandomNameMultipleVersionCustomResourceDefinition(apiextensionsv1.ClusterScoped)
   587  
   588  		var c apiextensionsv1.CustomResourceValidation
   589  		err = json.Unmarshal([]byte(`{
   590  		"openAPIV3Schema": {
   591  			"type": "object",
   592  			"properties": {
   593  				"spec": {
   594  					"type": "object",
   595  					"x-kubernetes-preserve-unknown-fields": true,
   596  					"properties": {
   597  						"cronSpec": {
   598  							"type": "string",
   599  							"pattern": "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$"
   600  						},
   601  						"ports": {
   602  							"type": "array",
   603  							"x-kubernetes-list-map-keys": [
   604  								"containerPort",
   605  								"protocol"
   606  							],
   607  							"x-kubernetes-list-type": "map",
   608  							"items": {
   609  								"properties": {
   610  									"containerPort": {
   611  										"format": "int32",
   612  										"type": "integer"
   613  									},
   614  									"hostIP": {
   615  										"type": "string"
   616  									},
   617  									"hostPort": {
   618  										"format": "int32",
   619  										"type": "integer"
   620  									},
   621  									"name": {
   622  										"type": "string"
   623  									},
   624  									"protocol": {
   625  										"type": "string"
   626  									}
   627  								},
   628  								"required": [
   629  									"containerPort",
   630  									"protocol"
   631  								],
   632  								"type": "object"
   633  							}
   634  						}
   635  					}
   636  				}
   637  			}
   638  		}
   639  	}`), &c)
   640  		if err != nil {
   641  			framework.Failf("%s", err)
   642  		}
   643  		for i := range noxuDefinition.Spec.Versions {
   644  			noxuDefinition.Spec.Versions[i].Schema = &c
   645  		}
   646  
   647  		noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   648  		if err != nil {
   649  			framework.Failf("cannot create crd %s", err)
   650  		}
   651  
   652  		defer func() {
   653  			err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient)
   654  			framework.ExpectNoError(err, "deleting CustomResourceDefinition")
   655  		}()
   656  
   657  		kind := noxuDefinition.Spec.Names.Kind
   658  		apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
   659  		name := "mytest"
   660  
   661  		rest := apiExtensionClient.Discovery().RESTClient()
   662  		yamlBody := []byte(fmt.Sprintf(`
   663  apiVersion: %s
   664  kind: %s
   665  metadata:
   666    name: %s
   667    finalizers:
   668    - test-finalizer
   669  spec:
   670    cronSpec: "* * * * */5"
   671    replicas: 1
   672    ports:
   673    - name: x
   674      containerPort: 80
   675      protocol: TCP`, apiVersion, kind, name))
   676  		result, err := rest.Patch(types.ApplyPatchType).
   677  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   678  			Name(name).
   679  			Param("fieldManager", "apply_test").
   680  			Body(yamlBody).
   681  			DoRaw(ctx)
   682  		if err != nil {
   683  			framework.Failf("failed to create custom resource with apply: %v:\n%v", err, string(result))
   684  		}
   685  		verifyNumFinalizers(result, 1)
   686  		verifyFinalizersIncludes(result, "test-finalizer")
   687  		verifyReplicas(result, 1)
   688  		verifyNumPorts(result, 1)
   689  
   690  		// Ensure that apply works with multiple resource versions
   691  		apiVersionBeta := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[1].Name
   692  		yamlBodyBeta := []byte(fmt.Sprintf(`
   693  apiVersion: %s
   694  kind: %s
   695  metadata:
   696    name: %s
   697  spec:
   698    cronSpec: "* * * * */5"
   699    replicas: 1
   700    ports:
   701    - name: x
   702      containerPort: 80
   703      protocol: TCP`, apiVersionBeta, kind, name))
   704  		result, err = rest.Patch(types.ApplyPatchType).
   705  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[1].Name, noxuDefinition.Spec.Names.Plural).
   706  			Name(name).
   707  			Param("fieldManager", "apply_test").
   708  			Body(yamlBodyBeta).
   709  			DoRaw(ctx)
   710  		if err != nil {
   711  			framework.Failf("failed to create custom resource with apply: %v:\n%v", err, string(result))
   712  		}
   713  		verifyReplicas(result, 1)
   714  		verifyNumPorts(result, 1)
   715  
   716  		// Reset the finalizers after the test so the objects can be deleted
   717  		defer func() {
   718  			result, err = rest.Patch(types.MergePatchType).
   719  				AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   720  				Name(name).
   721  				Body([]byte(`{"metadata":{"finalizers":[]}}`)).
   722  				DoRaw(ctx)
   723  			if err != nil {
   724  				framework.Failf("failed to reset finalizers: %v:\n%v", err, string(result))
   725  			}
   726  		}()
   727  
   728  		// Patch object to add another finalizer to the finalizers list
   729  		result, err = rest.Patch(types.MergePatchType).
   730  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   731  			Name(name).
   732  			Body([]byte(`{"metadata":{"finalizers":["test-finalizer","another-one"]}}`)).
   733  			DoRaw(ctx)
   734  		if err != nil {
   735  			framework.Failf("failed to add finalizer with merge patch: %v:\n%v", err, string(result))
   736  		}
   737  		verifyNumFinalizers(result, 2)
   738  		verifyFinalizersIncludes(result, "test-finalizer")
   739  		verifyFinalizersIncludes(result, "another-one")
   740  
   741  		// Re-apply the same config, should work fine, since finalizers should have the list-type extension 'set'.
   742  		result, err = rest.Patch(types.ApplyPatchType).
   743  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   744  			Name(name).
   745  			Param("fieldManager", "apply_test").
   746  			SetHeader("Accept", "application/json").
   747  			Body(yamlBody).
   748  			DoRaw(ctx)
   749  		if err != nil {
   750  			framework.Failf("failed to apply same config after adding a finalizer: %v:\n%v", err, string(result))
   751  		}
   752  		verifyNumFinalizers(result, 2)
   753  		verifyFinalizersIncludes(result, "test-finalizer")
   754  		verifyFinalizersIncludes(result, "another-one")
   755  
   756  		// Patch object to change the number of replicas
   757  		result, err = rest.Patch(types.MergePatchType).
   758  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   759  			Name(name).
   760  			Body([]byte(`{"spec":{"replicas": 5}}`)).
   761  			DoRaw(ctx)
   762  		if err != nil {
   763  			framework.Failf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
   764  		}
   765  		verifyReplicas(result, 5)
   766  
   767  		// Re-apply, we should get conflicts now, since the number of replicas was changed.
   768  		result, err = rest.Patch(types.ApplyPatchType).
   769  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   770  			Name(name).
   771  			Param("fieldManager", "apply_test").
   772  			Body(yamlBody).
   773  			DoRaw(ctx)
   774  		if err == nil {
   775  			framework.Failf("Expecting to get conflicts when applying object after updating replicas, got no error: %s", result)
   776  		}
   777  		status, ok := err.(*apierrors.StatusError)
   778  		if !ok {
   779  			framework.Failf("Expecting to get conflicts as API error")
   780  		}
   781  		if len(status.Status().Details.Causes) != 1 {
   782  			framework.Failf("Expecting to get one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
   783  		}
   784  
   785  		// Re-apply with force, should work fine.
   786  		result, err = rest.Patch(types.ApplyPatchType).
   787  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   788  			Name(name).
   789  			Param("force", "true").
   790  			Param("fieldManager", "apply_test").
   791  			Body(yamlBody).
   792  			DoRaw(ctx)
   793  		if err != nil {
   794  			framework.Failf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
   795  		}
   796  		verifyReplicas(result, 1)
   797  
   798  		// New applier tries to edit an existing list item, we should get conflicts.
   799  		result, err = rest.Patch(types.ApplyPatchType).
   800  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   801  			Name(name).
   802  			Param("fieldManager", "apply_test_2").
   803  			Body([]byte(fmt.Sprintf(`
   804  apiVersion: %s
   805  kind: %s
   806  metadata:
   807    name: %s
   808  spec:
   809    ports:
   810    - name: "y"
   811      containerPort: 80
   812      protocol: TCP`, apiVersion, kind, name))).
   813  			DoRaw(ctx)
   814  		if err == nil {
   815  			framework.Failf("Expecting to get conflicts when a different applier updates existing list item, got no error: %s", result)
   816  		}
   817  		status, ok = err.(*apierrors.StatusError)
   818  		if !ok {
   819  			framework.Failf("Expecting to get conflicts as API error")
   820  		}
   821  		if len(status.Status().Details.Causes) != 1 {
   822  			framework.Failf("Expecting to get one conflict when a different applier updates existing list item, got: %v", status.Status().Details.Causes)
   823  		}
   824  
   825  		// New applier tries to add a new list item, should work fine.
   826  		result, err = rest.Patch(types.ApplyPatchType).
   827  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   828  			Name(name).
   829  			Param("fieldManager", "apply_test_2").
   830  			Body([]byte(fmt.Sprintf(`
   831  apiVersion: %s
   832  kind: %s
   833  metadata:
   834    name: %s
   835  spec:
   836    ports:
   837    - name: "y"
   838      containerPort: 8080
   839      protocol: TCP`, apiVersion, kind, name))).
   840  			SetHeader("Accept", "application/json").
   841  			DoRaw(ctx)
   842  		if err != nil {
   843  			framework.Failf("failed to add a new list item to the object as a different applier: %v:\n%v", err, string(result))
   844  		}
   845  		verifyNumPorts(result, 2)
   846  
   847  		// UpdateOnCreate
   848  		notExistingYAMLBody := []byte(fmt.Sprintf(`
   849  	{
   850  		"apiVersion": "%s",
   851  		"kind": "%s",
   852  		"metadata": {
   853  		  "name": "%s",
   854  		  "finalizers": [
   855  			"test-finalizer"
   856  		  ]
   857  		},
   858  		"spec": {
   859  		  "cronSpec": "* * * * */5",
   860  		  "replicas": 1,
   861  		  "ports": [
   862  			{
   863  			  "name": "x",
   864  			  "containerPort": 80
   865  			}
   866  		  ]
   867  		},
   868  		"protocol": "TCP"
   869  	}`, apiVersion, kind, "should-not-exist"))
   870  		_, err = rest.Put().
   871  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   872  			Name("should-not-exist").
   873  			Param("fieldManager", "apply_test").
   874  			Body(notExistingYAMLBody).
   875  			DoRaw(ctx)
   876  		if !apierrors.IsNotFound(err) {
   877  			framework.Failf("create on update should fail with notFound, got %v", err)
   878  		}
   879  
   880  		// Create a CRD to test atomic lists
   881  		crd := fixtures.NewRandomNameV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
   882  		err = json.Unmarshal([]byte(`{
   883  		"openAPIV3Schema": {
   884  			"type": "object",
   885  			"properties": {
   886  				"spec": {
   887  					"type": "object",
   888  					"x-kubernetes-preserve-unknown-fields": true,
   889  					"properties": {
   890  						"atomicList": {
   891  							"type": "array",
   892  							"x-kubernetes-list-type": "atomic",
   893  							"items": {
   894  								"type": "string"
   895  							}
   896  						}
   897  					}
   898  				}
   899  			}
   900  		}
   901  	}`), &c)
   902  		if err != nil {
   903  			framework.Failf("%s", err)
   904  		}
   905  		for i := range crd.Spec.Versions {
   906  			crd.Spec.Versions[i].Schema = &c
   907  		}
   908  
   909  		crd, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
   910  		if err != nil {
   911  			framework.Failf("cannot create crd %s", err)
   912  		}
   913  
   914  		defer func() {
   915  			err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient)
   916  			framework.ExpectNoError(err, "deleting CustomResourceDefinition")
   917  		}()
   918  
   919  		crdKind := crd.Spec.Names.Kind
   920  		crdApiVersion := crd.Spec.Group + "/" + crd.Spec.Versions[0].Name
   921  
   922  		crdYamlBody := []byte(fmt.Sprintf(`
   923  apiVersion: %s
   924  kind: %s
   925  metadata:
   926    name: %s
   927  spec:
   928    atomicList:
   929    - "item1"`, crdApiVersion, crdKind, name))
   930  		result, err = rest.Patch(types.ApplyPatchType).
   931  			AbsPath("/apis", crd.Spec.Group, crd.Spec.Versions[0].Name, crd.Spec.Names.Plural).
   932  			Name(name).
   933  			Param("fieldManager", "apply_test").
   934  			Body(crdYamlBody).
   935  			DoRaw(ctx)
   936  		if err != nil {
   937  			framework.Failf("failed to create custom resource with apply: %v:\n%v", err, string(result))
   938  		}
   939  
   940  		verifyList(result, []interface{}{"item1"})
   941  
   942  		crdYamlBody = []byte(fmt.Sprintf(`
   943  apiVersion: %s
   944  kind: %s
   945  metadata:
   946    name: %s
   947  spec:
   948    atomicList:
   949    - "item2"`, crdApiVersion, crdKind, name))
   950  		result, err = rest.Patch(types.ApplyPatchType).
   951  			AbsPath("/apis", crd.Spec.Group, crd.Spec.Versions[0].Name, crd.Spec.Names.Plural).
   952  			Name(name).
   953  			Param("fieldManager", "apply_test_2").
   954  			Param("force", "true").
   955  			Body(crdYamlBody).
   956  			DoRaw(ctx)
   957  		if err != nil {
   958  			framework.Failf("failed to create custom resource with apply: %v:\n%v", err, string(result))
   959  		}
   960  
   961  		// Since the list is atomic the contents of the list must completely be replaced by the latest apply
   962  		verifyList(result, []interface{}{"item2"})
   963  	})
   964  
   965  	/*
   966  		Release : v1.21
   967  		Testname: Server Side Apply, Update take ownership
   968  		Description: Apply an object. Send an Update request which should take ownership of a field. The field should be owned by the new manager and a subsequent apply from the original manager MUST not change the field it does not have ownership of.
   969  	*/
   970  	ginkgo.It("should give up ownership of a field if forced applied by a controller", func(ctx context.Context) {
   971  		// Applier creates a deployment with replicas set to 3
   972  		apply := []byte(`{
   973  		"apiVersion": "apps/v1",
   974  		"kind": "Deployment",
   975  		"metadata": {
   976  			"name": "deployment-shared-map-item-removal",
   977  			"labels": {"app": "nginx"}
   978  		},
   979  		"spec": {
   980  			"replicas": 3,
   981  			"selector": {
   982  				"matchLabels": {
   983  					"app": "nginx"
   984  				}
   985  			},
   986  			"template": {
   987  				"metadata": {
   988  					"labels": {
   989  						"app": "nginx"
   990  					}
   991  				},
   992  				"spec": {
   993  					"containers": [{
   994  						"name":  "nginx",
   995  						"image": "` + imageutils.GetE2EImage(imageutils.NginxNew) + `",
   996  					}]
   997  				}
   998  			}
   999  		}
  1000  	}`)
  1001  
  1002  		_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  1003  			AbsPath("/apis/apps/v1").
  1004  			Namespace(ns).
  1005  			Resource("deployments").
  1006  			Name("deployment-shared-map-item-removal").
  1007  			Param("fieldManager", "test_applier").
  1008  			Body(apply).
  1009  			Do(ctx).
  1010  			Get()
  1011  		if err != nil {
  1012  			framework.Failf("Failed to create object using Apply patch: %v", err)
  1013  		}
  1014  
  1015  		replicas := int32(4)
  1016  		_, err = e2edeployment.UpdateDeploymentWithRetries(client, ns, "deployment-shared-map-item-removal", func(update *appsv1.Deployment) {
  1017  			update.Spec.Replicas = &replicas
  1018  		})
  1019  		framework.ExpectNoError(err)
  1020  
  1021  		// applier omits replicas
  1022  		apply = []byte(`{
  1023  		"apiVersion": "apps/v1",
  1024  		"kind": "Deployment",
  1025  		"metadata": {
  1026  			"name": "deployment-shared-map-item-removal",
  1027  			"labels": {"app": "nginx"}
  1028  		},
  1029  		"spec": {
  1030  			"selector": {
  1031  				"matchLabels": {
  1032  					"app": "nginx"
  1033  				}
  1034  			},
  1035  			"template": {
  1036  				"metadata": {
  1037  					"labels": {
  1038  						"app": "nginx"
  1039  					}
  1040  				},
  1041  				"spec": {
  1042  					"containers": [{
  1043  						"name":  "nginx",
  1044  						"image": "` + imageutils.GetE2EImage(imageutils.NginxNew) + `",
  1045  					}]
  1046  				}
  1047  			}
  1048  		}
  1049  	}`)
  1050  
  1051  		patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
  1052  			AbsPath("/apis/apps/v1").
  1053  			Namespace(ns).
  1054  			Resource("deployments").
  1055  			Name("deployment-shared-map-item-removal").
  1056  			Param("fieldManager", "test_applier").
  1057  			Body(apply).
  1058  			Do(ctx).
  1059  			Get()
  1060  		if err != nil {
  1061  			framework.Failf("Failed to create object using Apply patch: %v", err)
  1062  		}
  1063  
  1064  		// ensure the container is deleted even though a controller updated a field of the container
  1065  		deployment, ok := patched.(*appsv1.Deployment)
  1066  		if !ok {
  1067  			framework.Failf("Failed to convert response object to Deployment")
  1068  		}
  1069  		if *deployment.Spec.Replicas != 4 {
  1070  			framework.Failf("Expected deployment.spec.replicas to be 4, but got %d", deployment.Spec.Replicas)
  1071  		}
  1072  	})
  1073  })
  1074  
  1075  // verifyNumFinalizers checks that len(.metadata.finalizers) == n
  1076  func verifyNumFinalizers(b []byte, n int) {
  1077  	obj := unstructured.Unstructured{}
  1078  	err := obj.UnmarshalJSON(b)
  1079  	if err != nil {
  1080  		framework.Failf("failed to unmarshal response: %v", err)
  1081  	}
  1082  	if actual, expected := len(obj.GetFinalizers()), n; actual != expected {
  1083  		framework.Failf("expected %v finalizers but got %v:\n%v", expected, actual, string(b))
  1084  	}
  1085  }
  1086  
  1087  // verifyFinalizersIncludes checks that .metadata.finalizers includes e
  1088  func verifyFinalizersIncludes(b []byte, e string) {
  1089  	obj := unstructured.Unstructured{}
  1090  	err := obj.UnmarshalJSON(b)
  1091  	if err != nil {
  1092  		framework.Failf("failed to unmarshal response: %v", err)
  1093  	}
  1094  	for _, a := range obj.GetFinalizers() {
  1095  		if a == e {
  1096  			return
  1097  		}
  1098  	}
  1099  	framework.Failf("expected finalizers to include %q but got: %v", e, obj.GetFinalizers())
  1100  }
  1101  
  1102  // verifyReplicas checks that .spec.replicas == r
  1103  func verifyReplicas(b []byte, r int) {
  1104  	obj := unstructured.Unstructured{}
  1105  	err := obj.UnmarshalJSON(b)
  1106  	if err != nil {
  1107  		framework.Failf("failed to find replicas number in response: %v:\n%v", err, string(b))
  1108  	}
  1109  	spec, ok := obj.Object["spec"]
  1110  	if !ok {
  1111  		framework.Failf("failed to find replicas number in response:\n%v", string(b))
  1112  	}
  1113  	specMap, ok := spec.(map[string]interface{})
  1114  	if !ok {
  1115  		framework.Failf("failed to find replicas number in response:\n%v", string(b))
  1116  	}
  1117  	replicas, ok := specMap["replicas"]
  1118  	if !ok {
  1119  		framework.Failf("failed to find replicas number in response:\n%v", string(b))
  1120  	}
  1121  	replicasNumber, ok := replicas.(int64)
  1122  	if !ok {
  1123  		framework.Failf("failed to find replicas number in response: expected int64 but got: %v", reflect.TypeOf(replicas))
  1124  	}
  1125  	if actual, expected := replicasNumber, int64(r); actual != expected {
  1126  		framework.Failf("expected %v ports but got %v:\n%v", expected, actual, string(b))
  1127  	}
  1128  }
  1129  
  1130  // verifyNumPorts checks that len(.spec.ports) == n
  1131  func verifyNumPorts(b []byte, n int) {
  1132  	obj := unstructured.Unstructured{}
  1133  	err := obj.UnmarshalJSON(b)
  1134  	if err != nil {
  1135  		framework.Failf("failed to find ports list in response: %v:\n%v", err, string(b))
  1136  	}
  1137  	spec, ok := obj.Object["spec"]
  1138  	if !ok {
  1139  		framework.Failf("failed to find ports list in response:\n%v", string(b))
  1140  	}
  1141  	specMap, ok := spec.(map[string]interface{})
  1142  	if !ok {
  1143  		framework.Failf("failed to find ports list in response:\n%v", string(b))
  1144  	}
  1145  	ports, ok := specMap["ports"]
  1146  	if !ok {
  1147  		framework.Failf("failed to find ports list in response:\n%v", string(b))
  1148  	}
  1149  	portsList, ok := ports.([]interface{})
  1150  	if !ok {
  1151  		framework.Failf("failed to find ports list in response: expected array but got: %v", reflect.TypeOf(ports))
  1152  	}
  1153  	if actual, expected := len(portsList), n; actual != expected {
  1154  		framework.Failf("expected %v ports but got %v:\n%v", expected, actual, string(b))
  1155  	}
  1156  }
  1157  
  1158  // verifyList checks that .spec.atomicList is the exact same as the expectedList provided
  1159  func verifyList(b []byte, expectedList []interface{}) {
  1160  	obj := unstructured.Unstructured{}
  1161  	err := obj.UnmarshalJSON(b)
  1162  	if err != nil {
  1163  		framework.Failf("failed to find atomicList in response: %v:\n%v", err, string(b))
  1164  	}
  1165  	spec, ok := obj.Object["spec"]
  1166  	if !ok {
  1167  		framework.Failf("failed to find atomicList in response:\n%v", string(b))
  1168  	}
  1169  	specMap, ok := spec.(map[string]interface{})
  1170  	if !ok {
  1171  		framework.Failf("failed to find atomicList in response:\n%v", string(b))
  1172  	}
  1173  	list, ok := specMap["atomicList"]
  1174  	if !ok {
  1175  		framework.Failf("failed to find atomicList in response:\n%v", string(b))
  1176  	}
  1177  	listString, ok := list.([]interface{})
  1178  	if !ok {
  1179  		framework.Failf("failed to find atomicList in response:\n%v", string(b))
  1180  	}
  1181  	if !reflect.DeepEqual(listString, expectedList) {
  1182  		framework.Failf("Expected list %s, got %s", expectedList, listString)
  1183  	}
  1184  }