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

opendefensecloud / solution-arsenal / 24497764971

16 Apr 2026 07:28AM UTC coverage: 72.258% (+0.2%) from 72.044%
24497764971

push

github

web-flow
fix(deps): update module sigs.k8s.io/structured-merge-diff/v6 to v6.4.0 (#429)

This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[sigs.k8s.io/structured-merge-diff/v6](https://redirect.github.com/kubernetes-sigs/structured-merge-diff)
| `v6.3.2` → `v6.4.0` |
![age](https://developer.mend.io/api/mc/badges/age/go/sigs.k8s.io%2fstructured-merge-diff%2fv6/v6.4.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/sigs.k8s.io%2fstructured-merge-diff%2fv6/v6.3.2/v6.4.0?slim=true)
|

---

### Release Notes

<details>
<summary>kubernetes-sigs/structured-merge-diff
(sigs.k8s.io/structured-merge-diff/v6)</summary>

###
[`v6.4.0`](https://redirect.github.com/kubernetes-sigs/structured-merge-diff/compare/v6.3.2...v6.4.0)

[Compare
Source](https://redirect.github.com/kubernetes-sigs/structured-merge-diff/compare/v6.3.2...v6.4.0)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/opendefensecloud/solution-arsenal).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMjAuMiIsInVwZGF0ZWRJblZlciI6IjQzLjEyMC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

2029 of 2808 relevant lines covered (72.26%)

28.1 hits per line

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

74.26
/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
        rtName   string
49
        chartURL string
50
}
51

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

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

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

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

92✔
80
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
123✔
81
                return ctrl.Result{}, nil
31✔
82
        }
31✔
83

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

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

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

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

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

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

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

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

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

136
                return ctrl.Result{}, nil
15✔
137
        }
138

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

151
                        return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
17✔
152
                }
153

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

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

163
                return ctrl.Result{}, nil
2✔
164
        }
165

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

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

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

187
                return ctrl.Result{}, nil
6✔
188
        }
189

190
        // For each bound release, ensure a per-release RenderTask exists
191
        var releases []releaseInfo
18✔
192

18✔
193
        pendingDeps := false
18✔
194

18✔
195
        for _, binding := range bindingList.Items {
44✔
196
                rel := &solarv1alpha1.Release{}
26✔
197
                if err := r.Get(ctx, client.ObjectKey{
26✔
198
                        Name:      binding.Spec.ReleaseRef.Name,
26✔
199
                        Namespace: target.Namespace,
26✔
200
                }, rel); err != nil {
26✔
201
                        if apierrors.IsNotFound(err) {
×
202
                                log.V(1).Info("Release not found", "release", binding.Spec.ReleaseRef.Name)
×
203
                                pendingDeps = true
×
204

×
205
                                continue
×
206
                        }
207

208
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Release")
×
209
                }
210

211
                cv := &solarv1alpha1.ComponentVersion{}
26✔
212
                if err := r.Get(ctx, client.ObjectKey{
26✔
213
                        Name:      rel.Spec.ComponentVersionRef.Name,
26✔
214
                        Namespace: target.Namespace,
26✔
215
                }, cv); err != nil {
26✔
216
                        if apierrors.IsNotFound(err) {
×
217
                                log.V(1).Info("ComponentVersion not found", "cv", rel.Spec.ComponentVersionRef.Name)
×
218
                                pendingDeps = true
×
219

×
220
                                continue
×
221
                        }
222

223
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get ComponentVersion")
×
224
                }
225

226
                rtName := releaseRenderTaskName(rel.Name, target.Name, rel.GetGeneration())
26✔
227
                releases = append(releases, releaseInfo{
26✔
228
                        name:    rel.Name,
26✔
229
                        release: rel,
26✔
230
                        cv:      cv,
26✔
231
                        rtName:  rtName,
26✔
232
                })
26✔
233
        }
234

235
        // Create per-release RenderTasks (one per target+release pair).
236
        // The renderer job handles dedup by skipping if the chart already exists in the registry.
237
        allRendered := true
18✔
238

