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

opendefensecloud / solution-arsenal / 24140640371

08 Apr 2026 02:25PM UTC coverage: 73.528% (-0.06%) from 73.591%
24140640371

Pull #369

github

web-flow
Merge 56c65c629 into 21bdbcc7c
Pull Request #369: Fix release deployment

9 of 9 new or added lines in 1 file covered. (100.0%)

8 existing lines in 2 files now uncovered.

2286 of 3109 relevant lines covered (73.53%)

29.08 hits per line

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

89.1
/pkg/controller/rendertask_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
        "encoding/json"
9
        "fmt"
10
        "strings"
11
        "time"
12

13
        batchv1 "k8s.io/api/batch/v1"
14
        corev1 "k8s.io/api/core/v1"
15
        apierrors "k8s.io/apimachinery/pkg/api/errors"
16
        apimeta "k8s.io/apimachinery/pkg/api/meta"
17
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18
        "k8s.io/apimachinery/pkg/runtime"
19
        "k8s.io/client-go/tools/events"
20
        ctrl "sigs.k8s.io/controller-runtime"
21
        "sigs.k8s.io/controller-runtime/pkg/client"
22
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
23

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

27
const (
28
        annotationJobName    = "solar.opendefense.cloud/job-name"
29
        annotationSecretName = "solar.opendefense.cloud/secret-name"
30

31
        // Condition types
32
        ConditionTypeJobScheduled = "JobScheduled"
33
        ConditionTypeJobSucceeded = "JobSucceeded"
34
        ConditionTypeJobFailed    = "JobFailed"
35

36
        ConditionTypeTaskCompleted = "TaskCompleted"
37
        ConditionTypeTaskFailed    = "TaskFailed"
38
)
39

40
// RenderTaskReconciler reconciles a RenderTask object
41
type RenderTaskReconciler struct {
42
        client.Client
43
        Scheme              *runtime.Scheme
44
        Recorder            events.EventRecorder
45
        RendererImage       string
46
        RendererCommand     string
47
        RendererArgs        []string
48
        PushSecretRef       *corev1.SecretReference
49
        BaseURL             string
50
        RendererCAConfigMap string
51
        // Namespace is the namespace where Jobs and Secrets are created.
52
        // Passed via --namespace flag from the controller-manager.
53
        Namespace string
54
}
55

56
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks,verbs=get;list;watch;create;update;patch;delete
57
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks/status,verbs=get;update;patch
58
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks/finalizers,verbs=update
59
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
60
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
61
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
62
//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch
63

64
// Reconcile moves the current state of the cluster closer to the desired state
65
func (r *RenderTaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
348✔
66
        log := ctrl.LoggerFrom(ctx)
348✔
67
        ctrlResult := ctrl.Result{}
348✔
68

348✔
69
        log.V(1).Info("RenderTask is being reconciled", "req", req)
348✔
70

348✔
71
        // Fetch the RenderTask instance
348✔
72
        res := &solarv1alpha1.RenderTask{}
348✔
73
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
391✔
74
                if apierrors.IsNotFound(err) {
86✔
75
                        // Object not found, return. Created objects are automatically garbage collected.
43✔
76
                        return ctrlResult, nil
43✔
77
                }
43✔
78

79
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
80
        }
81

82
        // RenderTask instance marked for deletion, stop reconciling
83
        if !res.DeletionTimestamp.IsZero() {
305✔
84
                log.V(1).Info("RenderTask is being deleted")
×
85
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "Deleting", "Delete", "RenderTask is being deleted, cleaning up secret and job")
×
86

×
87
                return ctrlResult, nil
×
88
        }
×
89

90
        // Check if renderjob has already completed successfully
91
        sc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobSucceeded)
305✔
92
        if sc != nil && sc.ObservedGeneration >= res.Generation && sc.Status == metav1.ConditionTrue {
312✔
93
                log.V(1).Info("RenderTask has already completed successfully, no further action needed")
7✔
94
                return ctrlResult, nil
7✔
95
        }
7✔
96

97
        // Reconcile Config Secret
98
        configSecret := &corev1.Secret{}
298✔
99
        err := r.Get(ctx, r.configSecretKey(res), configSecret)
