Skip to content

Commit 8361246

Browse files
authored
Merge pull request #2523 from troy0820/troy0820/cherry-pick-to-release-0.16
[release-0.16] 🐛 Handle unstructured status update with fake client
2 parents 2d6b699 + f7b7450 commit 8361246

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed

pkg/client/fake/client.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,9 @@ func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Ob
404404
return fmt.Errorf("failed to copy non-status field for object with status subresouce: %w", err)
405405
}
406406
passedRV := accessor.GetResourceVersion()
407-
reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(oldObject.DeepCopyObject()).Elem())
407+
if err := copyFrom(oldObject, obj); err != nil {
408+
return fmt.Errorf("failed to restore non-status fields: %w", err)
409+
}
408410
accessor.SetResourceVersion(passedRV)
409411
} else { // copy status from original object
410412
if err := copyStatusFrom(oldObject, obj); err != nil {
@@ -972,6 +974,19 @@ func copyStatusFrom(old, new runtime.Object) error {
972974
return nil
973975
}
974976

977+
// copyFrom copies from old into new
978+
func copyFrom(old, new runtime.Object) error {
979+
oldMapStringAny, err := toMapStringAny(old)
980+
if err != nil {
981+
return fmt.Errorf("failed to convert old to *unstructured.Unstructured: %w", err)
982+
}
983+
if err := fromMapStringAny(oldMapStringAny, new); err != nil {
984+
return fmt.Errorf("failed to convert back from map[string]any: %w", err)
985+
}
986+
987+
return nil
988+
}
989+
975990
func toMapStringAny(obj runtime.Object) (map[string]any, error) {
976991
if unstructured, isUnstructured := obj.(*unstructured.Unstructured); isUnstructured {
977992
return unstructured.Object, nil

pkg/client/fake/client_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,84 @@ var _ = Describe("Fake client", func() {
17001700
Expect(obj.Object["spec"]).To(BeEquivalentTo("original"))
17011701
})
17021702

1703+
It("should not change the status of known unstructured objects that have a status subresource on update", func() {
1704+
obj := &corev1.Pod{
1705+
ObjectMeta: metav1.ObjectMeta{
1706+
Name: "pod",
1707+
},
1708+
Spec: corev1.PodSpec{
1709+
RestartPolicy: corev1.RestartPolicyAlways,
1710+
},
1711+
Status: corev1.PodStatus{
1712+
Phase: corev1.PodPending,
1713+
},
1714+
}
1715+
cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1716+
1717+
// update using unstructured
1718+
u := &unstructured.Unstructured{}
1719+
u.SetAPIVersion("v1")
1720+
u.SetKind("Pod")
1721+
u.SetName(obj.Name)
1722+
err := cl.Get(context.Background(), client.ObjectKeyFromObject(u), u)
1723+
Expect(err).NotTo(HaveOccurred())
1724+
1725+
err = unstructured.SetNestedField(u.Object, string(corev1.RestartPolicyNever), "spec", "restartPolicy")
1726+
Expect(err).NotTo(HaveOccurred())
1727+
err = unstructured.SetNestedField(u.Object, string(corev1.PodRunning), "status", "phase")
1728+
Expect(err).NotTo(HaveOccurred())
1729+
1730+
Expect(cl.Update(context.Background(), u)).To(Succeed())
1731+
1732+
actual := &corev1.Pod{}
1733+
Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), actual)).To(Succeed())
1734+
obj.APIVersion = u.GetAPIVersion()
1735+
obj.Kind = u.GetKind()
1736+
obj.ResourceVersion = actual.ResourceVersion
1737+
// only the spec mutation should persist
1738+
obj.Spec.RestartPolicy = corev1.RestartPolicyNever
1739+
Expect(cmp.Diff(obj, actual)).To(BeEmpty())
1740+
})
1741+
1742+
It("should not change non-status field of known unstructured objects that have a status subresource on status update", func() {
1743+
obj := &corev1.Pod{
1744+
ObjectMeta: metav1.ObjectMeta{
1745+
Name: "pod",
1746+
},
1747+
Spec: corev1.PodSpec{
1748+
RestartPolicy: corev1.RestartPolicyAlways,
1749+
},
1750+
Status: corev1.PodStatus{
1751+
Phase: corev1.PodPending,
1752+
},
1753+
}
1754+
cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build()
1755+
1756+
// status update using unstructured
1757+
u := &unstructured.Unstructured{}
1758+
u.SetAPIVersion("v1")
1759+
u.SetKind("Pod")
1760+
u.SetName(obj.Name)
1761+
err := cl.Get(context.Background(), client.ObjectKeyFromObject(u), u)
1762+
Expect(err).NotTo(HaveOccurred())
1763+
1764+
err = unstructured.SetNestedField(u.Object, string(corev1.RestartPolicyNever), "spec", "restartPolicy")
1765+
Expect(err).NotTo(HaveOccurred())
1766+
err = unstructured.SetNestedField(u.Object, string(corev1.PodRunning), "status", "phase")
1767+
Expect(err).NotTo(HaveOccurred())
1768+
1769+
Expect(cl.Status().Update(context.Background(), u)).To(Succeed())
1770+
1771+
actual := &corev1.Pod{}
1772+
Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), actual)).To(Succeed())
1773+
obj.APIVersion = "v1"
1774+
obj.Kind = "Pod"
1775+
obj.ResourceVersion = actual.ResourceVersion
1776+
// only the status mutation should persist
1777+
obj.Status.Phase = corev1.PodRunning
1778+
Expect(cmp.Diff(obj, actual)).To(BeEmpty())
1779+
})
1780+
17031781
It("should not change the status of unstructured objects that are configured to have a status subresource on patch", func() {
17041782
obj := &unstructured.Unstructured{}
17051783
obj.SetAPIVersion("foo/v1")

0 commit comments

Comments
 (0)