• 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

71.15
/pkg/controller/helpers.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
        "crypto/sha256"
9
        "encoding/hex"
10
        "fmt"
11
        "slices"
12
        "sort"
13
        "strings"
14

15
        apierrors "k8s.io/apimachinery/pkg/api/errors"
16
        "k8s.io/apimachinery/pkg/types"
17
        ctrl "sigs.k8s.io/controller-runtime"
18
        "sigs.k8s.io/controller-runtime/pkg/client"
19
        "sigs.k8s.io/controller-runtime/pkg/handler"
20
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
21

22
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
23
)
24

25
const (
26
        // Field index keys for looking up RenderTasks by owner.
27
        indexOwnerKind      = "spec.ownerKind"
28
        indexOwnerName      = "spec.ownerName"
29
        indexOwnerNamespace = "spec.ownerNamespace"
30

31
        // Field index keys for looking up ReleaseBindings by target or release name.
32
        indexReleaseBindingTargetName      = "spec.targetRef.name"
33
        indexReleaseBindingTargetNamespace = "spec.targetNamespace"
34
        indexReleaseBindingReleaseName     = "spec.releaseRef.name"
35

36
        // Field index key for looking up RegistryBindings by target name.
37
        indexRegistryBindingTargetName = "spec.targetRef.name"
38

39
        // Field index key for looking up RenderBindings by the RenderArtifact they reference.
40
        indexRenderBindingArtifactName = "spec.renderArtifactRef.name"
41

42
        // Field index keys for deletion-protection reference lookups.
43
        // Release: composite "<cvNamespace>/<cvName>" resolving cross-namespace refs.
44
        indexReleaseByCVRef = "dp.spec.componentVersionRef"
45
        // ComponentVersion: same-namespace lookup by component name.
46
        indexCVByComponentName = "dp.spec.componentRef.name"
47
        // Profile: same-namespace lookup by release name.
48
        indexProfileByReleaseName = "dp.spec.releaseRef.name"
49
        // Target: composite "<registryNamespace>/<registryName>" resolving cross-namespace refs.
50
        indexTargetByRegistryRef = "dp.spec.renderRegistryRef"
51
        // RegistryBinding: same-namespace lookup by registry name.
52
        indexRegistryBindingByRegistryName = "dp.spec.registryRef.name"
53

54
        maxK8sObjectNameLen = 253
55
        maxK8sLabelValueLen = 63
56

57
        // Self-finalizers: added to the referencing resource so the controller can observe deletion.
58
        releaseFinalizer          = "solar.opendefense.cloud/release-finalizer"
59
        profileFinalizer          = "solar.opendefense.cloud/profile-finalizer"
60
        releaseBindingFinalizer   = "solar.opendefense.cloud/releasebinding-finalizer"
61
        componentVersionFinalizer = "solar.opendefense.cloud/componentversion-finalizer"
62
        registryBindingFinalizer  = "solar.opendefense.cloud/registrybinding-finalizer"
63

64
        // Protection finalizers: added to the referenced resource to block deletion while referenced.
65
        componentVersionRefFinalizer = "solar.opendefense.cloud/componentversion-ref"
66
        componentRefFinalizer        = "solar.opendefense.cloud/component-ref"
67
        releaseRefFinalizer          = "solar.opendefense.cloud/release-ref"
68
        registryRefFinalizer         = "solar.opendefense.cloud/registry-ref"
69
)
70

71
// truncateName truncates a name to maxLen characters. If truncation is needed,
72
// it appends a short hash suffix to preserve uniqueness.
73
func truncateName(name string, maxLen int) string {
1,116✔
74
        if maxLen < 10 {
1,116✔
75
                maxLen = 10
×
76
        }
×
77
        if len(name) <= maxLen {
2,232✔
78
                return name
1,116✔
79
        }
1,116✔
80
        hash := sha256.Sum256([]byte(name))
×
81
        hashStr := hex.EncodeToString(hash[:])[:8]
×
82

×
83
        return name[:maxLen-9] + "-" + hashStr
×
84
}
85

86
// mapRenderTaskToOwner returns a handler.MapFunc that maps RenderTask events
87
// to reconcile requests for the owning resource of the specified kind.
88
func mapRenderTaskToOwner(kind string) handler.MapFunc {
1✔
89
        return func(_ context.Context, obj client.Object) []reconcile.Request {
269✔
90
                rt, ok := obj.(*solarv1alpha1.RenderTask)
268✔
91
                if !ok {
268✔
92
                        return nil
×
93
                }
×
94

95
                if rt.Spec.OwnerKind != kind || rt.Spec.OwnerName == "" || rt.Spec.OwnerNamespace == "" {
406✔
96
                        return nil
138✔
97
                }
138✔
98

99
                return []reconcile.Request{
130✔
100
                        {
130✔
101
                                NamespacedName: types.NamespacedName{
130✔
102
                                        Name:      rt.Spec.OwnerName,
130✔
103
                                        Namespace: rt.Spec.OwnerNamespace,
130✔
104
                                },
130✔
105
                        },
130✔
106
                }
130✔
107
        }
108
}
109

110
// effectiveUniqueName returns the deduplication key for a release: Spec.UniqueName
111
// when set, otherwise the parent Component name from the referenced ComponentVersion.
112
// This mirrors the logic in the Release controller that writes Status.EffectiveUniqueName.
113
func effectiveUniqueName(rel *solarv1alpha1.Release, cv *solarv1alpha1.ComponentVersion) string {
248✔
114
        if rel.Spec.UniqueName != "" {
485✔
115
                return rel.Spec.UniqueName
237✔
116
        }
237✔
117

118
        return cv.Spec.ComponentRef.Name
11✔
119
}
120

121
// releaseRenderTaskName returns a deterministic name for a per-release RenderTask
122
// scoped to a specific target. Each target creates its own release RenderTasks;
123
// the renderer job handles deduplication by skipping rendering if the chart
124
// already exists in the registry.
125
func releaseRenderTaskName(releaseNamespace, releaseName, targetName string, generation int64) string {
150✔
126
        input := fmt.Sprintf("%s/%s-%s-%d", releaseNamespace, releaseName, targetName, generation)
150✔
127
        hash := sha256.Sum256([]byte(input))
150✔
128
        hashStr := hex.EncodeToString(hash[:])[:8]
150✔
129

150✔
130
        return truncateName(fmt.Sprintf("render-rel-%s-%s", releaseName, hashStr), maxK8sObjectNameLen)
150✔
131
}
150✔
132

133
// targetRenderTaskName returns a deterministic name for a per-target bootstrap RenderTask.
134
// The bootstrapVersion is incremented each time the bootstrap needs re-rendering.
135
func targetRenderTaskName(targetName string, bootstrapVersion int64) string {
41✔
136
        return truncateName(fmt.Sprintf("render-tgt-%s-%d", targetName, bootstrapVersion), maxK8sObjectNameLen)
41✔
137
}
41✔
138

139
// renderArtifactName returns a deterministic name for a RenderArtifact based on the
140
// artifact's registry coordinates. Multiple RenderTasks that push to the same (namespace,
141
// baseURL, repository, tag) tuple will resolve to the same artifact name, enabling sharing.
142
func renderArtifactName(namespace, baseURL, repository, tag string) string {
56✔
143
        input := fmt.Sprintf("%s/%s/%s/%s", namespace, baseURL, repository, tag)
56✔
144
        hash := sha256.Sum256([]byte(input))
56✔
145
        hashStr := hex.EncodeToString(hash[:])[:8]
56✔
146

56✔
147
        return "render-art-" + hashStr
56✔
148
}
56✔
149

150
// renderBindingName returns a deterministic name for a RenderBinding linking a Target
151
// to a RenderArtifact.
152
func renderBindingName(artifactName, ownerName string) string {
55✔
153
        input := fmt.Sprintf("%s/Target/%s", artifactName, ownerName)
55✔
154
        hash := sha256.Sum256([]byte(input))
55✔
155
        hashStr := hex.EncodeToString(hash[:])[:8]
55✔
156

55✔
157
        return "render-bind-" + hashStr
55✔
158
}
55✔
159

160
// renderChartURL constructs the fully-qualified OCI chart URL from push coordinates.
161
func renderChartURL(baseURL, repository, tag string) string {
39✔
162
        base := baseURL
39✔
163
        if !strings.HasPrefix(base, "oci://") {
78✔
164
                base = "oci://" + base
39✔
165
        }
39✔
166

167
        return strings.TrimSuffix(base, "/") + "/" + repository + ":" + tag
39✔
168
}
169