298✔
100
        if err != nil && apierrors.IsNotFound(err) {
538✔
101
                createdSecret, err := r.createConfigSecret(ctx, res)
240✔
102
                if err != nil {
243✔
103
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateSecretFailed", "CreateConfigSecret", fmt.Sprintf("Failed to create config secret: %s", err))
3✔
104
                        return ctrlResult, errLogAndWrap(log, err, "failed to create secret")
3✔
105
                }
3✔
106
                configSecret = createdSecret
237✔
107
        } else if err != nil {
58✔
108
                return ctrlResult, errLogAndWrap(log, err, "could not get secret")
×
109
        }
×
110

111
        // Resolve push secret (lives in controller namespace, referenced directly by Jobs)
112
        var pushSecret *corev1.Secret
295✔
113
        if r.PushSecretRef != nil {
590✔
114
                pushSecret = &corev1.Secret{}
295✔
115
                if err := r.Get(ctx, client.ObjectKey{Name: r.PushSecretRef.Name, Namespace: r.PushSecretRef.Namespace}, pushSecret); err != nil {
295✔
116
                        return ctrlResult, errLogAndWrap(log, err, "failed to get push secret")
×
117
                }
×
118
        }
119

120
        // Reconcile Job
121
        job := &batchv1.Job{}
295✔
122
        err = r.Get(ctx, r.renderJobKey(res), job)
295✔
123
        if err != nil && apierrors.IsNotFound(err) {
335✔
124
                err := r.createRenderJob(ctx, res, configSecret, pushSecret)
40✔
125
                if err != nil {
44✔
126
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateJobFailed", "CreateJob", fmt.Sprintf("Failed to create job: %s", err))
4✔
127
                        return ctrlResult, errLogAndWrap(log, err, "failed to create job")
4✔
128
                }
4✔
129
        } else if err != nil {
255✔
130
                return ctrlResult, errLogAndWrap(log, err, "could not get job")
×
131
        }
×
132

133
        // Update Status
134
        if changed := r.updateResourceStatusFromJob(ctx, res, job); changed {
335✔
135
                if err := r.Status().Update(ctx, res); err != nil {
47✔
136
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
3✔
137
                }
3✔
138
        }
139

140
        ttlDuration := time.Duration(ttlSeconds(res.Spec.FailedJobTTL)) * time.Second
288✔
141

288✔
142
        switch {
288✔
143
        case job.Status.Succeeded > 0:
3✔
144
                cleanupRenderResources(ctx, r, res, job)
3✔
145
                log.V(1).Info("Cleaned up after successful job")
3✔
146

3✔
147
                return ctrlResult, nil
3✔
148

149
        case job.Status.Failed > 0:
205✔
150
                if shouldCleanupSecrets(res, ttlDuration) {
406✔
151
                        cleanupSecrets(ctx, r, res)
201✔
152
                        log.V(1).Info("Cleaned up secrets after failed job TTL")
201✔
153

201✔
154
                        return ctrlResult, nil
201✔
155
                }
201✔
156
                remaining := remainingTTL(res, ttlDuration)
4✔
157
                log.V(1).Info("Waiting for TTL to expire before cleaning up secrets", "remainingSeconds", remaining.Seconds())
4✔
158

4✔
159
                return ctrl.Result{RequeueAfter: remaining + time.Second}, nil
4✔
160
        }
161

162
        return ctrlResult, nil
80✔
163
}
164

165
// updateResourceStatusFromJob updates the resource status based on job status
166
func (r *RenderTaskReconciler) updateResourceStatusFromJob(ctx context.Context, res *solarv1alpha1.RenderTask, job *batchv1.Job) (changed bool) {
291✔
167
        log := ctrl.LoggerFrom(ctx)
291✔
168

291✔
169
        if job == nil {
291✔
170
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
×
171
                        Type:               ConditionTypeJobScheduled,
×
172
                        Status:             metav1.ConditionFalse,
×
173
                        ObservedGeneration: res.Generation,
×
174
                        Reason:             "DoesNotExist",
×
175
                        Message:            "Renderer job does not exist",
×
176
                })
×
177

×
178
                return changed
×
179
        }
×
180

181
        if job.Status.Succeeded > 0 {
294✔
182
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
3✔
183
                        Type:               ConditionTypeJobSucceeded,
3✔
184
                        Status:             metav1.ConditionTrue,
3✔
185
                        ObservedGeneration: res.Generation,
3✔
186
                        Reason:             "JobSucceeded",
3✔
187
                        Message:            fmt.Sprintf("Renderer job completed successfully at %v", job.Status.CompletionTime),
3✔
188
                })
