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

opendefensecloud / solution-arsenal / 24239656491

10 Apr 2026 10:58AM UTC coverage: 73.887%. First build
24239656491

Pull #395

github

web-flow
Merge 3babd17a2 into ef59b87e2
Pull Request #395: feat: split of additional resources from Target and refactor rendering

476 of 581 new or added lines in 7 files covered. (81.93%)

1975 of 2673 relevant lines covered (73.89%)

26.53 hits per line

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

81.28
/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
        apierrors "k8s.io/apimachinery/pkg/api/errors"
19
        apimeta "k8s.io/apimachinery/pkg/api/meta"
20
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21
        "k8s.io/apimachinery/pkg/runtime"
22
        "k8s.io/apimachinery/pkg/types"
23
        "k8s.io/client-go/tools/events"
24
        ctrl "sigs.k8s.io/controller-runtime"
25
        "sigs.k8s.io/controller-runtime/pkg/builder"
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
)
32

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

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

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

43
type releaseInfo struct {
44
        name     string
45
        release  *solarv1alpha1.Release
46
        cv       *solarv1alpha1.ComponentVersion
47
        rtName   string
48
        chartURL string
49
}
50

51
type TargetReconciler struct {
52
        client.Client
53
        Scheme   *runtime.Scheme
54
        Recorder events.EventRecorder
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=targets,verbs=get;list;watch;create;update;patch;delete
62
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets/status,verbs=get;update;patch
63
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets/finalizers,verbs=update
64
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=registries,verbs=get;list;watch
65
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=releasebindings,verbs=get;list;watch
66
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=releases,verbs=get;list;watch
67
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=componentversions,verbs=get;list;watch
68
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks,verbs=get;list;watch;create;update;patch;delete
69
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
70
//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch
71

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

96✔
77
        log.V(1).Info("Target is being reconciled", "req", req)
96✔
78

96✔
79
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
126✔
80
                return ctrl.Result{}, nil
30✔
81
        }
30✔
82

83
        // Fetch target
84
        target := &solarv1alpha1.Target{}
66✔
85
        if err := r.Get(ctx, req.NamespacedName, target); err != nil {
67✔
86
                if apierrors.IsNotFound(err) {
2✔
87
                        return ctrl.Result{}, nil
1✔
88
                }
1✔
89

NEW
90
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get object")
×
91
        }
92

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

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

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

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

119
                return ctrl.Result{}, nil
1✔
120
        }
121

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

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

135
                return ctrl.Result{}, nil
14✔
136
        }
137

138
        // Resolve render registry
139
        registry := &solarv1alpha1.Registry{}
50✔
140
        if err := r.Get(ctx, client.ObjectKey{
50✔
141
                Name:      target.Spec.RenderRegistryRef.Name,
50✔
142
                Namespace: target.Namespace,
50✔
143
        }, registry); err != nil {
69✔
144
                if apierrors.IsNotFound(err) {
38✔
145
                        r.setCondition(ctx, target, ConditionTypeRegistryResolved, metav1.ConditionFalse, "NotFound",
19✔
146
                                "Registry not found: "+target.Spec.RenderRegistryRef.Name)
19✔
147

19✔
148
                        return ctrl.Result{}, nil
19✔
149
                }
19✔
150

NEW
151
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Registry")
×
152
        }
153

154
        if registry.Spec.SolarSecretRef == nil {
33✔
155
                r.setCondition(ctx, target, ConditionTypeRegistryResolved, metav1.ConditionFalse, "MissingSolarSecretRef",
2✔
156
                        "Registry does not have SolarSecretRef set, required for rendering")
2✔
157

2✔
158
                return ctrl.Result{}, nil
2✔
159
        }
2✔
160

161
        r.setCondition(ctx, target, ConditionTypeRegistryResolved, metav1.ConditionTrue, "Resolved",
29✔
162
                "Registry resolved: "+registry.Name)
29✔
163

29✔
164
        // Collect ReleaseBindings for this target
29✔
165
        bindingList := &solarv1alpha1.ReleaseBindingList{}
29✔
166
        if err := r.List(ctx, bindingList,
29✔
167
                client.InNamespace(target.Namespace),
29✔
168
                client.MatchingFields{indexReleaseBindingTargetName: target.Name},
29✔
169
        ); err != nil {
29✔
NEW
170
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to list ReleaseBindings")
×
NEW
171
        }