170
// registryHost extracts the registry host from a repository string and
171
// normalises it to lower-case (hostnames are case-insensitive per RFC 4343).
172
// For example, "Registry.Example.COM:5000/foo/bar" returns "registry.example.com:5000".
173
func registryHost(repository string) string {
137✔
174
        repo := strings.TrimPrefix(repository, "oci://")
137✔
175
        if before, _, ok := strings.Cut(repo, "/"); ok {
272✔
176
                return strings.ToLower(before)
135✔
177
        }
135✔
178

179
        return strings.ToLower(repo)
2✔
180
}
181

182
// resolveResources converts ResourceAccess entries from a ComponentVersion into
183
// ResolvedResourceAccess for the renderer. PullSecretName is looked up from
184
// pullSecretsByHost by extracting the registry host from each resource's repository.
185
// In strict mode, an error is returned if any resource's host has no matching
186
// RegistryBinding.
187
func resolveResources(resources map[string]solarv1alpha1.ResourceAccess, pullSecretsByHost map[string]string, strict bool) (map[string]solarv1alpha1.ResolvedResourceAccess, error) {
124✔
188
        resolved := make(map[string]solarv1alpha1.ResolvedResourceAccess, len(resources))
124✔
189
        for name, ra := range resources {
253✔
190
                host := registryHost(ra.Repository)
129✔
191
                pullSecret, found := pullSecretsByHost[host]
129✔
192
                if strict && !found {
141✔
193
                        return nil, fmt.Errorf("no RegistryBinding for host %q (resource %q); create a RegistryBinding or use relaxed mode", host, name)
12✔
194
                }
12✔
195

196
                resolved[name] = solarv1alpha1.ResolvedResourceAccess{
117✔
197
                        Repository:     ra.Repository,
117✔
198
                        Insecure:       ra.Insecure,
117✔
199
                        Tag:            ra.Tag,
117✔
200
                        Helm:           ra.Helm,
117✔
201
                        PullSecretName: pullSecret,
117✔
202
                }
117✔
203
        }
204

205
        return resolved, nil
112✔
206
}
207

208
// pullSecretsTag returns a short hash derived from the pull-secret names in
209
// resolved resources. It is appended to the chart tag so that charts whose
210
// content differs only in secretRef (due to RegistryBinding changes) get
211
// unique OCI tags, preventing the renderer's exists-check from skipping a
212
// necessary re-push.
213
func pullSecretsTag(resolved map[string]solarv1alpha1.ResolvedResourceAccess) string {
115✔
214
        keys := make([]string, 0, len(resolved))
115✔
215
        for k := range resolved {
230✔
216
                keys = append(keys, k)
115✔
217
        }
115✔
218

219
        sort.Strings(keys)
115✔
220

115✔
221
        h := sha256.New()
115✔
222

115✔
223
        for _, k := range keys {
230✔
224
                fmt.Fprintf(h, "%s=%s;", k, resolved[k].PullSecretName)
115✔
225
        }
115✔
226

227
        return hex.EncodeToString(h.Sum(nil))[:8]
115✔
228
}
229

230
// IndexFields registers field indexers on the manager for efficient lookups.
231
// Must be called once before any controller that uses these indexes is set up.
232
func IndexFields(ctx context.Context, mgr ctrl.Manager) error {
1✔
233
        if err := indexReleaseBindingFields(ctx, mgr); err != nil {
1✔
234
                return err
×
235
        }
×
236

237
        if err := indexRenderTaskOwnerFields(ctx, mgr); err != nil {
1✔
238
                return err
×
239
        }
×
240

241
        if err := indexRegistryBindingFields(ctx, mgr); err != nil {
1✔
242
                return err
×
243
        }
×
244

245
        if err := indexRenderBindingFields(ctx, mgr); err != nil {
1✔
NEW
246
                return err
×
NEW
247
        }
×
248

249
        return indexDeletionProtectionFields(ctx, mgr)
1✔
250
}
251

