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

opendefensecloud / solution-arsenal / 22721885493

05 Mar 2026 02:13PM UTC coverage: 70.576%. First build
22721885493

Pull #195

github

web-flow
Merge 65ffe8a40 into b03730095
Pull Request #195: Reference Credentials from Secret

236 of 261 new or added lines in 5 files covered. (90.42%)

1998 of 2831 relevant lines covered (70.58%)

20.46 hits per line

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

89.2
/pkg/controller/hydratedtarget_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
        "fmt"
9
        "net/url"
10
        "slices"
11
        "strings"
12
        "time"
13

14
        ociname "github.com/google/go-containerregistry/pkg/name"
15
        corev1 "k8s.io/api/core/v1"
16
        apierrors "k8s.io/apimachinery/pkg/api/errors"
17
        apimeta "k8s.io/apimachinery/pkg/api/meta"
18
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
        "k8s.io/apimachinery/pkg/runtime"
20
        "k8s.io/client-go/tools/events"
21
        ctrl "sigs.k8s.io/controller-runtime"
22
        "sigs.k8s.io/controller-runtime/pkg/client"
23
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
24

25
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
26
)
27

28
const (
29
        hydratedTargetFinalizer = "solar.opendefense.cloud/hydrated-target-finalizer"
30
)
31

32
// HydratedTargetReconciler reconciles a HydratedTarget object
33
type HydratedTargetReconciler struct {
34
        client.Client
35
        Scheme   *runtime.Scheme
36
        Recorder events.EventRecorder
37
}
38

39
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=hydratedtargets,verbs=get;list;watch;create;update;patch;delete
40
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=hydratedtargets/status,verbs=get;update;patch
41
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=hydratedtargets/finalizers,verbs=update
42
// FIXME: Switch out releases for profiles                      👇
43
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=releases,verbs=get;list;watch
44
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks,verbs=get;list;watch;create;update;patch;delete
45
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
46

47
// Reconcile moves the current state of the cluster closer to the desired state
48
//
49
// Reconciliation Flow:
50
//
51
//        HydratedTarget created
52
//            ↓
53
//        Add finalizer
54
//            ↓
55
//        Check if already succeeded → YES → Return (no-op)
56
//            ↓ NO
57
//        Get or create RenderTask
58
//            ↓
59
//        Update status from RenderTask
60

61
func (r *HydratedTargetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
137✔
62
        log := ctrl.LoggerFrom(ctx)
137✔
63
        ctrlResult := ctrl.Result{}
137✔
64

137✔
65
        log.V(1).Info("HydratedTarget is being reconciled", "req", req)
137✔
66

137✔
67
        // Fetch the HydratedTarget instance
137✔
68
        res := &solarv1alpha1.HydratedTarget{}
137✔
69
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
139✔
70
                if apierrors.IsNotFound(err) {
4✔
71
                        // Object not found, return. Created objects are automatically garbage collected.
2✔
72
                        return ctrlResult, nil
2✔
73
                }
2✔
74

75
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
76
        }
77

78
        // Handle deletion: cleanup rendertask, then remove finalizer
79
        if !res.DeletionTimestamp.IsZero() {
137✔
80
                log.V(1).Info("HydratedTarget is being deleted")
2✔
81
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "Deleting", "Delete", "HydratedTarget is being deleted, cleaning up resources")
2✔
82

2✔
83
                if err := r.deleteRenderTask(ctx, res); client.IgnoreNotFound(err) != nil {
2✔
84
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete render task")
×
85
                }
×
86

87
                // Remove finalizer
88
                if slices.Contains(res.Finalizers, hydratedTargetFinalizer) {
4✔
89
                        log.V(1).Info("Removing finalizer from resource")
2✔
90
                        res.Finalizers = slices.DeleteFunc(res.Finalizers, func(f string) bool {
4✔
91
                                return f == hydratedTargetFinalizer
2✔
92
                        })
2✔
93
                        if err := r.Update(ctx, res); err != nil {
2✔
94
                                return ctrlResult, errLogAndWrap(log, err, "failed to remove finalizer")
×
95
                        }
×
96
                }
