Skip to content

Commit 3554729

Browse files
authored
Merge pull request #3283 from alvaroaleman/fix-unmarshal
🐛 Fakeclient: Update passed object in Apply
2 parents b5aad1b + 4bfda0b commit 3554729

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
@@ -1085,7 +1085,30 @@ func (c *fakeClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration,
10851085
patchOpts := &client.PatchOptions{}
10861086
patchOpts.Raw = applyOpts.AsPatchOptions()
10871087

1088-
return c.patch(u, applyPatch, patchOpts)
1088+
if err := c.patch(u, applyPatch, patchOpts); err != nil {
1089+
return err
1090+
}
1091+
1092+
acJSON, err := json.Marshal(u)
1093+
if err != nil {
1094+
return fmt.Errorf("failed to marshal patched object: %w", err)
1095+
}
1096+
1097+
// We have to zero the object in case it contained a status and there is a
1098+
// status subresource. If its the private `unstructuredApplyConfiguration`
1099+
// we can not zero all of it, as that will cause the embedded Unstructured
1100+
// to be nil which then causes a NPD in the json.Unmarshal below.
1101+
switch reflect.TypeOf(obj).String() {
1102+
case "*client.unstructuredApplyConfiguration":
1103+
zero(reflect.ValueOf(obj).Elem().FieldByName("Unstructured").Interface())
1104+
default:
1105+
zero(obj)
1106+
}
1107+
if err := json.Unmarshal(acJSON, obj); err != nil {
1108+
return fmt.Errorf("failed to unmarshal patched object: %w", err)
1109+
}
1110+
1111+
return nil
10891112
}
10901113

10911114
type fakeApplyPatch struct{}

pkg/client/fake/client_test.go

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

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

28122880
It("allows to set deletionTimestamp on an object during SSA create", func(ctx SpecContext) {
2813-
now := metav1.Now()
2881+
now := metav1.Time{Time: time.Now().Round(time.Second)}
28142882
obj := corev1applyconfigurations.
28152883
ConfigMap("foo", "default").
28162884
WithDeletionTimestamp(now).
@@ -2823,8 +2891,7 @@ var _ = Describe("Fake client", func() {
28232891
})
28242892

28252893
It("will silently ignore a deletionTimestamp update through SSA", func(ctx SpecContext) {
2826-
Skip("the apply logic in the managedFieldObjectTracker seems to override this")
2827-
now := metav1.Now()
2894+
now := metav1.Time{Time: time.Now().Round(time.Second)}
28282895
obj := corev1applyconfigurations.
28292896
ConfigMap("foo", "default").
28302897
WithDeletionTimestamp(now).

0 commit comments

Comments
 (0)