252
// indexDeletionProtectionFields registers field indexers used to count active references
253
// when deciding whether to remove a protection finalizer from a referenced resource.
254
func indexDeletionProtectionFields(ctx context.Context, mgr ctrl.Manager) error {
1✔
255
        indexer := mgr.GetFieldIndexer()
1✔
256

1✔
257
        // Release → ComponentVersion: composite "<cvNamespace>/<cvName>" to handle cross-namespace refs.
1✔
258
        if err := indexer.IndexField(ctx, &solarv1alpha1.Release{}, indexReleaseByCVRef, func(obj client.Object) []string {
431✔
259
                rel := obj.(*solarv1alpha1.Release)
430✔
260
                if rel.Spec.ComponentVersionRef.Name == "" {
430✔
NEW
261
                        return nil
×
NEW
262
                }
×
263
                cvNs := rel.Namespace
430✔
264
                if rel.Spec.ComponentVersionNamespace != "" {
456✔
265
                        cvNs = rel.Spec.ComponentVersionNamespace
26✔
266
                }
26✔
267

268
                return []string{cvNs + "/" + rel.Spec.ComponentVersionRef.Name}
430✔
NEW
269
        }); err != nil {
×
NEW
270
                return err
×
NEW
271
        }
×
272

273
        // ComponentVersion → Component: same-namespace, index by component name.
274
        if err := indexer.IndexField(ctx, &solarv1alpha1.ComponentVersion{}, indexCVByComponentName, func(obj client.Object) []string {
230✔
275
                cv := obj.(*solarv1alpha1.ComponentVersion)
229✔
276
                if cv.Spec.ComponentRef.Name == "" {
229✔
NEW
277
                        return nil
×
NEW
278
                }
×
279

280
                return []string{cv.Spec.ComponentRef.Name}
229✔
NEW
281
        }); err != nil {
×
NEW
282
                return err
×
NEW
283
        }
×
284

285
        // Profile → Release: same-namespace, index by release name.
286
        if err := indexer.IndexField(ctx, &solarv1alpha1.Profile{}, indexProfileByReleaseName, func(obj client.Object) []string {
85✔
287
                p := obj.(*solarv1alpha1.Profile)
84✔
288
                if p.Spec.ReleaseRef.Name == "" {
84✔
NEW
289
                        return nil
×
NEW
290
                }
×
291

292
                return []string{p.Spec.ReleaseRef.Name}
84✔
NEW
293
        }); err != nil {
×
NEW
294
                return err
×
NEW
295
        }
×
296

297
        // Target → Registry: composite "<registryNamespace>/<registryName>" to handle cross-namespace refs.
298
        if err := indexer.IndexField(ctx, &solarv1alpha1.Target{}, indexTargetByRegistryRef, func(obj client.Object) []string {
414✔
299
                t := obj.(*solarv1alpha1.Target)
413✔
300
                if t.Spec.RenderRegistryRef.Name == "" {
491✔
301
                        return nil
78✔
302
                }
78✔
303
                regNs := t.Namespace
335✔
304
                if t.Spec.RenderRegistryNamespace != "" {
335✔
NEW
305
                        regNs = t.Spec.RenderRegistryNamespace
×
NEW
306
                }
×
307

308
                return []string{regNs + "/" + t.Spec.RenderRegistryRef.Name}
335✔
NEW
309
        }); err != nil {
×
NEW
310
                return err
×
NEW
311
        }
×
312

313
        // RegistryBinding → Registry: same-namespace, index by registry name.
314
        return indexer.IndexField(ctx, &solarv1alpha1.RegistryBinding{}, indexRegistryBindingByRegistryName, func(obj client.Object) []string {
67✔
315
                rb := obj.(*solarv1alpha1.RegistryBinding)
66✔
316
                if rb.Spec.RegistryRef.Name == "" {
66✔
NEW
317
                        return nil
×
NEW
318
                }
×
319

320
                return []string{rb.Spec.RegistryRef.Name}
66✔
321
        })
322
}
323

324
func indexReleaseBindingFields(ctx context.Context, mgr ctrl.Manager) error {
1✔
325
        indexer := mgr.GetFieldIndexer()
1✔
326

1✔
327
        if err := indexer.IndexField(ctx, &solarv1alpha1.ReleaseBinding{}, indexReleaseBindingTargetName, func(obj client.Object) []string {
236✔
328
                rb := obj.(*solarv1alpha1.ReleaseBinding)
235✔
329
                if rb.Spec.TargetRef.Name == "" {
235✔
330
                        return nil
×
331
                }
×
332

333
                return []string{rb.Spec.TargetRef.Name}
235✔
334
        }); err != nil {
×
335
                return err
×
336
        }
×
337

338
        if err := indexer.IndexField(ctx, &solarv1alpha1.ReleaseBinding{}, indexReleaseBindingTargetNamespace, func(obj client.Object) []string {
236✔
339
                rb := obj.(*solarv1alpha1.ReleaseBinding)
235✔
340
                // Empty TargetNamespace is intentionally indexed as "" — the same-namespace binding
235✔
341
                // query in target_controller.go filters on "" to exclude cross-namespace bindings.
235✔
342
                return []string{rb.Spec.TargetNamespace}
235✔
343
        }); err != nil {
235✔
344
                return err
×
345
        }
×
346

347
        return indexer.IndexField(ctx, &solarv1alpha1.ReleaseBinding{}, indexReleaseBindingReleaseName, func(obj client.Object) []string {
236✔
348
                rb := obj.(*solarv1alpha1.ReleaseBinding)
235✔
349
                if rb.Spec.ReleaseRef.Name == "" {
235✔
350
                        return nil
×
351
                }
×
352

353
                return []string{rb.Spec.ReleaseRef.Name}
235✔
354
        })
355
}
356

