@@ -66,27 +66,33 @@ package {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ el
66
66
import (
67
67
appsv1 "k8s.io/api/apps/v1"
68
68
corev1 "k8s.io/api/core/v1"
69
- "k8s.io/apimachinery/pkg/api/errors"
69
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
70
70
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
71
71
"k8s.io/apimachinery/pkg/types"
72
72
73
73
"context"
74
74
"time"
75
+ "fmt"
75
76
76
77
"k8s.io/apimachinery/pkg/runtime"
78
+ "k8s.io/client-go/tools/record"
77
79
ctrl "sigs.k8s.io/controller-runtime"
78
80
"sigs.k8s.io/controller-runtime/pkg/client"
79
81
"sigs.k8s.io/controller-runtime/pkg/log"
82
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
80
83
81
84
{{ if not (isEmptyStr .Resource.Path) -}}
82
85
{{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
83
86
{{- end }}
84
87
)
85
88
89
+ const {{ lower .Resource.Kind }}Finalizer = "{{ .Resource.Group }}.{{ .Resource.Domain }}/finalizer"
90
+
86
91
// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object
87
92
type {{ .Resource.Kind }}Reconciler struct {
88
93
client.Client
89
94
Scheme *runtime.Scheme
95
+ Recorder record.EventRecorder
90
96
}
91
97
// The following markers are used to generate the rules permissions on config/rbac using controller-gen
92
98
// when the command <make manifests> is executed.
@@ -95,6 +101,7 @@ type {{ .Resource.Kind }}Reconciler struct {
95
101
//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete
96
102
//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch
97
103
//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/finalizers,verbs=update
104
+ //+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
98
105
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
99
106
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
100
107
@@ -119,7 +126,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
119
126
{{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
120
127
err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }})
121
128
if err != nil {
122
- if errors .IsNotFound(err) {
129
+ if apierrors .IsNotFound(err) {
123
130
// Request object not found, could have been deleted after reconcile request.
124
131
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
125
132
// Return and don't requeue
@@ -131,10 +138,52 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
131
138
return ctrl.Result{}, err
132
139
}
133
140
141
+ // Let's add a finalizer. Then, we can define some operations which should
142
+ // occurs before the custom resource to be deleted.
143
+ // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/
144
+ // NOTE: You should not use finalizer to delete the resources that are
145
+ // created in this reconciliation and have the ownerRef set by ctrl.SetControllerReference
146
+ // because these will get deleted via k8s api
147
+ if !controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) {
148
+ log.Info("Adding Finalizer for {{ .Resource.Kind }}")
149
+ controllerutil.AddFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer)
150
+ err = r.Update(ctx, {{ lower .Resource.Kind }})
151
+ if err != nil {
152
+ return ctrl.Result{}, err
153
+ }
154
+ }
155
+
156
+ // Check if the {{ .Resource.Kind }} instance is marked to be deleted, which is
157
+ // indicated by the deletion timestamp being set.
158
+ is{{ .Resource.Kind }}MarkedToBeDeleted := {{ lower .Resource.Kind }}.GetDeletionTimestamp() != nil
159
+ if is{{ .Resource.Kind }}MarkedToBeDeleted {
160
+ if controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) {
161
+ // Run finalization logic for memcachedFinalizer. If the
162
+ // finalization logic fails, don't remove the finalizer so
163
+ // that we can retry during the next reconciliation.
164
+ log.Info("Performing Finalizer Operations for {{ .Resource.Kind }} before delete CR")
165
+ r.doFinalizerOperationsFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})
166
+
167
+ // Remove memcachedFinalizer. Once all finalizers have been
168
+ // removed, the object will be deleted.
169
+ if ok:= controllerutil.RemoveFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer); !ok{
170
+ if err != nil {
171
+ log.Error(err, "Failed to remove finalizer for {{ .Resource.Kind }}")
172
+ return ctrl.Result{}, err
173
+ }
174
+ }
175
+ err := r.Update(ctx, {{ lower .Resource.Kind }})
176
+ if err != nil {
177
+ log.Error(err, "Failed to remove finalizer for {{ .Resource.Kind }}")
178
+ }
179
+ }
180
+ return ctrl.Result{}, nil
181
+ }
182
+
134
183
// Check if the deployment already exists, if not create a new one
135
184
found := &appsv1.Deployment{}
136
185
err = r.Get(ctx, types.NamespacedName{Name: {{ lower .Resource.Kind }}.Name, Namespace: {{ lower .Resource.Kind }}.Namespace}, found)
137
- if err != nil && errors .IsNotFound(err) {
186
+ if err != nil && apierrors .IsNotFound(err) {
138
187
// Define a new deployment
139
188
dep := r.deploymentFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})
140
189
log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
@@ -168,9 +217,23 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
168
217
// the desired state on the cluster
169
218
return ctrl.Result{Requeue: true}, nil
170
219
}
220
+
171
221
return ctrl.Result{}, nil
172
222
}
173
223
224
+ // finalize{{ .Resource.Kind }} will perform the required operations before delete the CR.
225
+ func (r *{{ .Resource.Kind }}Reconciler) doFinalizerOperationsFor{{ .Resource.Kind }}(cr *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) {
226
+ // TODO(user): Add the cleanup steps that the operator
227
+ // needs to do before the CR can be deleted. Examples
228
+ // of finalizers include performing backups and deleting
229
+ // resources that are not owned by this CR, like a PVC.
230
+ // The following implementation will raise an event
231
+ r.Recorder.Event(cr, "Warning", "Deleting",
232
+ fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s",
233
+ cr.Name,
234
+ cr.Namespace))
235
+ }
236
+
174
237
// deploymentFor{{ .Resource.Kind }} returns a {{ .Resource.Kind }} Deployment object
175
238
func (r *{{ .Resource.Kind }}Reconciler) deploymentFor{{ .Resource.Kind }}({{ lower .Resource.Kind }} *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) *appsv1.Deployment {
176
239
ls := labelsFor{{ .Resource.Kind }}({{ lower .Resource.Kind }}.Name)
0 commit comments