×
172

173
        if len(bindingList.Items) == 0 {
36✔
174
                log.V(1).Info("No ReleaseBindings found for target")
7✔
175
                r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "NoBindings",
7✔
176
                        "No ReleaseBindings found for this target")
7✔
177

7✔
178
                return ctrl.Result{}, nil
7✔
179
        }
7✔
180

181
        // For each bound release, ensure a per-release RenderTask exists
182
        var releases []releaseInfo
22✔
183

22✔
184
        for _, binding := range bindingList.Items {
53✔
185
                rel := &solarv1alpha1.Release{}
31✔
186
                if err := r.Get(ctx, client.ObjectKey{
31✔
187
                        Name:      binding.Spec.ReleaseRef.Name,
31✔
188
                        Namespace: target.Namespace,
31✔
189
                }, rel); err != nil {
31✔
NEW
190
                        if apierrors.IsNotFound(err) {
×
NEW
191
                                log.V(1).Info("Release not found", "release", binding.Spec.ReleaseRef.Name)
×
NEW
192

×
NEW
193
                                continue
×
194
                        }
195

NEW
196
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Release")
×
197
                }
198

199
                cv := &solarv1alpha1.ComponentVersion{}
31✔
200
                if err := r.Get(ctx, client.ObjectKey{
31✔
201
                        Name:      rel.Spec.ComponentVersionRef.Name,
31✔
202
                        Namespace: target.Namespace,
31✔
203
                }, cv); err != nil {
31✔
NEW
204
                        if apierrors.IsNotFound(err) {
×
NEW
205
                                log.V(1).Info("ComponentVersion not found", "cv", rel.Spec.ComponentVersionRef.Name)
×
NEW
206

×
NEW
207
                                continue
×
208
                        }
209

NEW
210
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get ComponentVersion")
×
211
                }
212

213
                rtName := releaseRenderTaskName(rel.Name, registry.Name)
31✔
214
                releases = append(releases, releaseInfo{
31✔
215
                        name:    rel.Name,
31✔
216
                        release: rel,
31✔
217
                        cv:      cv,
31✔
218
                        rtName:  rtName,
31✔
219
                })
31✔
220
        }
221

222
        // Create per-release RenderTasks (dedup: if another target shares same registry, reuses same RenderTask)
223
        allRendered := true
22✔
224

22✔
225
        for i, ri := range releases {
53✔
226
                rt := &solarv1alpha1.RenderTask{}
31✔
227
                err := r.Get(ctx, client.ObjectKey{Name: ri.rtName, Namespace: target.Namespace}, rt)
31✔
228

31✔
229
                if apierrors.IsNotFound(err) {
34✔
230
                        spec := r.computeReleaseRenderTaskSpec(ri.release, ri.cv, registry, target)
3✔
231
                        rt = &solarv1alpha1.RenderTask{
3✔
232
                                ObjectMeta: metav1.ObjectMeta{
3✔
233
                                        Name:      ri.rtName,
3✔
234
                                        Namespace: target.Namespace,
3✔
235
                                },
3✔
236
                                Spec: spec,
3✔
237
                        }
3✔
238

3✔
239
                        if err := r.Create(ctx, rt); err != nil {
3✔
NEW
240
                                if apierrors.IsAlreadyExists(err) {
×
NEW
241
                                        // Another target with the same registry already created it; fetch it
×
NEW
242
                                        if err := r.Get(ctx, client.ObjectKey{Name: ri.rtName, Namespace: target.Namespace}, rt); err != nil {
×
NEW
243
                                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get existing RenderTask")
×
NEW
244
                                        }
×
NEW
245
                                } else {
×
NEW
246
                                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to create release RenderTask")
×
NEW
247
                                }
×
248
                        } else {
3✔
249
                                log.V(1).Info("Created release RenderTask", "release", ri.name, "renderTask", ri.rtName)
3✔
250
                                r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Created", "Create",
3✔
251
                                        "Created release RenderTask %s for release %s", ri.rtName, ri.name)
3✔
252
                        }
3✔
253
                } else if err != nil {
28✔
NEW
254
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get release RenderTask")
×
NEW
255
                }
×
256

257
                // Check if release RenderTask is complete