3✔
189

3✔
190
                if res.Status.ChartURL != r.reference(res.Spec.Repository, res.Spec.Tag) {
6✔
191
                        res.Status.ChartURL = r.reference(res.Spec.Repository, res.Spec.Tag)
3✔
192
                        changed = true
3✔
193
                }
3✔
194

195
                r.Recorder.Eventf(res, job, corev1.EventTypeNormal, "JobSucceeded", "RunJob", "Renderer job completed successfully")
3✔
196
                log.V(1).Info("Job succeeded", "name", job.Name)
3✔
197

3✔
198
                return changed
3✔
199
        }
200

201
        if job.Status.Failed > 0 {
493✔
202
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
205✔
203
                        Type:               ConditionTypeJobFailed,
205✔
204
                        Status:             metav1.ConditionTrue,
205✔
205
                        ObservedGeneration: res.Generation,
205✔
206
                        Reason:             "JobFailed",
205✔
207
                        Message:            "Renderer job failed",
205✔
208
                })
205✔
209
                r.Recorder.Eventf(res, job, corev1.EventTypeWarning, "JobFailed", "RunJob", "Renderer job failed")
205✔
210
                log.V(1).Info("Job failed", "name", job.Name)
205✔
211

205✔
212
                return changed
205✔
213
        }
205✔
214

215
        return apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
83✔
216
                Type:               ConditionTypeJobScheduled,
83✔
217
                Status:             metav1.ConditionTrue,
83✔
218
                ObservedGeneration: res.Generation,
83✔
219
                Reason:             "JobScheduled",
83✔
220
                Message:            fmt.Sprintf("Renderer job is running (active: %d, succeeded: %d, failed: %d)", job.Status.Active, job.Status.Succeeded, job.Status.Failed),
83✔
221
        })
83✔
222
}
223

224
func (r *RenderTaskReconciler) deleteRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask) error {
3✔
225
        job := &batchv1.Job{}
3✔
226
        if err := r.Get(ctx, r.renderJobKey(res), job); err != nil {
3✔
227
                return err
×
228
        }
×
229

230
        return r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground))
3✔
231
}
232

233
func (r *RenderTaskReconciler) deleteConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask) error {
204✔
234
        secret := &corev1.Secret{}
204✔
235
        if err := r.Get(ctx, r.configSecretKey(res), secret); err != nil {
204✔
UNCOV
236
                return err
×
UNCOV
237
        }
×
238

239
        return r.Delete(ctx, secret, client.PropagationPolicy(metav1.DeletePropagationBackground))
204✔
240
}
241