18✔
239
        for i, ri := range releases {
44✔
240
                rt := &solarv1alpha1.RenderTask{}
26✔
241
                err := r.Get(ctx, client.ObjectKey{Name: ri.rtName, Namespace: target.Namespace}, rt)
26✔
242

26✔
243
                if apierrors.IsNotFound(err) {
29✔
244
                        spec := r.computeReleaseRenderTaskSpec(ri.release, ri.cv, registry, target)
3✔
245
                        rt = &solarv1alpha1.RenderTask{
3✔
246
                                ObjectMeta: metav1.ObjectMeta{
3✔
247
                                        Name:      ri.rtName,
3✔
248
                                        Namespace: target.Namespace,
3✔
249
                                },
3✔
250
                                Spec: spec,
3✔
251
                        }
3✔
252

3✔
253
                        if err := r.Create(ctx, rt); err != nil {
3✔
254
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to create release RenderTask")
×
255
                        }
×
256

257
                        log.V(1).Info("Created release RenderTask", "release", ri.name, "renderTask", ri.rtName)
3✔
258
                        r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Created", "Create",
3✔
259
                                "Created release RenderTask %s for release %s", ri.rtName, ri.name)
3✔
260
                } else if err != nil {
23✔
261
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to get release RenderTask")
×
262
                }
×
263

264
                // Check if release RenderTask is complete
265
                if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobFailed) {
26✔
266
                        if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "ReleaseFailed",
×
267
                                fmt.Sprintf("Release %s rendering failed", ri.name)); condErr != nil {
×
268
                                return ctrl.Result{}, condErr
×
269
                        }
×
270

271
                        return ctrl.Result{}, nil
×
272
                }
273

274
                if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobSucceeded) && rt.Status.ChartURL != "" {
45✔
275
                        releases[i].chartURL = rt.Status.ChartURL
19✔
276
                } else {
26✔
277
                        allRendered = false
7✔
278
                }
7✔
279
        }
280

281
        if pendingDeps {
18✔
282
                if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "MissingDependencies",
×
283
                        "One or more bound Releases or ComponentVersions not found"); condErr != nil {
×
284
                        return ctrl.Result{}, condErr
×
285
                }
×
286

287
                return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
×
288
        }
289

290
        if !allRendered {
25✔
291
                if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionFalse, "Pending",
7✔
292
                        "Waiting for release RenderTasks to complete"); condErr != nil {
8✔
293
                        return ctrl.Result{}, condErr
1✔
294
                }
1✔
295

296
                return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
6✔
297
        }
298

299
        if condErr := r.setCondition(ctx, target, ConditionTypeReleasesRendered, metav1.ConditionTrue, "AllRendered",
11✔
300
                "All releases rendered successfully"); condErr != nil {
11✔
301
                return ctrl.Result{}, condErr
×
302
        }
×
303

304
        // Determine if a new bootstrap render is needed by checking whether the
305
        // current bootstrapVersion's RenderTask still matches the desired release set.
306
        bootstrapVersion := target.Status.BootstrapVersion
11✔
307
        bootstrapRTName := targetRenderTaskName(target.Name, bootstrapVersion)
11✔
308
        bootstrapRT := &solarv1alpha1.RenderTask{}
11✔
309
        err := r.Get(ctx, client.ObjectKey{Name: bootstrapRTName, Namespace: target.Namespace}, bootstrapRT)
11✔
310

11✔
311
        needsNewBootstrap := false
11✔
312

11✔
313
        switch {
11✔
314
        case apierrors.IsNotFound(err):
1✔
315
                // No RenderTask for the current version yet — create one
1✔
316
                needsNewBootstrap = true
1✔
317
        case err != nil:
×
318
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get bootstrap RenderTask")
×
319
        default:
10✔
320
                // RenderTask exists — check if the desired bootstrap input changed
10✔
321
                // (release set, resolved refs/tags, or userdata)
10✔
322
                desiredInput, inputErr := buildBootstrapInput(target, releases)
10✔
323
                if inputErr != nil {
10✔
324
                        return ctrl.Result{}, errLogAndWrap(log, inputErr, "failed to build desired bootstrap input for comparison")
×
325
                }
×
326

327
                existingInput := bootstrapRT.Spec.RendererConfig.BootstrapConfig.Input
10✔
328
                if !apiequality.Semantic.DeepEqual(desiredInput, existingInput) {
11✔
329
                        bootstrapVersion++
1✔
330
                        needsNewBootstrap = true
1✔
331
                }
1✔
332
        }
