diff --git a/pkg/client/fake/client.go b/pkg/client/fake/client.go index 91886d278f..d4a47c0974 100644 --- a/pkg/client/fake/client.go +++ b/pkg/client/fake/client.go @@ -403,6 +403,7 @@ func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Ob if err := copyStatusFrom(obj, oldObject); err != nil { return fmt.Errorf("failed to copy non-status field for object with status subresouce: %w", err) } + obj = oldObject.DeepCopyObject().(client.Object) } else { // copy status from original object if err := copyStatusFrom(oldObject, obj); err != nil { @@ -436,6 +437,14 @@ func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Ob intResourceVersion++ accessor.SetResourceVersion(strconv.FormatUint(intResourceVersion, 10)) + // obtain the current obj accessor's pointer, + // so we can make an update on the current obj + currentAccessor, err := meta.Accessor(obj) + if err != nil { + return fmt.Errorf("failed to get accessor for object: %w", err) + } + currentAccessor.SetResourceVersion(strconv.FormatUint(intResourceVersion, 10)) + if !deleting && !deletionTimestampEqual(accessor, oldAccessor) { return fmt.Errorf("error: Unable to edit %s: metadata.deletionTimestamp field is immutable", accessor.GetName()) } diff --git a/pkg/client/fake/client_test.go b/pkg/client/fake/client_test.go index 81a2d4ac43..f2ebf6ce60 100644 --- a/pkg/client/fake/client_test.go +++ b/pkg/client/fake/client_test.go @@ -1476,6 +1476,84 @@ var _ = Describe("Fake client", func() { objOriginal.Status.NodeInfo.MachineID = "machine-id-from-status-update" Expect(cmp.Diff(objOriginal, actual)).To(BeEmpty()) }) + + It("should be able to update an object after updating an object's status", func() { + obj := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + Spec: corev1.NodeSpec{ + PodCIDR: "old-cidr", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + MachineID: "machine-id", + }, + }, + } + cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build() + expectedObj := obj.DeepCopy() + + obj.Status.NodeInfo.MachineID = "machine-id-from-status-update" + Expect(cl.Status().Update(context.Background(), obj)).NotTo(HaveOccurred()) + + obj.Annotations = map[string]string{ + "some-annotation-key": "some", + } + expectedObj.Annotations = map[string]string{ + "some-annotation-key": "some", + } + Expect(cl.Update(context.Background(), obj)).NotTo(HaveOccurred()) + + actual := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: obj.Name}} + Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(actual), actual)).NotTo(HaveOccurred()) + + expectedObj.APIVersion = actual.APIVersion + expectedObj.Kind = actual.Kind + expectedObj.ResourceVersion = actual.ResourceVersion + expectedObj.Status.NodeInfo.MachineID = "machine-id-from-status-update" + Expect(cmp.Diff(expectedObj, actual)).To(BeEmpty()) + }) + + It("should be able to update an object's status after updating an object", func() { + obj := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + Spec: corev1.NodeSpec{ + PodCIDR: "old-cidr", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + MachineID: "machine-id", + }, + }, + } + cl := NewClientBuilder().WithStatusSubresource(obj).WithObjects(obj).Build() + expectedObj := obj.DeepCopy() + + obj.Annotations = map[string]string{ + "some-annotation-key": "some", + } + expectedObj.Annotations = map[string]string{ + "some-annotation-key": "some", + } + Expect(cl.Update(context.Background(), obj)).NotTo(HaveOccurred()) + + obj.Spec.PodCIDR = "cidr-from-status-update" + obj.Status.NodeInfo.MachineID = "machine-id-from-status-update" + Expect(cl.Status().Update(context.Background(), obj)).NotTo(HaveOccurred()) + + actual := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: obj.Name}} + Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(actual), actual)).NotTo(HaveOccurred()) + + expectedObj.APIVersion = actual.APIVersion + expectedObj.Kind = actual.Kind + expectedObj.ResourceVersion = actual.ResourceVersion + expectedObj.Status.NodeInfo.MachineID = "machine-id-from-status-update" + Expect(cmp.Diff(expectedObj, actual)).To(BeEmpty()) + }) + It("Should only override status fields of typed objects that have a status subresource on status update", func() { obj := &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/client/interfaces.go b/pkg/client/interfaces.go index 0ddda3163d..3cd745e4c0 100644 --- a/pkg/client/interfaces.go +++ b/pkg/client/interfaces.go @@ -142,6 +142,7 @@ type SubResourceWriter interface { // Create saves the subResource object in the Kubernetes cluster. obj must be a // struct pointer so that obj can be updated with the content returned by the Server. Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error + // Update updates the fields corresponding to the status subresource for the // given obj. obj must be a struct pointer so that obj can be updated // with the content returned by the Server.