357
func indexRegistryBindingFields(ctx context.Context, mgr ctrl.Manager) error {
1✔
358
        return mgr.GetFieldIndexer().IndexField(ctx, &solarv1alpha1.RegistryBinding{}, indexRegistryBindingTargetName, func(obj client.Object) []string {
67✔
359
                rb := obj.(*solarv1alpha1.RegistryBinding)
66✔
360
                if rb.Spec.TargetRef.Name == "" {
66✔
361
                        return nil
×
362
                }
×
363

364
                return []string{rb.Spec.TargetRef.Name}
66✔
365
        })
366
}
367

368
// indexRenderTaskOwnerFields registers field indexers on the manager for
369
// looking up RenderTasks by owner kind, name, and namespace.
370
func indexRenderTaskOwnerFields(ctx context.Context, mgr ctrl.Manager) error {
1✔
371
        indexer := mgr.GetFieldIndexer()
1✔
372

1✔
373
        if err := indexer.IndexField(ctx, &solarv1alpha1.RenderTask{}, indexOwnerKind, func(obj client.Object) []string {
269✔
374
                rt := obj.(*solarv1alpha1.RenderTask)
268✔
375
                if rt.Spec.OwnerKind == "" {
406✔
376
                        return nil
138✔
377
                }
138✔
378

379
                return []string{rt.Spec.OwnerKind}
130✔
380
        }); err != nil {
×
381
                return err
×
382
        }
×
383

384
        if err := indexer.IndexField(ctx, &solarv1alpha1.RenderTask{}, indexOwnerName, func(obj client.Object) []string {
269✔
385
                rt := obj.(*solarv1alpha1.RenderTask)
268✔
386
                if rt.Spec.OwnerName == "" {
406✔
387
                        return nil
138✔
388
                }
138✔
389

390
                return []string{rt.Spec.OwnerName}
130✔
391
        }); err != nil {
×
392
                return err
×
393
        }
×
394

395
        return indexer.IndexField(ctx, &solarv1alpha1.RenderTask{}, indexOwnerNamespace, func(obj client.Object) []string {
269✔
396
                rt := obj.(*solarv1alpha1.RenderTask)
268✔
397
                if rt.Spec.OwnerNamespace == "" {
406✔
398
                        return nil
138✔
399
                }
138✔
400

401
                return []string{rt.Spec.OwnerNamespace}
130✔
402
        })
403
}
404

405
// indexRenderBindingFields registers field indexers for RenderBinding lookups.
406
// The same owner field key names as RenderTask are used; controller-runtime
407
// indexes are keyed per object type, so there is no conflict.
408
func indexRenderBindingFields(ctx context.Context, mgr ctrl.Manager) error {
1✔
409
        indexer := mgr.GetFieldIndexer()
1✔
410

1✔
411
        if err := indexer.IndexField(ctx, &solarv1alpha1.RenderBinding{}, indexOwnerKind, func(obj client.Object) []string {
31✔
412
                rb := obj.(*solarv1alpha1.RenderBinding)
30✔
413
                if rb.Spec.OwnerKind == "" {
30✔
414
                        return nil
×
415
                }
×
416

417
                return []string{rb.Spec.OwnerKind}
30✔
418
        }); err != nil {
×
419
                return err
×
420
        }
×
421

422
        if err := indexer.IndexField(ctx, &solarv1alpha1.RenderBinding{}, indexOwnerName, func(obj client.Object) []string {
31✔
423
                rb := obj.(*solarv1alpha1.RenderBinding)
30✔
424
                if rb.Spec.OwnerName == "" {
30✔
425
                        return nil
×
426
                }
×
427

428
                return []string{rb.Spec.OwnerName}
30✔
429
        }); err != nil {
×
430
                return err
×
431
        }
×
432

433
        if err := indexer.IndexField(ctx, &solarv1alpha1.RenderBinding{}, indexOwnerNamespace, func(obj client.Object) []string {
31✔
434
                rb := obj.(*solarv1alpha1.RenderBinding)
30✔
435
                if rb.Spec.OwnerNamespace == "" {
30✔
436
                        return nil
×
437
                }
×
438

439
                return []string{rb.Spec.OwnerNamespace}
30✔
440
        }); err != nil {
×
441
                return err
×
442
        }
×
443

444
        return indexer.IndexField(ctx, &solarv1alpha1.RenderBinding{}, indexRenderBindingArtifactName, func(obj client.Object) []string {
31✔
445
                rb := obj.(*solarv1alpha1.RenderBinding)
30✔
446
                if rb.Spec.RenderArtifactRef.Name == "" {
30✔
447
                        return nil
×
448
                }
×
449

450
                return []string{rb.Spec.RenderArtifactRef.Name}
30✔
451
        })
452
}
453