97

98
                return ctrlResult, nil
2✔
99
        }
100

101
        // Add finalizer if not present and not deleting
102
        if res.DeletionTimestamp.IsZero() {
266✔
103
                if !slices.Contains(res.Finalizers, hydratedTargetFinalizer) {
152✔
104
                        log.V(1).Info("Adding finalizer to resource")
19✔
105
                        res.Finalizers = append(res.Finalizers, hydratedTargetFinalizer)
19✔
106
                        if err := r.Update(ctx, res); err != nil {
19✔
107
                                return ctrlResult, errLogAndWrap(log, err, "failed to add finalizer")
×
108
                        }
×
109
                        // Return without requeue; the Update event will trigger reconciliation again
110
                        return ctrlResult, nil
19✔
111
                }
112
        }
113

114
        // Check if rendertask has already completed successfully
115
        sc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeTaskCompleted)
114✔
116
        if sc != nil && sc.ObservedGeneration >= res.Generation && sc.Status == metav1.ConditionTrue {
115✔
117
                log.V(1).Info("RenderTask has already completed successfully, no further action needed")
1✔
118
                return ctrlResult, nil
1✔
119
        }
1✔
120

121
        // Check if rendertask has already failed
122
        fc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeTaskFailed)
113✔
123
        if fc != nil && fc.ObservedGeneration >= res.Generation && fc.Status == metav1.ConditionTrue {
114✔
124
                log.V(1).Info("RenderTask has already failed, no further action needed")
1✔
125
                return ctrlResult, nil
1✔
126
        }
1✔
127

128
        // Reconcile RenderTask
129
        rt := &solarv1alpha1.RenderTask{}
112✔
130
        err := r.Get(ctx, client.ObjectKey{Name: generationName(res), Namespace: res.Namespace}, rt)
112✔
131
        if client.IgnoreNotFound(err) != nil {
112✔
132
                log.V(1).Info("Failed to get render task", "res", res)
×
133
                return ctrlResult, errLogAndWrap(log, err, "failed to get RenderTask")
×
134
        }
×
135

136
        if apierrors.IsNotFound(err) {
153✔
137
                if err := r.createRenderTask(ctx, res); err != nil {
61✔
138
                        log.V(1).Info("Failed to create RenderTask", "res", res)
20✔
139
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "failed to create RenderTask")
20✔
140

20✔
141
                        if apierrors.IsNotFound(err) {
25✔
142
                                return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
5✔
143
                        }
5✔
144

145
                        return ctrlResult, errLogAndWrap(log, err, "failed to create RenderTask")
15✔
146
                }
147
                log.V(1).Info("Created RenderTask", "res", res)
21✔
148
                r.Recorder.Eventf(res, rt, corev1.EventTypeNormal, "Created", "Create", "RenderTask was created")
21✔
149
        }
150

151
        if changed := r.updateStatusConditionsFromRenderTask(ctx, res, rt); changed {
94✔
152
                if err := r.Status().Update(ctx, res); err != nil {
2✔
153
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
×
154
                }
×
155
        }
156

157
        // RenderTask still running, requeue
158
        return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
92✔
159
}
160

161
func (r *HydratedTargetReconciler) updateStatusConditionsFromRenderTask(ctx context.Context, res *solarv1alpha1.HydratedTarget, rt *solarv1alpha1.RenderTask) (changed bool) {
92✔
162
        if rt == nil || res == nil {
92✔
163
                return false
×
164
        }
×
165

166
        log := ctrl.LoggerFrom(ctx)
92✔
167

92✔
168
        if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobFailed) {
93✔
169
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
1✔
170
                        Type:               ConditionTypeTaskFailed,
1✔
171
                        Status:             metav1.ConditionTrue,
1✔
172
                        ObservedGeneration: res.Generation,
1✔
173
                        Reason:             "TaskFailed",
1✔
174
                        Message:            "RenderTask failed",
1✔
175
                })