242
func (r *RenderTaskReconciler) createRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask, configSecret, pushSecret *corev1.Secret) error {
40✔
243
        log := ctrl.LoggerFrom(ctx)
40✔
244

40✔
245
        jobName := r.renderJobKey(res).Name
40✔
246
        backoffLimit := int32(3)
40✔
247
        ttlSecondsAfterFinished := int32(3600)
40✔
248
        if res.Spec.FailedJobTTL != nil {
43✔
249
                ttlSecondsAfterFinished = *res.Spec.FailedJobTTL
3✔
250
        }
3✔
251

252
        volumes := []corev1.Volume{
40✔
253
                {
40✔
254
                        Name: "config",
40✔
255
                        VolumeSource: corev1.VolumeSource{
40✔
256
                                Secret: &corev1.SecretVolumeSource{
40✔
257
                                        SecretName: configSecret.Name,
40✔
258
                                        Items: []corev1.KeyToPath{
40✔
259
                                                {
40✔
260
                                                        Key:  "config.json",
40✔
261
                                                        Path: "config.json",
40✔
262
                                                },
40✔
263
                                        },
40✔
264
                                },
40✔
265
                        },
40✔
266
                },
40✔
267
        }
40✔
268
        volumeMounts := []corev1.VolumeMount{
40✔
269
                {
40✔
270
                        Name:      "config",
40✔
271
                        MountPath: "/etc/renderer/config.json",
40✔
272
                        SubPath:   "config.json",
40✔
273
                        ReadOnly:  true,
40✔
274
                },
40✔
275
        }
40✔
276
        envVars := []corev1.EnvVar{
40✔
277
                {
40✔
278
                        Name: "POD_NAMESPACE",
40✔
279
                        ValueFrom: &corev1.EnvVarSource{
40✔
280
                                FieldRef: &corev1.ObjectFieldSelector{
40✔
281
                                        FieldPath: "metadata.namespace",
40✔
282
                                },
40✔
283
                        },
40✔
284
                },
40✔
285
                {
40✔
286
                        Name: "POD_NAME",
40✔
287
                        ValueFrom: &corev1.EnvVarSource{
40✔
288
                                FieldRef: &corev1.ObjectFieldSelector{
40✔
289
                                        FieldPath: "metadata.name",
40✔
290
                                },
40✔
291
                        },
40✔
292
                },
40✔
293
        }
40✔
294

40✔
295
        if r.RendererCAConfigMap != "" {
80✔
296
                volumes = append(volumes, corev1.Volume{
40✔
297
                        Name: "ca-bundle",
40✔
298
                        VolumeSource: corev1.VolumeSource{
40✔
299
                                ConfigMap: &corev1.ConfigMapVolumeSource{
40✔
300
                                        LocalObjectReference: corev1.LocalObjectReference{
40✔
301
                                                Name: r.RendererCAConfigMap,
40✔
302
                                        },
40✔
303
                                        Items: []corev1.KeyToPath{
40✔
304
                                                {
40✔
305
                                                        Key:  "trust-bundle.pem",
40✔
306
                                                        Path: "ca-bundle.pem",
40✔
307
                                                },
40✔
308
                                        },
40✔
309
                                },
40✔
310
                        },
40✔
311
                })
40✔
312
                volumeMounts = append(volumeMounts, corev1.VolumeMount{
40✔
313
                        Name:      "ca-bundle",
40✔
314
                        MountPath: "/etc/ssl/certs",
40✔
315
                        ReadOnly:  true,
40✔
316
                })
40✔
317
                envVars = append(envVars, corev1.EnvVar{
40✔
318
                        Name:  "SSL_CERT_FILE",
40✔
319
                        Value: "/etc/ssl/certs/ca-bundle.pem",
40✔
320
                })
40✔
321
        }
40✔
322

323
        job := &batchv1.Job{
40✔
324
                ObjectMeta: metav1.ObjectMeta{
40✔
325
                        Name:      jobName,
40✔
326
                        Namespace: r.renderJobKey(res).Namespace,
40✔
327
                        Annotations: map[string]string{
40✔
328
                                annotationJobName: jobName,
40✔
329
                        },
40✔
330
                },
40✔
331
                Spec: batchv1.JobSpec{
40✔
332
                        BackoffLimit:            &backoffLimit,
40✔
333
                        TTLSecondsAfterFinished: &ttlSecondsAfterFinished,
40✔
334
                        Template: corev1.PodTemplateSpec{
40✔
335
                                Spec: corev1.PodSpec{
40✔
336
                                        RestartPolicy: corev1.RestartPolicyNever,
40✔
337
                                        Containers: []corev1.Container{
40✔
338
                                                {
40✔
339
                                                        Name:    "renderer",
40✔
340
                                                        Image:   r.RendererImage,
40✔
341
                                                        Command: []string{r.RendererCommand},
40✔
342
                                                        Args: append(r.RendererArgs,
40✔
343
                                                                "/etc/renderer/config.json",
40✔
344
                                                                fmt.Sprintf("--url=%s", r.reference(res.Spec.Repository, res.Spec.Tag)),
40✔
345
                                                        ),
40✔
346
                                                        Env:          envVars,
40✔
347
                                                        VolumeMounts: volumeMounts,
40✔
348
                                                },
40✔
349
                                        },
40✔
350
                                        Volumes: volumes,
40✔
351
                                },
40✔
352
                        },
40✔
353
                },
40✔
354
        }
40✔
355

40✔
356
        if pushSecret != nil {
80✔
357
                switch pushSecret.Type {
40✔
358
                case corev1.SecretTypeBasicAuth:
1✔
359
                        job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env,
1✔
360
                                corev1.EnvVar{
1✔
361
                                        Name: "REGISTRY_USERNAME",
1✔
362
                                        ValueFrom: &corev1.EnvVarSource{
1✔
363
                                                SecretKeyRef: &corev1.SecretKeySelector{
1✔
364
                                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
365
                                                                Name: pushSecret.Name,
1✔
366
                                                        },
1✔
367
                                                        Key: "username",
1✔
368
                                                },
1✔
369
                                        },
1✔
370
                                },
1✔
371
                                corev1.EnvVar{
1✔
372
                                        Name: "REGISTRY_PASSWORD",
1✔
373
                                        ValueFrom: &corev1.EnvVarSource{
1✔
374
                                                SecretKeyRef: &corev1.SecretKeySelector{
1✔
375
                                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
376
                                                                Name: pushSecret.Name,
1✔
377
                                                        },
1✔
378
                                                        Key: "password",
1✔
379
                                                },
1✔
380
                                        },
1✔
381
                                },
1✔
382
                        )
