• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

opendefensecloud / solution-arsenal / 27755794823

18 Jun 2026 11:18AM UTC coverage: 75.292% (-1.7%) from 77.003%
27755794823

Pull #621

github

web-flow
Merge 3e94622f3 into 69595b7d1
Pull Request #621: feat(controller): implement deletion protection via controller-managed finalizers

397 of 636 new or added lines in 7 files covered. (62.42%)

15 existing lines in 3 files now uncovered.

4001 of 5314 relevant lines covered (75.29%)

33.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.73
/pkg/controller/registrybinding_controller.go
1
// Copyright 2026 BWI GmbH and Solution Arsenal contributors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package controller
5

6
import (
7
        "context"
8
        "slices"
9

10
        apierrors "k8s.io/apimachinery/pkg/api/errors"
11
        "k8s.io/apimachinery/pkg/runtime"
12
        "k8s.io/apimachinery/pkg/types"
13
        ctrl "sigs.k8s.io/controller-runtime"
14
        "sigs.k8s.io/controller-runtime/pkg/client"
15

16
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
17
)
18

19
// RegistryBindingReconciler manages the deletion-protection finalizer on the Registry referenced
20
// by each RegistryBinding, preventing Registry deletion while RegistryBindings exist.
21
type RegistryBindingReconciler struct {
22
        client.Client
23
        Scheme *runtime.Scheme
24
        // WatchNamespace restricts reconciliation to this namespace.
25
        // Should be empty in production (watches all namespaces).
26
        // Intended for use in integration tests only.
27
        WatchNamespace string
28
}
29

30
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=registrybindings,verbs=get;list;watch;update;patch
31
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=registrybindings/finalizers,verbs=update
32
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=registries,verbs=get;list;watch;update;patch
33
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=registries/finalizers,verbs=update
34

35
func (r *RegistryBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
44✔
36
        log := ctrl.LoggerFrom(ctx)
44✔
37

44✔
38
        log.V(1).Info("RegistryBinding is being reconciled", "req", req)
44✔
39

44✔
40
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
58✔
41
                return ctrl.Result{}, nil
14✔
42
        }
14✔
43

44
        rb := &solarv1alpha1.RegistryBinding{}
30✔
45
        if err := r.Get(ctx, req.NamespacedName, rb); err != nil {
34✔
46
                if apierrors.IsNotFound(err) {
8✔
47
                        return ctrl.Result{}, nil
4✔
48
                }
4✔
49

NEW
50
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get RegistryBinding")
×
51
        }
52

53
        // Handle deletion: remove registryRefFinalizer from Registry if no other active referencer exists.
54
        if !rb.DeletionTimestamp.IsZero() {
30✔
55
                if rb.Spec.RegistryRef.Name != "" {
8✔
56
                        registry := &solarv1alpha1.Registry{}
4✔
57
                        if err := r.Get(ctx, types.NamespacedName{Name: rb.Spec.RegistryRef.Name, Namespace: rb.Namespace}, registry); err != nil {
4✔
NEW
58
                                if !apierrors.IsNotFound(err) {
×
NEW
59
                                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Registry for finalizer cleanup")
×
NEW
60
                                }
×
61
                        } else if err := r.removeRegistryRefFinalizer(ctx, rb, registry); err != nil {
4✔
NEW
62
                                return ctrl.Result{}, err
×
NEW
63
                        }
×
64
                }
65

66
                if slices.Contains(rb.Finalizers, registryBindingFinalizer) {
8✔
67
                        latest := &solarv1alpha1.RegistryBinding{}
4✔
68
                        if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
4✔
NEW
69
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get latest RegistryBinding for finalizer removal")
×
NEW
70
                        }
×
71
                        original := latest.DeepCopy()
4✔
72
                        latest.Finalizers = slices.DeleteFunc(latest.Finalizers, func(s string) bool { return s == registryBindingFinalizer })
8✔
73
                        if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
4✔
NEW
74
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to remove finalizer from RegistryBinding")
×
NEW
75
                        }
×
76
                }
77

78
                return ctrl.Result{}, nil
4✔
79
        }
80

81
        // Ensure self-finalizer exists.
82
        if !slices.Contains(rb.Finalizers, registryBindingFinalizer) {
33✔
83
                latest := &solarv1alpha1.RegistryBinding{}
11✔
84
                if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
11✔
NEW
85
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get latest RegistryBinding for finalizer addition")
×
NEW
86
                }
×
87
                original := latest.DeepCopy()
11✔
88
                latest.Finalizers = append(latest.Finalizers, registryBindingFinalizer)
11✔
89
                if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
11✔
NEW
90
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to add finalizer to RegistryBinding")
×
NEW
91
                }
×
92
        }
93

94
        // Protect the referenced Registry from deletion.
95
        if rb.Spec.RegistryRef.Name != "" {
44✔
96
                registry := &solarv1alpha1.Registry{}
22✔
97
                if err := r.Get(ctx, types.NamespacedName{Name: rb.Spec.RegistryRef.Name, Namespace: rb.Namespace}, registry); err != nil {
24✔
98
                        if !apierrors.IsNotFound(err) {
2✔
NEW
99
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Registry for protection finalizer")
×
NEW
100
                        }
×
101
                } else if !slices.Contains(registry.Finalizers, registryRefFinalizer) {
29✔
102
                        latest := registry.DeepCopy()
9✔
103
                        latest.Finalizers = append(latest.Finalizers, registryRefFinalizer)
9✔
104
                        if err := r.Patch(ctx, latest, client.MergeFrom(registry)); err != nil {
9✔
NEW
105
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to add protection finalizer to Registry")
×
NEW
106
                        }
×
107
                }
108
        }
109

110
        return ctrl.Result{}, nil
22✔
111
}
112

113
// removeRegistryRefFinalizer removes registryRefFinalizer from registry when no other active
114
// Target or RegistryBinding (excluding the deleting RegistryBinding) still references it.
115
func (r *RegistryBindingReconciler) removeRegistryRefFinalizer(ctx context.Context, deletingRB *solarv1alpha1.RegistryBinding, registry *solarv1alpha1.Registry) error {
4✔
116
        return removeRegistryRefFinalizer(ctx, r.Client, nil, deletingRB, registry)
4✔
117
}
4✔
118

119
// SetupWithManager sets up the controller with the Manager.
120
func (r *RegistryBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
121
        return ctrl.NewControllerManagedBy(mgr).
1✔
122
                For(&solarv1alpha1.RegistryBinding{}).
1✔
123
                Complete(r)
1✔
124
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc