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

opendefensecloud / solution-arsenal / 24822429227

23 Apr 2026 07:21AM UTC coverage: 72.505% (-0.5%) from 73.044%
24822429227

Pull #436

github

web-flow
Merge 4275ff704 into 7e082458b
Pull Request #436: Registry credentials PoC

190 of 212 new or added lines in 6 files covered. (89.62%)

41 existing lines in 2 files now uncovered.

2165 of 2986 relevant lines covered (72.51%)

34.18 hits per line

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

73.38
/pkg/controller/target_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
        "errors"
9
        "fmt"
10
        "net/url"
11
        "slices"
12
        "sort"
13
        "strings"
14
        "time"
15

16
        ociname "github.com/google/go-containerregistry/pkg/name"
17
        corev1 "k8s.io/api/core/v1"
18
        apiequality "k8s.io/apimachinery/pkg/api/equality"
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/builder"
27
        "sigs.k8s.io/controller-runtime/pkg/client"
28
        "sigs.k8s.io/controller-runtime/pkg/handler"
29
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
30

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

34
const (
35
        targetFinalizer = "solar.opendefense.cloud/target-finalizer"
36

37
        ConditionTypeRegistryResolved = "RegistryResolved"
38
        ConditionTypeReleasesRendered = "ReleasesRendered"
39
        ConditionTypeBootstrapReady   = "BootstrapReady"
40
)
41

42
var ErrReleaseNotRenderedYet = errors.New("release is not rendered yet")
43

44
type releaseInfo struct {
45
        name              string
46
        release           *solarv1alpha1.Release
47
        cv                *solarv1alpha1.ComponentVersion
48
        resolvedResources map[string]solarv1alpha1.ResourceAccess
49
        rtName            string
50
        chartURL          string
51
}
52

53
type TargetReconciler struct {
54
        client.Client
55
        Scheme   *runtime.Scheme
56
        Recorder events.EventRecorder
57
        // WatchNamespace restricts reconciliation to this namespace.
58
        // Should be empty in production (watches all namespaces).
59
        // Intended for use in integration tests only.
60
        WatchNamespace string
61
}
62

63
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets,verbs=get;list;watch;create;update;patch;delete
64
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets/status,verbs=get;update;patch
65
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets/finalizers,verbs=update
66
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=registries,verbs=get;list;watch
67
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=releasebindings,verbs=get;list;watch
68
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=registrybindings,verbs=get;list;watch
69
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=releases,verbs=get;list;watch
70
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=componentversions,verbs=get;list;watch
71
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks,verbs=get;list;watch;create;update;patch;delete
72
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
73
//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch
74

75
// Reconcile collects ReleaseBindings, resolves the render registry, creates per-release
76
// RenderTasks (with dedup), and creates a per-target bootstrap RenderTask.
77
func (r *TargetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
97✔
78
        log := ctrl.LoggerFrom(ctx)
97✔
79

97✔
80
        log.V(1).Info("Target is being reconciled", "req", req)
97✔
81

97✔
82
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
126✔
83
                return ctrl.Result{}, nil
29✔
84
        }
29✔
85

86
        // Fetch target
87
        target := &solarv1alpha1.Target{}
68✔
88
        if err := r.Get(ctx, req.NamespacedName, target); err != nil {
69✔
89
                if apierrors.IsNotFound(err) {
2✔
90
                        return ctrl.Result{}, nil
1✔
91
                }
1✔
92

93
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get object")
×
94
        }
95

96
        // Handle deletion
97
        if !target.DeletionTimestamp.IsZero() {
68✔
98
                log.V(1).Info("Target is being deleted")
1✔
99
                r.Recorder.Eventf(target, nil, corev1.EventTypeWarning, "Deleting", "Reconcile", "Target is being deleted, cleaning up RenderTasks")
1✔
100

1✔
101
                // Delete owned RenderTasks
1✔
102
                if err := r.deleteOwnedRenderTasks(ctx, target); err != nil {
1✔
103
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to delete owned RenderTasks")
×
104
                }
×
105

106
                // Remove finalizer
107
                if slices.Contains(target.Finalizers, targetFinalizer) {
2✔
108
                        latest := &solarv1alpha1.Target{}
1✔
109
                        if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
1✔
110
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get latest Target for finalizer removal")
×
111
                        }
×
112

113
                        original := latest.DeepCopy()
1✔
114
                        latest.Finalizers = slices.DeleteFunc(latest.Finalizers, func(s string) bool {
2✔
115
                                return s == targetFinalizer
1✔
116
                        })
1✔
117
                        if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
1✔
118
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to remove finalizer from Target")
×
119
                        }
×
120
                }
121

122
                return ctrl.Result{}, nil
1✔
123
        }
124

125
        // Set finalizer if not set
126
        if !slices.Contains(target.Finalizers, targetFinalizer) {
81✔
127
                latest := &solarv1alpha1.Target{}
15✔
128
                if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
15✔
129
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get latest Target for finalizer addition")
×
130
                }
×
131

132
                original := latest.DeepCopy()
15✔
133
                latest.Finalizers = append(latest.Finalizers, targetFinalizer)
15✔
134
                if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
15✔
135
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to add finalizer to Target")
×
136
                }
×
137

138
                return ctrl.Result{}, nil
15✔
139
        }
140

141
        // Resolve render registry
142
        registry := &solarv1alpha1.Registry{}
51✔
143
        if err := r.Get(ctx, client.ObjectKey{
51✔
144
                Name:      target.Spec.RenderRegistryRef.Name,
51✔
145
                Namespace: target.Namespace,
51✔
146
        }, registry); err != nil {
71✔
147
                if apierrors.IsNotFound(err) {
40✔
148
                        if condErr := r.setCondition(ctx, target, ConditionTypeRegistryResolved, metav1.ConditionFalse, "NotFound",
20✔
149
                                "Registry not found: "+target.Spec.RenderRegistryRef.Name); condErr != nil {
20✔
150
                                return ctrl.Result{}, condErr
×
151
                        }
×
152

153
                        return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
20✔
154
                }
155

156
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Registry")
×
157
        }
158

159
        if registry.Spec.SolarSecretRef == nil {
33✔
160
                if condErr := r.setCondition(ctx, target, ConditionTypeRegistryResolved, metav1.ConditionFalse, "MissingSolarSecretRef",
2✔
161
                        "Registry does not have SolarSecretRef set, required for rendering"); condErr != nil {
2✔
162
                        return ctrl.Result{}, condErr
×
163
                }
×
164

165
                return ctrl.Result{}, nil
2✔
166
        }
167

168
        if condErr := r.setCondition(ctx, target, ConditionTypeRegistryResolved, metav1.ConditionTrue, "Resolved",
29✔
169
                "Registry resolved: "+registry.Name); condErr != nil {
29✔
170
                return ctrl.Result{}, condErr
×
171
        }
×
172

173
        // Collect ReleaseBindings for this target
174
        bindingList := &solarv1alpha1.ReleaseBindingList{}
29✔
175
        if err := r.List(ctx, bindingList,
29✔
176
                client.InNamespace(target.Namespace),
29✔
177
                client.MatchingFields{indexReleaseBindingTargetName: target.Name},
29✔
178
        ); err != nil {
29✔
179
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to list ReleaseBindings")
×
180
        }
×
181

182
        if len(bindingList.Items) == 0 {
37✔
183
                log.V(1).Info("No ReleaseBindings found for target")
8✔
184
                if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "NoBindings",
8✔
185
                        "No ReleaseBindings found for this target"); condErr != nil {
8✔
UNCOV
186
                        return ctrl.Result{}, condErr
×
UNCOV
187
                }
×
188

189
                return ctrl.Result{}, nil
8✔
190
        }
191

192
        // Collect RegistryBindings for this target
193
        regBindingList := &solarv1alpha1.RegistryBindingList{}
21✔
194
        if err := r.List(ctx, regBindingList,
21✔
195
                client.InNamespace(target.Namespace),
21✔
196
                client.MatchingFields{indexRegistryBindingTargetName: target.Name},
21✔
197
        ); err != nil {
21✔
NEW
198
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to list RegistryBindings")
×
NEW
199
        }
×
200

201
        // Resolve each RegistryBinding to its Registry
202
        var regBindings []registryBindingInfo
21✔
203

21✔
204
        for i := range regBindingList.Items {
60✔
205
                rb := &regBindingList.Items[i]
39✔
206
                reg := &solarv1alpha1.Registry{}
39✔
207

39✔
208
                if err := r.Get(ctx, client.ObjectKey{
39✔
209
                        Name:      rb.Spec.RegistryRef.Name,
39✔
210
                        Namespace: target.Namespace,
39✔
211
                }, reg); err != nil {
39✔
NEW
212
                        if apierrors.IsNotFound(err) {
×
NEW
213
                                log.V(1).Info("Registry for RegistryBinding not found", "registry", rb.Spec.RegistryRef.Name)
×
NEW
214

×
NEW
215
                                continue
×
216
                        }
217

NEW
218
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Registry for RegistryBinding")
×
219
                }
220

221
                regBindings = append(regBindings, registryBindingInfo{binding: rb, registry: reg})
39✔
222
        }
223

224
        // For each bound release, ensure a per-release RenderTask exists
225
        var releases []releaseInfo
21✔
226

21✔
227
        pendingDeps := false
21✔
228

21✔
229
        for _, binding := range bindingList.Items {
51✔
230
                rel := &solarv1alpha1.Release{}
30✔
231
                if err := r.Get(ctx, client.ObjectKey{
30✔
232
                        Name:      binding.Spec.ReleaseRef.Name,
30✔
233
                        Namespace: target.Namespace,
30✔
234
                }, rel); err != nil {
30✔
235
                        if apierrors.IsNotFound(err) {
×
236
                                log.V(1).Info("Release not found", "release", binding.Spec.ReleaseRef.Name)
×
237
                                pendingDeps = true
×
238

×
239
                                continue
×
240
                        }
241

242
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Release")
×
243
                }
244

245
                cv := &solarv1alpha1.ComponentVersion{}
30✔
246
                if err := r.Get(ctx, client.ObjectKey{
30✔
247
                        Name:      rel.Spec.ComponentVersionRef.Name,
30✔
248
                        Namespace: target.Namespace,
30✔
249
                }, cv); err != nil {
30✔
250
                        if apierrors.IsNotFound(err) {
×
251
                                log.V(1).Info("ComponentVersion not found", "cv", rel.Spec.ComponentVersionRef.Name)
×
252
                                pendingDeps = true
×
253

×
254
                                continue
×
255
                        }
256

257
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get ComponentVersion")
×
258
                }
259

260
                // Resolve resources through RegistryBindings
261
                resolvedRes, resolveErr := resolveResources(cv.Spec.Resources, regBindings)
30✔
262
                if resolveErr != nil {
30✔
NEW
263
                        log.V(1).Info("Failed to resolve resources for release", "release", rel.Name, "error", resolveErr)
×
NEW
264
                        pendingDeps = true
×
NEW
265

×
NEW
266
                        continue
×
267
                }
268

269
                rtName := releaseRenderTaskName(rel.Name, target.Name, rel.GetGeneration())
30✔
270
                releases = append(releases, releaseInfo{
30✔
271
                        name:              rel.Name,
30✔
272
                        release:           rel,
30✔
273
                        cv:                cv,
30✔
274
                        resolvedResources: resolvedRes,
30✔
275
                        rtName:            rtName,
30✔
276
                })
30✔
277
        }
278

279
        // Create per-release RenderTasks (one per target+release pair).
280
        // The renderer job handles dedup by skipping if the chart already exists in the registry.
281
        allRendered := true
21✔
282

21✔
283
        for i, ri := range releases {
51✔
284
                rt := &solarv1alpha1.RenderTask{}
30✔
285
                err := r.Get(ctx, client.ObjectKey{Name: ri.rtName, Namespace: target.Namespace}, rt)
30✔
286

30✔
287
                if apierrors.IsNotFound(err) {
33✔
288
                        spec := r.computeReleaseRenderTaskSpec(ri, registry, target)
3✔
289
                        rt = &solarv1alpha1.RenderTask{
3✔
290
                                ObjectMeta: metav1.ObjectMeta{
3✔
291
                                        Name:      ri.rtName,
3✔
292
                                        Namespace: target.Namespace,
3✔
293
                                },
3✔
294
                                Spec: spec,
3✔
295
                        }
3✔
296

3✔
297
                        if err := r.Create(ctx, rt); err != nil {
3✔
298
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to create release RenderTask")
×
299
                        }
×
300

301
                        log.V(1).Info("Created release RenderTask", "release", ri.name, "renderTask", ri.rtName)
3✔
302
                        r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Created", "Create",
3✔
303
                                "Created release RenderTask %s for release %s", ri.rtName, ri.name)
3✔
304
                } else if err != nil {
27✔
305
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get release RenderTask")
×
306
                }
×
307

308
                // Check if release RenderTask is complete
309
                if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobFailed) {
30✔
310
                        if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "ReleaseFailed",
×
311
                                fmt.Sprintf("Release %s rendering failed", ri.name)); condErr != nil {
×
312
                                return ctrl.Result{}, condErr
×
313
                        }
×
314

315
                        return ctrl.Result{}, nil
×
316
                }
317

318
                if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobSucceeded) && rt.Status.ChartURL != "" {
50✔
319
                        releases[i].chartURL = rt.Status.ChartURL
20✔
320
                } else {
30✔
321
                        allRendered = false
10✔
322
                }
10✔
323
        }
324

325
        if pendingDeps {
21✔
326
                if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "MissingDependencies",
×
327
                        "One or more bound Releases or ComponentVersions not found"); condErr != nil {
×
328
                        return ctrl.Result{}, condErr
×
329
                }
×
330

331
                return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
×
332
        }
333

334
        if !allRendered {
31✔
335
                if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "Pending",
10✔
336
                        "Waiting for release RenderTasks to complete"); condErr != nil {
10✔
UNCOV
337
                        return ctrl.Result{}, condErr
×
UNCOV
338
                }
×
339

340
                return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
10✔
341
        }
342

343
        if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionTrue, "AllRendered",
11✔
344
                "All releases rendered successfully"); condErr != nil {
11✔
345
                return ctrl.Result{}, condErr
×
346
        }
×
347

348
        // Determine if a new bootstrap render is needed by checking whether the
349
        // current bootstrapVersion's RenderTask still matches the desired release set.
350
        bootstrapVersion := target.Status.BootstrapVersion
11✔
351
        bootstrapRTName := targetRenderTaskName(target.Name, bootstrapVersion)
11✔
352
        bootstrapRT := &solarv1alpha1.RenderTask{}
11✔
353
        err := r.Get(ctx, client.ObjectKey{Name: bootstrapRTName, Namespace: target.Namespace}, bootstrapRT)
11✔
354

11✔
355
        needsNewBootstrap := false
11✔
356

11✔
357
        switch {
11✔
358
        case apierrors.IsNotFound(err):
1✔
359
                // No RenderTask for the current version yet — create one
1✔
360
                needsNewBootstrap = true
1✔
361
        case err != nil:
×
362
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get bootstrap RenderTask")
×
363
        default:
10✔
364
                // RenderTask exists — check if the desired bootstrap input changed
10✔
365
                // (release set, resolved refs/tags, or userdata)
10✔
366
                desiredInput, inputErr := buildBootstrapInput(target, releases, registry, regBindings)
10✔
367
                if inputErr != nil {
10✔
368
                        return ctrl.Result{}, errLogAndWrap(log, inputErr, "failed to build desired bootstrap input for comparison")
×
369
                }
×
370

371
                existingInput := bootstrapRT.Spec.RendererConfig.BootstrapConfig.Input
10✔
372
                if !apiequality.Semantic.DeepEqual(desiredInput, existingInput) {
11✔
373
                        bootstrapVersion++
1✔
374
                        needsNewBootstrap = true
1✔
375
                }
1✔
376
        }
377

378
        if needsNewBootstrap {
13✔
379
                spec, specErr := r.computeBootstrapRenderTaskSpec(target, releases, registry, regBindings, bootstrapVersion)
2✔
380
                if specErr != nil {
2✔
381
                        return ctrl.Result{}, errLogAndWrap(log, specErr, "failed to compute bootstrap RenderTask spec")
×
382
                }
×
383

384
                bootstrapRTName = targetRenderTaskName(target.Name, bootstrapVersion)
2✔
385
                bootstrapRT = &solarv1alpha1.RenderTask{
2✔
386
                        ObjectMeta: metav1.ObjectMeta{
2✔
387
                                Name:      bootstrapRTName,
2✔
388
                                Namespace: target.Namespace,
2✔
389
                        },
2✔
390
                        Spec: spec,
2✔
391
                }
2✔
392

2✔
393
                if err := r.Create(ctx, bootstrapRT); err != nil {
2✔
UNCOV
394
                        if !apierrors.IsAlreadyExists(err) {
×
395
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to create bootstrap RenderTask")
×
396
                        }
×
397

UNCOV
398
                        if err := r.Get(ctx, client.ObjectKey{Name: bootstrapRTName, Namespace: target.Namespace}, bootstrapRT); err != nil {
×
399
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get existing bootstrap RenderTask")
×
400
                        }
×
401
                } else {
2✔
402
                        log.V(1).Info("Created bootstrap RenderTask", "renderTask", bootstrapRTName, "bootstrapVersion", bootstrapVersion)
2✔
403
                        r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Created", "Create",
2✔
404
                                "Created bootstrap RenderTask %s (version %d)", bootstrapRTName, bootstrapVersion)
2✔
405
                }
2✔
406

407
                // Persist the new bootstrapVersion in status
408
                if bootstrapVersion != target.Status.BootstrapVersion {
3✔
409
                        target.Status.BootstrapVersion = bootstrapVersion
1✔
410
                        if err := r.Status().Update(ctx, target); err != nil {
1✔
UNCOV
411
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to update Target bootstrapVersion")
×
UNCOV
412
                        }
×
413
                }
414
        }
415

416
        // Update target status from bootstrap RenderTask
417
        if apimeta.IsStatusConditionTrue(bootstrapRT.Status.Conditions, ConditionTypeJobFailed) {
11✔
418
                if condErr := r.setCondition(ctx, target, ConditionTypeBootstrapReady, metav1.ConditionFalse, "Failed",
×
419
                        "Bootstrap rendering failed"); condErr != nil {
×
420
                        return ctrl.Result{}, condErr
×
421
                }
×
422

423
                return ctrl.Result{}, nil
×
424
        }
425

426
        if apimeta.IsStatusConditionTrue(bootstrapRT.Status.Conditions, ConditionTypeJobSucceeded) {
16✔
427
                if condErr := r.setCondition(ctx, target, ConditionTypeBootstrapReady, metav1.ConditionTrue, "Ready",
5✔
428
                        "Bootstrap rendered successfully: "+bootstrapRT.Status.ChartURL); condErr != nil {
5✔
429
                        return ctrl.Result{}, condErr
×
430
                }
×
431

432
                // Clean up stale RenderTasks owned by this target (old versions)
433
                currentRTNames := map[string]struct{}{bootstrapRTName: {}}
5✔
434
                for _, ri := range releases {
13✔
435
                        currentRTNames[ri.rtName] = struct{}{}
8✔
436
                }
8✔
437
                if err := r.deleteStaleRenderTasks(ctx, target, currentRTNames); err != nil {
5✔
438
                        log.Error(err, "failed to clean up stale RenderTasks")
×
439
                }
×
440

441
                return ctrl.Result{}, nil
5✔
442
        }
443

444
        // Still running
445
        return ctrl.Result{}, nil
6✔
446
}
447

448
func (r *TargetReconciler) setCondition(ctx context.Context, target *solarv1alpha1.Target, condType string, status metav1.ConditionStatus, reason, message string) error {
85✔
449
        changed := apimeta.SetStatusCondition(&target.Status.Conditions, metav1.Condition{
85✔
450
                Type:               condType,
85✔
451
                Status:             status,
85✔
452
                ObservedGeneration: target.Generation,
85✔
453
                Reason:             reason,
85✔
454
                Message:            message,
85✔
455
        })
85✔
456
        if changed {
110✔
457
                if err := r.Status().Update(ctx, target); err != nil {
25✔
UNCOV
458
                        return fmt.Errorf("failed to update Target status condition %s: %w", condType, err)
×
UNCOV
459
                }
×
460
        }
461

462
        return nil
85✔
463
}
464

465
// deleteStaleRenderTasks removes RenderTasks owned by this target that are no
466
// longer needed. Any owned RenderTask whose name is not in currentRTNames is
467
// deleted. This covers both old bootstrap versions and old release generations.
468
func (r *TargetReconciler) deleteStaleRenderTasks(ctx context.Context, target *solarv1alpha1.Target, currentRTNames map[string]struct{}) error {
5✔
469
        log := ctrl.LoggerFrom(ctx)
5✔
470

5✔
471
        rtList := &solarv1alpha1.RenderTaskList{}
5✔
472
        if err := r.List(ctx, rtList,
5✔
473
                client.InNamespace(target.Namespace),
5✔
474
                client.MatchingFields{indexOwnerKind: "Target"},
5✔
475
        ); err != nil {
5✔
476
                return err
×
477
        }
×
478

479
        for i := range rtList.Items {
20✔
480
                rt := &rtList.Items[i]
15✔
481
                if rt.Spec.OwnerName != target.Name || rt.Spec.OwnerNamespace != target.Namespace {
15✔
482
                        continue
×
483
                }
484

485
                if _, current := currentRTNames[rt.Name]; current {
28✔
486
                        continue
13✔
487
                }
488

489
                log.V(1).Info("Deleting stale RenderTask", "renderTask", rt.Name)
2✔
490
                if err := r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
2✔
491
                        return err
×
492
                }
×
493

494
                r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Deleted", "Delete",
2✔
495
                        "Deleted stale RenderTask %s", rt.Name)
2✔
496
        }
497

498
        return nil
5✔
499
}
500

501
func (r *TargetReconciler) deleteOwnedRenderTasks(ctx context.Context, target *solarv1alpha1.Target) error {
1✔
502
        rtList := &solarv1alpha1.RenderTaskList{}
1✔
503
        if err := r.List(ctx, rtList,
1✔
504
                client.InNamespace(target.Namespace),
1✔
505
                client.MatchingFields{indexOwnerKind: "Target"},
1✔
506
        ); err != nil {
1✔
507
                return err
×
508
        }
×
509

510
        for i := range rtList.Items {
1✔
511
                rt := &rtList.Items[i]
×
512
                if rt.Spec.OwnerName == target.Name && rt.Spec.OwnerNamespace == target.Namespace {
×
513
                        if err := r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
×
514
                                return err
×
515
                        }
×
516
                }
517
        }
518

519
        return nil
1✔
520
}
521

522
func (r *TargetReconciler) computeReleaseRenderTaskSpec(ri releaseInfo, registry *solarv1alpha1.Registry, target *solarv1alpha1.Target) solarv1alpha1.RenderTaskSpec {
3✔
523
        chartName := fmt.Sprintf("release-%s", ri.release.Name)
3✔
524
        repo := fmt.Sprintf("%s/%s", target.Namespace, chartName)
3✔
525
        tag := fmt.Sprintf("v0.0.%d", ri.release.GetGeneration())
3✔
526

3✔
527
        var targetNamespace string
3✔
528
        if ri.release.Spec.TargetNamespace != nil {
6✔
529
                targetNamespace = *ri.release.Spec.TargetNamespace
3✔
530
        }
3✔
531

532
        return solarv1alpha1.RenderTaskSpec{
3✔
533
                RendererConfig: solarv1alpha1.RendererConfig{
3✔
534
                        Type: solarv1alpha1.RendererConfigTypeRelease,
3✔
535
                        ReleaseConfig: solarv1alpha1.ReleaseConfig{
3✔
536
                                Chart: solarv1alpha1.ChartConfig{
3✔
537
                                        Name:        chartName,
3✔
538
                                        Description: fmt.Sprintf("Release of %s", ri.release.Spec.ComponentVersionRef.Name),
3✔
539
                                        Version:     tag,
3✔
540
                                        AppVersion:  tag,
3✔
541
                                },
3✔
542
                                Input: solarv1alpha1.ReleaseInput{
3✔
543
                                        Component:  solarv1alpha1.ReleaseComponent{Name: ri.cv.Spec.ComponentRef.Name},
3✔
544
                                        Resources:  ri.resolvedResources,
3✔
545
                                        Entrypoint: ri.cv.Spec.Entrypoint,
3✔
546
                                },
3✔
547
                                Values:          ri.release.Spec.Values,
3✔
548
                                TargetNamespace: targetNamespace,
3✔
549
                        },
3✔
550
                },
3✔
551
                Repository:     repo,
3✔
552
                Tag:            tag,
3✔
553
                BaseURL:        registry.Spec.Hostname,
3✔
554
                Insecure:       registry.Spec.PlainHTTP,
3✔
555
                SecretRef:      registry.Spec.SolarSecretRef,
3✔
556
                FailedJobTTL:   ri.release.Spec.FailedJobTTL,
3✔
557
                OwnerName:      target.Name,
3✔
558
                OwnerNamespace: target.Namespace,
3✔
559
                OwnerKind:      "Target",
3✔
560
        }
3✔
561
}
562

563
// buildBootstrapInput constructs the desired BootstrapInput from the current
564
// target and resolved releases. Used for both comparison and spec construction.
565
func buildBootstrapInput(target *solarv1alpha1.Target, releases []releaseInfo, registry *solarv1alpha1.Registry, regBindings []registryBindingInfo) (solarv1alpha1.BootstrapInput, error) {
12✔
566
        resolvedReleases := map[string]solarv1alpha1.ResourceAccess{}
12✔
567

12✔
568
        for _, ri := range releases {
31✔
569
                ref, err := ociname.ParseReference(ri.chartURL)
19✔
570
                if err != nil {
19✔
571
                        return solarv1alpha1.BootstrapInput{}, fmt.Errorf("failed to parse chartURL %s: %w", ri.chartURL, err)
×
572
                }
×
573

574
                repo, err := url.JoinPath(ref.Context().RegistryStr(), ref.Context().RepositoryStr())
19✔
575
                if err != nil {
19✔
576
                        return solarv1alpha1.BootstrapInput{}, err
×
577
                }
×
578

579
                resolvedReleases[ri.name] = solarv1alpha1.ResourceAccess{
19✔
580
                        Repository:     strings.TrimPrefix(repo, "oci://"),
19✔
581
                        Tag:            ref.Identifier(),
19✔
582
                        PullSecretName: registry.Spec.TargetPullSecretName,
19✔
583
                }
19✔
584
        }
585

586
        // Apply RegistryBinding rewrites to bootstrap releases
587
        resolvedReleases = resolveBootstrapReleases(resolvedReleases, regBindings)
12✔
588

12✔
589
        return solarv1alpha1.BootstrapInput{
12✔
590
                Releases: resolvedReleases,
12✔
591
                Userdata: target.Spec.Userdata,
12✔
592
        }, nil
12✔
593
}
594

