Skip to content

Commit f3126bc

Browse files
committed
🐛 Fakeclient: Update passed object in Apply
Currently, the object that is passed into the fakeclients `Apply` will not get updated, this change fixes that. It also adds tests that validate that all mutating methods except Delete update the object.
1 parent c7df6d0 commit f3126bc

File tree

2 files changed

+94
-4
lines changed

2 files changed

+94
-4
lines changed

pkg/client/fake/client.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,30 @@ func (c *fakeClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration,
10711071
patchOpts := &client.PatchOptions{}
10721072
patchOpts.Raw = applyOpts.AsPatchOptions()
10731073

1074-
return c.patch(u, applyPatch, patchOpts)
1074+
if err := c.patch(u, applyPatch, patchOpts); err != nil {
1075+
return err
1076+
}
1077+
1078+
acJson, err := json.Marshal(u)
1079+
if err != nil {
1080+
return fmt.Errorf("failed to marshal patched object: %w", err)
1081+
}
1082+
1083+
// We have to zero the object in case it contained a status and there is a
1084+
// status subresource. If its the private `unstructuredApplyConfiguration`
1085+
// we can not zero all of it, as that will cause the embedded Unstructured
1086+
// to be nil which then causes a NPD in the json.Unmarshal below.
1087+
switch reflect.TypeOf(obj).String() {
1088+
case "*client.unstructuredApplyConfiguration":
1089+
zero(reflect.ValueOf(obj).Elem().FieldByName("Unstructured").Interface())
1090+
default:
1091+
zero(obj)
1092+
}
1093+
if err := json.Unmarshal(acJson, obj); err != nil {
1094+
return fmt.Errorf("failed to unmarshal patched object: %w", err)
1095+
}
1096+
1097+
return nil
10751098
}
10761099

10771100
type fakeApplyPatch struct{}

pkg/client/fake/client_test.go

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,6 +2540,74 @@ var _ = Describe("Fake client", func() {
25402540
wg.Wait()
25412541
})
25422542

2543+
DescribeTable("mutating operations return the updated object",
2544+
func(ctx SpecContext, mutate func(ctx SpecContext) (*corev1.ConfigMap, error)) {
2545+
mutated, err := mutate(ctx)
2546+
Expect(err).NotTo(HaveOccurred())
2547+
2548+
var retrieved corev1.ConfigMap
2549+
Expect(cl.Get(ctx, client.ObjectKeyFromObject(mutated), &retrieved)).To(Succeed())
2550+
2551+
Expect(&retrieved).To(BeComparableTo(mutated))
2552+
},
2553+
2554+
Entry("create", func(ctx SpecContext) (*corev1.ConfigMap, error) {
2555+
cl = NewClientBuilder().Build()
2556+
cm.ResourceVersion = ""
2557+
return cm, cl.Create(ctx, cm)
2558+
}),
2559+
Entry("update", func(ctx SpecContext) (*corev1.ConfigMap, error) {
2560+
cl = NewClientBuilder().WithObjects(cm).Build()
2561+
cm.Labels = map[string]string{"updated-label": "update-test"}
2562+
cm.Data["new-key"] = "new-value"
2563+
return cm, cl.Update(ctx, cm)
2564+
}),
2565+
Entry("patch", func(ctx SpecContext) (*corev1.ConfigMap, error) {
2566+
cl = NewClientBuilder().WithObjects(cm).Build()
2567+
original := cm.DeepCopy()
2568+
2569+
cm.Labels = map[string]string{"updated-label": "update-test"}
2570+
cm.Data["new-key"] = "new-value"
2571+
return cm, cl.Patch(ctx, cm, client.MergeFrom(original))
2572+
}),
2573+
Entry("Create through Apply", func(ctx SpecContext) (*corev1.ConfigMap, error) {
2574+
ac := corev1applyconfigurations.ConfigMap(cm.Name, cm.Namespace).WithData(cm.Data)
2575+
2576+
cl = NewClientBuilder().Build()
2577+
Expect(cl.Apply(ctx, ac, client.FieldOwner("foo"))).To(Succeed())
2578+
2579+
serialized, err := json.Marshal(ac)
2580+
Expect(err).NotTo(HaveOccurred())
2581+
2582+
var cm corev1.ConfigMap
2583+
Expect(json.Unmarshal(serialized, &cm)).To(Succeed())
2584+
2585+
// ApplyConfigurations always have TypeMeta set as they do not support using the scheme
2586+
// to retrieve gvk.
2587+
cm.TypeMeta = metav1.TypeMeta{}
2588+
return &cm, nil
2589+
}),
2590+
Entry("Update through Apply", func(ctx SpecContext) (*corev1.ConfigMap, error) {
2591+
ac := corev1applyconfigurations.ConfigMap(cm.Name, cm.Namespace).
2592+
WithLabels(map[string]string{"updated-label": "update-test"}).
2593+
WithData(map[string]string{"new-key": "new-value"})
2594+
2595+
cl = NewClientBuilder().WithObjects(cm).Build()
2596+
Expect(cl.Apply(ctx, ac, client.FieldOwner("foo"))).To(Succeed())
2597+
2598+
serialized, err := json.Marshal(ac)
2599+
Expect(err).NotTo(HaveOccurred())
2600+
2601+
var cm corev1.ConfigMap
2602+
Expect(json.Unmarshal(serialized, &cm)).To(Succeed())
2603+
2604+
// ApplyConfigurations always have TypeMeta set as they do not support using the scheme
2605+
// to retrieve gvk.
2606+
cm.TypeMeta = metav1.TypeMeta{}
2607+
return &cm, nil
2608+
}),
2609+
)
2610+
25432611
It("supports server-side apply of a client-go resource", func(ctx SpecContext) {
25442612
cl := NewClientBuilder().Build()
25452613
obj := &unstructured.Unstructured{}
@@ -2808,7 +2876,7 @@ var _ = Describe("Fake client", func() {
28082876
})
28092877

28102878
It("allows to set deletionTimestamp on an object during SSA create", func(ctx SpecContext) {
2811-
now := metav1.Now()
2879+
now := metav1.Time{Time: time.Now().Round(time.Second)}
28122880
obj := corev1applyconfigurations.
28132881
ConfigMap("foo", "default").
28142882
WithDeletionTimestamp(now).
@@ -2821,8 +2889,7 @@ var _ = Describe("Fake client", func() {
28212889
})
28222890

28232891
It("will silently ignore a deletionTimestamp update through SSA", func(ctx SpecContext) {
2824-
Skip("the apply logic in the managedFieldObjectTracker seems to override this")
2825-
now := metav1.Now()
2892+
now := metav1.Time{Time: time.Now().Round(time.Second)}
28262893
obj := corev1applyconfigurations.
28272894
ConfigMap("foo", "default").
28282895
WithDeletionTimestamp(now).

0 commit comments

Comments
 (0)