Skip to content

Commit 9fb1b57

Browse files
committed
Add a finalizer to the NSTemplateTier to prevent its deletion
as long as some spaces are still derived from it.
1 parent 4f30db6 commit 9fb1b57

File tree

7 files changed

+186
-58
lines changed

7 files changed

+186
-58
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package nstemplatetier
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
8+
"github.com/codeready-toolchain/toolchain-common/pkg/hash"
9+
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
10+
"sigs.k8s.io/controller-runtime/pkg/finalizer"
11+
)
12+
13+
type nsTemplateTierUsedFinalizer struct {
14+
runtimeclient.Client
15+
}
16+
17+
// Finalize implements finalizer.Finalizer.
18+
func (n *nsTemplateTierUsedFinalizer) Finalize(ctx context.Context, tier runtimeclient.Object) (finalizer.Result, error) {
19+
list := &toolchainv1alpha1.SpaceList{}
20+
if err := n.List(ctx, list,
21+
runtimeclient.InNamespace(tier.GetNamespace()),
22+
runtimeclient.HasLabels{hash.TemplateTierHashLabelKey(tier.GetName())},
23+
runtimeclient.Limit(1)); err != nil {
24+
return finalizer.Result{}, err
25+
}
26+
27+
if len(list.Items) > 0 {
28+
return finalizer.Result{}, fmt.Errorf("the tier is still used by some spaces")
29+
}
30+
31+
return finalizer.Result{}, nil
32+
}
33+
34+
var _ finalizer.Finalizer = (*nsTemplateTierUsedFinalizer)(nil)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package nstemplatetier
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
8+
toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
9+
"github.com/codeready-toolchain/toolchain-common/pkg/hash"
10+
"github.com/codeready-toolchain/toolchain-common/pkg/test"
11+
"github.com/stretchr/testify/require"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
14+
)
15+
16+
func TestNSTemplateTierUsedFinalizer(t *testing.T) {
17+
t.Run("succeeds when tier not used", func(t *testing.T) {
18+
// given
19+
tier := &toolchainv1alpha1.NSTemplateTier{
20+
ObjectMeta: metav1.ObjectMeta{
21+
Name: "tier",
22+
Namespace: test.HostOperatorNs,
23+
},
24+
}
25+
cl := test.NewFakeClient(t, tier)
26+
f := &nsTemplateTierUsedFinalizer{Client: cl}
27+
28+
// when
29+
_, err := f.Finalize(context.TODO(), tier)
30+
31+
// then
32+
require.NoError(t, err)
33+
})
34+
t.Run("fails when tier used", func(t *testing.T) {
35+
// given
36+
tier := &toolchainv1alpha1.NSTemplateTier{
37+
ObjectMeta: metav1.ObjectMeta{
38+
Name: "tier",
39+
Namespace: test.HostOperatorNs,
40+
},
41+
}
42+
space := &toolchainv1alpha1.Space{
43+
ObjectMeta: metav1.ObjectMeta{
44+
Name: "test-space",
45+
Namespace: tier.Namespace,
46+
Labels: map[string]string{hash.TemplateTierHashLabelKey(tier.Name): "1234"},
47+
},
48+
}
49+
cl := test.NewFakeClient(t, tier, space)
50+
f := &nsTemplateTierUsedFinalizer{Client: cl}
51+
52+
// when
53+
_, err := f.Finalize(context.TODO(), tier)
54+
55+
// then
56+
require.Error(t, err)
57+
})
58+
t.Run("fails on list error", func(t *testing.T) {
59+
// given
60+
tier := &toolchainv1alpha1.NSTemplateTier{
61+
ObjectMeta: metav1.ObjectMeta{
62+
Name: "tier",
63+
Namespace: test.HostOperatorNs,
64+
},
65+
}
66+
cl := test.NewFakeClient(t, tier)
67+
cl.MockList = func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
68+
return fmt.Errorf("fake error")
69+
}
70+
f := &nsTemplateTierUsedFinalizer{Client: cl}
71+
72+
// when
73+
_, err := f.Finalize(context.TODO(), tier)
74+
75+
// then
76+
require.Error(t, err)
77+
})
78+
}

