Skip to content

Commit d4062d6

Browse files
committed
attempt at server side apply
Signed-off-by: Troy Connor <troy0820@users.noreply.github.com>
1 parent 5615941 commit d4062d6

10 files changed

+129
-1
lines changed

pkg/client/client.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"k8s.io/apimachinery/pkg/api/meta"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2829
"k8s.io/apimachinery/pkg/runtime"
2930
"k8s.io/apimachinery/pkg/runtime/schema"
3031
"k8s.io/apimachinery/pkg/runtime/serializer"
@@ -347,6 +348,21 @@ func (c *client) Patch(ctx context.Context, obj Object, patch Patch, opts ...Pat
347348
}
348349
}
349350

351+
func (c *client) Apply(ctx context.Context, obj Object, fieldOwner string) error {
352+
var err error
353+
switch obj.(type) {
354+
case runtime.Unstructured:
355+
return c.Patch(ctx, obj, Apply, ForceOwnership, FieldOwner(fieldOwner))
356+
default:
357+
u := &unstructured.Unstructured{}
358+
u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
359+
if err != nil {
360+
return err
361+
}
362+
return c.Patch(ctx, u, Apply, ForceOwnership, FieldOwner(fieldOwner))
363+
}
364+
}
365+
350366
// Get implements client.Client.
351367
func (c *client) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
352368
if isUncached, err := c.shouldBypassCache(obj); err != nil {

pkg/client/client_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,30 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
795795
})
796796
})
797797
})
798-
798+
Describe("Server side apply", func() {
799+
Context("with a core k8s object", func() {
800+
It("should not error", func() {
801+
cl, err := client.New(cfg, client.Options{})
802+
Expect(err).NotTo(HaveOccurred())
803+
Expect(cl).NotTo(BeNil())
804+
cm := &corev1.ConfigMap{
805+
TypeMeta: metav1.TypeMeta{
806+
APIVersion: "v1",
807+
Kind: "ConfigMap",
808+
},
809+
ObjectMeta: metav1.ObjectMeta{
810+
Name: "config-map",
811+
Namespace: "default",
812+
},
813+
Data: map[string]string{
814+
"key": "value",
815+
},
816+
}
817+
err = cl.Apply(ctx, cm, "test-client")
818+
Expect(err).NotTo(HaveOccurred())
819+
})
820+
})
821+
})
799822
Describe("SubResourceClient", func() {
800823
Context("with structured objects", func() {
801824
It("should be able to read the Scale subresource", func() {

pkg/client/dryrun.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ func (c *dryRunClient) Patch(ctx context.Context, obj Object, patch Patch, opts
8282
return c.client.Patch(ctx, obj, patch, append(opts, DryRunAll)...)
8383
}
8484

85+
func (c *dryRunClient) Apply(ctx context.Context, obj Object, fieldOwner string) error {
86+
return c.client.Apply(ctx, obj, fieldOwner)
87+
}
88+
8589
// Get implements client.Client.
8690
func (c *dryRunClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
8791
return c.client.Get(ctx, key, obj, opts...)

pkg/client/fake/client.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,10 @@ func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.
801801
return c.patch(obj, patch, opts...)
802802
}
803803

804+
func (c *fakeClient) Apply(ctx context.Context, obj client.Object, fieldOwner string) error {
805+
return c.Patch(ctx, obj, client.Apply, client.ForceOwnership, client.FieldOwner(fieldOwner))
806+
}
807+
804808
func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
805809
patchOptions := &client.PatchOptions{}
806810
patchOptions.ApplyOptions(opts)

pkg/client/interceptor/intercept.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Funcs struct {
1919
DeleteAllOf func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.DeleteAllOfOption) error
2020
Update func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.UpdateOption) error
2121
Patch func(ctx context.Context, client client.WithWatch, obj client.Object, patch client.Patch, opts ...client.PatchOption) error
22+
Apply func(ctx context.Context, client client.WithWatch, obj client.Object, fieldOwner string) error
2223
Watch func(ctx context.Context, client client.WithWatch, obj client.ObjectList, opts ...client.ListOption) (watch.Interface, error)
2324
SubResource func(client client.WithWatch, subResource string) client.SubResourceClient
2425
SubResourceGet func(ctx context.Context, client client.Client, subResourceName string, obj client.Object, subResource client.Object, opts ...client.SubResourceGetOption) error
@@ -92,6 +93,13 @@ func (c interceptor) Patch(ctx context.Context, obj client.Object, patch client.
9293
return c.client.Patch(ctx, obj, patch, opts...)
9394
}
9495

96+
func (c interceptor) Apply(ctx context.Context, obj client.Object, fieldOwner string) error {
97+
if c.funcs.Patch != nil {
98+
return c.funcs.Apply(ctx, c.client, obj, fieldOwner)
99+
}
100+
return c.client.Apply(ctx, obj, fieldOwner)
101+
}
102+
95103
func (c interceptor) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
96104
if c.funcs.DeleteAllOf != nil {
97105
return c.funcs.DeleteAllOf(ctx, c.client, obj, opts...)

pkg/client/interceptor/intercept_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,10 @@ func (d dummyClient) Patch(ctx context.Context, obj client.Object, patch client.
360360
return nil
361361
}
362362

363+
func (d dummyClient) Apply(ctx context.Context, obj client.Object, fieldOwner string) error {
364+
return nil
365+
}
366+
363367
func (d dummyClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
364368
return nil
365369
}

pkg/client/interfaces.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ type Writer interface {
7676
// struct pointer so that obj can be updated with the content returned by the Server.
7777
Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error
7878

79+
// Apply patches the given object in the Kubernetes cluster with a server side
80+
// apply. obj must be a struct pointer so that obj can be updated with the
81+
// content returned by the Server
82+
Apply(ctx context.Context, obj Object, fieldOwner string) error
83+
7984
// DeleteAllOf deletes all objects of the given type matching the given options.
8085
DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error
8186
}

pkg/client/namespaced_client.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,23 @@ func (n *namespacedClient) Patch(ctx context.Context, obj Object, patch Patch, o
147147
return n.client.Patch(ctx, obj, patch, opts...)
148148
}
149149

150+
func (n *namespacedClient) Apply(ctx context.Context, obj Object, fieldOwner string) error {
151+
isNamespaceScoped, err := n.IsObjectNamespaced(obj)
152+
if err != nil {
153+
return fmt.Errorf("error finding the scope of the object: %w", err)
154+
}
155+
156+
objectNamespace := obj.GetNamespace()
157+
if objectNamespace != n.namespace && objectNamespace != "" {
158+
return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), n.namespace)
159+
}
160+
161+
if isNamespaceScoped && objectNamespace == "" {
162+
obj.SetNamespace(n.namespace)
163+
}
164+
return n.client.Apply(ctx, obj, fieldOwner)
165+
}
166+
150167
// Get implements client.Client.
151168
func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
152169
isNamespaceScoped, err := n.IsObjectNamespaced(obj)

pkg/client/typed_client.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"context"
2121

2222
"k8s.io/apimachinery/pkg/runtime"
23+
"k8s.io/apimachinery/pkg/types"
24+
"k8s.io/apimachinery/pkg/util/json"
2325
)
2426

2527
var _ Reader = &typedClient{}
@@ -132,6 +134,25 @@ func (c *typedClient) Patch(ctx context.Context, obj Object, patch Patch, opts .
132134
Into(obj)
133135
}
134136

137+
func (c *typedClient) Apply(ctx context.Context, obj Object, fieldOwner string) error {
138+
o, err := c.resources.getObjMeta(obj)
139+
if err != nil {
140+
return err
141+
}
142+
143+
data, err := json.Marshal(o)
144+
if err != nil {
145+
return err
146+
}
147+
return o.Patch(types.ApplyPatchType).
148+
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
149+
Resource(o.resource()).
150+
Name(o.GetName()).
151+
Body(data).
152+
Do(ctx).
153+
Into(obj)
154+
}
155+
135156
// Get implements client.Client.
136157
func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
137158
r, err := c.resources.getResource(obj)

pkg/client/unstructured_client.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"strings"
2323

2424
"k8s.io/apimachinery/pkg/runtime"
25+
"k8s.io/apimachinery/pkg/types"
26+
"k8s.io/apimachinery/pkg/util/json"
2527
)
2628

2729
var _ Reader = &unstructuredClient{}
@@ -166,6 +168,30 @@ func (uc *unstructuredClient) Patch(ctx context.Context, obj Object, patch Patch
166168
Into(obj)
167169
}
168170

171+
func (uc *unstructuredClient) Apply(ctx context.Context, obj Object, fieldOwner string) error {
172+
if _, ok := obj.(runtime.Unstructured); !ok {
173+
return fmt.Errorf("unstructured client did not understand object: %T", obj)
174+
}
175+
176+
o, err := uc.resources.getObjMeta(obj)
177+
if err != nil {
178+
return err
179+
}
180+
181+
data, err := json.Marshal(obj)
182+
if err != nil {
183+
return err
184+
}
185+
186+
return o.Patch(types.ApplyPatchType).
187+
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
188+
Resource(o.resource()).
189+
Name(o.GetName()).
190+
Body(data).
191+
Do(ctx).
192+
Into(obj)
193+
}
194+
169195
// Get implements client.Client.
170196
func (uc *unstructuredClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
171197
u, ok := obj.(runtime.Unstructured)

0 commit comments

Comments
 (0)