Skip to content

Commit 55bafc8

Browse files
authored
Merge pull request #2831 from camilamacedo86/logs
✨ (deployimage/v1-alpha): small improvements on the controller scaffold
2 parents a06d98c + 4a7bc67 commit 55bafc8

File tree

5 files changed

+371
-320
lines changed

5 files changed

+371
-320
lines changed

pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go

Lines changed: 75 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,17 @@ const controllerTemplate = `{{ .Boilerplate }}
6363
package {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}controllers{{ end }}
6464
6565
import (
66-
appsv1 "k8s.io/api/apps/v1"
67-
corev1 "k8s.io/api/core/v1"
68-
apierrors "k8s.io/apimachinery/pkg/api/errors"
69-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
70-
"k8s.io/apimachinery/pkg/types"
71-
7266
"context"
7367
"strings"
7468
"time"
7569
"fmt"
7670
"os"
7771
72+
appsv1 "k8s.io/api/apps/v1"
73+
corev1 "k8s.io/api/core/v1"
74+
apierrors "k8s.io/apimachinery/pkg/api/errors"
75+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
76+
"k8s.io/apimachinery/pkg/types"
7877
"k8s.io/apimachinery/pkg/runtime"
7978
"k8s.io/client-go/tools/record"
8079
ctrl "sigs.k8s.io/controller-runtime"
@@ -95,7 +94,8 @@ type {{ .Resource.Kind }}Reconciler struct {
9594
Scheme *runtime.Scheme
9695
Recorder record.EventRecorder
9796
}
98-
// The following markers are used to generate the rules permissions on config/rbac using controller-gen
97+
98+
// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen
9999
// when the command <make manifests> is executed.
100100
// To know more about markers see: https://book.kubebuilder.io/reference/markers.html
101101
@@ -109,28 +109,27 @@ type {{ .Resource.Kind }}Reconciler struct {
109109
// Reconcile is part of the main kubernetes reconciliation loop which aims to
110110
// move the current state of the cluster closer to the desired state.
111111
112-
// Note: It is essential for the controller's reconciliation loop to be idempotent. By following the Operator
113-
// pattern(https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) you will create
114-
// Controllers(https://kubernetes.io/docs/concepts/architecture/controller/) which provide a reconcile function
115-
// responsible for synchronizing resources until the desired state is reached on the cluster. Breaking this
116-
// recommendation goes against the design principles of Controller-runtime(https://github.com/kubernetes-sigs/controller-runtime)
112+
// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator
113+
// pattern you will create Controllers which provide a reconcile function
114+
// responsible for synchronizing resources until the desired state is reached on the cluster.
115+
// Breaking this recommendation goes against the design principles of controller-runtime.
117116
// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention.
118-
//
119-
// For more details, check Reconcile and its Result here:
117+
// For further info:
118+
// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/
119+
// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/
120120
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/reconcile
121121
func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
122122
log := log.FromContext(ctx)
123123
124124
// Fetch the {{ .Resource.Kind }} instance
125125
// The purpose is check if the Custom Resource for the Kind {{ .Resource.Kind }}
126-
// is applied on the cluster if not we return nill to stop the reconciliation
126+
// is applied on the cluster if not we return nil to stop the reconciliation
127127
{{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
128128
err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }})
129129
if err != nil {
130130
if apierrors.IsNotFound(err) {
131-
// Request object not found, could have been deleted after reconcile request.
132-
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
133-
// Return and don't requeue
131+
// If the custom resource is not found then, it usually means that it was deleted or not created
132+
// In this way, we will stop the reconciliation
134133
log.Info("{{ lower .Resource.Kind }} resource not found. Ignoring since object must be deleted")
135134
return ctrl.Result{}, nil
136135
}
@@ -141,15 +140,16 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
141140
142141
// Let's add a finalizer. Then, we can define some operations which should
143142
// occurs before the custom resource to be deleted.
144-
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/
145-
// NOTE: You should not use finalizer to delete the resources that are
146-
// created in this reconciliation and have the ownerRef set by ctrl.SetControllerReference
147-
// because these will get deleted via k8s api
143+
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers
148144
if !controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) {
149145
log.Info("Adding Finalizer for {{ .Resource.Kind }}")
150-
controllerutil.AddFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer)
151-
err = r.Update(ctx, {{ lower .Resource.Kind }})
152-
if err != nil {
146+
if ok := controllerutil.AddFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer); !ok {
147+
log.Error(err, "Failed to add finalizer into the custom resource")
148+
return ctrl.Result{Requeue: true}, nil
149+
}
150+
151+
if err = r.Update(ctx, {{ lower .Resource.Kind }}); err != nil {
152+
log.Error(err, "Failed to update custom resource to add finalizer")
153153
return ctrl.Result{}, err
154154
}
155155
}
@@ -159,23 +159,18 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
159159
is{{ .Resource.Kind }}MarkedToBeDeleted := {{ lower .Resource.Kind }}.GetDeletionTimestamp() != nil
160160
if is{{ .Resource.Kind }}MarkedToBeDeleted {
161161
if controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) {
162-
// Run finalization logic for memcachedFinalizer. If the
163-
// finalization logic fails, don't remove the finalizer so
164-
// that we can retry during the next reconciliation.
165162
log.Info("Performing Finalizer Operations for {{ .Resource.Kind }} before delete CR")
166163
r.doFinalizerOperationsFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})
167164
168-
// Remove memcachedFinalizer. Once all finalizers have been
169-
// removed, the object will be deleted.
165+
log.Info("Removing Finalizer for {{ .Resource.Kind }} after successfully perform the operations")
170166
if ok:= controllerutil.RemoveFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer); !ok{
171-
if err != nil {
172-
log.Error(err, "Failed to remove finalizer for {{ .Resource.Kind }}")
173-
return ctrl.Result{}, err
174-
}
167+
log.Error(err, "Failed to remove finalizer for {{ .Resource.Kind }}")
168+
return ctrl.Result{Requeue: true}, nil
175169
}
176-
err := r.Update(ctx, {{ lower .Resource.Kind }})
177-
if err != nil {
170+
171+
if err := r.Update(ctx, {{ lower .Resource.Kind }}); err != nil {
178172
log.Error(err, "Failed to remove finalizer for {{ .Resource.Kind }}")
173+
return ctrl.Result{}, err
179174
}
180175
}
181176
return ctrl.Result{}, nil
@@ -186,13 +181,20 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
186181
err = r.Get(ctx, types.NamespacedName{Name: {{ lower .Resource.Kind }}.Name, Namespace: {{ lower .Resource.Kind }}.Namespace}, found)
187182
if err != nil && apierrors.IsNotFound(err) {
188183
// Define a new deployment
189-
dep := r.deploymentFor{{ .Resource.Kind }}(ctx, {{ lower .Resource.Kind }})
190-
log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
191-
err = r.Create(ctx, dep)
184+
dep, err := r.deploymentFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})
192185
if err != nil {
193-
log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
186+
log.Error(err, "Failed to define new Deployment resource for {{ .Resource.Kind }}")
194187
return ctrl.Result{}, err
195188
}
189+
190+
log.Info("Creating a new Deployment",
191+
"Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
192+
if err = r.Create(ctx, dep); err != nil {
193+
log.Error(err, "Failed to create new Deployment",
194+
"Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
195+
return ctrl.Result{}, err
196+
}
197+
196198
// Deployment created successfully
197199
// We will requeue the reconciliation so that we can ensure the state
198200
// and move forward for the next operations
@@ -203,16 +205,19 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
203205
return ctrl.Result{}, err
204206
}
205207
206-
// The API is defining that the {{ .Resource.Kind }} type, have a {{ .Resource.Kind }}Spec.Size field to set the quantity of {{ .Resource.Kind }} instances (CRs) to be deployed.
207-
// The following code ensure the deployment size is the same as the spec
208+
// The CRD API is defining that the {{ .Resource.Kind }} type, have a {{ .Resource.Kind }}Spec.Size field
209+
// to set the quantity of Deployment instances is the desired state on the cluster.
210+
// Therefore, the following code will ensure the Deployment size is the same as defined
211+
// via the Size spec of the Custom Resource which we are reconciling.
208212
size := {{ lower .Resource.Kind }}.Spec.Size
209213
if *found.Spec.Replicas != size {
210214
found.Spec.Replicas = &size
211-
err = r.Update(ctx, found)
212-
if err != nil {
213-
log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
215+
if err = r.Update(ctx, found); err != nil {
216+
log.Error(err, "Failed to update Deployment",
217+
"Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
214218
return ctrl.Result{}, err
215219
}
220+
216221
// Since it fails we want to re-queue the reconciliation
217222
// The reconciliation will only stop when we be able to ensure
218223
// the desired state on the cluster
@@ -228,6 +233,13 @@ func (r *{{ .Resource.Kind }}Reconciler) doFinalizerOperationsFor{{ .Resource.Ki
228233
// needs to do before the CR can be deleted. Examples
229234
// of finalizers include performing backups and deleting
230235
// resources that are not owned by this CR, like a PVC.
236+
237+
// Note: It is not recommended to use finalizers with the purpose of delete resources which are
238+
// created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile,
239+
// are defined as depended of the custom resource. See that we use the method ctrl.SetControllerReference.
240+
// to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API.
241+
// More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/
242+
231243
// The following implementation will raise an event
232244
r.Recorder.Event(cr, "Warning", "Deleting",
233245
fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s",
@@ -236,13 +248,15 @@ func (r *{{ .Resource.Kind }}Reconciler) doFinalizerOperationsFor{{ .Resource.Ki
236248
}
237249
238250
// deploymentFor{{ .Resource.Kind }} returns a {{ .Resource.Kind }} Deployment object
239-
func (r *{{ .Resource.Kind }}Reconciler) deploymentFor{{ .Resource.Kind }}(ctx context.Context, {{ lower .Resource.Kind }} *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) *appsv1.Deployment {
251+
func (r *{{ .Resource.Kind }}Reconciler) deploymentFor{{ .Resource.Kind }}(
252+
{{ lower .Resource.Kind }} *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) (*appsv1.Deployment, error) {
240253
ls := labelsFor{{ .Resource.Kind }}({{ lower .Resource.Kind }}.Name)
241254
replicas := {{ lower .Resource.Kind }}.Spec.Size
242-
log := log.FromContext(ctx)
255+
256+
// Get the Operand image
243257
image, err := imageFor{{ .Resource.Kind }}()
244258
if err != nil {
245-
log.Error(err, "unable to get image for {{ .Resource.Kind }}")
259+
return nil, err
246260
}
247261
248262
dep := &appsv1.Deployment{
@@ -274,18 +288,17 @@ func (r *{{ .Resource.Kind }}Reconciler) deploymentFor{{ .Resource.Kind }}(ctx c
274288
},
275289
},
276290
}
277-
// Set {{ .Resource.Kind }} instance as the owner and controller
278-
// You should use the method ctrl.SetControllerReference for all resources
279-
// which are created by your controller so that when the Custom Resource be deleted
280-
// all resources owned by it (child) will also be deleted.
281-
// To know more about it see: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/
282-
ctrl.SetControllerReference({{ lower .Resource.Kind }}, dep, r.Scheme)
283-
return dep
291+
292+
// Set the ownerRef for the Deployment
293+
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/
294+
if err := ctrl.SetControllerReference({{ lower .Resource.Kind }}, dep, r.Scheme); err != nil {
295+
return nil, err
296+
}
297+
return dep, nil
284298
}
285299
286300
// labelsFor{{ .Resource.Kind }} returns the labels for selecting the resources
287-
// belonging to the given {{ .Resource.Kind }} CR name.
288-
// Note that the labels follows the standards defined in: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
301+
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/
289302
func labelsFor{{ .Resource.Kind }}(name string) map[string]string {
290303
var imageTag string
291304
image, err := imageFor{{ .Resource.Kind }}()
@@ -300,22 +313,20 @@ func labelsFor{{ .Resource.Kind }}(name string) map[string]string {
300313
}
301314
}
302315
303-
// imageFor{{ .Resource.Kind }} gets the image for the resources belonging to the given {{ .Resource.Kind }} CR,
304-
// from the {{ upper .Resource.Kind }}_IMAGE ENV VAR defined in the config/manager/manager.yaml
316+
// imageFor{{ .Resource.Kind }} gets the Operand image which is managed by this controller
317+
// from the {{ upper .Resource.Kind }}_IMAGE environment variable defined in the config/manager/manager.yaml
305318
func imageFor{{ .Resource.Kind }}() (string, error) {
306319
var imageEnvVar = "{{ upper .Resource.Kind }}_IMAGE"
307320
image, found := os.LookupEnv(imageEnvVar)
308321
if !found {
309-
return "", fmt.Errorf("%s must be set", imageEnvVar)
322+
return "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar)
310323
}
311324
return image, nil
312325
}
313326
314327
// SetupWithManager sets up the controller with the Manager.
315-
// The following code specifies how the controller is built to watch a CR
316-
// and other resources that are owned and managed by that controller.
317-
// In this way, the reconciliation can be re-trigged when the CR and/or the Deployment
318-
// be created/edit/delete.
328+
// Note that the Deployment will be also watched in order to ensure its
329+
// desirable state on the cluster
319330
func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {
320331
return ctrl.NewControllerManagedBy(mgr).
321332
{{ if not (isEmptyStr .Resource.Path) -}}

0 commit comments

Comments
 (0)