Skip to content

Commit

Permalink
Status Writer working with unstructured as well as typed objects
Browse files Browse the repository at this point in the history
  • Loading branch information
Shawn Hurley committed Sep 19, 2018
1 parent 00c5c79 commit 7471369
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 109 deletions.
21 changes: 5 additions & 16 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,21 +153,10 @@ type statusWriter struct {
var _ StatusWriter = &statusWriter{}

// Update implements client.StatusWriter
func (sw *statusWriter) Update(_ context.Context, obj runtime.Object) error {
o, err := sw.client.typedClient.cache.getObjMeta(obj)
if err != nil {
return err
func (sw *statusWriter) Update(ctx context.Context, obj runtime.Object) error {
_, ok := obj.(*unstructured.Unstructured)
if ok {
return sw.client.unstructuredClient.UpdateStatus(ctx, obj)
}
// TODO(droot): examine the returned error and check if it error needs to be
// wrapped to improve the UX ?
// It will be nice to receive an error saying the object doesn't implement
// status subresource and check CRD definition
return o.Put().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource("status").
Body(obj).
Do().
Into(obj)
return sw.client.typedClient.UpdateStatus(ctx, obj)
}
270 changes: 187 additions & 83 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,114 +541,218 @@ var _ = Describe("Client", func() {
})

Describe("StatusClient", func() {
It("should update status of an existing object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())
Context("with structured objects", func() {
It("should update status of an existing object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

By("initially creating a Deployment")
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
Expect(err).NotTo(HaveOccurred())
By("initially creating a Deployment")
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
Expect(err).NotTo(HaveOccurred())

By("updating the status of Deployment")
dep.Status.Replicas = 1
err = cl.Status().Update(context.TODO(), dep)
Expect(err).NotTo(HaveOccurred())
By("updating the status of Deployment")
dep.Status.Replicas = 1
err = cl.Status().Update(context.TODO(), dep)
Expect(err).NotTo(HaveOccurred())

By("validating updated Deployment has new status")
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))
By("validating updated Deployment has new status")
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))

close(done)
})
close(done)
})

It("should not update spec of an existing object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())
It("should not update spec of an existing object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

By("initially creating a Deployment")
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
Expect(err).NotTo(HaveOccurred())
By("initially creating a Deployment")
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
Expect(err).NotTo(HaveOccurred())

By("updating the spec and status of Deployment")
var rc int32 = 1
dep.Status.Replicas = 1
dep.Spec.Replicas = &rc
err = cl.Status().Update(context.TODO(), dep)
Expect(err).NotTo(HaveOccurred())
By("updating the spec and status of Deployment")
var rc int32 = 1
dep.Status.Replicas = 1
dep.Spec.Replicas = &rc
err = cl.Status().Update(context.TODO(), dep)
Expect(err).NotTo(HaveOccurred())

By("validating updated Deployment has new status and unchanged spec")
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))
Expect(*actual.Spec.Replicas).To(BeEquivalentTo(replicaCount))
By("validating updated Deployment has new status and unchanged spec")
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))
Expect(*actual.Spec.Replicas).To(BeEquivalentTo(replicaCount))

close(done)
})
close(done)
})

It("should update an existing object non-namespace object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())
It("should update an existing object non-namespace object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

node, err := clientset.CoreV1().Nodes().Create(node)
Expect(err).NotTo(HaveOccurred())
node, err := clientset.CoreV1().Nodes().Create(node)
Expect(err).NotTo(HaveOccurred())

By("updating status of the object")
node.Status.Phase = corev1.NodeRunning
err = cl.Status().Update(context.TODO(), node)
Expect(err).NotTo(HaveOccurred())
By("updating status of the object")
node.Status.Phase = corev1.NodeRunning
err = cl.Status().Update(context.TODO(), node)
Expect(err).NotTo(HaveOccurred())

By("validate updated Node had new annotation")
actual, err := clientset.CoreV1().Nodes().Get(node.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Phase).To(Equal(corev1.NodeRunning))
By("validate updated Node had new annotation")
actual, err := clientset.CoreV1().Nodes().Get(node.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Phase).To(Equal(corev1.NodeRunning))

close(done)
})
close(done)
})

It("should fail if the object does not exists", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())
It("should fail if the object does not exists", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

By("updating status of a non-existent object")
err = cl.Status().Update(context.TODO(), dep)
Expect(err).To(HaveOccurred())
By("updating status of a non-existent object")
err = cl.Status().Update(context.TODO(), dep)
Expect(err).To(HaveOccurred())

close(done)
})
close(done)
})

It("should fail if the object cannot be mapped to a GVK", func(done Done) {
By("creating client with empty Scheme")
emptyScheme := runtime.NewScheme()
cl, err := client.New(cfg, client.Options{Scheme: emptyScheme})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())
It("should fail if the object cannot be mapped to a GVK", func(done Done) {
By("creating client with empty Scheme")
emptyScheme := runtime.NewScheme()
cl, err := client.New(cfg, client.Options{Scheme: emptyScheme})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

By("initially creating a Deployment")
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
Expect(err).NotTo(HaveOccurred())
By("initially creating a Deployment")
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
Expect(err).NotTo(HaveOccurred())

By("updating status of the Deployment")
dep.Status.Replicas = 1
err = cl.Status().Update(context.TODO(), dep)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no kind is registered for the type"))
By("updating status of the Deployment")
dep.Status.Replicas = 1
err = cl.Status().Update(context.TODO(), dep)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no kind is registered for the type"))

close(done)
})
close(done)
})

