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

opendefensecloud / solution-arsenal / 30623048857

31 Jul 2026 10:17AM UTC coverage: 81.918% (-0.04%) from 81.954%
30623048857

push

github

web-flow
fix: restructured renderartifact and -binding to solve a known problem (#724)

## What
Changed the structure of RenderArtifacts and -bindings to reference
Registries instead of having a copy of the secret
Closes #616 

## Why
RenderArtifacts had possibly stale secrets with the old implementation,
and secrets would need to be repinned, dependent on remaining
ArtifatBindings etc., which was convoluted and not ideal for the simple
reason that we don't want the registry secrets or other sensitive data
copied in too many ressources.

## Testing
make test

## Notes for reviewers
RenderArtifact lost some Registry-Specific Fields, and RenderArtifact as
well as ArtifatBindings got a RegistryRef field.

## Checklist
- [x] Tests added/updated
- [x] No breaking changes (or upgrade path documented above)
- [x] Readable commit history (squashed and cleaned up as desired)
- [x] AI code review considered and comments resolved


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Render artifacts now reference a Registry for credentials and
transport settings.
* Render bindings preserve Registry references and support automatic
artifact re-pinning.
* Cross-namespace Registry access is supported through explicit
authorization grants.
  * Registry changes are propagated to existing artifacts and bindings.

* **Bug Fixes**
* Improved OCI cleanup authentication, host validation, and
insecure-transport handling.
* Unauthorized cleanup now reports a clear authentication failure
status.

* **Documentation**
* Updated API references, user guidance, and configuration examples for
Registry references and access grants.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

92 of 102 new or added lines in 2 files covered. (90.2%)

7 existing lines in 3 files now uncovered.

5246 of 6404 relevant lines covered (81.92%)

29.47 hits per line

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

78.68
/pkg/controller/renderartifact_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
        "encoding/json"
9
        "errors"
10
        "fmt"
11
        "net/http"
12
        "slices"
13
        "strings"
14
        "time"
15

16
        "github.com/google/go-containerregistry/pkg/authn"
17
        "github.com/google/go-containerregistry/pkg/v1/remote/transport"
18
        corev1 "k8s.io/api/core/v1"
19
        apierrors "k8s.io/apimachinery/pkg/api/errors"
20
        apimeta "k8s.io/apimachinery/pkg/api/meta"
21
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22
        "k8s.io/apimachinery/pkg/runtime"
23
        "k8s.io/apimachinery/pkg/types"
24
        "k8s.io/client-go/tools/events"
25
        ctrl "sigs.k8s.io/controller-runtime"
26
        "sigs.k8s.io/controller-runtime/pkg/client"
27
        "sigs.k8s.io/controller-runtime/pkg/handler"
28
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
29

30
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
31
        "go.opendefense.cloud/solar/pkg/ociregistry"
32
)
33

34
const (
35
        renderArtifactFinalizer = "solar.opendefense.cloud/render-artifact-finalizer"
36
        ConditionTypeOCICleanup = "OCICleanup"
37
)
38

39
// RenderArtifactReconciler reconciles RenderArtifact objects.
40
// It sets status.ChartURL and acts as the GC controller: when the last RenderBinding
41
// referencing a RenderArtifact is removed, it attempts to delete the OCI tag
42
// and then deletes the RenderArtifact object itself.
43
//
44
// OCI tag deletion failures are surfaced as a status condition and a Warning event
45
// so users have visibility; the finalizer is kept until the deletion succeeds,
46
// making the artifact object "stuck" in a visible state.
47
type RenderArtifactReconciler struct {
48
        client.Client
49
        Scheme    *runtime.Scheme
50
        Recorder  events.EventRecorder
51
        APIReader client.Reader
52
        // DeleteTag overrides the OCI tag deletion function used during GC.
53
        // Defaults to ociregistry.DeleteTag; replaced in tests.
54
        DeleteTag func(ctx context.Context, rawRef string, auth authn.Authenticator, insecure bool) error
55
        // WatchNamespace restricts reconciliation to this namespace.
56
        // Should be empty in production (watches all namespaces).
57
        // Intended for use in integration tests only.
58
        WatchNamespace string
59
}
60