454
// removeRegistryRefFinalizer removes registryRefFinalizer from registry when no other active
455
// Target (excluding skipTarget if non-nil) or RegistryBinding (excluding skipRegistryBinding
456
// if non-nil) still references it.
457
func removeRegistryRefFinalizer(ctx context.Context, c client.Client, skipTarget *solarv1alpha1.Target, skipRegistryBinding *solarv1alpha1.RegistryBinding, registry *solarv1alpha1.Registry) error {
6✔
458
        if !slices.Contains(registry.Finalizers, registryRefFinalizer) {
6✔
NEW
459
                return nil
×
NEW
460
        }
×
461

462
        refKey := registry.Namespace + "/" + registry.Name
6✔
463

6✔
464
        targetList := &solarv1alpha1.TargetList{}
6✔
465
        if err := c.List(ctx, targetList, client.MatchingFields{indexTargetByRegistryRef: refKey}); err != nil {
6✔
NEW
466
                return errLogAndWrap(ctrl.LoggerFrom(ctx), err, "failed to list Targets for Registry finalizer check")
×
NEW
467
        }
×
468

469
        for _, t := range targetList.Items {
8✔
470
                if skipTarget != nil && t.Name == skipTarget.Name && t.Namespace == skipTarget.Namespace {
4✔
471
                        continue
2✔
472
                }
NEW
473
                if !t.DeletionTimestamp.IsZero() {
×
NEW
474
                        continue
×
475
                }
476

NEW
477
                return nil
×
478
        }
479

480
        rbList := &solarv1alpha1.RegistryBindingList{}
6✔
481
        if err := c.List(ctx, rbList,
6✔
482
                client.InNamespace(registry.Namespace),
6✔
483
                client.MatchingFields{indexRegistryBindingByRegistryName: registry.Name},
6✔
484
        ); err != nil {
6✔
NEW
485
                return errLogAndWrap(ctrl.LoggerFrom(ctx), err, "failed to list RegistryBindings for Registry finalizer check")
×
NEW
486
        }
×
487

488
        for _, rb := range rbList.Items {
11✔
489
                if skipRegistryBinding != nil && rb.Name == skipRegistryBinding.Name {
9✔
490
                        continue
4✔
491
                }
492
                if !rb.DeletionTimestamp.IsZero() {
1✔
NEW
493
                        continue
×
494
                }
495

496
                return nil
1✔
497
        }
498

499
        freshRegistry := &solarv1alpha1.Registry{}
5✔
500
        if err := c.Get(ctx, client.ObjectKeyFromObject(registry), freshRegistry); err != nil {
5✔
NEW
501
                if apierrors.IsNotFound(err) {
×
NEW
502
                        return nil
×
NEW
503
                }
×
504

NEW
505
                return errLogAndWrap(ctrl.LoggerFrom(ctx), err, "failed to get latest Registry for finalizer removal")
×
506
        }
507
        original := freshRegistry.DeepCopy()
5✔
508
        freshRegistry.Finalizers = slices.DeleteFunc(freshRegistry.Finalizers, func(s string) bool { return s == registryRefFinalizer })
10✔
509
        if err := c.Patch(ctx, freshRegistry, client.MergeFrom(original)); err != nil {
5✔
NEW
510
                return errLogAndWrap(ctrl.LoggerFrom(ctx), err, "failed to remove protection finalizer from Registry")
×
NEW
511
        }
×
512

513
        ctrl.LoggerFrom(ctx).V(1).Info("Removed protection finalizer from Registry", "registry", registry.Name)
5✔
514

5✔
515
        return nil
5✔
516
}
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