mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
Merge pull request #109443 from kevindelgado/dynamic-apply
Add Apply and ApplyStatus methods to dynamic ResourceInterface
This commit is contained in:
commit
e8ef77514b
@ -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))
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user