61
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=renderartifacts,verbs=get;list;watch;update;patch;delete
62
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=renderartifacts/status,verbs=get;update;patch
63
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=renderartifacts/finalizers,verbs=update
64
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=renderbindings,verbs=get;list;watch
65
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=registries,verbs=get
66
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=referencegrants,verbs=get;list
67
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get
68

69
func (r *RenderArtifactReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
191✔
70
        log := ctrl.LoggerFrom(ctx)
191✔
71

191✔
72
        log.V(1).Info("RenderArtifact is being reconciled", "req", req)
191✔
73

191✔
74
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
237✔
75
                return ctrl.Result{}, nil
46✔
76
        }
46✔
77

78
        artifact := &solarv1alpha1.RenderArtifact{}
145✔
79
        if err := r.Get(ctx, req.NamespacedName, artifact); err != nil {
172✔
80
                if apierrors.IsNotFound(err) {
54✔
81
                        return ctrl.Result{}, nil
27✔
82
                }
27✔
83

84
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get RenderArtifact")
×
85
        }
86

87
        // Handle deletion: attempt OCI tag cleanup, surface errors explicitly, then remove finalizer.
88
        if !artifact.DeletionTimestamp.IsZero() {
159✔
89
                if slices.Contains(artifact.Finalizers, renderArtifactFinalizer) {
82✔
90
                        if err := r.cleanupOCIArtifact(ctx, artifact); err != nil {
74✔
91
                                // Failure is already logged + event fired inside cleanupOCIArtifact.
33✔
92
                                // Keep the finalizer by returning the error so the object stays visible
33✔
93
                                // with the OCICleanup=False condition set.
33✔
94
                                return ctrl.Result{}, err
33✔
95
                        }
33✔
96

97
                        // OCI cleanup succeeded — remove finalizer to allow K8s deletion.
98
                        latest := artifact.DeepCopy()
8✔
99
                        latest.Finalizers = slices.DeleteFunc(latest.Finalizers, func(s string) bool {
16✔
100
                                return s == renderArtifactFinalizer
8✔
101
                        })
8✔
102
                        if err := r.Patch(ctx, latest, client.MergeFrom(artifact)); err != nil {
8✔
103
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to remove finalizer from RenderArtifact")
×
104
                        }
×
105
                }
106

107
                return ctrl.Result{}, nil
8✔
108
        }
109

110
        // Ensure finalizer is set.
111
        if !slices.Contains(artifact.Finalizers, renderArtifactFinalizer) {
102✔
112
                latest := artifact.DeepCopy()
25✔
113
                latest.Finalizers = append(latest.Finalizers, renderArtifactFinalizer)
25✔
114
                if err := r.Patch(ctx, latest, client.MergeFrom(artifact)); err != nil {
25✔
115
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to add finalizer to RenderArtifact")
×
116
                }
×
117

118
                return ctrl.Result{}, nil
25✔
119
        }
120

121
        // Populate status.ChartURL from spec coordinates if not yet set.
122
        chartURL := renderChartURL(artifact.Spec.BaseURL, artifact.Spec.Repository, artifact.Spec.Tag)
52✔
123
        if artifact.Status.ChartURL != chartURL {
77✔
124
                base := artifact.DeepCopy()
25✔
125
                artifact.Status.ChartURL = chartURL
25✔
126
                if err := r.Status().Patch(ctx, artifact, client.MergeFrom(base)); err != nil {
25✔
127
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to update RenderArtifact status")
×
128
                }
×
129
        }
130

131
        // List RenderBindings referencing this artifact.
132
        bindingList := &solarv1alpha1.RenderBindingList{}
52✔
133
        if err := r.List(ctx, bindingList,
52✔
134
                client.InNamespace(artifact.Namespace),
52✔
135
                client.MatchingFields{indexRenderBindingArtifactName: artifact.Name},
52✔
136
        ); err != nil {
52✔
137
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to list RenderBindings for RenderArtifact")
×
138
        }
×
139

