Merge pull request #109443 from kevindelgado/dynamic-apply

Add Apply and ApplyStatus methods to dynamic ResourceInterface
This commit is contained in:
Kubernetes Prow Robot 2022-05-04 01:27:53 -07:00 committed by GitHub
commit e8ef77514b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 28 deletions

View File

@ -454,6 +454,51 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types
return ret, err 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) { func convertObjectsToUnstructured(s *runtime.Scheme, objs []runtime.Object) ([]runtime.Object, error) {
ul := make([]runtime.Object, 0, len(objs)) ul := make([]runtime.Object, 0, len(objs))

View File

@ -40,6 +40,8 @@ type ResourceInterface interface {
List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error)
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, 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) 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 { type NamespaceableResourceInterface interface {

View File

@ -324,6 +324,48 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types
return uncastObj.(*unstructured.Unstructured), nil 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 { func (c *dynamicResourceClient) makeURLSegments(name string) []string {
url := []string{} url := []string{}
if len(c.resource.Group) == 0 { if len(c.resource.Group) == 0 {

View File

@ -29,7 +29,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
genericfeatures "k8s.io/apiserver/pkg/features" genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
@ -42,7 +41,6 @@ import (
"k8s.io/kubernetes/test/integration/etcd" "k8s.io/kubernetes/test/integration/etcd"
"k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/integration/framework"
"k8s.io/kubernetes/test/utils/image" "k8s.io/kubernetes/test/utils/image"
"sigs.k8s.io/yaml"
) )
// namespace used for all tests, do not change this // 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.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String())
obj1.SetKind(mapping.GroupVersionKind.Kind) obj1.SetKind(mapping.GroupVersionKind.Kind)
obj1.SetName(name) obj1.SetName(name)
obj1YAML, err := yaml.Marshal(obj1.Object)
if err != nil {
t.Fatal(err)
}
// apply the spec of the first object // apply the spec of the first object
_, err = dynamicClient. _, err = dynamicClient.
Resource(mapping.Resource). Resource(mapping.Resource).
Namespace(namespace). 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 { if err != nil {
t.Fatalf("Failed to apply obj1: %v", err) 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) 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 // apply the status of the second object
// this won't conflict if resetfields are set correctly // this won't conflict if resetfields are set correctly
// and will conflict if they are not // and will conflict if they are not
_, err = dynamicClient. _, err = dynamicClient.
Resource(mapping.Resource). Resource(mapping.Resource).
Namespace(namespace). 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 { if err != nil {
t.Fatalf("Failed to apply obj2: %v", err) t.Fatalf("Failed to apply obj2: %v", err)
} }
@ -284,7 +273,7 @@ func TestApplyResetFields(t *testing.T) {
_, err = dynamicClient. _, err = dynamicClient.
Resource(mapping.Resource). Resource(mapping.Resource).
Namespace(namespace). 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") { if err == nil || !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected conflict, got error %v", err) t.Fatalf("expected conflict, got error %v", err)
} }
@ -294,7 +283,7 @@ func TestApplyResetFields(t *testing.T) {
_, err = dynamicClient. _, err = dynamicClient.
Resource(mapping.Resource). Resource(mapping.Resource).
Namespace(namespace). 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") { if err == nil || !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected conflict, got error %v", err) t.Fatalf("expected conflict, got error %v", err)
} }

View File

@ -28,7 +28,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
genericfeatures "k8s.io/apiserver/pkg/features" genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
@ -37,7 +36,6 @@ import (
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration/etcd" "k8s.io/kubernetes/test/integration/etcd"
"k8s.io/kubernetes/test/integration/framework" "k8s.io/kubernetes/test/integration/framework"
"sigs.k8s.io/yaml"
) )
// namespace used for all tests, do not change this // 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 // etcd test stub data doesn't contain apiVersion/kind (!), but apply requires it
newObj.SetGroupVersionKind(mapping.GroupVersionKind) newObj.SetGroupVersionKind(mapping.GroupVersionKind)
createData, err := json.Marshal(newObj.Object)
if err != nil {
t.Fatal(err)
}
rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace) rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace)
// apply to create // 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -180,16 +174,11 @@ func TestApplyStatus(t *testing.T) {
statusObj.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String()) statusObj.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String())
statusObj.SetKind(mapping.GroupVersionKind.Kind) statusObj.SetKind(mapping.GroupVersionKind.Kind)
statusObj.SetName(name) statusObj.SetName(name)
statusYAML, err := yaml.Marshal(statusObj.Object)
if err != nil {
t.Fatal(err)
}
True := true
obj, err := dynamicClient. obj, err := dynamicClient.
Resource(mapping.Resource). Resource(mapping.Resource).
Namespace(namespace). 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 { if err != nil {
t.Fatalf("Failed to apply: %v", err) t.Fatalf("Failed to apply: %v", err)
} }