mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 03:11:40 +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
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user