258
                if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobFailed) {
31✔
NEW
259
                        r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "ReleaseFailed",
×
NEW
260
                                fmt.Sprintf("Release %s rendering failed", ri.name))
×
NEW
261

×
NEW
262
                        return ctrl.Result{}, nil
×
NEW
263
                }
×
264

265
                if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobSucceeded) && rt.Status.ChartURL != "" {
52✔
266
                        releases[i].chartURL = rt.Status.ChartURL
21✔
267
                } else {
31✔
268
                        allRendered = false
10✔
269
                }
10✔
270
        }
271

272
        if !allRendered {
32✔
273
                r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "Pending",
10✔
274
                        "Waiting for release RenderTasks to complete")
10✔
275

10✔
276
                return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
10✔
277
        }
10✔
278

279
        r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionTrue, "AllRendered",
12✔
280
                "All releases rendered successfully")
12✔
281

12✔
282
        // Determine if a new bootstrap render is needed by checking whether the
12✔
283
        // current bootstrapVersion's RenderTask still matches the desired release set.
12✔
284
        bootstrapVersion := target.Status.BootstrapVersion
12✔
285
        bootstrapRTName := targetRenderTaskName(target.Name, bootstrapVersion)
12✔
286
        bootstrapRT := &solarv1alpha1.RenderTask{}
12✔
287
        err := r.Get(ctx, client.ObjectKey{Name: bootstrapRTName, Namespace: target.Namespace}, bootstrapRT)
12✔
288

12✔
289
        needsNewBootstrap := false
12✔
290

12✔
291
        switch {
12✔
292
        case apierrors.IsNotFound(err):
1✔
293
                // No RenderTask for the current version yet — create one
1✔
294
                needsNewBootstrap = true
1✔
NEW
295
        case err != nil:
×
NEW
296
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get bootstrap RenderTask")
×
297
        default:
11✔
298
                // RenderTask exists — check if the release set changed
11✔
299
                releaseNames := make([]string, 0, len(releases))
11✔
300
                for _, ri := range releases {
28✔
301
                        releaseNames = append(releaseNames, ri.name)
17✔
302
                }
17✔
303

304
                sort.Strings(releaseNames)
11✔
305

11✔
306
                existingNames := make([]string, 0, len(bootstrapRT.Spec.RendererConfig.BootstrapConfig.Input.Releases))
11✔
307
                for name := range bootstrapRT.Spec.RendererConfig.BootstrapConfig.Input.Releases {
27✔
308
                        existingNames = append(existingNames, name)
16✔
309
                }
16✔
310

311
                sort.Strings(existingNames)
11✔
312

11✔
313
                if !slices.Equal(releaseNames, existingNames) {
12✔
314
                        // Release set changed — bump version and create a new RenderTask
1✔
315
                        bootstrapVersion++
1✔
316
                        needsNewBootstrap = true
1✔
317
                }
1✔
318
        }
319

320
        if needsNewBootstrap {
14✔
321
                spec, specErr := r.computeBootstrapRenderTaskSpec(target, releases, registry, bootstrapVersion)
2✔
322
                if specErr != nil {
2✔
NEW
323
                        return ctrl.Result{}, errLogAndWrap(log, specErr, "failed to compute bootstrap RenderTask spec")
×
NEW
324
                }
×
325

326
                bootstrapRTName = targetRenderTaskName(target.Name, bootstrapVersion)
2✔
327
                bootstrapRT = &solarv1alpha1.RenderTask{
2✔
328
                        ObjectMeta: metav1.ObjectMeta{
2✔
329
                                Name:      bootstrapRTName,
2✔
330
                                Namespace: target.Namespace,
2✔
331
                        },
2✔
332
                        Spec: spec,
2✔
333
                }
2✔
334

2✔
335
                if err := r.Create(ctx, bootstrapRT); err != nil {
2✔
336
                        if !apierrors.IsAlreadyExists(err) {
×
NEW
337
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to create bootstrap RenderTask")
×
NEW
338
                        }
×
339

NEW
340
                        if err := r.Get(ctx, client.ObjectKey{Name: bootstrapRTName, Namespace: target.Namespace}, bootstrapRT); err != nil {
×
NEW
341
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get existing bootstrap RenderTask")
×
342
                        }
×
343
                } else {
2✔
344
                        log.V(1).Info("Created bootstrap RenderTask", "renderTask", bootstrapRTName, "bootstrapVersion", bootstrapVersion)
2✔
345
                        r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Created", "Create",
2✔
346
                                "Created bootstrap RenderTask %s (version %d)", bootstrapRTName, bootstrapVersion)
2✔
347
                }