333

334
        if needsNewBootstrap {
13✔
335
                spec, specErr := r.computeBootstrapRenderTaskSpec(target, releases, registry, bootstrapVersion)
2✔
336
                if specErr != nil {
2✔
337
                        return ctrl.Result{}, errLogAndWrap(log, specErr, "failed to compute bootstrap RenderTask spec")
×
338
                }
×
339

340
                bootstrapRTName = targetRenderTaskName(target.Name, bootstrapVersion)
2✔
341
                bootstrapRT = &solarv1alpha1.RenderTask{
2✔
342
                        ObjectMeta: metav1.ObjectMeta{
2✔
343
                                Name:      bootstrapRTName,
2✔
344
                                Namespace: target.Namespace,
2✔
345
                        },
2✔
346
                        Spec: spec,
2✔
347
                }
2✔
348

2✔
349
                if err := r.Create(ctx, bootstrapRT); err != nil {
2✔
350
                        if !apierrors.IsAlreadyExists(err) {
×
351
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to create bootstrap RenderTask")
×
352
                        }
×
353

354
                        if err := r.Get(ctx, client.ObjectKey{Name: bootstrapRTName, Namespace: target.Namespace}, bootstrapRT); err != nil {
×
355
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get existing bootstrap RenderTask")
×
356
                        }
×
357
                } else {
2✔
358
                        log.V(1).Info("Created bootstrap RenderTask", "renderTask", bootstrapRTName, "bootstrapVersion", bootstrapVersion)
2✔
359
                        r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Created", "Create",
2✔
360
                                "Created bootstrap RenderTask %s (version %d)", bootstrapRTName, bootstrapVersion)
2✔
361
                }
2✔
362

363
                // Persist the new bootstrapVersion in status
364
                if bootstrapVersion != target.Status.BootstrapVersion {
3✔
365
                        target.Status.BootstrapVersion = bootstrapVersion
1✔
366
                        if err := r.Status().Update(ctx, target); err != nil {
1✔
367
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to update Target bootstrapVersion")
×
368
                        }
×
369
                }
370
        }
371

372
        // Update target status from bootstrap RenderTask
373
        if apimeta.IsStatusConditionTrue(bootstrapRT.Status.Conditions, ConditionTypeJobFailed) {
11✔
374
                if condErr := r.setCondition(ctx, target, ConditionTypeBootstrapReady, metav1.ConditionFalse, "Failed",
×
375
                        "Bootstrap rendering failed"); condErr != nil {
×
376
                        return ctrl.Result{}, condErr
×
377
                }
×
378

379
                return ctrl.Result{}, nil
×
380
        }
381

382
        if apimeta.IsStatusConditionTrue(bootstrapRT.Status.Conditions, ConditionTypeJobSucceeded) {
16✔
383
                if condErr := r.setCondition(ctx, target, ConditionTypeBootstrapReady, metav1.ConditionTrue, "Ready",
5✔
384
                        "Bootstrap rendered successfully: "+bootstrapRT.Status.ChartURL); condErr != nil {
5✔
385
                        return ctrl.Result{}, condErr
×
386
                }
×
387

388
                // Clean up stale RenderTasks owned by this target (old versions)
389
                currentRTNames := map[string]struct{}{bootstrapRTName: {}}
5✔
390
                for _, ri := range releases {
13✔
391
                        currentRTNames[ri.rtName] = struct{}{}
8✔
392
                }
8✔
393
                if err := r.deleteStaleRenderTasks(ctx, target, currentRTNames); err != nil {
5✔
394
                        log.Error(err, "failed to clean up stale RenderTasks")
×
395
                }
×
396

397
                return ctrl.Result{}, nil
5✔
398
        }
399

400
        // Still running
401
        return ctrl.Result{}, nil
6✔
402
}
403

404
func (r *TargetReconciler) setCondition(ctx context.Context, target *solarv1alpha1.Target, condType string, status metav1.ConditionStatus, reason, message string) error {
74✔
405
        changed := apimeta.SetStatusCondition(&target.Status.Conditions, metav1.Condition{
74✔
406
                Type:               condType,
74✔
407
                Status:             status,
74✔
408
                ObservedGeneration: target.Generation,
74✔
409
                Reason:             reason,
74✔
410
                Message:            message,
74✔
411
        })
74✔
412
        if changed {
100✔
413
                if err := r.Status().Update(ctx, target); err != nil {
28✔
414
                        return fmt.Errorf("failed to update Target status condition %s: %w", condType, err)
2✔
415
                }
2✔
416
        }
417

418
        return nil
72✔
419
}
420

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

