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

opendefensecloud / solution-arsenal / 24241142292

10 Apr 2026 11:40AM UTC coverage: 72.944%. First build
24241142292

Pull #395

github

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

514 of 657 new or added lines in 9 files covered. (78.23%)

1987 of 2724 relevant lines covered (72.94%)

23.18 hits per line

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

82.41
/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) {
94✔
75
        log := ctrl.LoggerFrom(ctx)
94✔
76

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

94✔
79
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
125✔
80
                return ctrl.Result{}, nil
31✔
81
        }
31✔
82

83
        // Fetch target
84
        target := &solarv1alpha1.Target{}
63✔
85
        if err := r.Get(ctx, req.NamespacedName, target); err != nil {
64✔
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() {
63✔
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) {
75✔
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{}
47✔
140
        if err := r.Get(ctx, client.ObjectKey{
47✔
141
                Name:      target.Spec.RenderRegistryRef.Name,
47✔
142
                Namespace: target.Namespace,
47✔
143
        }, registry); err != nil {
64✔
144
                if apierrors.IsNotFound(err) {
34✔
145
                        r.setCondition(ctx, target, ConditionTypeRegistryResolved, metav1.ConditionFalse, "NotFound",
17✔
146
                                "Registry not found: "+target.Spec.RenderRegistryRef.Name)
17✔
147

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

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

154
        if registry.Spec.SolarSecretRef == nil {
32✔
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",
28✔
162
                "Registry resolved: "+registry.Name)
28✔
163

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

173
        if len(bindingList.Items) == 0 {
35✔
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
21✔
183

21✔
184
        for _, binding := range bindingList.Items {
51✔
185
                rel := &solarv1alpha1.Release{}
30✔
186
                if err := r.Get(ctx, client.ObjectKey{
30✔
187
                        Name:      binding.Spec.ReleaseRef.Name,
30✔
188
                        Namespace: target.Namespace,
30✔
189
                }, rel); err != nil {
30✔
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{}
30✔
200
                if err := r.Get(ctx, client.ObjectKey{
30✔
201
                        Name:      rel.Spec.ComponentVersionRef.Name,
30✔
202
                        Namespace: target.Namespace,
30✔
203
                }, cv); err != nil {
30✔
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, target.Name)
30✔
214
                releases = append(releases, releaseInfo{
30✔
215
                        name:    rel.Name,
30✔
216
                        release: rel,
30✔
217
                        cv:      cv,
30✔
218
                        rtName:  rtName,
30✔
219
                })
30✔
220
        }
221

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

21✔
226
        for i, ri := range releases {
51✔
227
                rt := &solarv1alpha1.RenderTask{}
30✔
228
                err := r.Get(ctx, client.ObjectKey{Name: ri.rtName, Namespace: target.Namespace}, rt)
30✔
229

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

3✔
240
                        if err := r.Create(ctx, rt); err != nil {
3✔
NEW
241
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to create release RenderTask")
×
NEW
242
                        }
×
243

244
                        log.V(1).Info("Created release RenderTask", "release", ri.name, "renderTask", ri.rtName)
3✔
245
                        r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Created", "Create",
3✔
246
                                "Created release RenderTask %s for release %s", ri.rtName, ri.name)
3✔
247
                } else if err != nil {
27✔
NEW
248
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get release RenderTask")
×
NEW
249
                }
×
250

251
                // Check if release RenderTask is complete
252
                if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobFailed) {
30✔
NEW
253
                        r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "ReleaseFailed",
×
NEW
254
                                fmt.Sprintf("Release %s rendering failed", ri.name))
×
NEW
255

×
NEW
256
                        return ctrl.Result{}, nil
×
NEW
257
                }
×
258

259
                if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobSucceeded) && rt.Status.ChartURL != "" {
50✔
260
                        releases[i].chartURL = rt.Status.ChartURL
20✔
261
                } else {
30✔
262
                        allRendered = false
10✔
263
                }
10✔
264
        }
265

266
        if !allRendered {
31✔
267
                r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "Pending",
10✔
268
                        "Waiting for release RenderTasks to complete")
10✔
269

10✔
270
                return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
10✔
271
        }
10✔
272

273
        r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionTrue, "AllRendered",
11✔
274
                "All releases rendered successfully")
11✔
275

11✔
276
        // Determine if a new bootstrap render is needed by checking whether the
11✔
277
        // current bootstrapVersion's RenderTask still matches the desired release set.
11✔
278
        bootstrapVersion := target.Status.BootstrapVersion