2✔
348

349
                // Persist the new bootstrapVersion in status
350
                if bootstrapVersion != target.Status.BootstrapVersion {
3✔
351
                        target.Status.BootstrapVersion = bootstrapVersion
1✔
352
                        if err := r.Status().Update(ctx, target); err != nil {
1✔
NEW
353
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to update Target bootstrapVersion")
×
NEW
354
                        }
×
355
                }
356
        }
357

358
        // Update target status from bootstrap RenderTask
359
        if apimeta.IsStatusConditionTrue(bootstrapRT.Status.Conditions, ConditionTypeJobFailed) {
12✔
NEW
360
                r.setCondition(ctx, target, ConditionTypeBootstrapReady, metav1.ConditionFalse, "Failed",
×
NEW
361
                        "Bootstrap rendering failed")
×
362

×
NEW
363
                return ctrl.Result{}, nil
×
NEW
364
        }
×
365

366
        if apimeta.IsStatusConditionTrue(bootstrapRT.Status.Conditions, ConditionTypeJobSucceeded) {
17✔
367
                r.setCondition(ctx, target, ConditionTypeBootstrapReady, metav1.ConditionTrue, "Ready",
5✔
368
                        "Bootstrap rendered successfully: "+bootstrapRT.Status.ChartURL)
5✔
369

5✔
370
                // Clean up stale RenderTasks owned by this target (old bootstrap versions)
5✔
371
                if err := r.deleteStaleRenderTasks(ctx, target, bootstrapRTName); err != nil {
5✔
NEW
372
                        log.Error(err, "failed to clean up stale RenderTasks")
×
373
                }
×
374

375
                return ctrl.Result{}, nil
5✔
376
        }
377

378
        // Still running
379
        return ctrl.Result{}, nil
7✔
380
}
381

382
func (r *TargetReconciler) setCondition(ctx context.Context, target *solarv1alpha1.Target, condType string, status metav1.ConditionStatus, reason, message string) {
84✔
383
        changed := apimeta.SetStatusCondition(&target.Status.Conditions, metav1.Condition{
84✔
384
                Type:               condType,
84✔
385
                Status:             status,
84✔
386
                ObservedGeneration: target.Generation,
84✔
387
                Reason:             reason,
84✔
388
                Message:            message,
84✔
389
        })
84✔
390
        if changed {
109✔
391
                if err := r.Status().Update(ctx, target); err != nil {
26✔
392
                        ctrl.LoggerFrom(ctx).Error(err, "failed to update Target status condition", "type", condType)
1✔
393
                }
1✔
394
        }
395
}
396

397
// deleteStaleRenderTasks removes RenderTasks owned by this target that are no
398
// longer needed. It keeps the current bootstrap RenderTask and all release
399
// RenderTasks (which may be shared across targets). Only old bootstrap
400
// RenderTasks from previous versions are deleted.
401
func (r *TargetReconciler) deleteStaleRenderTasks(ctx context.Context, target *solarv1alpha1.Target, currentBootstrapRT string) error {
5✔
402
        log := ctrl.LoggerFrom(ctx)
5✔
403

5✔
404
        rtList := &solarv1alpha1.RenderTaskList{}
5✔
405
        if err := r.List(ctx, rtList,
5✔
406
                client.InNamespace(target.Namespace),
5✔
407
                client.MatchingFields{indexOwnerKind: "Target"},
5✔
408
        ); err != nil {
5✔
NEW
409
                return err
×
NEW
410
        }
×
411

412
        for i := range rtList.Items {
19✔
413
                rt := &rtList.Items[i]
14✔
414
                if rt.Spec.OwnerName != target.Name || rt.Spec.OwnerNamespace != target.Namespace {
14✔
NEW
415
                        continue
×
416
                }
417

418
                if rt.Name == currentBootstrapRT {
19✔
419
                        continue
5✔
420
                }
421

422
                // Only clean up bootstrap RenderTasks (render-tgt-*), not release ones (render-rel-*)
423
                if rt.Spec.RendererConfig.Type != solarv1alpha1.RendererConfigTypeBootstrap {
17✔
424
                        continue
8✔
425
                }
426

427
                log.V(1).Info("Deleting stale bootstrap RenderTask", "renderTask", rt.Name)
1✔
428
                if err := r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
1✔
NEW
429
                        return err
×
NEW
430
                }
×
431

432
                r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Deleted", "Delete",
1✔
433
                        "Deleted stale bootstrap RenderTask %s", rt.Name)
