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

opendefensecloud / solution-arsenal / 27760471962

18 Jun 2026 12:47PM UTC coverage: 75.441% (-1.1%) from 76.578%
27760471962

Pull #621

github

web-flow
Merge 86594318e into 8ad8e9940
Pull Request #621: feat(controller): implement deletion protection via controller-managed finalizers

416 of 657 new or added lines in 7 files covered. (63.32%)

2 existing lines in 1 file now uncovered.

4021 of 5330 relevant lines covered (75.44%)

33.71 hits per line

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

73.13
/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
                if !slices.Contains(latest.Finalizers, registryBindingFinalizer) {
22✔
88
                        original := latest.DeepCopy()
11✔
89
                        latest.Finalizers = append(latest.Finalizers, registryBindingFinalizer)
11✔
90
                        if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
11✔
NEW
91
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to add finalizer to RegistryBinding")
×
NEW
92
                        }
×
93
                }
94
        }
95

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

112
        return ctrl.Result{}, nil
22✔
113
}
114

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

121
// SetupWithManager sets up the controller with the Manager.
122
func (r *RegistryBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
123
        return ctrl.NewControllerManagedBy(mgr).
1✔
124
                For(&solarv1alpha1.RegistryBinding{}).
1✔
125
                Complete(r)
1✔
126
}
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