1✔
383

384
                case corev1.SecretTypeDockerConfigJson:
7✔
385
                        job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
7✔
386
                                Name: "dockerconfig",
7✔
387
                                VolumeSource: corev1.VolumeSource{
7✔
388
                                        Secret: &corev1.SecretVolumeSource{
7✔
389
                                                SecretName: pushSecret.Name,
7✔
390
                                                Items: []corev1.KeyToPath{
7✔
391
                                                        {
7✔
392
                                                                Key:  ".dockerconfigjson",
7✔
393
                                                                Path: "dockerconfig.json",
7✔
394
                                                        },
7✔
395
                                                },
7✔
396
                                        },
7✔
397
                                },
7✔
398
                        })
7✔
399

7✔
400
                        job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
7✔
401
                                Name:      "dockerconfig",
7✔
402
                                MountPath: "/etc/renderer/dockerconfig.json",
7✔
403
                                SubPath:   "dockerconfig.json",
7✔
404
                                ReadOnly:  true,
7✔
405
                        })
7✔
406

7✔
407
                        job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
7✔
408
                                Name:  "DOCKER_CONFIG",
7✔
409
                                Value: "/etc/renderer/dockerconfig.json",
7✔
410
                        })
7✔
411
                default:
32✔
412
                }
413
        }
414

415
        // Set owner references
416
        if err := controllerutil.SetControllerReference(res, job, r.Scheme); err != nil {
40✔
417
                return errLogAndWrap(log, err, "failed to set controller reference")
×
418
        }
×
419

420
        if err := r.Create(ctx, job); err != nil {
40✔
421
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create job: %s", err)
×
422
                return errLogAndWrap(log, err, "job creation failed")
×
423
        }
×
424

425
        res.Status.JobRef = &corev1.ObjectReference{
40✔
426
                APIVersion: batchv1.SchemeGroupVersion.String(),
40✔
427
                Kind:       "Job",
40✔
428
                Namespace:  job.Namespace,
40✔
429
                Name:       job.Name,
40✔
430
        }
40✔
431

40✔
432
        if err := r.Status().Update(ctx, res); err != nil {
44✔
433
                return errLogAndWrap(log, err, "failed to update status")
4✔
434
        }
4✔
435

436
        return nil
36✔
437
}
438

439
func (r *RenderTaskReconciler) createConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask) (*corev1.Secret, error) {
240✔
440
        log := ctrl.LoggerFrom(ctx)
240✔
441

240✔
442
        cfgJson, err := json.Marshal(res.Spec.RendererConfig)
240✔
443
        if err != nil {
240✔
444
                return nil, err
×
445
        }
×
446

447
        secret := &corev1.Secret{
240✔
448
                ObjectMeta: metav1.ObjectMeta{
240✔
449
                        Name:      r.configSecretKey(res).Name,
240✔
450
                        Namespace: r.configSecretKey(res).Namespace,
240✔
451
                        Annotations: map[string]string{
240✔
452
                                annotationSecretName: r.configSecretKey(res).Name,
240✔
453
                        },
240✔
454
                },
240✔
455
                Type: corev1.SecretTypeOpaque,
240✔
456
                Data: map[string][]byte{
240✔
457
                        "config.json": cfgJson,
240✔
458
                },
240✔
459
        }
240✔
460

240✔
461
        // Set owner references
240✔
462
        if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
240✔
463
                return nil, errLogAndWrap(log, err, "failed to set controller reference")
×
464
        }
×
465