1✔
434
        }
435

436
        return nil
5✔
437
}
438

439
func (r *TargetReconciler) deleteOwnedRenderTasks(ctx context.Context, target *solarv1alpha1.Target) error {
1✔
440
        rtList := &solarv1alpha1.RenderTaskList{}
1✔
441
        if err := r.List(ctx, rtList,
1✔
442
                client.InNamespace(target.Namespace),
1✔
443
                client.MatchingFields{indexOwnerKind: "Target"},
1✔
444
        ); err != nil {
1✔
NEW
445
                return err
×
NEW
446
        }
×
447

448
        for i := range rtList.Items {
1✔
NEW
449
                rt := &rtList.Items[i]
×
NEW
450
                if rt.Spec.OwnerName == target.Name && rt.Spec.OwnerNamespace == target.Namespace {
×
NEW
451
                        if err := r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
×
NEW
452
                                return err
×
453
                        }
×
454
                }
455
        }
456

457
        return nil
1✔
458
}
459

460
func (r *TargetReconciler) computeReleaseRenderTaskSpec(rel *solarv1alpha1.Release, cv *solarv1alpha1.ComponentVersion, registry *solarv1alpha1.Registry, target *solarv1alpha1.Target) solarv1alpha1.RenderTaskSpec {
3✔
461
        chartName := fmt.Sprintf("release-%s", rel.Name)
3✔
462
        repo := fmt.Sprintf("%s/%s", target.Namespace, chartName)
3✔
463
        tag := fmt.Sprintf("v0.0.%d", rel.GetGeneration())
3✔
464

3✔
465
        return solarv1alpha1.RenderTaskSpec{
3✔
466
                RendererConfig: solarv1alpha1.RendererConfig{
3✔
467
                        Type: solarv1alpha1.RendererConfigTypeRelease,
3✔
468
                        ReleaseConfig: solarv1alpha1.ReleaseConfig{
3✔
469
                                Chart: solarv1alpha1.ChartConfig{
3✔
470
                                        Name:        chartName,
3✔
471
                                        Description: fmt.Sprintf("Release of %s", rel.Spec.ComponentVersionRef.Name),
3✔
472
                                        Version:     tag,
3✔
473
                                        AppVersion:  tag,
3✔
474
                                },
3✔
475
                                Input: solarv1alpha1.ReleaseInput{
3✔
476
                                        Component:  solarv1alpha1.ReleaseComponent{Name: cv.Spec.ComponentRef.Name},
3✔
477
                                        Resources:  cv.Spec.Resources,
3✔
478
                                        Entrypoint: cv.Spec.Entrypoint,
3✔
479
                                },
3✔
480
                                Values: rel.Spec.Values,
3✔
481
                        },
3✔
482
                },
3✔
483
                Repository:     repo,
3✔
484
                Tag:            tag,
3✔
485
                BaseURL:        registry.Spec.Hostname,
3✔
486
                PushSecretRef:  registry.Spec.SolarSecretRef,
3✔
487
                FailedJobTTL:   rel.Spec.FailedJobTTL,
3✔
488
                OwnerName:      target.Name,
3✔
489
                OwnerNamespace: target.Namespace,
3✔
490
                OwnerKind:      "Target",
3✔
491
        }
3✔
492
}
3✔
493