5✔
427
        rtList := &solarv1alpha1.RenderTaskList{}
5✔
428
        if err := r.List(ctx, rtList,
5✔
429
                client.InNamespace(target.Namespace),
5✔
430
                client.MatchingFields{indexOwnerKind: "Target"},
5✔
431
        ); err != nil {
5✔
432
                return err
×
433
        }
×
434

435
        for i := range rtList.Items {
20✔
436
                rt := &rtList.Items[i]
15✔
437
                if rt.Spec.OwnerName != target.Name || rt.Spec.OwnerNamespace != target.Namespace {
15✔
438
                        continue
×
439
                }
440

441
                if _, current := currentRTNames[rt.Name]; current {
28✔
442
                        continue
13✔
443
                }
444

445
                log.V(1).Info("Deleting stale RenderTask", "renderTask", rt.Name)
2✔
446
                if err := r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
2✔
447
                        return err
×
448
                }
×
449

450
                r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Deleted", "Delete",
2✔
451
                        "Deleted stale RenderTask %s", rt.Name)
2✔
452
        }
453

454
        return nil
5✔
455
}
456

457
func (r *TargetReconciler) deleteOwnedRenderTasks(ctx context.Context, target *solarv1alpha1.Target) error {
1✔
458
        rtList := &solarv1alpha1.RenderTaskList{}
1✔
459
        if err := r.List(ctx, rtList,
1✔
460
                client.InNamespace(target.Namespace),
1✔
461
                client.MatchingFields{indexOwnerKind: "Target"},
1✔
462
        ); err != nil {
1✔
463
                return err
×
464
        }
×
465

466
        for i := range rtList.Items {
1✔
467
                rt := &rtList.Items[i]
×
468
                if rt.Spec.OwnerName == target.Name && rt.Spec.OwnerNamespace == target.Namespace {
×
469
                        if err := r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {
×
470
                                return err
×
471
                        }
×
472
                }
473
        }
474

475
        return nil
1✔
476
}
477

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

3✔
483
        return solarv1alpha1.RenderTaskSpec{
3✔
484
                RendererConfig: solarv1alpha1.RendererConfig{
3✔
485
                        Type: solarv1alpha1.RendererConfigTypeRelease,
3✔
486
                        ReleaseConfig: solarv1alpha1.ReleaseConfig{
3✔
487
                                Chart: solarv1alpha1.ChartConfig{
3✔
488
                                        Name:        chartName,
3✔
489
                                        Description: fmt.Sprintf("Release of %s", rel.Spec.ComponentVersionRef.Name),
3✔
490
                                        Version:     tag,
3✔
491
                                        AppVersion:  tag,
3✔
492
                                },
3✔
493
                                Input: solarv1alpha1.ReleaseInput{
3✔
494
                                        Component:  solarv1alpha1.ReleaseComponent{Name: cv.Spec.ComponentRef.Name},
3✔
495
                                        Resources:  cv.Spec.Resources,
3✔
496
                                        Entrypoint: cv.Spec.Entrypoint,
3✔
497
                                },
3✔
498
                                Values: rel.Spec.Values,
3✔
499
                        },
3✔
500
                },
3✔
501
                Repository:     repo,
3✔
502
                Tag:            tag,
3✔
503
                BaseURL:        registry.Spec.Hostname,
3✔
504
                PushSecretRef:  registry.Spec.SolarSecretRef,
3✔
505
                FailedJobTTL:   rel.Spec.FailedJobTTL,
3✔
506
                OwnerName:      target.Name,
3✔
507
                OwnerNamespace: target.Namespace,
3✔
508
                OwnerKind:      "Target",
3✔
509
        }
3✔
510
}
3✔
511

