sigs.k8s.io/kubebuilder/v3@v3.14.0/designs/code-generate-image-plugin.md (about) 1 | Authors | Creation Date | Status | Extra | 2 |---------------|---------------|-------------|---| 3 | @camilamacedo86 | 2021-02-14 | Implemented | [deploy-image-plugin-v1-alpha](https://book.kubebuilder.io/plugins/deploy-image-plugin-v1-alpha.html) | 4 5 # New Plugin (`deploy-image.go.kubebuilder.io/v1beta1`) to generate code 6 7 ## Summary 8 9 This proposal defines a new plugin which allow users get the scaffold with the 10 required code to have a project that will deploy and manage an image on the cluster following the guidelines and what have been considered as good practices. 11 12 ## Motivation 13 14 The biggest part of the Kubebuilder users looking for to create a project that will at the end only deploy an image. In this way, one of the mainly motivations of this proposal is to abstract the complexities to achieve this goal and still giving the possibility of users improve and customize their projects according to their requirements. 15 16 **Note:** This plugin will address requests that has been raised for a while and for many users in the community. Check [here](https://github.com/operator-framework/operator-sdk/pull/2158), for example, a request done in the past for the SDK project which is integrated with Kubebuidler to address the same need. 17 18 ### Goals 19 20 - Add a new plugin to generate the code required to deploy and manage an image on the cluster 21 - Promote the best practices as give example of common implementations 22 - Make the process to develop operators projects easier and more agil. 23 - Give flexibility to the users and allow them to change the code according to their needs 24 - Provide examples of code implementations and of the most common features usage and reduce the learning curve 25 26 ### Non-Goals 27 28 The idea of this proposal is provide a facility for the users. This plugin can be improved 29 in the future, however, this proposal just covers the basic requirements. In this way, is a non-goal 30 allow extra configurations such as; scaffold the project using webhooks and the controller covered by tests. 31 32 ## Proposal 33 34 Add the new plugin code generate which will scaffold code implementation to deploy the image informed which would like such as; `kubebuilder create api --group=crew --version=v1 --image=myexample:0.0.1 --kind=App --plugins=deploy-image.go.kubebuilder.io/v1beta1` which will: 35 36 - Add a code implementation which will do the Custom Resource reconciliation and create a Deployment resource for the `--image`; 37 38 - Add an EnvVar on the manager manifest (`config/manager/manager.yaml`) which will store the image informed and shows its possibility to users: 39 40 ```yaml 41 .. 42 spec: 43 containers: 44 - name: manager 45 env: 46 - name: {{ resource}}-IMAGE 47 value: {{image:tag}} 48 image: controller:latest 49 ... 50 ``` 51 52 - Add a check into reconcile to ensure that the replicas of the deployment on cluster are equals the size defined in the CR: 53 54 ```go 55 // Ensure the deployment size is the same as the spec 56 size := {{ resource }}.Spec.Size 57 if *found.Spec.Replicas != size { 58 found.Spec.Replicas = &size 59 err = r.Update(ctx, found) 60 if err != nil { 61 log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) 62 return ctrl.Result{}, err 63 } 64 // Spec updated - return and requeue 65 return ctrl.Result{Requeue: true}, nil 66 } 67 ``` 68 69 - Add the watch feature for the Deployment managed by the controller: 70 71 ```go 72 func (r *{{ resource }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { 73 return ctrl.NewControllerManagedBy(mgr). 74 For(&cachev1alpha1.{{ resource }}{}). 75 Owns(&appsv1.Deployment{}). 76 Complete(r) 77 } 78 ``` 79 80 - Add the RBAC permissions required for the scenario such as: 81 82 ```go 83 // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete 84 ``` 85 86 - A status [conditions][conditions] to allow users check that if the deployment occurred successfully or its errors 87 88 - Add a [marker][markers] in the spec definition to demonstrate how to use OpenAPI schemas validation such as `+kubebuilder:validation:Minimum=1` 89 90 - Add the specs on the `_types.go` to generate the CRD/CR sample with default values for `ImagePullPolicy` (`Always`), `ContainerPort` (`80`) and the `Replicas Size` (`3`) 91 92 - Add a finalizer implementation with TODO for the CR managed by the controller such as described in the SDK doc [Handle Cleanup on Deletion](https://sdk.operatorframework.io/docs/building-operators/golang/advanced-topics/#handle-cleanup-on-deletion) 93 94 ### User Stories 95 96 - I am as user, would like to use a command to scaffold my common need which is deploy an image of my application, so that I do not need to know exactly how to implement it 97 98 - I am as user, would like to have a good example code base which uses the common features, so that I can easily learn its concepts and have a good start point to address my needs. 99 100 - I am as maintainer, would like to have a good example to address the common questions, so that I can easily describe how to implement the projects and/or use the common features. 101 102 ### Implementation Details/Notes/Constraints 103 104 **Example of the controller template** 105 106 ```go 107 // +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }},verbs=get;list;watch;create;update;patch;delete 108 // +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }}/status,verbs=get;update;patch 109 // +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }}/finalizers,verbs=update 110 // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete 111 112 func (r *{{ resource }}.Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 113 ctx := context.Background() 114 log := r.Log.WithValues("{{ resource }}", req.NamespacedName) 115 116 // Fetch the {{ resource }} instance 117 {{ resource }} := &{{ apiimportalias }}.{{ resource }}{} 118 err := r.Get(ctx, req.NamespacedName, {{ resource }}) 119 if err != nil { 120 if errors.IsNotFound(err) { 121 // Request object not found, could have been deleted after reconcile request. 122 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 123 // Return and don't requeue 124 log.Info("{{ resource }} resource not found. Ignoring since object must be deleted") 125 return ctrl.Result{}, nil 126 } 127 // Error reading the object - requeue the request. 128 log.Error(err, "Failed to get {{ resource }}") 129 return ctrl.Result{}, err 130 } 131 132 // Check if the deployment already exists, if not create a new one 133 found := &appsv1.Deployment{} 134 err = r.Get(ctx, types.NamespacedName{Name: {{ resource }}.Name, Namespace: {{ resource }}.Namespace}, found) 135 if err != nil && errors.IsNotFound(err) { 136 // Define a new deployment 137 dep := r.deploymentFor{{ resource }}({{ resource }}) 138 log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) 139 err = r.Create(ctx, dep) 140 if err != nil { 141 log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) 142 return ctrl.Result{}, err 143 } 144 // Deployment created successfully - return and requeue 145 return ctrl.Result{Requeue: true}, nil 146 } else if err != nil { 147 log.Error(err, "Failed to get Deployment") 148 return ctrl.Result{}, err 149 } 150 151 // Ensure the deployment size is the same as the spec 152 size := {{ resource }}.Spec.Size 153 if *found.Spec.Replicas != size { 154 found.Spec.Replicas = &size 155 err = r.Update(ctx, found) 156 if err != nil { 157 log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) 158 return ctrl.Result{}, err 159 } 160 // Spec updated - return and requeue 161 return ctrl.Result{Requeue: true}, nil 162 } 163 164 // TODO: add here code implementation to update/manage the status 165 166 return ctrl.Result{}, nil 167 } 168 169 // deploymentFor{{ resource }} returns a {{ resource }} Deployment object 170 func (r *{{ resource }}Reconciler) deploymentFor{{ resource }}(m *{{ apiimportalias }}.{{ resource }}) *appsv1.Deployment { 171 ls := labelsFor{{ resource }}(m.Name) 172 replicas := m.Spec.Size 173 174 dep := &appsv1.Deployment{ 175 ObjectMeta: metav1.ObjectMeta{ 176 Name: m.Name, 177 Namespace: m.Namespace, 178 }, 179 Spec: appsv1.DeploymentSpec{ 180 Replicas: &replicas, 181 Selector: &metav1.LabelSelector{ 182 MatchLabels: ls, 183 }, 184 Template: corev1.PodTemplateSpec{ 185 ObjectMeta: metav1.ObjectMeta{ 186 Labels: ls, 187 }, 188 Spec: corev1.PodSpec{ 189 Containers: []corev1.Container{{ 190 Image: imageFor{{ resource }}(m.Name), 191 Name: {{ resource }}, 192 ImagePullPolicy: {{ resource }}.Spec.ContainerImagePullPolicy, 193 Command: []string{"{{ resource }}"}, 194 Ports: []corev1.ContainerPort{{ 195 ContainerPort: {{ resource }}.Spec.ContainerPort, 196 Name: "{{ resource }}", 197 }}, 198 }}, 199 }, 200 }, 201 }, 202 } 203 // Set {{ resource }} instance as the owner and controller 204 ctrl.SetControllerReference(m, dep, r.Scheme) 205 return dep 206 } 207 208 // labelsFor{{ resource }} returns the labels for selecting the resources 209 // belonging to the given {{ resource }} CR name. 210 func labelsFor{{ resource }}(name string) map[string]string { 211 return map[string]string{"type": "{{ resource }}", "{{ resource }}_cr": name} 212 } 213 214 // imageFor{{ resource }} returns the image for the resources 215 // belonging to the given {{ resource }} CR name. 216 func imageFor{{ resource }}(name string) string { 217 // TODO: this method will return the value of the envvar create to store the image:tag informed 218 } 219 220 func (r *{{ resource }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { 221 return ctrl.NewControllerManagedBy(mgr). 222 For(&cachev1alpha1.{{ resource }}{}). 223 Owns(&appsv1.Deployment{}). 224 Complete(r) 225 } 226 227 ``` 228 229 **Example of the spec for the <kind>_types.go template** 230 231 ```go 232 // {{ resource }}Spec defines the desired state of {{ resource }} 233 type {{ resource }}Spec struct { 234 // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 235 // Important: Run "make" to regenerate code after modifying this file 236 237 // +kubebuilder:validation:Minimum=1 238 // Size defines the number of {{ resource }} instances 239 Size int32 `json:"size,omitempty"` 240 241 // ImagePullPolicy defines the policy to pull the container images 242 ImagePullPolicy string `json:"image-pull-policy,omitempty"` 243 244 // ContainerPort specifies the port which will be used by the image container 245 ContainerPort int `json:"container-port,omitempty"` 246 247 } 248 ``` 249 250 ## Design Details 251 252 ### Test Plan 253 254 To ensure this implementation a new project example should be generated in the [testdata](../testdata/) directory of the project. See the [test/testadata/generate.sh](../test/testadata/generate.sh). Also, we should use this scaffold in the [integration tests](../test/e2e/) to ensure that the data scaffolded with works on the cluster as expected. 255 256 ### Graduation Criteria 257 258 - The new plugin will only be support `project-version=3` 259 - The attribute image with the value informed should be added to the resources model in the PROJECT file to let the tool know that the Resource get done with the common basic code implementation: 260 261 ```yaml 262 plugins: 263 deploy-image.go.kubebuilder.io/v1beta1: 264 resources: 265 - domain: example.io 266 group: crew 267 kind: Captain 268 version: v1 269 image: "<some-registry>/<project-name>:<tag> 270 ``` 271 272 For further information check the definition agreement register in the comment https://github.com/kubernetes-sigs/kubebuilder/issues/1941#issuecomment-778649947. 273 274 ## Open Questions 275 276 1. Should we allow to scaffold the code for an API that is already created for the project? 277 No, at least in the first moment to keep the simplicity. 278 279 2. Should we support StatefulSet and Deployments? 280 The idea is we start it by using a Deployment. However, we can improve the feature in follow-ups to support more default types of scaffolds which could be like `kubebuilder create api --group=crew --version=v1 --image=myexample:0.0.1 --kind=App --plugins=deploy-image.go.kubebuilder.io/v1beta1 --type=[deployment|statefulset|webhook]` 281 282 3. Could this feature be useful to other languages or is it just valid to Go based operators? 283 284 This plugin would is reponsable to scaffold content and files for Go-based operators. In a future, if other language-based operators starts to be supported (either officially or by the community) this plugin could be used as reference to create an equivalent one for their languages. Therefore, it should probably not to be a `subdomain` of `go.kubebuilder.io.` 285 286 For its integration with SDK, it might be valid for the Ansible-based operators where a new `playbook/role` could be generated as well. However, for example, for the Helm plugin it might to be useless. E.g `deploy-image.ansible.sdk.operatorframework.io/v1beta1` 287 288 4. Should we consider create a separate repo for plugins? 289 290 In the long term yes. However, see that currently, Kubebuilder has not too many plugins yet. And then, and the preliminary support for plugins did not indeed release. For more info see the [Extensible CLI and Scaffolding Plugins][plugins-phase1-design-doc]. 291 292 In this way, at this moment, it shows to be a little Premature Optimization. Note that the issue [#2016](https://github.com/kubernetes-sigs/kubebuilder/issues/1378) will check the possibility of the plugins be as separate binaries that can be discovered by the Kubebuilder CLI binary via user-specified plugin file paths. Then, the discussion over the best approach to dealing with many plugins and if they should or not leave in the Kubebuilder repository would be better addressed after that. 293 294 5. Is Kubebuilder prepared to receive this implementation already? 295 296 The [Extensible CLI and Scaffolding Plugins - Phase 1.5](extensible-cli-and-scaffolding-plugins-phase-1-5.md) and the issue #1941 requires to be implemented before this proposal. Also, to have a better idea over the proposed solutions made so for the Plugin Ecosystem see the meta issue [#2016](https://github.com/kubernetes-sigs/kubebuilder/issues/2016) 297 298 [markers]: ../docs/book/src/reference/markers.md 299 [conditions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties 300 [plugins-phase1-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1.md