466
        if err := r.Create(ctx, secret); err != nil {
240✔
467
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create secret: %s", err)
×
468
                return nil, errLogAndWrap(log, err, "secret creation failed")
×
469
        }
×
470

471
        res.Status.ConfigSecretRef = &corev1.ObjectReference{
240✔
472
                APIVersion: corev1.SchemeGroupVersion.String(),
240✔
473
                Kind:       "Secret",
240✔
474
                Namespace:  secret.Namespace,
240✔
475
                Name:       secret.Name,
240✔
476
        }
240✔
477

240✔
478
        if err := r.Status().Update(ctx, res); err != nil {
243✔
479
                return nil, errLogAndWrap(log, err, "failed to update status")
3✔
480
        }
3✔
481

482
        return secret, nil
237✔
483
}
484

485
func (r *RenderTaskReconciler) configSecretKey(res *solarv1alpha1.RenderTask) client.ObjectKey {
1,222✔
486
        return client.ObjectKey{
1,222✔
487
                Name:      truncateName(fmt.Sprintf("render-%s", res.Name), maxK8sLabelValueLen),
1,222✔
488
                Namespace: r.Namespace,
1,222✔
489
        }
1,222✔
490
}
1,222✔
491

492
func (r *RenderTaskReconciler) renderJobKey(res *solarv1alpha1.RenderTask) client.ObjectKey {
378✔
493
        return client.ObjectKey{
378✔
494
                Name:      truncateName(fmt.Sprintf("render-%s", res.Name), maxK8sLabelValueLen),
378✔
495
                Namespace: r.Namespace,
378✔
496
        }
378✔
497
}
378✔
498

499
func (r *RenderTaskReconciler) reference(repo string, tag string) string {
46✔
500
        base := r.BaseURL
46✔
501
        if !strings.HasPrefix(base, "oci://") {
92✔
502
                base = fmt.Sprintf("oci://%s", base)
46✔
503
        }
46✔
504
        base = strings.TrimSuffix(base, "/")
46✔
505
        url := fmt.Sprintf("%s/%s:%s", base, repo, tag)
46✔
506

46✔
507
        return url
46✔
508
}
509

510
func ttlSeconds(ttl *int32) int32 {
288✔
511
        if ttl != nil {
498✔
512
                return *ttl
210✔
513
        }
210✔
514

515
        return 3600
78✔
516
}
517

518
func shouldCleanupSecrets(res *solarv1alpha1.RenderTask, ttl time.Duration) bool {
205✔
519
        cond := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobFailed)
205✔
520
        return cond != nil && time.Since(cond.LastTransitionTime.Time) >= ttl
205✔
521
}
205✔
522

523
func remainingTTL(res *solarv1alpha1.RenderTask, ttl time.Duration) time.Duration {
4✔
524
        cond := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobFailed)
4✔
525
        if cond == nil {
4✔
526
                return ttl
×
527
        }
×
528
        remaining := ttl - time.Since(cond.LastTransitionTime.Time)
4✔
529
        if remaining < 0 {
4✔
530
                return 0
×
531
        }
×
532

533
        return remaining
4✔
534
}
535

536
func cleanupSecrets(ctx context.Context, r *RenderTaskReconciler, res *solarv1alpha1.RenderTask) {
204✔
537
        if err := r.deleteConfigSecret(ctx, res); err != nil && !apierrors.IsNotFound(err) {
204✔
538
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete config secret", err)
×
539
        }
×
540
}
541

542
func cleanupRenderResources(ctx context.Context, r *RenderTaskReconciler, res *solarv1alpha1.RenderTask, job *batchv1.Job) {
3✔
543
        cleanupSecrets(ctx, r, res)
3✔
544
        if err := r.deleteRenderJob(ctx, res); err != nil && !apierrors.IsNotFound(err) {
3✔
545
                r.Recorder.Eventf(res, job, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete job", err)
×
546
        }
×
547
}
548

549
// SetupWithManager sets up the controller with the Manager.
550
func (r *RenderTaskReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
551
        return ctrl.NewControllerManagedBy(mgr).
1✔
552
                For(&solarv1alpha1.RenderTask{}).
1✔
553
                Owns(&batchv1.Job{}).
1✔
554
                Owns(&corev1.Secret{}).
1✔
555
                Complete(r)
1✔
556
}
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