140
        if len(bindingList.Items) > 0 {
92✔
141
                // While at least one binding exists, keep the artifact's RegistryRef pinned to
40✔
142
                // a binding that still exists.
40✔
143
                if err := r.repinCredentials(ctx, artifact, bindingList.Items); err != nil {
40✔
NEW
144
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to re-pin RenderArtifact credentials")
×
NEW
145
                }
×
146
        } else {
12✔
147
                // If no bindings remain, trigger GC by deleting this object.
12✔
148
                // The finalizer above will intercept the deletion and handle OCI cleanup.
12✔
149
                // Confirm via direct API call — cache may lag on concurrent creates.
12✔
150
                confirmed := &solarv1alpha1.RenderBindingList{}
12✔
151
                if err := r.APIReader.List(ctx, confirmed, client.InNamespace(artifact.Namespace)); err != nil {
12✔
152
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to confirm RenderBinding absence via API")
×
153
                }
×
154
                for i := range confirmed.Items {
20✔
155
                        if confirmed.Items[i].Spec.RenderArtifactRef.Name == artifact.Name {
8✔
156
                                // A binding exists in the API server that the cache missed.
×
157
                                return ctrl.Result{}, nil
×
158
                        }
×
159
                }
160
                log.V(1).Info("No RenderBindings remain for RenderArtifact — triggering GC",
12✔
161
                        "artifact", artifact.Name)
12✔
162
                if err := r.Delete(ctx, artifact); client.IgnoreNotFound(err) != nil {
12✔
163
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to delete orphaned RenderArtifact")
×
164
                }
×
165
        }
166

167
        return ctrl.Result{}, nil
52✔
168
}
169

170
// cleanupOCIArtifact attempts to delete the OCI tag from the registry.
171
// On failure it sets a status condition and fires a Warning event so the user
172
// can see why the RenderArtifact is stuck, then returns the error to keep the
173
// finalizer in place.
174
func (r *RenderArtifactReconciler) cleanupOCIArtifact(ctx context.Context, artifact *solarv1alpha1.RenderArtifact) error {
41✔
175
        log := ctrl.LoggerFrom(ctx)
41✔
176

41✔
177
        registryHost := normalizeRegistryHost(artifact.Spec.BaseURL)
41✔
178
        rawRef := registryHost + "/" + strings.TrimPrefix(artifact.Spec.Repository, "/") + ":" + artifact.Spec.Tag
41✔
179
        log.V(1).Info("Attempting OCI tag cleanup", "ref", rawRef)
41✔
180

41✔
181
        deleteFn := r.DeleteTag
41✔
182
        if deleteFn == nil {
41✔
183
                deleteFn = ociregistry.DeleteTag
×
184
        }
×
185

186
        auth, plainHTTP, err := r.resolveAuth(ctx, artifact, registryHost)
41✔
187
        if err != nil {
67✔
188
                log.Error(err, "Failed to resolve OCI auth; RenderArtifact will remain until secret is accessible",
26✔
189
                        "artifact", artifact.Name)
26✔
190
                r.Recorder.Eventf(artifact, nil, corev1.EventTypeWarning,
26✔
191
                        "OCICleanupFailed", "Delete",
26✔
192
                        "Failed to resolve OCI auth for %s: %s", rawRef, err.Error())
26✔
193

26✔
194
                latest := artifact.DeepCopy()
26✔
195
                apimeta.SetStatusCondition(&latest.Status.Conditions, metav1.Condition{
26✔
196
                        Type:               ConditionTypeOCICleanup,
26✔
197
                        Status:             metav1.ConditionFalse,
26✔
198
                        ObservedGeneration: artifact.Generation,
26✔
199
                        Reason:             "AuthFailed",
26✔
200
                        Message:            err.Error(),
26✔
201
                })
26✔
202
                if sErr := r.Status().Patch(ctx, latest, client.MergeFrom(artifact)); sErr != nil {
26✔
203
                        log.Error(sErr, "failed to update status condition after OCI auth failure")
×
204
                }
×
205

206
                return err
26✔
207
        }
208

209
        deleteCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
15✔
210
        defer cancel()
15✔
211
        if err := deleteFn(deleteCtx, rawRef, auth, plainHTTP); err != nil {
23✔
212
                // If the tag is already gone, proceed normally.
8✔
213
                var transportErr *transport.Error
8✔
214
                if errors.As(err, &transportErr) && transportErr.StatusCode == http.StatusNotFound {
9✔
215
                        log.V(1).Info("OCI tag already absent — skipping delete", "ref", rawRef)
1✔
216
                        return nil
1✔
217
                }
1✔
218

219
                log.Error(err, "Failed to delete OCI tag; RenderArtifact will remain until deletion succeeds",
7✔
220
                        "ref", rawRef, "artifact", artifact.Name)
7✔
221
                r.Recorder.Eventf(artifact, nil, corev1.EventTypeWarning,
7✔
222
                        "OCICleanupFailed", "Delete",
7✔
223
                        "Failed to delete OCI tag %s: %s", rawRef, err.Error())
7✔
224

7✔
225
                latest := artifact.DeepCopy()
7✔
226
                apimeta.SetStatusCondition(&latest.Status.Conditions, metav1.Condition{
7✔
227
                        Type:               ConditionTypeOCICleanup,
7✔
228
                        Status:             metav1.ConditionFalse,
7✔
229
                        ObservedGeneration: artifact.Generation,
7✔
230
                        Reason:             "DeleteFailed",
7✔
231
                        Message:            err.Error(),
7✔
232
                })
7✔
233
                // Status patch, if it fails, the event + log are visible in kubectl
7✔
234
                if sErr := r.Status().Patch(ctx, latest, client.MergeFrom(artifact)); sErr != nil {
7✔
235
                        log.Error(sErr, "failed to update status condition after OCI cleanup failure")
×
236
                }
×
237

238
                return err
7✔
239
        }
240

241
        log.V(1).Info("OCI tag deleted successfully", "ref", rawRef)
7✔
242
        r.Recorder.Eventf(artifact, nil, corev1.EventTypeNormal,
7✔
243
                "OCICleanupSucceeded", "Delete",
7✔
244
                "Successfully deleted OCI tag %s", rawRef)
7✔
245

7✔
246
        return nil
7✔
247
}
248