11✔
279
        bootstrapRTName := targetRenderTaskName(target.Name, bootstrapVersion)
11✔
280
        bootstrapRT := &solarv1alpha1.RenderTask{}
11✔
281
        err := r.Get(ctx, client.ObjectKey{Name: bootstrapRTName, Namespace: target.Namespace}, bootstrapRT)
11✔
282

11✔
283
        needsNewBootstrap := false
11✔
284

11✔
285
        switch {
11✔
286
        case apierrors.IsNotFound(err):
1✔
287
                // No RenderTask for the current version yet — create one
1✔
288
                needsNewBootstrap = true
1✔
NEW
289
        case err != nil:
×
NEW
290
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get bootstrap RenderTask")
×
291
        default:
10✔
292
                // RenderTask exists — check if the release set changed
10✔
293
                releaseNames := make([]string, 0, len(releases))
10✔
294
                for _, ri := range releases {
26✔
295
                        releaseNames = append(releaseNames, ri.name)
16✔
296
                }
16✔
297

298
                sort.Strings(releaseNames)
10✔
299

10✔
300
                existingNames := make([]string, 0, len(bootstrapRT.Spec.RendererConfig.BootstrapConfig.Input.Releases))
10✔
301
                for name := range bootstrapRT.Spec.RendererConfig.BootstrapConfig.Input.Releases {
25✔
302
                        existingNames = append(existingNames, name)
15✔
303
                }
15✔
304

305
                sort.Strings(existingNames)
10✔
306

10✔
307
                if !slices.Equal(releaseNames, existingNames) {
11✔
308
                        // Release set changed — bump version and create a new RenderTask
1✔
309
                        bootstrapVersion++
1✔
310
                        needsNewBootstrap = true
1✔
311
                }
1✔
312
        }
313

314
        if needsNewBootstrap {
13✔
315
                spec, specErr := r.computeBootstrapRenderTaskSpec(target, releases, registry, bootstrapVersion)
2✔
316
                if specErr != nil {
2✔
NEW
317
                        return ctrl.Result{}, errLogAndWrap(log, specErr, "failed to compute bootstrap RenderTask spec")
×
NEW
318
                }
×
319

320
                bootstrapRTName = targetRenderTaskName(target.Name, bootstrapVersion)
2✔
321
                bootstrapRT = &solarv1alpha1.RenderTask{
2✔
322
                        ObjectMeta: metav1.ObjectMeta{
2✔
323
                                Name:      bootstrapRTName,
2✔
324
                                Namespace: target.Namespace,
2✔
325
                        },
2✔
326
                        Spec: spec,
2✔
327
                }
2✔
328

2✔
329
                if err := r.Create(ctx, bootstrapRT); err != nil {
2✔
330
                        if !apierrors.IsAlreadyExists(err) {
×
NEW
331
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to create bootstrap RenderTask")
×
NEW
332
                        }
×
333

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

343
                // Persist the new bootstrapVersion in status
344
                if bootstrapVersion != target.Status.BootstrapVersion {
3✔
345
                        target.Status.BootstrapVersion = bootstrapVersion
1✔
346
                        if err := r.Status().Update(ctx, target); err != nil {
1✔
NEW
347
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to update Target bootstrapVersion")
×
NEW
348
                        }
×
349
                }
350
        }
351

352
        // Update target status from bootstrap RenderTask
353
        if apimeta.IsStatusConditionTrue(bootstrapRT.Status.Conditions, ConditionTypeJobFailed) {
11✔
NEW
354
                r.setCondition(ctx, target, ConditionTypeBootstrapReady, metav1.ConditionFalse, "Failed",
×
NEW
355
                        "Bootstrap rendering failed")
×
NEW
356

×
NEW
357
                return ctrl.Result{}, nil
×
NEW
358
        }
×
359

360
        if apimeta.IsStatusConditionTrue(bootstrapRT.Status.Conditions, ConditionTypeJobSucceeded) {
16✔
361
                r.setCondition(ctx, target, ConditionTypeBootstrapReady, metav1.ConditionTrue, "Ready",
5✔
362
                        "Bootstrap rendered successfully: "+bootstrapRT.Status.ChartURL)
5✔
363

5✔
364
                // Clean up stale RenderTasks owned by this target (old bootstrap versions)
5✔
365
                if err := r.deleteStaleRenderTasks(ctx, target, bootstrapRTName); err != nil {
5✔
NEW
366
                        log.Error(err, "failed to clean up stale RenderTasks")
×
367
                }
×
368

369
                return ctrl.Result{}, nil
5✔
370
        }
371

372
        // Still running
373
        return ctrl.Result{}, nil
6✔
374
}
375

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

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