1✔
176

1✔
177
                log.V(1).Info("RenderTask failed", "name", rt.Name)
1✔
178
                r.Recorder.Eventf(res, rt, corev1.EventTypeWarning, "TaskFailed", "RunTask", "RenderTask failed")
1✔
179

1✔
180
                return changed
1✔
181
        }
1✔
182

183
        if apimeta.IsStatusConditionTrue(rt.Status.Conditions, ConditionTypeJobSucceeded) {
92✔
184
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
1✔
185
                        Type:               ConditionTypeTaskCompleted,
1✔
186
                        Status:             metav1.ConditionTrue,
1✔
187
                        ObservedGeneration: res.Generation,
1✔
188
                        Reason:             "TaskCompleted",
1✔
189
                        Message:            "RenderTask completed",
1✔
190
                })
1✔
191

1✔
192
                log.V(1).Info("RenderTask completed", "name", rt.Name)
1✔
193
                r.Recorder.Eventf(res, rt, corev1.EventTypeWarning, "TaskCompleted", "RunTask", "RenderTask completed successfully")
1✔
194

1✔
195
                return changed
1✔
196
        }
1✔
197

198
        log.V(1).Info("RenderTask has no final condtions yet", "name", rt.Name)
90✔
199

90✔
200
        return false
90✔
201
}
202

203
func (r *HydratedTargetReconciler) createRenderTask(ctx context.Context, res *solarv1alpha1.HydratedTarget) error {
41✔
204
        log := ctrl.LoggerFrom(ctx)
41✔
205

41✔
206
        // Check if we need to cleanup an old task
41✔
207
        if res.Status.RenderTaskRef != nil && res.Status.RenderTaskRef.Name != "" {
50✔
208
                if err := r.deleteRenderTask(ctx, res); err != nil {
10✔
209
                        return errLogAndWrap(log, err, "failed to cleanup old task")
1✔
210
                }
1✔
211
        }
212

213
        spec, err := r.computeRenderTaskSpec(ctx, res)
40✔
214
        if err != nil {
44✔
215
                return err
4✔
216
        }
4✔
217
        rt := &solarv1alpha1.RenderTask{
36✔
218
                ObjectMeta: metav1.ObjectMeta{
36✔
219
                        Name:      generationName(res),
36✔
220
                        Namespace: res.Namespace,
36✔
221
                },
36✔
222
                Spec: spec,
36✔
223
        }
36✔
224

36✔
225
        if err := r.Create(ctx, rt); err != nil {
49✔
226
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create RenderTask", err)
13✔
227
                return errLogAndWrap(log, err, "secret creation failed")
13✔
228
        }
13✔
229

230
        // Set owner references
231
        if err := controllerutil.SetControllerReference(res, rt, r.Scheme); err != nil {
23✔
232
                return errLogAndWrap(log, err, "failed to set controller reference")
×
233
        }
×
234

235
        // Set Reference in Status
236
        res.Status.RenderTaskRef = &corev1.ObjectReference{
23✔
237
                APIVersion: solarv1alpha1.SchemeGroupVersion.String(),
23✔
238
                Kind:       "RenderTask",
23✔
239
                Namespace:  rt.Namespace,
23✔
240
                Name:       rt.Name,
23✔
241
        }
23✔
242

23✔
243
        if err := r.Status().Update(ctx, res); err != nil {
25✔
244
                return errLogAndWrap(log, err, "failed to update status")
2✔
245
        }
2✔
246

247
        return nil
21✔
248
}
249

