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