diff --git a/staging/src/k8s.io/client-go/dynamic/fake/simple.go b/staging/src/k8s.io/client-go/dynamic/fake/simple.go index dee16b245a3..197508c81d4 100644 --- a/staging/src/k8s.io/client-go/dynamic/fake/simple.go +++ b/staging/src/k8s.io/client-go/dynamic/fake/simple.go @@ -454,6 +454,51 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types return ret, err } +// TODO: opts are currently ignored. +func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) { + outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return nil, err + } + var uncastRet runtime.Object + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootPatchAction(c.resource, name, types.ApplyPatchType, outBytes), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, types.ApplyPatchType, outBytes, subresources...), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewPatchAction(c.resource, c.namespace, name, types.ApplyPatchType, outBytes), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, types.ApplyPatchType, outBytes, subresources...), &metav1.Status{Status: "dynamic patch fail"}) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err + return nil, nil +} + +func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error) { + return c.Apply(ctx, name, obj, options, "status") +} + func convertObjectsToUnstructured(s *runtime.Scheme, objs []runtime.Object) ([]runtime.Object, error) { ul := make([]runtime.Object, 0, len(objs)) diff --git a/staging/src/k8s.io/client-go/dynamic/interface.go b/staging/src/k8s.io/client-go/dynamic/interface.go index b08067c341d..a310b63e5ef 100644 --- a/staging/src/k8s.io/client-go/dynamic/interface.go +++ b/staging/src/k8s.io/client-go/dynamic/interface.go @@ -40,6 +40,8 @@ type ResourceInterface interface { List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) + Apply(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) + ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, options metav1.ApplyOptions) (*unstructured.Unstructured, error) } type NamespaceableResourceInterface interface { diff --git a/staging/src/k8s.io/client-go/dynamic/simple.go b/staging/src/k8s.io/client-go/dynamic/simple.go index 87594bf2e16..9dc0fb5c0ba 100644 --- a/staging/src/k8s.io/client-go/dynamic/simple.go +++ b/staging/src/k8s.io/client-go/dynamic/simple.go @@ -324,6 +324,48 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types return uncastObj.(*unstructured.Unstructured), nil } +func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions, subresources ...string) (*unstructured.Unstructured, error) { + if len(name) == 0 { + return nil, fmt.Errorf("name is required") + } + outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return nil, err + } + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + managedFields := accessor.GetManagedFields() + if len(managedFields) > 0 { + return nil, fmt.Errorf(`cannot apply an object with managed fields already set. + Use the client-go/applyconfigurations "UnstructructuredExtractor" to obtain the unstructured ApplyConfiguration for the given field manager that you can use/modify here to apply`) + } + patchOpts := opts.ToPatchOptions() + + result := c.client.client. + Patch(types.ApplyPatchType). + AbsPath(append(c.makeURLSegments(name), subresources...)...). + Body(outBytes). + SpecificallyVersionedParams(&patchOpts, dynamicParameterCodec, versionV1). + Do(ctx) + if err := result.Error(); err != nil { + return nil, err + } + retBytes, err := result.Raw() + if err != nil { + return nil, err + } + uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) + if err != nil { + return nil, err + } + return uncastObj.(*unstructured.Unstructured), nil +} +func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) { + return c.Apply(ctx, name, obj, opts, "status") +} + func (c *dynamicResourceClient) makeURLSegments(name string) []string { url := []string{} if len(c.resource.Group) == 0 { diff --git a/test/integration/apiserver/apply/reset_fields_test.go b/test/integration/apiserver/apply/reset_fields_test.go index 1c1781906ac..2a0fed6cb97 100644 --- a/test/integration/apiserver/apply/reset_fields_test.go +++ b/test/integration/apiserver/apply/reset_fields_test.go @@ -29,7 +29,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" genericfeatures "k8s.io/apiserver/pkg/features" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/dynamic" @@ -42,7 +41,6 @@ import ( "k8s.io/kubernetes/test/integration/etcd" "k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/utils/image" - "sigs.k8s.io/yaml" ) // namespace used for all tests, do not change this @@ -228,16 +226,12 @@ func TestApplyResetFields(t *testing.T) { obj1.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String()) obj1.SetKind(mapping.GroupVersionKind.Kind) obj1.SetName(name) - obj1YAML, err := yaml.Marshal(obj1.Object) - if err != nil { - t.Fatal(err) - } // apply the spec of the first object _, err = dynamicClient. Resource(mapping.Resource). Namespace(namespace). - Patch(context.TODO(), name, types.ApplyPatchType, obj1YAML, metav1.PatchOptions{FieldManager: "fieldmanager1"}, "") + Apply(context.TODO(), name, &obj1, metav1.ApplyOptions{FieldManager: "fieldmanager1"}) if err != nil { t.Fatalf("Failed to apply obj1: %v", err) } @@ -260,18 +254,13 @@ func TestApplyResetFields(t *testing.T) { t.Fatalf("obj1 and obj2 should not be equal %v", obj2) } - obj2YAML, err := yaml.Marshal(obj2.Object) - if err != nil { - t.Fatal(err) - } - // apply the status of the second object // this won't conflict if resetfields are set correctly // and will conflict if they are not _, err = dynamicClient. Resource(mapping.Resource). Namespace(namespace). - Patch(context.TODO(), name, types.ApplyPatchType, obj2YAML, metav1.PatchOptions{FieldManager: "fieldmanager2"}, "status") + ApplyStatus(context.TODO(), name, obj2, metav1.ApplyOptions{FieldManager: "fieldmanager2"}) if err != nil { t.Fatalf("Failed to apply obj2: %v", err) } @@ -284,7 +273,7 @@ func TestApplyResetFields(t *testing.T) { _, err = dynamicClient. Resource(mapping.Resource). Namespace(namespace). - Patch(context.TODO(), name, types.ApplyPatchType, obj2YAML, metav1.PatchOptions{FieldManager: "fieldmanager2"}, "") + Apply(context.TODO(), name, obj2, metav1.ApplyOptions{FieldManager: "fieldmanager2"}) if err == nil || !strings.Contains(err.Error(), "conflict") { t.Fatalf("expected conflict, got error %v", err) } @@ -294,7 +283,7 @@ func TestApplyResetFields(t *testing.T) { _, err = dynamicClient. Resource(mapping.Resource). Namespace(namespace). - Patch(context.TODO(), name, types.ApplyPatchType, obj1YAML, metav1.PatchOptions{FieldManager: "fieldmanager1"}, "status") + ApplyStatus(context.TODO(), name, &obj1, metav1.ApplyOptions{FieldManager: "fieldmanager1"}) if err == nil || !strings.Contains(err.Error(), "conflict") { t.Fatalf("expected conflict, got error %v", err) } diff --git a/test/integration/apiserver/apply/status_test.go b/test/integration/apiserver/apply/status_test.go index d80f224ec3b..81e4857dcd7 100644 --- a/test/integration/apiserver/apply/status_test.go +++ b/test/integration/apiserver/apply/status_test.go @@ -28,7 +28,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" genericfeatures "k8s.io/apiserver/pkg/features" utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/dynamic" @@ -37,7 +36,6 @@ import ( apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" "k8s.io/kubernetes/test/integration/etcd" "k8s.io/kubernetes/test/integration/framework" - "sigs.k8s.io/yaml" ) // namespace used for all tests, do not change this @@ -161,14 +159,10 @@ func TestApplyStatus(t *testing.T) { // etcd test stub data doesn't contain apiVersion/kind (!), but apply requires it newObj.SetGroupVersionKind(mapping.GroupVersionKind) - createData, err := json.Marshal(newObj.Object) - if err != nil { - t.Fatal(err) - } rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace) // apply to create - _, err = rsc.Patch(context.TODO(), name, types.ApplyPatchType, []byte(createData), metav1.PatchOptions{FieldManager: "create_test"}) + _, err = rsc.Apply(context.TODO(), name, &newObj, metav1.ApplyOptions{FieldManager: "create_test"}) if err != nil { t.Fatal(err) } @@ -180,16 +174,11 @@ func TestApplyStatus(t *testing.T) { statusObj.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String()) statusObj.SetKind(mapping.GroupVersionKind.Kind) statusObj.SetName(name) - statusYAML, err := yaml.Marshal(statusObj.Object) - if err != nil { - t.Fatal(err) - } - True := true obj, err := dynamicClient. Resource(mapping.Resource). Namespace(namespace). - Patch(context.TODO(), name, types.ApplyPatchType, statusYAML, metav1.PatchOptions{FieldManager: "apply_status_test", Force: &True}, "status") + ApplyStatus(context.TODO(), name, &statusObj, metav1.ApplyOptions{FieldManager: "apply_status_test", Force: true}) if err != nil { t.Fatalf("Failed to apply: %v", err) }