From ad67096f17e0ef1c48f761370cb2a2261fe84c42 Mon Sep 17 00:00:00 2001 From: Nikhita Raghunath Date: Sun, 18 Feb 2018 18:45:38 +0530 Subject: [PATCH 1/2] sample-controller: add status subresource support - Add an example to show how to use status subresources with custom resources. - Update the comment in the controller to mention that `UpdateStatus` can now be used. - Generate `UpdateStatus` for Foo. - Update the README to remove feature gate information for CRD validation since the current example requires a v1.9 cluster and it is enabled by default. - Update the README to add feature gate information for CustomResourceSubResources. Kubernetes-commit: 7c06d6fb17ee46f587c1facff93b9bb185522855 --- README.md | 37 ++++++++++++++++--- .../examples/crd-status-subresource.yaml | 13 +++++++ artifacts/examples/crd-validation.yaml | 20 ++++++++++ artifacts/examples/crd.yaml | 9 ----- controller.go | 8 ++-- pkg/apis/samplecontroller/v1alpha1/types.go | 1 - 6 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 artifacts/examples/crd-status-subresource.yaml create mode 100644 artifacts/examples/crd-validation.yaml diff --git a/README.md b/README.md index e2776a21..7d2ab577 100644 --- a/README.md +++ b/README.md @@ -79,17 +79,44 @@ type User struct { To validate custom resources, use the [`CustomResourceValidation`](https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/#validation) feature. -This feature is beta and enabled by default in v1.9. If you are using v1.8, enable the feature using -the `CustomResourceValidation` feature gate on the [kube-apiserver](https://kubernetes.io/docs/admin/kube-apiserver): +This feature is beta and enabled by default in v1.9. + +### Example + +The schema in [`crd-validation.yaml`](./artifacts/examples/crd-validation.yaml) applies the following validation on the custom resource: +`spec.replicas` must be an integer and must have a minimum value of 1 and a maximum value of 10. + +In the above steps, use `crd-validation.yaml` to create the CRD: ```sh ---feature-gates=CustomResourceValidation=true +# create a CustomResourceDefinition supporting validation +$ kubectl create -f artifacts/examples/crd-validation.yaml +``` + +## Subresources + +Custom Resources support `/status` and `/scale` subresources as an +[alpha feature](https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/#subresources) in v1.10. +Enable this feature using the `CustomResourceSubresources` feature gate on the [kube-apiserver](https://kubernetes.io/docs/admin/kube-apiserver): + +```sh +--feature-gates=CustomResourceSubresources=true ``` ### Example -The schema in the [example CRD](./artifacts/examples/crd.yaml) applies the following validation on the custom resource: -`spec.replicas` must be an integer and must have a minimum value of 1 and a maximum value of 10. +The CRD in [`crd-status-subresource.yaml`](./artifacts/examples/crd-status-subresource.yaml) enables the `/status` subresource +for custom resources. +This means that [`UpdateStatus`](./controller.go#L330) can be used by the controller to update only the status part of the custom resource. + +To understand why only the status part of the custom resource should be updated, please refer to the [Kubernetes API conventions](https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status). + +In the above steps, use `crd-status-subresource.yaml` to create the CRD: + +```sh +# create a CustomResourceDefinition supporting the status subresource +$ kubectl create -f artifacts/examples/crd-status-subresource.yaml +``` ## Cleanup diff --git a/artifacts/examples/crd-status-subresource.yaml b/artifacts/examples/crd-status-subresource.yaml new file mode 100644 index 00000000..af74dbc5 --- /dev/null +++ b/artifacts/examples/crd-status-subresource.yaml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: foos.samplecontroller.k8s.io +spec: + group: samplecontroller.k8s.io + version: v1alpha1 + names: + kind: Foo + plural: foos + scope: Namespaced + subresources: + status: {} diff --git a/artifacts/examples/crd-validation.yaml b/artifacts/examples/crd-validation.yaml new file mode 100644 index 00000000..36469161 --- /dev/null +++ b/artifacts/examples/crd-validation.yaml @@ -0,0 +1,20 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: foos.samplecontroller.k8s.io +spec: + group: samplecontroller.k8s.io + version: v1alpha1 + names: + kind: Foo + plural: foos + scope: Namespaced + validation: + openAPIV3Schema: + properties: + spec: + properties: + replicas: + type: integer + minimum: 1 + maximum: 10 diff --git a/artifacts/examples/crd.yaml b/artifacts/examples/crd.yaml index 36469161..4a457068 100644 --- a/artifacts/examples/crd.yaml +++ b/artifacts/examples/crd.yaml @@ -9,12 +9,3 @@ spec: kind: Foo plural: foos scope: Namespaced - validation: - openAPIV3Schema: - properties: - spec: - properties: - replicas: - type: integer - minimum: 1 - maximum: 10 diff --git a/controller.go b/controller.go index a98ff1f9..c3fa44bc 100644 --- a/controller.go +++ b/controller.go @@ -327,10 +327,10 @@ func (c *Controller) updateFooStatus(foo *samplev1alpha1.Foo, deployment *appsv1 // Or create a copy manually for better performance fooCopy := foo.DeepCopy() fooCopy.Status.AvailableReplicas = deployment.Status.AvailableReplicas - // Until #38113 is merged, we must use Update instead of UpdateStatus to - // update the Status block of the Foo resource. UpdateStatus will not - // allow changes to the Spec of the resource, which is ideal for ensuring - // nothing other than resource status has been updated. + // If the CustomResourceSubresources feature gate is not enabled, + // we must use Update instead of UpdateStatus to update the Status block of the Foo resource. + // UpdateStatus will not allow changes to the Spec of the resource, + // which is ideal for ensuring nothing other than resource status has been updated. _, err := c.sampleclientset.SamplecontrollerV1alpha1().Foos(foo.Namespace).Update(fooCopy) return err } diff --git a/pkg/apis/samplecontroller/v1alpha1/types.go b/pkg/apis/samplecontroller/v1alpha1/types.go index 1f6eb1f9..74ffc672 100644 --- a/pkg/apis/samplecontroller/v1alpha1/types.go +++ b/pkg/apis/samplecontroller/v1alpha1/types.go @@ -21,7 +21,6 @@ import ( ) // +genclient -// +genclient:noStatus // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Foo is a specification for a Foo resource From 5bc8a4494b2e592d75b8e67c3302c753012a78ad Mon Sep 17 00:00:00 2001 From: Nikhita Raghunath Date: Sun, 18 Feb 2018 18:48:53 +0530 Subject: [PATCH 2/2] sample-controller: generate UpdateStatus for Foo resource Kubernetes-commit: 68db7fe8b040d43fc2b888f1331b2845c31a201b --- .../samplecontroller/v1alpha1/fake/fake_foo.go | 12 ++++++++++++ .../typed/samplecontroller/v1alpha1/foo.go | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/fake/fake_foo.go b/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/fake/fake_foo.go index 23adabb9..b8ae1df3 100644 --- a/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/fake/fake_foo.go +++ b/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/fake/fake_foo.go @@ -100,6 +100,18 @@ func (c *FakeFoos) Update(foo *v1alpha1.Foo) (result *v1alpha1.Foo, err error) { return obj.(*v1alpha1.Foo), err } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeFoos) UpdateStatus(foo *v1alpha1.Foo) (*v1alpha1.Foo, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(foosResource, "status", c.ns, foo), &v1alpha1.Foo{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Foo), err +} + // Delete takes name of the foo and deletes it. Returns an error if one occurs. func (c *FakeFoos) Delete(name string, options *v1.DeleteOptions) error { _, err := c.Fake. diff --git a/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/foo.go b/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/foo.go index 6867c7e0..667edf52 100644 --- a/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/foo.go +++ b/pkg/client/clientset/versioned/typed/samplecontroller/v1alpha1/foo.go @@ -37,6 +37,7 @@ type FoosGetter interface { type FooInterface interface { Create(*v1alpha1.Foo) (*v1alpha1.Foo, error) Update(*v1alpha1.Foo) (*v1alpha1.Foo, error) + UpdateStatus(*v1alpha1.Foo) (*v1alpha1.Foo, error) Delete(name string, options *v1.DeleteOptions) error DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error Get(name string, options v1.GetOptions) (*v1alpha1.Foo, error) @@ -120,6 +121,22 @@ func (c *foos) Update(foo *v1alpha1.Foo) (result *v1alpha1.Foo, err error) { return } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *foos) UpdateStatus(foo *v1alpha1.Foo) (result *v1alpha1.Foo, err error) { + result = &v1alpha1.Foo{} + err = c.client.Put(). + Namespace(c.ns). + Resource("foos"). + Name(foo.Name). + SubResource("status"). + Body(foo). + Do(). + Into(result) + return +} + // Delete takes name of the foo and deletes it. Returns an error if one occurs. func (c *foos) Delete(name string, options *v1.DeleteOptions) error { return c.client.Delete().