PIt("should fail if the GVK cannot be mapped to a Resource", func() {
PIt("should fail if the GVK cannot be mapped to a Resource", func() {

})

PIt("should fail if an API does not implement Status subresource", func() {

})
})

PIt("should fail if an API does not implement Status subresource", func() {
Context("with unstructured objects", func() {
It("should update status of an existing object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

By("initially creating a Deployment")
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
Expect(err).NotTo(HaveOccurred())

By("updating the status of Deployment")
u := &unstructured.Unstructured{}
dep.Status.Replicas = 1
scheme.Convert(dep, u, nil)
err = cl.Status().Update(context.TODO(), u)
Expect(err).NotTo(HaveOccurred())

By("validating updated Deployment has new status")
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))

close(done)
})

It("should not update spec of an existing object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

By("initially creating a Deployment")
dep, err := clientset.AppsV1().Deployments(ns).Create(dep)
Expect(err).NotTo(HaveOccurred())

By("updating the spec and status of Deployment")
u := &unstructured.Unstructured{}
var rc int32 = 1
dep.Status.Replicas = 1
dep.Spec.Replicas = &rc
scheme.Convert(dep, u, nil)
err = cl.Status().Update(context.TODO(), u)
Expect(err).NotTo(HaveOccurred())

By("validating updated Deployment has new status and unchanged spec")
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Replicas).To(BeEquivalentTo(1))
Expect(*actual.Spec.Replicas).To(BeEquivalentTo(replicaCount))

close(done)
})

It("should update an existing object non-namespace object", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

node, err := clientset.CoreV1().Nodes().Create(node)
Expect(err).NotTo(HaveOccurred())

By("updating status of the object")
u := &unstructured.Unstructured{}
node.Status.Phase = corev1.NodeRunning
scheme.Convert(node, u, nil)
err = cl.Status().Update(context.TODO(), u)
Expect(err).NotTo(HaveOccurred())

By("validate updated Node had new annotation")
actual, err := clientset.CoreV1().Nodes().Get(node.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(actual).NotTo(BeNil())
Expect(actual.Status.Phase).To(Equal(corev1.NodeRunning))

close(done)
})

It("should fail if the object does not exists", func(done Done) {
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(cl).NotTo(BeNil())

By("updating status of a non-existent object")
u := &unstructured.Unstructured{}
scheme.Convert(dep, u, nil)
err = cl.Status().Update(context.TODO(), u)
Expect(err).To(HaveOccurred())

close(done)
})

PIt("should fail if the GVK cannot be mapped to a Resource", func() {

})

PIt("should fail if an API does not implement Status subresource", func() {

})

})
})
Expand Down
30 changes: 25 additions & 5 deletions pkg/client/typed_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type typedClient struct {
}

// Create implements client.Client
func (c *typedClient) Create(ctx context.Context, obj runtime.Object) error {
func (c *typedClient) Create(_ context.Context, obj runtime.Object) error {
o, err := c.cache.getObjMeta(obj)
if err != nil {
return err
Expand All @@ -44,7 +44,7 @@ func (c *typedClient) Create(ctx context.Context, obj runtime.Object) error {
}

// Update implements client.Client
func (c *typedClient) Update(ctx context.Context, obj runtime.Object) error {
func (c *typedClient) Update(_ context.Context, obj runtime.Object) error {
o, err := c.cache.getObjMeta(obj)
if err != nil {
return err
Expand All @@ -59,7 +59,7 @@ func (c *typedClient) Update(ctx context.Context, obj runtime.Object) error {
}

// Delete implements client.Client
func (c *typedClient) Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error {
func (c *typedClient) Delete(_ context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error {
o, err := c.cache.getObjMeta(obj)
if err != nil {
return err
Expand All @@ -76,7 +76,7 @@ func (c *typedClient) Delete(ctx context.Context, obj runtime.Object, opts ...De
}

// Get implements client.Client
func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error {
func (c *typedClient) Get(_ context.Context, key ObjectKey, obj runtime.Object) error {
r, err := c.cache.getResource(obj)
if err != nil {
return err
Expand All @@ -88,7 +88,7 @@ func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj runtime.Object
}

// List implements client.Client
func (c *typedClient) List(ctx context.Context, opts *ListOptions, obj runtime.Object) error {
func (c *typedClient) List(_ context.Context, opts *ListOptions, obj runtime.Object) error {
r, err := c.cache.getResource(obj)
if err != nil {
return err
Expand All @@ -105,3 +105,23 @@ func (c *typedClient) List(ctx context.Context, opts *ListOptions, obj runtime.O
Do().
Into(obj)
}

// UpdateStatus used by StatusWriter to write status.
func (c *typedClient) UpdateStatus(_ context.Context, obj runtime.Object) error {
o, err := c.cache.getObjMeta(obj)
if err != nil {
return err
}
// TODO(droot): examine the returned error and check if it error needs to be
// wrapped to improve the UX ?
// It will be nice to receive an error saying the object doesn't implement
// status subresource and check CRD definition
return o.Put().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource("status").
Body(obj).
Do().
Into(obj)
}
Loading

0 comments on commit 7471369

Please sign in to comment.