249
// repinCredentials keeps artifact.Spec.RegistryRef pinned to the Registry snapshotted on
250
// a still-existing RenderBinding. Bindings that carry no RegistryRef are not candidates;
251
// among the rest the lowest name wins, so repeated reconciles converge instead of flapping
252
// between equally-valid choices.
253
// Because this runs on every RenderBinding create/update/delete event (see
254
// mapRenderBindingToArtifact), the artifact's pinned RegistryRef is always synced to a
255
// binding that exists, including immediately after the second-to-last binding is
256
// removed, which is exactly the moment that matters: it leaves the artifact holding a
257
// Registry reference that was valid for the binding that survives until the final
258
// removal, which is what the finalizer step needs to delete the OCI tag.
259
func (r *RenderArtifactReconciler) repinCredentials(ctx context.Context, artifact *solarv1alpha1.RenderArtifact, bindings []solarv1alpha1.RenderBinding) error {
40✔
260
        slices.SortFunc(bindings, func(a, b solarv1alpha1.RenderBinding) int { return strings.Compare(a.Name, b.Name) })
42✔
261

262
        // RegistryRef is optional, so bindings written before it existed carry nil. Skip those
263
        // instead of pinning nil over a working reference
264
        // If no binding carries a reference, keep what the artifact
265
        // already has: a stale-but-valid ref deletes the tag, nil does not.
266
        idx := slices.IndexFunc(bindings, func(b solarv1alpha1.RenderBinding) bool {
82✔
267
                return b.Spec.RegistryRef != nil
42✔
268
        })
42✔
269
        if idx < 0 {
49✔
270
                return nil
9✔
271
        }
9✔
272
        chosen := bindings[idx]
31✔
273

31✔
274
        if registryRefEqual(artifact.Spec.RegistryRef, chosen.Spec.RegistryRef) {
59✔
275
                return nil
28✔
276
        }
28✔
277

278
        latest := artifact.DeepCopy()
3✔
279
        latest.Spec.RegistryRef = chosen.Spec.RegistryRef
3✔
280

3✔
281
        return r.Patch(ctx, latest, client.MergeFrom(artifact))
3✔
282
}
283

284
func registryRefEqual(a, b *solarv1alpha1.ObjectReference) bool {
75✔
285
        if a == nil || b == nil {
75✔
NEW
286
                return a == b
×
NEW
287
        }
×
288

289
        return *a == *b
75✔
290
}
291