512
// buildBootstrapInput constructs the desired BootstrapInput from the current
513
// target and resolved releases. Used for both comparison and spec construction.
514
func buildBootstrapInput(target *solarv1alpha1.Target, releases []releaseInfo) (solarv1alpha1.BootstrapInput, error) {
12✔
515
        resolvedReleases := map[string]solarv1alpha1.ResourceAccess{}
12✔
516

12✔
517
        for _, ri := range releases {
31✔
518
                ref, err := ociname.ParseReference(ri.chartURL)
19✔
519
                if err != nil {
19✔
520
                        return solarv1alpha1.BootstrapInput{}, fmt.Errorf("failed to parse chartURL %s: %w", ri.chartURL, err)
×
521
                }
×
522

523
                repo, err := url.JoinPath(ref.Context().RegistryStr(), ref.Context().RepositoryStr())
19✔
524
                if err != nil {
19✔
525
                        return solarv1alpha1.BootstrapInput{}, err
×
526
                }
×
527

528
                resolvedReleases[ri.name] = solarv1alpha1.ResourceAccess{
19✔
529
                        Repository: strings.TrimPrefix(repo, "oci://"),
19✔
530
                        Tag:        ref.Identifier(),
19✔
531
                }
19✔
532
        }
533

534
        return solarv1alpha1.BootstrapInput{
12✔
535
                Releases: resolvedReleases,
12✔
536
                Userdata: target.Spec.Userdata,
12✔
537
        }, nil
12✔
538
}
539

540
func (r *TargetReconciler) computeBootstrapRenderTaskSpec(target *solarv1alpha1.Target, releases []releaseInfo, registry *solarv1alpha1.Registry, bootstrapVersion int64) (solarv1alpha1.RenderTaskSpec, error) {
2✔
541
        input, err := buildBootstrapInput(target, releases)
2✔
542
        if err != nil {
2✔
543
                return solarv1alpha1.RenderTaskSpec{}, err
×
544
        }
×
545

546
        releaseNames := make([]string, 0, len(releases))
2✔
547
        for _, ri := range releases {
5✔
548
                releaseNames = append(releaseNames, ri.name)
3✔
549
        }
3✔
550

551
        sort.Strings(releaseNames)
2✔
552

2✔
553
        chartName := fmt.Sprintf("bootstrap-%s", target.Name)
2✔
554
        repo := fmt.Sprintf("%s/%s", target.Namespace, chartName)
2✔
555
        tag := fmt.Sprintf("v0.0.%d", bootstrapVersion)
2✔
556

2✔
557
        return solarv1alpha1.RenderTaskSpec{
2✔
558
                RendererConfig: solarv1alpha1.RendererConfig{
2✔
559
                        Type: solarv1alpha1.RendererConfigTypeBootstrap,
2✔
560
                        BootstrapConfig: solarv1alpha1.BootstrapConfig{
2✔
561
                                Chart: solarv1alpha1.ChartConfig{
2✔
562
                                        Name:        chartName,
2✔
563
                                        Description: fmt.Sprintf("Bootstrap of %v", releaseNames),
2✔
564
                                        Version:     tag,
2✔
565
                                        AppVersion:  tag,
2✔
566
                                },
2✔
567
                                Input: input,
2✔
568
                        },
2✔
569
                },
2✔
570
                Repository:     repo,
2✔
571
                Tag:            tag,
2✔
572
                BaseURL:        registry.Spec.Hostname,
2✔
573
                PushSecretRef:  registry.Spec.SolarSecretRef,
2✔
574
                OwnerName:      target.Name,
2✔
575
                OwnerNamespace: target.Namespace,
2✔
576
                OwnerKind:      "Target",
2✔
577
        }, nil
2✔
578
}
579

580
// SetupWithManager sets up the controller with the Manager.
581
func (r *TargetReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
582
        return ctrl.NewControllerManagedBy(mgr).
1✔
583
                For(&solarv1alpha1.Target{}).
1✔
584
                Watches(
1✔
585
                        &solarv1alpha1.ReleaseBinding{},
1✔
586
                        handler.EnqueueRequestsFromMapFunc(r.mapReleaseBindingToTarget),
1✔
587
                ).
1✔
588
                Watches(
1✔
589
                        &solarv1alpha1.RenderTask{},
1✔
590
                        handler.EnqueueRequestsFromMapFunc(mapRenderTaskToOwner("Target")),
1✔
591
                        builder.WithPredicates(renderTaskStatusChangePredicate()),
1✔
592
                ).
1✔
593
                Watches(
1✔
594
                        &solarv1alpha1.Registry{},
1✔
595
                        handler.EnqueueRequestsFromMapFunc(r.mapRegistryToTargets),
1✔
596
                ).
1✔
597
                Watches(
1✔
598
                        &solarv1alpha1.Release{},
1✔
599
                        handler.EnqueueRequestsFromMapFunc(r.mapReleaseToTargets),
1✔
600
                ).
1✔
601
                Complete(r)
1✔
602
}
1✔
603