595
func (r *TargetReconciler) computeBootstrapRenderTaskSpec(target *solarv1alpha1.Target, releases []releaseInfo, registry *solarv1alpha1.Registry, regBindings []registryBindingInfo, bootstrapVersion int64) (solarv1alpha1.RenderTaskSpec, error) {
2✔
596
        input, err := buildBootstrapInput(target, releases, registry, regBindings)
2✔
597
        if err != nil {
2✔
598
                return solarv1alpha1.RenderTaskSpec{}, err
×
599
        }
×
600

601
        releaseNames := make([]string, 0, len(releases))
2✔
602
        for _, ri := range releases {
5✔
603
                releaseNames = append(releaseNames, ri.name)
3✔
604
        }
3✔
605

606
        sort.Strings(releaseNames)
2✔
607

2✔
608
        chartName := fmt.Sprintf("bootstrap-%s", target.Name)
2✔
609
        repo := fmt.Sprintf("%s/%s", target.Namespace, chartName)
2✔
610
        tag := fmt.Sprintf("v0.0.%d", bootstrapVersion)
2✔
611

2✔
612
        return solarv1alpha1.RenderTaskSpec{
2✔
613
                RendererConfig: solarv1alpha1.RendererConfig{
2✔
614
                        Type: solarv1alpha1.RendererConfigTypeBootstrap,
2✔
615
                        BootstrapConfig: solarv1alpha1.BootstrapConfig{
2✔
616
                                Chart: solarv1alpha1.ChartConfig{
2✔
617
                                        Name:        chartName,
2✔
618
                                        Description: fmt.Sprintf("Bootstrap of %v", releaseNames),
2✔
619
                                        Version:     tag,
2✔
620
                                        AppVersion:  tag,
2✔
621
                                },
2✔
622
                                Input: input,
2✔
623
                        },
2✔
624
                },
2✔
625
                Repository:     repo,
2✔
626
                Tag:            tag,
2✔
627
                BaseURL:        registry.Spec.Hostname,
2✔
628
                Insecure:       registry.Spec.PlainHTTP,
2✔
629
                SecretRef:      registry.Spec.SolarSecretRef,
2✔
630
                OwnerName:      target.Name,
2✔
631
                OwnerNamespace: target.Namespace,
2✔
632
                OwnerKind:      "Target",
2✔
633
        }, nil
2✔
634
}
635

636
// SetupWithManager sets up the controller with the Manager.
637
func (r *TargetReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
638
        return ctrl.NewControllerManagedBy(mgr).
1✔
639
                For(&solarv1alpha1.Target{}).
1✔
640
                Watches(
1✔
641
                        &solarv1alpha1.ReleaseBinding{},
1✔
642
                        handler.EnqueueRequestsFromMapFunc(r.mapReleaseBindingToTarget),
1✔
643
                ).
1✔
644
                Watches(
1✔
645
                        &solarv1alpha1.RegistryBinding{},
1✔
646
                        handler.EnqueueRequestsFromMapFunc(r.mapRegistryBindingToTarget),
1✔
647
                ).
1✔
648
                Watches(
1✔
649
                        &solarv1alpha1.RenderTask{},
1✔
650
                        handler.EnqueueRequestsFromMapFunc(mapRenderTaskToOwner("Target")),
1✔
651
                        builder.WithPredicates(renderTaskStatusChangePredicate()),
1✔
652
                ).
1✔
653
                Watches(
1✔
654
                        &solarv1alpha1.Registry{},
1✔
655
                        handler.EnqueueRequestsFromMapFunc(r.mapRegistryToTargets),
1✔
656
                ).
1✔
657
                Watches(
1✔
658
                        &solarv1alpha1.Release{},
1✔
659
                        handler.EnqueueRequestsFromMapFunc(r.mapReleaseToTargets),
1✔
660
                ).
1✔
661
                Complete(r)
1✔
662
}
1✔
663

664
// mapRegistryToTargets maps a Registry event to reconcile requests for all
665
// Targets in the same namespace that reference it via renderRegistryRef.
666
func (r *TargetReconciler) mapRegistryToTargets(ctx context.Context, obj client.Object) []reconcile.Request {
8✔
667
        reg, ok := obj.(*solarv1alpha1.Registry)
8✔
668
        if !ok {
8✔
669
                return nil
×
670
        }
×
671

672
        targetList := &solarv1alpha1.TargetList{}
8✔
673
        if err := r.List(ctx, targetList, client.InNamespace(reg.Namespace)); err != nil {
8✔
674
                ctrl.LoggerFrom(ctx).Error(err, "failed to list Targets for Registry", "registry", reg.Name)
×
675

×
676
                return nil
×
677
        }
×
678

679
        var requests []reconcile.Request
8✔
680
        for _, t := range targetList.Items {
8✔
UNCOV
681
                if t.Spec.RenderRegistryRef.Name == reg.Name {
×
UNCOV
682
                        requests = append(requests, reconcile.Request{
×
UNCOV
683
                                NamespacedName: types.NamespacedName{
×
UNCOV
684
                                        Name:      t.Name,
×
UNCOV
685
                                        Namespace: t.Namespace,
×
UNCOV
686
                                },
×
UNCOV
687
                        })
×
UNCOV
688
                }
×
689
        }
690

691
        return requests
8✔
692
}
693

694
// mapReleaseToTargets maps a Release event to reconcile requests for all
695
// Targets that are bound to the release via ReleaseBindings.
696
func (r *TargetReconciler) mapReleaseToTargets(ctx context.Context, obj client.Object) []reconcile.Request {
15✔
697
        rel, ok := obj.(*solarv1alpha1.Release)
15✔
698
        if !ok {
15✔
699
                return nil
×
700
        }
×
701

702
        bindingList := &solarv1alpha1.ReleaseBindingList{}
15✔
703
        if err := r.List(ctx, bindingList,
15✔
704
                client.InNamespace(rel.Namespace),
15✔
705
                client.MatchingFields{indexReleaseBindingReleaseName: rel.Name},
15✔
706
        ); err != nil {
15✔
707
                ctrl.LoggerFrom(ctx).Error(err, "failed to list ReleaseBindings for Release", "release", rel.Name)
×
708

×
709
                return nil
×
710
        }
×
711

712
        seen := map[string]struct{}{}
15✔
713
        var requests []reconcile.Request
15✔
714

15✔
715
        for _, rb := range bindingList.Items {
15✔
UNCOV
716
                targetName := rb.Spec.TargetRef.Name
×
UNCOV
717
                if _, ok := seen[targetName]; ok {
×
718
                        continue
×
719
                }
720

UNCOV
721
                seen[targetName] = struct{}{}
×
UNCOV
722
                requests = append(requests, reconcile.Request{
×
UNCOV
723
                        NamespacedName: types.NamespacedName{
×
UNCOV
724
                                Name:      targetName,
×
UNCOV
725
                                Namespace: rb.Namespace,
×
UNCOV
726
                        },
×
UNCOV
727
                })
×
728
        }
729

730
        return requests
15✔
731
}
732

733
func (r *TargetReconciler) mapReleaseBindingToTarget(_ context.Context, obj client.Object) []reconcile.Request {
10✔
734
        rb, ok := obj.(*solarv1alpha1.ReleaseBinding)
10✔
735
        if !ok || rb.Spec.TargetRef.Name == "" {
10✔
736
                return nil
×
737
        }
×
738

739
        return []reconcile.Request{
10✔
740
                {
10✔
741
                        NamespacedName: types.NamespacedName{
10✔
742
                                Name:      rb.Spec.TargetRef.Name,
10✔
743
                                Namespace: rb.Namespace,
10✔
744
                        },
10✔
745
                },
10✔
746
        }
10✔
747
}
748

749
func (r *TargetReconciler) mapRegistryBindingToTarget(_ context.Context, obj client.Object) []reconcile.Request {
3✔
750
        rb, ok := obj.(*solarv1alpha1.RegistryBinding)
3✔
751
        if !ok || rb.Spec.TargetRef.Name == "" {
3✔
NEW
752
                return nil
×
NEW
753
        }
×
754

755
        return []reconcile.Request{
3✔
756
                {
3✔
757
                        NamespacedName: types.NamespacedName{
3✔
758
                                Name:      rb.Spec.TargetRef.Name,
3✔
759
                                Namespace: rb.Namespace,
3✔
760
                        },
3✔
761
                },
3✔
762
        }
3✔
763
}
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