292
func (r *RenderArtifactReconciler) resolveAuth(ctx context.Context, artifact *solarv1alpha1.RenderArtifact, registryHost string) (authn.Authenticator, bool, error) {
46✔
293
        log := ctrl.LoggerFrom(ctx)
46✔
294

46✔
295
        if artifact.Spec.RegistryRef == nil {
59✔
296
                return authn.Anonymous, false, nil
13✔
297
        }
13✔
298

299
        registryNamespace := artifact.Namespace
33✔
300
        if artifact.Spec.RegistryRef.Namespace != "" {
36✔
301
                registryNamespace = artifact.Spec.RegistryRef.Namespace
3✔
302
        }
3✔
303

304
        // RegistryRef is meant to be controller-owned, but nothing stops a principal with
305
        // create/update on RenderArtifact from authoring one. Riding the Target's grant would
306
        // then hand those credentials to anyone who can write a RenderArtifact in a namespace
307
        // some Target happens to be granted from, so the grant must name RenderArtifact itself.
308
        if registryNamespace != artifact.Namespace {
36✔
309
                granted, err := registryGranted(ctx, r.APIReader, registryNamespace, "RenderArtifact", artifact.Namespace)
3✔
310
                if err != nil {
3✔
NEW
311
                        return nil, false, fmt.Errorf("failed to check ReferenceGrant for Registry %s/%s: %w",
×
NEW
312
                                registryNamespace, artifact.Spec.RegistryRef.Name, err)
×
NEW
313
                }
×
314
                if !granted {
5✔
315
                        return nil, false, fmt.Errorf(
2✔
316
                                "no ReferenceGrant in namespace %s with from[].kind=RenderArtifact, from[].namespace=%s and to[].kind=Registry "+
2✔
317
                                        "allows RenderArtifact %s/%s to access Registry %s/%s",
2✔
318
                                registryNamespace, artifact.Namespace,
2✔
319
                                artifact.Namespace, artifact.Name, registryNamespace, artifact.Spec.RegistryRef.Name)
2✔
320
                }
2✔
321
        }
322

323
        registry := &solarv1alpha1.Registry{}
31✔
324
        if err := r.APIReader.Get(ctx, client.ObjectKey{
31✔
325
                Name:      artifact.Spec.RegistryRef.Name,
31✔
326
                Namespace: registryNamespace,
31✔
327
        }, registry); err != nil {
31✔
NEW
328
                return nil, false, fmt.Errorf("failed to get Registry %s/%s: %w", registryNamespace, artifact.Spec.RegistryRef.Name, err)
×
NEW
329
        }
×
330

331
        // The grant authorizes this namespace to use the Registry, not to use its credentials
332
        // against an arbitrary host. spec.baseURL is what the delete is aimed at, and a Registry
333
        // secret may hold auths for several hosts, so refuse unless the artifact points at the
334
        // Registry's own hostname. Artifacts the Target controller produced always do
335
        if artifactHost, registryHostname := registryHost, normalizeRegistryHost(registry.Spec.Hostname); artifactHost != registryHostname {
32✔
336
                return nil, false, fmt.Errorf(
1✔
337
                        "RenderArtifact %s/%s targets host %q but Registry %s/%s serves %q; refusing to use its credentials",
1✔
338
                        artifact.Namespace, artifact.Name, artifactHost, registryNamespace, registry.Name, registryHostname)
1✔
339
        }
1✔
340

341
        if registry.Spec.SolarSecretRef == nil {
31✔
342
                return authn.Anonymous, registry.Spec.PlainHTTP, nil
1✔
343
        }
1✔
344

345
        secret := &corev1.Secret{}
29✔
346
        if err := r.Get(ctx, client.ObjectKey{
29✔
347
                Name:      registry.Spec.SolarSecretRef.Name,
29✔
348
                Namespace: registry.Namespace,
29✔
349
        }, secret); err != nil {
55✔
350
                log.Error(err, "Failed to get push secret for OCI auth",
26✔
351
                        "secret", registry.Spec.SolarSecretRef.Name)
26✔
352

26✔
353
                return nil, false, fmt.Errorf("failed to get push secret %s/%s: %w", registry.Namespace, registry.Spec.SolarSecretRef.Name, err)
26✔
354
        }
26✔
355

356
        auth, err := ociAuthFromSecret(secret, registryHost)
3✔
357
        if err != nil {
3✔
358
                // A malformed dockerconfigjson is a configuration error; log it so the operator
×
359
                // is aware, but fall back to anonymous rather than blocking OCI cleanup.
×
360
                log.Error(err, "Malformed push secret; falling back to anonymous OCI auth",
×
NEW
361
                        "secret", fmt.Sprintf("%s/%s", registry.Namespace, registry.Spec.SolarSecretRef.Name))
×
362
        }
×
363

364
        return auth, registry.Spec.PlainHTTP, nil
3✔
365
}
366