5✔
398
        rtList := &solarv1alpha1.RenderTaskList{}
5✔
399
        if err := r.List(ctx, rtList,
5✔
400
                client.InNamespace(target.Namespace),
5✔
401
                client.MatchingFields{indexOwnerKind: "Target"},
5✔
402
        ); err != nil {
5✔
NEW
403
                return err
×
NEW
404
        }
×
405

406
        for i := range rtList.Items {
19✔
407
                rt := &rtList.Items[i]
14✔
408
                if rt.Spec.OwnerName != target.Name || rt.Spec.OwnerNamespace != target.Namespace {
14✔
NEW
409
                        continue
×
410
                }
411

412
                if rt.Name == currentBootstrapRT {
19✔
413
                        continue
5✔
414
                }
415

416
                // Only clean up bootstrap RenderTasks (render-tgt-*), not release ones (render-rel-*)
417
                if rt.Spec.RendererConfig.Type != solarv1alpha1.RendererConfigTypeBootstrap {
17✔
418
                        continue
8✔
419
                }
420

421
                log.V(1).Info("Deleting stale bootstrap RenderTask", "renderTask", rt.Name)
1✔
422
                if err := r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
1✔
NEW
423
                        return err
×
NEW
424
                }
×
425

426
                r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Deleted", "Delete",
1✔
427
                        "Deleted stale bootstrap RenderTask %s", rt.Name)
1✔
428
        }
429

430
        return nil
5✔
431
}
432

433
func (r *TargetReconciler) deleteOwnedRenderTasks(ctx context.Context, target *solarv1alpha1.Target) error {
1✔
434
        rtList := &solarv1alpha1.RenderTaskList{}
1✔
435
        if err := r.List(ctx, rtList,
1✔
436
                client.InNamespace(target.Namespace),
1✔
437
                client.MatchingFields{indexOwnerKind: "Target"},
1✔
438
        ); err != nil {
1✔
NEW
439
                return err
×
NEW
440
        }
×
441

442
        for i := range rtList.Items {
1✔
NEW
443
                rt := &rtList.Items[i]
×
NEW
444
                if rt.Spec.OwnerName == target.Name && rt.Spec.OwnerNamespace == target.Namespace {
×
NEW
445
                        if err := r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
×
NEW
446
                                return err
×
447
                        }
×
448
                }
449
        }
450

451
        return nil
1✔
452
}
453

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

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

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

2✔
492
        for _, ri := range releases {
5✔
493
                ref, err := ociname.ParseReference(ri.chartURL)
3✔
494
                if err != nil {
3✔
NEW
495
                        return solarv1alpha1.RenderTaskSpec{}, fmt.Errorf("failed to parse chartURL %s: %w", ri.chartURL, err)
×
NEW
496
                }
×
497

498
                repo, err := url.JoinPath(ref.Context().RegistryStr(), ref.Context().RepositoryStr())
3✔
499
                if err != nil {
3✔
NEW
500
                        return solarv1alpha1.RenderTaskSpec{}, err
×
NEW
501
                }
×
502

503
                resolvedReleases[ri.name] = solarv1alpha1.ResourceAccess{
3✔
504
                        Repository: strings.TrimPrefix(repo, "oci://"),
3✔
505
                        Tag:        ref.Identifier(),
3✔
506
                }
3✔
507
                resolvedReleaseNames = append(resolvedReleaseNames, ri.name)
3✔
508
        }
509

510
        sort.Strings(resolvedReleaseNames)
2✔
511

2✔
512
        chartName := fmt.Sprintf("bootstrap-%s", target.Name)
2✔
513
        repo := fmt.Sprintf("%s/%s", target.Namespace, chartName)
2✔
514
        tag := fmt.Sprintf("v0.0.%d", bootstrapVersion)
2✔
515

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

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

558
func (r *TargetReconciler) mapReleaseBindingToTarget(_ context.Context, obj client.Object) []reconcile.Request {
10✔
559
        rb, ok := obj.(*solarv1alpha1.ReleaseBinding)
10✔
560
        if !ok || rb.Spec.TargetRef.Name == "" {
10✔
561
                return nil
×
562
        }
×
563

564
        return []reconcile.Request{
10✔
565
                {
10✔
566
                        NamespacedName: types.NamespacedName{
10✔
567
                                Name:      rb.Spec.TargetRef.Name,
10✔
568
                                Namespace: rb.Namespace,
10✔
569
                        },
10✔
570
                },
10✔
571
        }
10✔
572
}
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