604
// mapRegistryToTargets maps a Registry event to reconcile requests for all
605
// Targets in the same namespace that reference it via renderRegistryRef.
606
func (r *TargetReconciler) mapRegistryToTargets(ctx context.Context, obj client.Object) []reconcile.Request {
6✔
607
        reg, ok := obj.(*solarv1alpha1.Registry)
6✔
608
        if !ok {
6✔
609
                return nil
×
610
        }
×
611

612
        targetList := &solarv1alpha1.TargetList{}
6✔
613
        if err := r.List(ctx, targetList, client.InNamespace(reg.Namespace)); err != nil {
6✔
614
                ctrl.LoggerFrom(ctx).Error(err, "failed to list Targets for Registry", "registry", reg.Name)
×
615

×
616
                return nil
×
617
        }
×
618

619
        var requests []reconcile.Request
6✔
620
        for _, t := range targetList.Items {
6✔
621
                if t.Spec.RenderRegistryRef.Name == reg.Name {
×
622
                        requests = append(requests, reconcile.Request{
×
623
                                NamespacedName: types.NamespacedName{
×
624
                                        Name:      t.Name,
×
625
                                        Namespace: t.Namespace,
×
626
                                },
×
627
                        })
×
628
                }
×
629
        }
630

631
        return requests
6✔
632
}
633

634
// mapReleaseToTargets maps a Release event to reconcile requests for all
635
// Targets that are bound to the release via ReleaseBindings.
636
func (r *TargetReconciler) mapReleaseToTargets(ctx context.Context, obj client.Object) []reconcile.Request {
15✔
637
        rel, ok := obj.(*solarv1alpha1.Release)
15✔
638
        if !ok {
15✔
639
                return nil
×
640
        }
×
641

642
        bindingList := &solarv1alpha1.ReleaseBindingList{}
15✔
643
        if err := r.List(ctx, bindingList,
15✔
644
                client.InNamespace(rel.Namespace),
15✔
645
                client.MatchingFields{indexReleaseBindingReleaseName: rel.Name},
15✔
646
        ); err != nil {
15✔
647
                ctrl.LoggerFrom(ctx).Error(err, "failed to list ReleaseBindings for Release", "release", rel.Name)
×
648

×
649
                return nil
×
650
        }
×
651

652
        seen := map[string]struct{}{}
15✔
653
        var requests []reconcile.Request
15✔
654

15✔
655
        for _, rb := range bindingList.Items {
15✔
656
                targetName := rb.Spec.TargetRef.Name
×
657
                if _, ok := seen[targetName]; ok {
×
658
                        continue
×
659
                }
660

661
                seen[targetName] = struct{}{}
×
662
                requests = append(requests, reconcile.Request{
×
663
                        NamespacedName: types.NamespacedName{
×
664
                                Name:      targetName,
×
665
                                Namespace: rb.Namespace,
×
666
                        },
×
667
                })
×
668
        }
669

670
        return requests
15✔
671
}
672

673
func (r *TargetReconciler) mapReleaseBindingToTarget(_ context.Context, obj client.Object) []reconcile.Request {
10✔
674
        rb, ok := obj.(*solarv1alpha1.ReleaseBinding)
10✔
675
        if !ok || rb.Spec.TargetRef.Name == "" {
10✔
676
                return nil
×
677
        }
×
678

679
        return []reconcile.Request{
10✔
680
                {
10✔
681
                        NamespacedName: types.NamespacedName{
10✔
682
                                Name:      rb.Spec.TargetRef.Name,
10✔
683
                                Namespace: rb.Namespace,
10✔
684
                        },
10✔
685
                },
10✔
686
        }
10✔
687
}
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