367
// normalizeRegistryHost strips the oci:// scheme and any trailing slash so a
368
// Registry hostname and an artifact baseURL can be compared as written by either side.
369
func normalizeRegistryHost(s string) string {
72✔
370
        return strings.TrimPrefix(strings.TrimSuffix(s, "/"), "oci://")
72✔
371
}
72✔
372

373
// ociAuthFromSecret extracts OCI credentials from a Kubernetes Secret.
374
// callers should log the error and decide whether to fall back to anonymous or abort.
375
func ociAuthFromSecret(secret *corev1.Secret, registryHost string) (authn.Authenticator, error) {
3✔
376
        if secret.Type == corev1.SecretTypeBasicAuth {
6✔
377
                user := string(secret.Data["username"])
3✔
378
                pass := string(secret.Data["password"])
3✔
379
                if user != "" || pass != "" {
6✔
380
                        return authn.FromConfig(authn.AuthConfig{Username: user, Password: pass}), nil
3✔
381
                }
3✔
382

383
                return authn.Anonymous, nil
×
384
        }
385

386
        data := secret.Data[corev1.DockerConfigJsonKey]
×
387
        if len(data) == 0 {
×
388
                return authn.Anonymous, nil
×
389
        }
×
390

391
        var cfg struct {
×
392
                Auths map[string]authn.AuthConfig `json:"auths"`
×
393
        }
×
394
        if err := json.Unmarshal(data, &cfg); err != nil {
×
395
                return authn.Anonymous, fmt.Errorf("failed to parse dockerconfigjson in secret %s/%s: %w", secret.Namespace, secret.Name, err)
×
396
        }
×
397

398
        if ac, ok := cfg.Auths[registryHost]; ok {
×
399
                return authn.FromConfig(ac), nil
×
400
        }
×
401

402
        if ac, ok := cfg.Auths["https://"+registryHost]; ok {
×
403
                return authn.FromConfig(ac), nil
×
404
        }
×
405

406
        return authn.Anonymous, nil
×
407
}
408

409
// mapRenderBindingToArtifact maps a RenderBinding event to a reconcile request
410
// for the RenderArtifact it references, so the GC controller is triggered on
411
// every RenderBinding deletion.
412
func mapRenderBindingToArtifact(_ context.Context, obj client.Object) []reconcile.Request {
48✔
413
        rb, ok := obj.(*solarv1alpha1.RenderBinding)
48✔
414
        if !ok {
48✔
415
                return nil
×
416
        }
×
417

418
        if rb.Spec.RenderArtifactRef.Name == "" {
48✔
419
                return nil
×
420
        }
×
421

422
        return []reconcile.Request{
48✔
423
                {
48✔
424
                        NamespacedName: types.NamespacedName{
48✔
425
                                Name:      rb.Spec.RenderArtifactRef.Name,
48✔
426
                                Namespace: rb.Namespace,
48✔
427
                        },
48✔
428
                },
48✔
429
        }
48✔
430
}
431

432
// SetupWithManager sets up the controller with the Manager.
433
func (r *RenderArtifactReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
434
        return ctrl.NewControllerManagedBy(mgr).
1✔
435
                For(&solarv1alpha1.RenderArtifact{}).
1✔
436
                Watches(
1✔
437
                        &solarv1alpha1.RenderBinding{},
1✔
438
                        handler.EnqueueRequestsFromMapFunc(mapRenderBindingToArtifact),
1✔
439
                ).
1✔
440
                Complete(r)
1✔
441
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc