Skip to content

Commit 927acf2

Browse files
committed
client: update status subresource support
1 parent 3aaf8cd commit 927acf2

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed

pkg/client/client.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,24 @@ func (c *client) Update(ctx context.Context, obj runtime.Object) error {
110110
Into(obj)
111111
}
112112

113+
// UpdateStatus implements client.Client
114+
func (c *client) UpdateStatus(ctx context.Context, obj runtime.Object) error {
115+
o, err := c.cache.getObjMeta(obj)
116+
if err != nil {
117+
return err
118+
}
119+
// TODO(droot): examine the returned error and check if it error needs to be
120+
// wrapped to improve the UX ?
121+
return o.Put().
122+
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
123+
Resource(o.resource()).
124+
Name(o.GetName()).
125+
SubResource("status").
126+
Body(obj).
127+
Do().
128+
Into(obj)
129+
}
130+
113131
// Delete implements client.Client
114132
func (c *client) Delete(ctx context.Context, obj runtime.Object) error {
115133
o, err := c.cache.getObjMeta(obj)

pkg/client/client_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ var _ = Describe("Client", func() {
5858
var pod *corev1.Pod
5959
var node *corev1.Node
6060
var count uint64 = 0
61+
var replicaCount int32 = 2
6162
var ns = "default"
6263

6364
BeforeEach(func(done Done) {
6465
atomic.AddUint64(&count, 1)
6566
dep = &appsv1.Deployment{
6667
ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("deployment-name-%v", count), Namespace: ns},
6768
Spec: appsv1.DeploymentSpec{
69+
Replicas: &replicaCount,
6870
Selector: &metav1.LabelSelector{
6971
MatchLabels: map[string]string{"foo": "bar"},
7072
},
@@ -391,6 +393,127 @@ var _ = Describe("Client", func() {
391393
})
392394
})
393395

396+
Describe("UpdateStatus", func() {
397+
It("should update status of an existing object", func(done Done) {
398+
cl, err := client.New(cfg, client.Options{})
399+
Expect(err).NotTo(HaveOccurred())
400+
Expect(cl).NotTo(BeNil())
401+
402+
By("initially creating a Deployment")
403+
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
404+
Expect(err).NotTo(HaveOccurred())
405+
406+
By("updating the status of Deployment")
407+
dep.Status.Replicas = 1
408+
err = cl.UpdateStatus(context.TODO(), dep)
409+
Expect(err).NotTo(HaveOccurred())
410+
411+
By("validating updated Deployment has new status")
412+
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
413+
Expect(err).NotTo(HaveOccurred())
414+
Expect(actual).NotTo(BeNil())
415+
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))
416+
417+
close(done)
418+
})
419+
420+
It("should not update spec of an existing object", func(done Done) {
421+
cl, err := client.New(cfg, client.Options{})
422+
Expect(err).NotTo(HaveOccurred())
423+
Expect(cl).NotTo(BeNil())
424+
425+
By("initially creating a Deployment")
426+
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
427+
Expect(err).NotTo(HaveOccurred())
428+
429+
By("updating the spec and status of Deployment")
430+
var rc int32 = 1
431+
dep.Status.Replicas = 1
432+
dep.Spec.Replicas = &rc
433+
err = cl.UpdateStatus(context.TODO(), dep)
434+
Expect(err).NotTo(HaveOccurred())
435+
436+
By("validating updated Deployment has new status and unchanged spec")
437+
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
438+
Expect(err).NotTo(HaveOccurred())
439+
Expect(actual).NotTo(BeNil())
440+
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))
441+
Expect(*actual.Spec.Replicas).To(BeEquivalentTo(replicaCount))
442+
443+
close(done)
444+
})
445+
446+
It("should update an existing object non-namespace object", func(done Done) {
447+
cl, err := client.New(cfg, client.Options{})
448+
Expect(err).NotTo(HaveOccurred())
449+
Expect(cl).NotTo(BeNil())
450+
451+
node, err := clientset.CoreV1().Nodes().Create(node)
452+
Expect(err).NotTo(HaveOccurred())
453+
454+
By("updating status of the object")
455+
node.Status.Phase = corev1.NodeRunning
456+
err = cl.UpdateStatus(context.TODO(), node)
457+
Expect(err).NotTo(HaveOccurred())
458+
459+
By("validate updated Node had new annotation")
460+
actual, err := clientset.CoreV1().Nodes().Get(node.Name, metav1.GetOptions{})
461+
Expect(err).NotTo(HaveOccurred())
462+
Expect(actual).NotTo(BeNil())
463+
Expect(actual.Status.Phase).To(Equal(corev1.NodeRunning))
464+
465+
close(done)
466+
})
467+
468+
It("should fail if the object does not exists", func(done Done) {
469+
cl, err := client.New(cfg, client.Options{})
470+
Expect(err).NotTo(HaveOccurred())
471+
Expect(cl).NotTo(BeNil())
472+
473+
By("updating status of a non-existent object")
474+
err = cl.UpdateStatus(context.TODO(), dep)
475+
Expect(err).To(HaveOccurred())
476+
477+
close(done)
478+
})
479+
480+
It("should fail if the object does not pass server-side validation", func() {
481+
482+
})
483+
484+
It("should fail if the object doesn't have meta", func() {
485+
486+
})
487+
488+
It("should fail if the object cannot be mapped to a GVK", func(done Done) {
489+
By("creating client with empty Scheme")
490+
emptyScheme := runtime.NewScheme()
491+
cl, err := client.New(cfg, client.Options{Scheme: emptyScheme})
492+
Expect(err).NotTo(HaveOccurred())
493+
Expect(cl).NotTo(BeNil())
494+
495+
By("initially creating a Deployment")
496+
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
497+
Expect(err).NotTo(HaveOccurred())
498+
499+
By("updating status of the Deployment")
500+
dep.Status.Replicas = 1
501+
err = cl.UpdateStatus(context.TODO(), dep)
502+
Expect(err).To(HaveOccurred())
503+
Expect(err.Error()).To(ContainSubstring("no kind is registered for the type"))
504+
505+
close(done)
506+
})
507+
508+
It("should fail if the GVK cannot be mapped to a Resource", func() {
509+
510+
})
511+
512+
It("should fail if an API does not implement Status subresource", func() {
513+
514+
})
515+
})
516+
394517
Describe("Delete", func() {
395518
It("should delete an existing object from a go struct", func(done Done) {
396519
cl, err := client.New(cfg, client.Options{})

pkg/client/fake/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ func (c *fakeClient) Update(ctx context.Context, obj runtime.Object) error {
123123
return c.tracker.Update(gvr, obj, accessor.GetNamespace())
124124
}
125125

126+
func (c *fakeClient) UpdateStatus(ctx context.Context, obj runtime.Object) error {
127+
// TODO(droot): implement for *real*
128+
return nil
129+
}
130+
126131
func getGVRFromObject(obj runtime.Object) (schema.GroupVersionResource, error) {
127132
gvk, err := apiutil.GVKForObject(obj, scheme.Scheme)
128133
if err != nil {

pkg/client/interfaces.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ type Writer interface {
5555
// Update updates the given obj in the Kubernetes cluster. obj must be a
5656
// struct pointer so that obj can be updated with the content returned by the Server.
5757
Update(ctx context.Context, obj runtime.Object) error
58+
59+
// UpdateStatus updates the fields corresponding to status subresource for
60+
// the given obj. obj must be a struct pointer so that obj can be updated
61+
// with the content returned by the Server.
62+
UpdateStatus(ctx context.Context, obj runtime.Object) error
5863
}
5964

6065
// Client knows how to perform CRUD operations on Kubernetes objects.

0 commit comments

Comments
 (0)