494
func (r *TargetReconciler) computeBootstrapRenderTaskSpec(target *solarv1alpha1.Target, releases []releaseInfo, registry *solarv1alpha1.Registry, bootstrapVersion int64) (solarv1alpha1.RenderTaskSpec, error) {
2✔
495
        resolvedReleases := map[string]solarv1alpha1.ResourceAccess{}
2✔
496
        resolvedReleaseNames := make([]string, 0, len(releases))
2✔
497

2✔
498
        for _, ri := range releases {
5✔
499
                ref, err := ociname.ParseReference(ri.chartURL)
3✔
500
                if err != nil {
3✔
NEW
501
                        return solarv1alpha1.RenderTaskSpec{}, fmt.Errorf("failed to parse chartURL %s: %w", ri.chartURL, err)
×
NEW
502
                }
×
503

504
                repo, err := url.JoinPath(ref.Context().RegistryStr(), ref.Context().RepositoryStr())
3✔
505
                if err != nil {
3✔
NEW
506
                        return solarv1alpha1.RenderTaskSpec{}, err
×
NEW
507
                }
×
508

509
                resolvedReleases[ri.name] = solarv1alpha1.ResourceAccess{
3✔
510
                        Repository: strings.TrimPrefix(repo, "oci://"),
3✔
511
                        Tag:        ref.Identifier(),
3✔
512
                }
3✔
513
                resolvedReleaseNames = append(resolvedReleaseNames, ri.name)
3✔
514
        }
515

516
        sort.Strings(resolvedReleaseNames)
2✔
517

2✔
518
        chartName := fmt.Sprintf("bootstrap-%s", target.Name)
2✔
519
        repo := fmt.Sprintf("%s/%s", target.Namespace, chartName)
2✔
520
        tag := fmt.Sprintf("v0.0.%d", bootstrapVersion)
2✔
521

2✔
522
        return solarv1alpha1.RenderTaskSpec{
2✔
523
                RendererConfig: solarv1alpha1.RendererConfig{
2✔
524
                        Type: solarv1alpha1.RendererConfigTypeBootstrap,
2✔
525
                        BootstrapConfig: solarv1alpha1.BootstrapConfig{
2✔
526
                                Chart: solarv1alpha1.ChartConfig{
2✔
527
                                        Name:        chartName,
2✔
528
                                        Description: fmt.Sprintf("Bootstrap of %v", resolvedReleaseNames),
2✔
529
                                        Version:     tag,
2✔
530
                                        AppVersion:  tag,
2✔
531
                                },
2✔
532
                                Input: solarv1alpha1.BootstrapInput{
2✔
533
                                        Releases: resolvedReleases,
2✔
534
                                        Userdata: target.Spec.Userdata,
2✔
535
                                },
2✔
536
                        },
2✔
537
                },
2✔
538
                Repository:     repo,
2✔
539
                Tag:            tag,
2✔
540
                BaseURL:        registry.Spec.Hostname,
2✔
541
                PushSecretRef:  registry.Spec.SolarSecretRef,
2✔
542
                OwnerName:      target.Name,
2✔
543
                OwnerNamespace: target.Namespace,
2✔
544
                OwnerKind:      "Target",
2✔
545
        }, nil
2✔
546
}
547

548
// SetupWithManager sets up the controller with the Manager.
549
func (r *TargetReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
550
        return ctrl.NewControllerManagedBy(mgr).
1✔
551
                For(&solarv1alpha1.Target{}).
1✔
552
                Watches(
1✔
553
                        &solarv1alpha1.ReleaseBinding{},
1✔
554
                        handler.EnqueueRequestsFromMapFunc(r.mapReleaseBindingToTarget),
1✔
555
                ).
1✔
556
                Watches(
1✔
557
                        &solarv1alpha1.RenderTask{},
1✔
558
                        handler.EnqueueRequestsFromMapFunc(mapRenderTaskToOwner("Target")),
1✔
559
                        builder.WithPredicates(renderTaskStatusChangePredicate()),
1✔
560
                ).
1✔
561
                Complete(r)
1✔
562
}
1✔
563

564
func (r *TargetReconciler) mapReleaseBindingToTarget(_ context.Context, obj client.Object) []reconcile.Request {
10✔
565
        rb, ok := obj.(*solarv1alpha1.ReleaseBinding)
10✔
566
        if !ok || rb.Spec.TargetRef.Name == "" {
10✔
567
                return nil
×
568
        }
×
569

570
        return []reconcile.Request{
10✔
571
                {
10✔
572
                        NamespacedName: types.NamespacedName{
10✔
573
                                Name:      rb.Spec.TargetRef.Name,
10✔
574
                                Namespace: rb.Namespace,
10✔
575
                        },
10✔
576
                },
10✔
577
        }
10✔
578
}
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