250
func (r *HydratedTargetReconciler) deleteRenderTask(ctx context.Context, res *solarv1alpha1.HydratedTarget) error {
11✔
251
        if res.Status.RenderTaskRef == nil {
12✔
252
                return nil
1✔
253
        }
1✔
254

255
        rt := &solarv1alpha1.RenderTask{}
10✔
256
        if err := r.Get(ctx, client.ObjectKey{Name: res.Status.RenderTaskRef.Name, Namespace: res.Status.RenderTaskRef.Namespace}, rt); client.IgnoreNotFound(err) != nil {
10✔
257
                return err
×
258
        } else if err == nil {
19✔
259
                return r.Delete(ctx, rt, client.PropagationPolicy(metav1.DeletePropagationBackground))
9✔
260
        }
9✔
261

262
        return nil
1✔
263
}
264

265
func (r *HydratedTargetReconciler) computeRenderTaskSpec(ctx context.Context, res *solarv1alpha1.HydratedTarget) (solarv1alpha1.RenderTaskSpec, error) {
40✔
266
        spec := solarv1alpha1.RenderTaskSpec{}
40✔
267

40✔
268
        resolvedReleases := map[string]solarv1alpha1.ResourceAccess{}
40✔
269
        for k, v := range res.Spec.Releases {
64✔
270
                rel := &solarv1alpha1.Release{}
24✔
271
                if err := r.Get(ctx, client.ObjectKey{Name: v.Name, Namespace: res.Namespace}, rel); err != nil {
28✔
272
                        return spec, err
4✔
273
                }
4✔
274

275
                ref, err := ociname.ParseReference(rel.Status.ChartURL)
20✔
276
                if err != nil {
20✔
NEW
277
                        return spec, err
×
278
                }
×
279

280
                repo, err := url.JoinPath(ref.Context().RegistryStr(), ref.Context().RepositoryStr())
20✔
281
                if err != nil {
20✔
NEW
282
                        return spec, err
×
283
                }
×
284

285
                resolvedReleases[k] = solarv1alpha1.ResourceAccess{
20✔
286
                        Repository: strings.TrimPrefix(repo, "oci://"),
20✔
287
                        Tag:        ref.Identifier(),
20✔
288
                }
20✔
289
        }
290

291
        resolvedReleaseNames := []string{}
36✔
292
        for k := range resolvedReleases {
56✔
293
                resolvedReleaseNames = append(resolvedReleaseNames, k)
20✔
294
        }
20✔
295

296
        chartName := fmt.Sprintf("ht-%s", res.Name)
36✔
297
        repo, err := url.JoinPath(res.Namespace, chartName)
36✔
298
        if err != nil {
36✔
NEW
299
                return spec, err
×
300
        }
×
301

302
        tag := fmt.Sprintf("v0.0.%d", res.GetGeneration())
36✔
303

36✔
304
        spec.RendererConfig = solarv1alpha1.RendererConfig{
36✔
305
                Type: solarv1alpha1.RendererConfigTypeHydratedTarget,
36✔
306
                HydratedTargetConfig: solarv1alpha1.HydratedTargetConfig{
36✔
307
                        Chart: solarv1alpha1.ChartConfig{
36✔
308
                                Name:        chartName,
36✔
309
                                Description: fmt.Sprintf("HydratedTarget of %v", resolvedReleaseNames),
36✔
310
                                Version:     tag,
36✔
311
                                AppVersion:  tag,
36✔
312
                        },
36✔
313
                        Input: solarv1alpha1.HydratedTargetInput{
36✔
314
                                Releases: resolvedReleases,
36✔
315
                                Userdata: res.Spec.Userdata,
36✔
316
                        },
36✔
317
                },
36✔
318
        }
36✔
319
        spec.Repository = repo
36✔
320
        spec.Tag = tag
36✔
321

36✔
322
        return spec, nil
36✔
323
}
324

325
// SetupWithManager sets up the controller with the Manager.
326
func (r *HydratedTargetReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
327
        return ctrl.NewControllerManagedBy(mgr).
1✔
328
                For(&solarv1alpha1.HydratedTarget{}).
1✔
329
                Owns(&solarv1alpha1.RenderTask{}).
1✔
330
                Complete(r)
1✔
331
}
1✔
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