Skip to content

Commit 2bc1a66

Browse files
add finalizers for the CR created
1 parent 60d0c6e commit 2bc1a66

File tree

11 files changed

+397
-27
lines changed

11 files changed

+397
-27
lines changed

pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ func (s *apiScaffolder) Scaffold() error {
105105
return fmt.Errorf("error updating controller: %v", err)
106106
}
107107

108+
if err := s.updateMainEventRecorder(); err != nil {
109+
return fmt.Errorf("error updating main.go: %v", err)
110+
}
111+
108112
if err := scaffold.Execute(
109113
&samples.CRDSample{Port: s.port},
110114
); err != nil {
@@ -120,6 +124,18 @@ func (s *apiScaffolder) Scaffold() error {
120124
return nil
121125
}
122126

127+
// TODO: replace this implementation by creating its own MainUpdater
128+
// which will have its own controller template which set the recorder
129+
func (s *apiScaffolder) updateMainEventRecorder() error {
130+
defaultMainPath := "main.go"
131+
err := util.InsertCode(defaultMainPath, `Scheme: mgr.GetScheme(),`,
132+
fmt.Sprintf(recorderTemplate, strings.ToLower(s.resource.Kind)))
133+
if err != nil {
134+
return fmt.Errorf("error scaffolding event recorder while creating the controller in main.go: %v", err)
135+
}
136+
return nil
137+
}
138+
123139
func (s *apiScaffolder) scafffoldControllerWithImage(scaffold *machinery.Scaffold) error {
124140
controller := &controllers.Controller{ControllerRuntimeVersion: golangv3scaffolds.ControllerRuntimeVersion,
125141
Image: s.image,
@@ -245,3 +261,7 @@ const portTemplate = `
245261
ContainerPort: %s.Spec.ContainerPort,
246262
Name: "%s",
247263
}},`
264+
265+
const recorderTemplate = `
266+
Recorder: mgr.GetEventRecorderFor("%s-controller"),
267+
`

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

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,27 +66,33 @@ package {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ el
6666
import (
6767
appsv1 "k8s.io/api/apps/v1"
6868
corev1 "k8s.io/api/core/v1"
69-
"k8s.io/apimachinery/pkg/api/errors"
69+
apierrors "k8s.io/apimachinery/pkg/api/errors"
7070
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7171
"k8s.io/apimachinery/pkg/types"
7272
7373
"context"
7474
"time"
75+
"fmt"
7576
7677
"k8s.io/apimachinery/pkg/runtime"
78+
"k8s.io/client-go/tools/record"
7779
ctrl "sigs.k8s.io/controller-runtime"
7880
"sigs.k8s.io/controller-runtime/pkg/client"
7981
"sigs.k8s.io/controller-runtime/pkg/log"
82+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
8083
8184
{{ if not (isEmptyStr .Resource.Path) -}}
8285
{{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
8386
{{- end }}
8487
)
8588
89+
const {{ lower .Resource.Kind }}Finalizer = "{{ .Resource.Group }}.{{ .Resource.Domain }}/finalizer"
90+
8691
// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object
8792
type {{ .Resource.Kind }}Reconciler struct {
8893
client.Client
8994
Scheme *runtime.Scheme
95+
Recorder record.EventRecorder
9096
}
9197
// The following markers are used to generate the rules permissions on config/rbac using controller-gen
9298
// when the command <make manifests> is executed.
@@ -95,6 +101,7 @@ type {{ .Resource.Kind }}Reconciler struct {
95101
//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete
96102
//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch
97103
//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/finalizers,verbs=update
104+
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
98105
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
99106
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
100107
@@ -119,7 +126,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
119126
{{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
120127
err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }})
121128
if err != nil {
122-
if errors.IsNotFound(err) {
129+
if apierrors.IsNotFound(err) {
123130
// Request object not found, could have been deleted after reconcile request.
124131
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
125132
// Return and don't requeue
@@ -131,10 +138,52 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl
131138
return ctrl.Result{}, err
132139
}
133140
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+
134183
// Check if the deployment already exists, if not create a new one
135184
found := &appsv1.Deployment{}
136185
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) {
138187
// Define a new deployment
139188
dep := r.deploymentFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})
140189
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
168217
// the desired state on the cluster
169218
return ctrl.Result{Requeue: true}, nil
170219
}
220+
171221
return ctrl.Result{}, nil
172222
}
173223
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+
174237
// deploymentFor{{ .Resource.Kind }} returns a {{ .Resource.Kind }} Deployment object
175238
func (r *{{ .Resource.Kind }}Reconciler) deploymentFor{{ .Resource.Kind }}({{ lower .Resource.Kind }} *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) *appsv1.Deployment {
176239
ls := labelsFor{{ .Resource.Kind }}({{ lower .Resource.Kind }}.Name)

test/e2e/deployimage/plugin_cluster_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,23 @@ func Run(kbc *utils.TestContext) {
262262
return nil
263263
}
264264
EventuallyWithOffset(1, getMemcachedPodStatus, time.Minute, time.Second).Should(Succeed())
265+
266+
//Testing the finalizer
267+
EventuallyWithOffset(1, func() error {
268+
_, err = kbc.Kubectl.Delete(true, "-f", sampleFilePath)
269+
return err
270+
}, time.Minute, time.Second).Should(Succeed())
271+
272+
EventuallyWithOffset(1, func() error {
273+
events, err := kbc.Kubectl.Get(true, "events", "--field-selector=type=Warning",
274+
"-o", "jsonpath={.items[*].message}",
275+
)
276+
ExpectWithOffset(2, err).NotTo(HaveOccurred())
277+
if !strings.Contains(events, "is being deleted from the namespace") {
278+
return err
279+
}
280+
return nil
281+
}, time.Minute, time.Second).Should(Succeed())
265282
}
266283

267284
func uncommentPodStandards(kbc *utils.TestContext) {

testdata/project-v3-with-deploy-image/config/rbac/role.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ rules:
1717
- patch
1818
- update
1919
- watch
20+
- apiGroups:
21+
- ""
22+
resources:
23+
- events
24+
verbs:
25+
- create
26+
- patch
2027
- apiGroups:
2128
- ""
2229
resources:

testdata/project-v3-with-deploy-image/controllers/busybox_controller.go

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,31 @@ package controllers
1919
import (
2020
appsv1 "k8s.io/api/apps/v1"
2121
corev1 "k8s.io/api/core/v1"
22-
"k8s.io/apimachinery/pkg/api/errors"
22+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2323
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2424
"k8s.io/apimachinery/pkg/types"
2525

2626
"context"
27+
"fmt"
2728
"time"
2829

2930
"k8s.io/apimachinery/pkg/runtime"
31+
"k8s.io/client-go/tools/record"
3032
ctrl "sigs.k8s.io/controller-runtime"
3133
"sigs.k8s.io/controller-runtime/pkg/client"
34+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3235
"sigs.k8s.io/controller-runtime/pkg/log"
3336

3437
examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v3-with-deploy-image/api/v1alpha1"
3538
)
3639

40+
const busyboxFinalizer = "example.com.testproject.org/finalizer"
41+
3742
// BusyboxReconciler reconciles a Busybox object
3843
type BusyboxReconciler struct {
3944
client.Client
40-
Scheme *runtime.Scheme
45+
Scheme *runtime.Scheme
46+
Recorder record.EventRecorder
4147
}
4248

4349
// The following markers are used to generate the rules permissions on config/rbac using controller-gen
@@ -47,6 +53,7 @@ type BusyboxReconciler struct {
4753
//+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes,verbs=get;list;watch;create;update;patch;delete
4854
//+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes/status,verbs=get;update;patch
4955
//+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes/finalizers,verbs=update
56+
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
5057
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
5158
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
5259

@@ -71,7 +78,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
7178
busybox := &examplecomv1alpha1.Busybox{}
7279
err := r.Get(ctx, req.NamespacedName, busybox)
7380
if err != nil {
74-
if errors.IsNotFound(err) {
81+
if apierrors.IsNotFound(err) {
7582
// Request object not found, could have been deleted after reconcile request.
7683
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
7784
// Return and don't requeue
@@ -83,10 +90,52 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
8390
return ctrl.Result{}, err
8491
}
8592

93+
// Let's add a finalizer. Then, we can define some operations which should
94+
// occurs before the custom resource to be deleted.
95+
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/
96+
// NOTE: You should not use finalizer to delete the resources that are
97+
// created in this reconciliation and have the ownerRef set by ctrl.SetControllerReference
98+
// because these will get deleted via k8s api
99+
if !controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) {
100+
log.Info("Adding Finalizer for Busybox")
101+
controllerutil.AddFinalizer(busybox, busyboxFinalizer)
102+
err = r.Update(ctx, busybox)
103+
if err != nil {
104+
return ctrl.Result{}, err
105+
}
106+
}
107+
108+
// Check if the Busybox instance is marked to be deleted, which is
109+
// indicated by the deletion timestamp being set.
110+
isBusyboxMarkedToBeDeleted := busybox.GetDeletionTimestamp() != nil
111+
if isBusyboxMarkedToBeDeleted {
112+
if controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) {
113+
// Run finalization logic for memcachedFinalizer. If the
114+
// finalization logic fails, don't remove the finalizer so
115+
// that we can retry during the next reconciliation.
116+
log.Info("Performing Finalizer Operations for Busybox before delete CR")
117+
r.doFinalizerOperationsForBusybox(busybox)
118+
119+
// Remove memcachedFinalizer. Once all finalizers have been
120+
// removed, the object will be deleted.
121+
if ok := controllerutil.RemoveFinalizer(busybox, busyboxFinalizer); !ok {
122+
if err != nil {
123+
log.Error(err, "Failed to remove finalizer for Busybox")
124+
return ctrl.Result{}, err
125+
}
126+
}
127+
err := r.Update(ctx, busybox)
128+
if err != nil {
129+
log.Error(err, "Failed to remove finalizer for Busybox")
130+
}
131+
}
132+
return ctrl.Result{}, nil
133+
}
134+
86135
// Check if the deployment already exists, if not create a new one
87136
found := &appsv1.Deployment{}
88137
err = r.Get(ctx, types.NamespacedName{Name: busybox.Name, Namespace: busybox.Namespace}, found)
89-
if err != nil && errors.IsNotFound(err) {
138+
if err != nil && apierrors.IsNotFound(err) {
90139
// Define a new deployment
91140
dep := r.deploymentForBusybox(busybox)
92141
log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
@@ -120,9 +169,23 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
120169
// the desired state on the cluster
121170
return ctrl.Result{Requeue: true}, nil
122171
}
172+
123173
return ctrl.Result{}, nil
124174
}
125175

176+
// finalizeBusybox will perform the required operations before delete the CR.
177+
func (r *BusyboxReconciler) doFinalizerOperationsForBusybox(cr *examplecomv1alpha1.Busybox) {
178+
// TODO(user): Add the cleanup steps that the operator
179+
// needs to do before the CR can be deleted. Examples
180+
// of finalizers include performing backups and deleting
181+
// resources that are not owned by this CR, like a PVC.
182+
// The following implementation will raise an event
183+
r.Recorder.Event(cr, "Warning", "Deleting",
184+
fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s",
185+
cr.Name,
186+
cr.Namespace))
187+
}
188+
126189
// deploymentForBusybox returns a Busybox Deployment object
127190
func (r *BusyboxReconciler) deploymentForBusybox(busybox *examplecomv1alpha1.Busybox) *appsv1.Deployment {
128191
ls := labelsForBusybox(busybox.Name)

0 commit comments

Comments
 (0)