controllers/nstemplatetier/nstemplatetier_controller.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/codeready-toolchain/host-operator/controllers/toolchainconfig"
1111
"github.com/codeready-toolchain/host-operator/pkg/templates/nstemplatetiers"
1212
"github.com/codeready-toolchain/toolchain-common/pkg/condition"
13-
"github.com/redhat-cop/operator-utils/pkg/util"
13+
"github.com/codeready-toolchain/toolchain-common/pkg/finalizers"
1414
corev1 "k8s.io/api/core/v1"
1515

1616
"k8s.io/apimachinery/pkg/api/errors"
@@ -29,6 +29,10 @@ import (
2929

3030
// SetupWithManager sets up the controller with the Manager.
3131
func (r *Reconciler) SetupWithManager(mgr manager.Manager) error {
32+
if err := r.RegisterFinalizers(); err != nil {
33+
return err
34+
}
35+
3236
return ctrl.NewControllerManagedBy(mgr).
3337
For(&toolchainv1alpha1.NSTemplateTier{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
3438
Watches(&toolchainv1alpha1.TierTemplate{},
@@ -38,15 +42,24 @@ func (r *Reconciler) SetupWithManager(mgr manager.Manager) error {
3842

3943
// Reconciler reconciles a NSTemplateTier object (only when this latter's specs were updated)
4044
type Reconciler struct {
41-
Client runtimeclient.Client
42-
Scheme *runtime.Scheme
45+
Client runtimeclient.Client
46+
Scheme *runtime.Scheme
47+
finalizers finalizers.Finalizers
4348
}
4449

4550
//+kubebuilder:rbac:groups=toolchain.dev.openshift.com,resources=nstemplatetiers,verbs=get;list;watch;create;update;patch;delete
4651
//+kubebuilder:rbac:groups=toolchain.dev.openshift.com,resources=spaces,verbs=get;list;watch
4752
//+kubebuilder:rbac:groups=toolchain.dev.openshift.com,resources=nstemplatetiers/status,verbs=get;update;patch
4853
//+kubebuilder:rbac:groups=toolchain.dev.openshift.com,resources=nstemplatetiers/finalizers,verbs=update
4954

55+
// RegisterFinalizers should only be called from the unit tests. At runtime, it is called during SetupWithManager.
56+
func (r *Reconciler) RegisterFinalizers() error {
57+
if err := r.finalizers.RegisterWithStandardName(&nsTemplateTierUsedFinalizer{Client: r.Client}); err != nil {
58+
return fmt.Errorf("failed to register the NSTemplateTier finalizer (this should not happen): %w", err)
59+
}
60+
return nil
61+
}
62+
5063
// Reconcile takes care of fetching the NSTemplateTier
5164
func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
5265
logger := log.FromContext(ctx)
@@ -62,9 +75,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
6275
logger.Error(err, "unable to get the current NSTemplateTier")
6376
return reconcile.Result{}, fmt.Errorf("unable to get the current NSTemplateTier: %w", err)
6477
}
65-
// if the NSTemplateTier is being deleted, then do nothing
66-
if util.IsBeingDeleted(tier) {
67-
return reconcile.Result{}, nil
78+
if updated, err := r.finalizers.FinalizeAndUpdate(ctx, r.Client, tier); updated || err != nil {
79+
return reconcile.Result{}, err
6880
}
6981

7082
_, err := toolchainconfig.GetToolchainConfig(r.Client)
@@ -183,7 +195,6 @@ func (r *Reconciler) ensureTTRforTemplate(ctx context.Context, nsTmplTier *toolc
183195
return false, "", err
184196
}
185197
return true, ttrName, nil
186-
187198
}
188199

189200
func (r *Reconciler) createNewTierTemplateRevision(ctx context.Context, nsTmplTier *toolchainv1alpha1.NSTemplateTier, tierTemplate *toolchainv1alpha1.TierTemplate) (string, error) {

0 commit comments

Comments
 (0)