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

opendefensecloud / solution-arsenal / 27406163754

12 Jun 2026 09:07AM UTC coverage: 73.953% (-0.5%) from 74.416%
27406163754

Pull #592

github

web-flow
Merge c8a5bc0d9 into 762f047da
Pull Request #592: fix: render image pull secrets

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

26 existing lines in 3 files now uncovered.

2950 of 3989 relevant lines covered (73.95%)

38.71 hits per line

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

86.97
/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
// Each RenderTask carries its own BaseURL and PushSecretRef for the target registry.
42
type RenderTaskReconciler struct {
43
        client.Client
44
        Scheme              *runtime.Scheme
45
        Recorder            events.EventRecorder
46
        RendererImage       string
47
        RendererCommand     string
48
        RendererArgs        []string
49
        RendererCAConfigMap string
50
        // RendererImagePullSecrets is the list of Secret names that kubelets in
51
        // each RenderTask namespace should use to pull the renderer image. Each
52
        // name must reference an existing Secret of type
53
        // kubernetes.io/dockerconfigjson in the RenderTask's namespace.
54
        RendererImagePullSecrets []string
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
        // See: https://book.kubebuilder.io/reference/envtest#testing-considerations
59
        WatchNamespace string
60
}
61

62
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks,verbs=get;list;watch;create;update;patch;delete
63
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks/status,verbs=get;update;patch
64
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks/finalizers,verbs=update
65
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
66
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
67
//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch
68

69
// Reconcile moves the current state of the cluster closer to the desired state
70
func (r *RenderTaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
598✔
71
        log := ctrl.LoggerFrom(ctx)
598✔
72
        ctrlResult := ctrl.Result{}
598✔
73

598✔
74
        log.V(1).Info("RenderTask is being reconciled", "req", req)
598✔
75

598✔
76
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
636✔
77
                return ctrlResult, nil
38✔
78
        }
38✔
79

80
        // Fetch the RenderTask instance
81
        res := &solarv1alpha1.RenderTask{}
560✔
82
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
567✔
83
                if apierrors.IsNotFound(err) {
14✔
84
                        return ctrlResult, nil
7✔
85
                }
7✔
86

87
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
88
        }
89

90
        // RenderTask instance marked for deletion, stop reconciling
91
        if !res.DeletionTimestamp.IsZero() {
553✔
92
                log.V(1).Info("RenderTask is being deleted")
×
93
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "Deleting", "Delete", "RenderTask is being deleted, cleaning up secret and job")
×
94

×
95
                return ctrlResult, nil
×
96
        }
×
97

98
        // Check if renderjob has already completed successfully
99
        sc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobSucceeded)
553✔
100
        if sc != nil && sc.ObservedGeneration >= res.Generation && sc.Status == metav1.ConditionTrue {
559✔
101
                log.V(1).Info("RenderTask has already completed successfully, no further action needed")
6✔
102

6✔
103
                return ctrlResult, nil
6✔
104
        }
6✔
105

106
        // Determine the namespace for Jobs/Secrets — use the RenderTask's namespace
107
        jobNS := r.taskNamespace(res)
547✔
108

547✔
109
        // Reconcile Config Secret
547✔
110
        configSecret := &corev1.Secret{}
547✔
111
        err := r.Get(ctx, r.configSecretKey(res, jobNS), configSecret)
547✔
112
        if err != nil && apierrors.IsNotFound(err) {
869✔
113
                createdSecret, err := r.createConfigSecret(ctx, res, jobNS)
322✔
114
                if err != nil {
322✔
UNCOV
115
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateSecretFailed", "CreateConfigSecret", "Failed to create config secret: %s", err)
×
UNCOV
116

×
UNCOV
117
                        return ctrlResult, errLogAndWrap(log, err, "failed to create secret")
×
UNCOV
118
                }
×
119

120
                configSecret = createdSecret
322✔
121
        } else if err != nil {
225✔
122
                return ctrlResult, errLogAndWrap(log, err, "could not get secret")
×
123
        }
×
124

125
        // Resolve push secret from the RenderTask's PushSecretRef
126
        var pushSecret *corev1.Secret
547✔
127
        if res.Spec.PushSecretRef != nil {
1,092✔
128
                pushSecret = &corev1.Secret{}
545✔
129
                if err := r.Get(ctx, client.ObjectKey{Name: res.Spec.PushSecretRef.Name, Namespace: jobNS}, pushSecret); err != nil {
766✔
130
                        return ctrlResult, errLogAndWrap(log, err, "failed to get push secret")
221✔
131
                }
221✔
132
        }
133

134
        // Reconcile Job
135
        job := &batchv1.Job{}
326✔
136
        err = r.Get(ctx, r.renderJobKey(res, jobNS), job)
326✔
137
        if err != nil && apierrors.IsNotFound(err) {
344✔
138
                err := r.createRenderJob(ctx, res, configSecret, pushSecret, jobNS)
18✔
139
                if err != nil {
18✔
140
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateJobFailed", "CreateJob", "Failed to create job: %s", err)
×
141

×
142
                        return ctrlResult, errLogAndWrap(log, err, "failed to create job")
×
143
                }
×
144
        } else if err != nil {
308✔
145
                return ctrlResult, errLogAndWrap(log, err, "could not get job")
×
146
        }
×
147

148
        // Update Status
149
        if changed := r.updateResourceStatusFromJob(ctx, res, job); changed {
349✔
150
                if err := r.Status().Update(ctx, res); err != nil {
23✔
151
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
×
152
                }
×
153
        }
154

155
        ttlDuration := time.Duration(ttlSeconds(res.Spec.FailedJobTTL)) * time.Second
326✔
156

326✔
157
        switch {
326✔
158
        case job.Status.Succeeded > 0:
3✔
159
                cleanupRenderResources(ctx, r, res, job, jobNS)
3✔
160
                log.V(1).Info("Cleaned up after successful job")
3✔
161

3✔
162
                return ctrlResult, nil
3✔
163

164
        case job.Status.Failed > 0:
289✔
165
                if shouldCleanupSecrets(res, ttlDuration) {
574✔
166
                        cleanupSecrets(ctx, r, res, jobNS)
285✔
167
                        log.V(1).Info("Cleaned up secrets after failed job TTL")
285✔
168

285✔
169
                        return ctrlResult, nil
285✔
170
                }
285✔
171

172
                remaining := remainingTTL(res, ttlDuration)
4✔
173
                log.V(1).Info("Waiting for TTL to expire before cleaning up secrets", "remainingSeconds", remaining.Seconds())
4✔
174

4✔
175
                return ctrl.Result{RequeueAfter: remaining + time.Second}, nil
4✔
176
        }
177

178
        return ctrlResult, nil
34✔
179
}
180

181
// taskNamespace returns the namespace to use for Jobs/Secrets.
182
func (r *RenderTaskReconciler) taskNamespace(res *solarv1alpha1.RenderTask) string {
547✔
183
        return res.Namespace
547✔
184
}
547✔
185

186
// updateResourceStatusFromJob updates the resource status based on job status
187
func (r *RenderTaskReconciler) updateResourceStatusFromJob(ctx context.Context, res *solarv1alpha1.RenderTask, job *batchv1.Job) (changed bool) {
326✔
188
        log := ctrl.LoggerFrom(ctx)
326✔
189

326✔
190
        if job == nil {
326✔
191
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
×
192
                        Type:               ConditionTypeJobScheduled,
×
193
                        Status:             metav1.ConditionFalse,
×
194
                        ObservedGeneration: res.Generation,
×
195
                        Reason:             "DoesNotExist",
×
196
                        Message:            "Renderer job does not exist",
×
197
                })
×
198

×
199
                return changed
×
200
        }
×
201

202
        if job.Status.Succeeded > 0 {
329✔
203
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
3✔
204
                        Type:               ConditionTypeJobSucceeded,
3✔
205
                        Status:             metav1.ConditionTrue,
3✔
206
                        ObservedGeneration: res.Generation,
3✔
207
                        Reason:             "JobSucceeded",
3✔
208
                        Message:            fmt.Sprintf("Renderer job completed successfully at %v", job.Status.CompletionTime),
3✔
209
                })
3✔
210

3✔
211
                chartURL := r.reference(res.Spec.BaseURL, res.Spec.Repository, res.Spec.Tag)
3✔
212
                if res.Status.ChartURL != chartURL {
6✔
213
                        res.Status.ChartURL = chartURL
3✔
214
                        changed = true
3✔
215
                }
3✔
216

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

3✔
220
                return changed
3✔
221
        }
222

223
        if job.Status.Failed > 0 {
612✔
224
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
289✔
225
                        Type:               ConditionTypeJobFailed,
289✔
226
                        Status:             metav1.ConditionTrue,
289✔
227
                        ObservedGeneration: res.Generation,
289✔
228
                        Reason:             "JobFailed",
289✔
229
                        Message:            "Renderer job failed",
289✔
230
                })
289✔
231
                r.Recorder.Eventf(res, job, corev1.EventTypeWarning, "JobFailed", "RunJob", "Renderer job failed")
289✔
232
                log.V(1).Info("Job failed", "name", job.Name)
289✔
233

289✔
234
                return changed
289✔
235
        }
289✔
236

237
        return apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
34✔
238
                Type:               ConditionTypeJobScheduled,
34✔
239
                Status:             metav1.ConditionTrue,
34✔
240
                ObservedGeneration: res.Generation,
34✔
241
                Reason:             "JobScheduled",
34✔
242
                Message:            fmt.Sprintf("Renderer job is running (active: %d, succeeded: %d, failed: %d)", job.Status.Active, job.Status.Succeeded, job.Status.Failed),
34✔
243
        })
34✔
244
}
245

246
func (r *RenderTaskReconciler) deleteRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask, jobNS string) error {
3✔
247
        job := &batchv1.Job{}
3✔
248
        if err := r.Get(ctx, r.renderJobKey(res, jobNS), job); err != nil {
3✔
249
                return err
×
250
        }
×
251

252
        return r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground))
3✔
253
}
254

255
func (r *RenderTaskReconciler) deleteConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask, jobNS string) error {
288✔
256
        secret := &corev1.Secret{}
288✔
257
        if err := r.Get(ctx, r.configSecretKey(res, jobNS), secret); err != nil {
290✔
258
                return err
2✔
259
        }
2✔
260

261
        return r.Delete(ctx, secret, client.PropagationPolicy(metav1.DeletePropagationBackground))
286✔
262
}
263

264
func (r *RenderTaskReconciler) createRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask, configSecret, pushSecret *corev1.Secret, jobNS string) error {
18✔
265
        log := ctrl.LoggerFrom(ctx)
18✔
266

18✔
267
        jobKey := r.renderJobKey(res, jobNS)
18✔
268
        jobName := jobKey.Name
18✔
269
        backoffLimit := int32(3)
18✔
270
        ttlSecondsAfterFinished := int32(3600)
18✔
271
        if res.Spec.FailedJobTTL != nil {
20✔
272
                ttlSecondsAfterFinished = *res.Spec.FailedJobTTL
2✔
273
        }
2✔
274

275
        volumes := []corev1.Volume{
18✔
276
                {
18✔
277
                        Name: "config",
18✔
278
                        VolumeSource: corev1.VolumeSource{
18✔
279
                                Secret: &corev1.SecretVolumeSource{
18✔
280
                                        SecretName: configSecret.Name,
18✔
281
                                        Items: []corev1.KeyToPath{
18✔
282
                                                {
18✔
283
                                                        Key:  "config.json",
18✔
284
                                                        Path: "config.json",
18✔
285
                                                },
18✔
286
                                        },
18✔
287
                                },
18✔
288
                        },
18✔
289
                },
18✔
290
        }
18✔
291
        volumeMounts := []corev1.VolumeMount{
18✔
292
                {
18✔
293
                        Name:      "config",
18✔
294
                        MountPath: "/etc/renderer/config.json",
18✔
295
                        SubPath:   "config.json",
18✔
296
                        ReadOnly:  true,
18✔
297
                },
18✔
298
        }
18✔
299
        envVars := []corev1.EnvVar{
18✔
300
                {
18✔
301
                        Name: "POD_NAMESPACE",
18✔
302
                        ValueFrom: &corev1.EnvVarSource{
18✔
303
                                FieldRef: &corev1.ObjectFieldSelector{
18✔
304
                                        FieldPath: "metadata.namespace",
18✔
305
                                },
18✔
306
                        },
18✔
307
                },
18✔
308
                {
18✔
309
                        Name: "POD_NAME",
18✔
310
                        ValueFrom: &corev1.EnvVarSource{
18✔
311
                                FieldRef: &corev1.ObjectFieldSelector{
18✔
312
                                        FieldPath: "metadata.name",
18✔
313
                                },
18✔
314
                        },
18✔
315
                },
18✔
316
        }
18✔
317

18✔
318
        if r.RendererCAConfigMap != "" {
34✔
319
                volumes = append(volumes, corev1.Volume{
16✔
320
                        Name: "ca-bundle",
16✔
321
                        VolumeSource: corev1.VolumeSource{
16✔
322
                                ConfigMap: &corev1.ConfigMapVolumeSource{
16✔
323
                                        LocalObjectReference: corev1.LocalObjectReference{
16✔
324
                                                Name: r.RendererCAConfigMap,
16✔
325
                                        },
16✔
326
                                        Items: []corev1.KeyToPath{
16✔
327
                                                {
16✔
328
                                                        Key:  "trust-bundle.pem",
16✔
329
                                                        Path: "ca-bundle.pem",
16✔
330
                                                },
16✔
331
                                        },
16✔
332
                                },
16✔
333
                        },
16✔
334
                })
16✔
335
                volumeMounts = append(volumeMounts, corev1.VolumeMount{
16✔
336
                        Name:      "ca-bundle",
16✔
337
                        MountPath: "/etc/ssl/certs",
16✔
338
                        ReadOnly:  true,
16✔
339
                })
16✔
340
                envVars = append(envVars, corev1.EnvVar{
16✔
341
                        Name:  "SSL_CERT_FILE",
16✔
342
                        Value: "/etc/ssl/certs/ca-bundle.pem",
16✔
343
                })
16✔
344
        }
16✔
345

346
        pushURL := r.reference(res.Spec.BaseURL, res.Spec.Repository, res.Spec.Tag)
18✔
347

18✔
348
        job := &batchv1.Job{
18✔
349
                ObjectMeta: metav1.ObjectMeta{
18✔
350
                        Name:      jobName,
18✔
351
                        Namespace: jobKey.Namespace,
18✔
352
                        Annotations: map[string]string{
18✔
353
                                annotationJobName: jobName,
18✔
354
                        },
18✔
355
                },
18✔
356
                Spec: batchv1.JobSpec{
18✔
357
                        BackoffLimit:            &backoffLimit,
18✔
358
                        TTLSecondsAfterFinished: &ttlSecondsAfterFinished,
18✔
359
                        Template: corev1.PodTemplateSpec{
18✔
360
                                Spec: corev1.PodSpec{
18✔
361
                                        RestartPolicy: corev1.RestartPolicyNever,
18✔
362
                                        Containers: []corev1.Container{
18✔
363
                                                {
18✔
364
                                                        Name:    "renderer",
18✔
365
                                                        Image:   r.RendererImage,
18✔
366
                                                        Command: []string{r.RendererCommand},
18✔
367
                                                        Args: append(r.RendererArgs,
18✔
368
                                                                "/etc/renderer/config.json",
18✔
369
                                                                fmt.Sprintf("--url=%s", pushURL),
18✔
370
                                                        ),
18✔
371
                                                        Env:          envVars,
18✔
372
                                                        VolumeMounts: volumeMounts,
18✔
373
                                                },
18✔
374
                                        },
18✔
375
                                        Volumes: volumes,
18✔
376
                                },
18✔
377
                        },
18✔
378
                },
18✔
379
        }
18✔
380

18✔
381
        if pushSecret != nil {
34✔
382
                switch pushSecret.Type {
16✔
383
                case corev1.SecretTypeBasicAuth:
1✔
384
                        job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env,
1✔
385
                                corev1.EnvVar{
1✔
386
                                        Name: "REGISTRY_USERNAME",
1✔
387
                                        ValueFrom: &corev1.EnvVarSource{
1✔
388
                                                SecretKeyRef: &corev1.SecretKeySelector{
1✔
389
                                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
390
                                                                Name: pushSecret.Name,
1✔
391
                                                        },
1✔
392
                                                        Key: "username",
1✔
393
                                                },
1✔
394
                                        },
1✔
395
                                },
1✔
396
                                corev1.EnvVar{
1✔
397
                                        Name: "REGISTRY_PASSWORD",
1✔
398
                                        ValueFrom: &corev1.EnvVarSource{
1✔
399
                                                SecretKeyRef: &corev1.SecretKeySelector{
1✔
400
                                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
401
                                                                Name: pushSecret.Name,
1✔
402
                                                        },
1✔
403
                                                        Key: "password",
1✔
404
                                                },
1✔
405
                                        },
1✔
406
                                },
1✔
407
                        )
1✔
408

409
                case corev1.SecretTypeDockerConfigJson:
1✔
410
                        job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
1✔
411
                                Name: "dockerconfig",
1✔
412
                                VolumeSource: corev1.VolumeSource{
1✔
413
                                        Secret: &corev1.SecretVolumeSource{
1✔
414
                                                SecretName: pushSecret.Name,
1✔
415
                                                Items: []corev1.KeyToPath{
1✔
416
                                                        {
1✔
417
                                                                Key:  ".dockerconfigjson",
1✔
418
                                                                Path: "dockerconfig.json",
1✔
419
                                                        },
1✔
420
                                                },
1✔
421
                                        },
1✔
422
                                },
1✔
423
                        })
1✔
424

1✔
425
                        job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
1✔
426
                                Name:      "dockerconfig",
1✔
427
                                MountPath: "/etc/renderer/dockerconfig.json",
1✔
428
                                SubPath:   "dockerconfig.json",
1✔
429
                                ReadOnly:  true,
1✔
430
                        })
1✔
431

1✔
432
                        job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
1✔
433
                                Name:  "DOCKER_CONFIG",
1✔
434
                                Value: "/etc/renderer/dockerconfig.json",
1✔
435
                        })
1✔
436
                default:
14✔
437
                }
438
        }
439

440
        if len(r.RendererImagePullSecrets) > 0 {
19✔
441
                refs := make([]corev1.LocalObjectReference, len(r.RendererImagePullSecrets))
1✔
442
                for i, n := range r.RendererImagePullSecrets {
3✔
443
                        refs[i] = corev1.LocalObjectReference{Name: n}
2✔
444
                }
2✔
445
                job.Spec.Template.Spec.ImagePullSecrets = refs
1✔
446
        }
447

448
        // Set owner references
449
        if err := controllerutil.SetControllerReference(res, job, r.Scheme); err != nil {
18✔
450
                return errLogAndWrap(log, err, "failed to set controller reference")
×
451
        }
×
452

453
        if err := r.Create(ctx, job); err != nil {
18✔
454
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create job: %s", err)
×
455

×
456
                return errLogAndWrap(log, err, "job creation failed")
×
457
        }
×
458

459
        res.Status.JobRef = &corev1.ObjectReference{
18✔
460
                APIVersion: batchv1.SchemeGroupVersion.String(),
18✔
461
                Kind:       "Job",
18✔
462
                Namespace:  job.Namespace,
18✔
463
                Name:       job.Name,
18✔
464
        }
18✔
465

18✔
466
        if err := r.Status().Update(ctx, res); err != nil {
18✔
467
                return errLogAndWrap(log, err, "failed to update status")
×
468
        }
×
469

470
        return nil
18✔
471
}
472

473
func (r *RenderTaskReconciler) createConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask, jobNS string) (*corev1.Secret, error) {
322✔
474
        log := ctrl.LoggerFrom(ctx)
322✔
475

322✔
476
        cfgJson, err := json.Marshal(res.Spec.RendererConfig)
322✔
477
        if err != nil {
322✔
478
                return nil, err
×
479
        }
×
480

481
        secretKey := r.configSecretKey(res, jobNS)
322✔
482
        secret := &corev1.Secret{
322✔
483
                ObjectMeta: metav1.ObjectMeta{
322✔
484
                        Name:      secretKey.Name,
322✔
485
                        Namespace: secretKey.Namespace,
322✔
486
                        Annotations: map[string]string{
322✔
487
                                annotationSecretName: secretKey.Name,
322✔
488
                        },
322✔
489
                },
322✔
490
                Type: corev1.SecretTypeOpaque,
322✔
491
                Data: map[string][]byte{
322✔
492
                        "config.json": cfgJson,
322✔
493
                },
322✔
494
        }
322✔
495

322✔
496
        // Set owner references
322✔
497
        if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
322✔
498
                return nil, errLogAndWrap(log, err, "failed to set controller reference")
×
499
        }
×
500

501
        if err := r.Create(ctx, secret); err != nil {
322✔
502
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create secret: %s", err)
×
503

×
504
                return nil, errLogAndWrap(log, err, "secret creation failed")
×
505
        }
×
506

507
        res.Status.ConfigSecretRef = &corev1.ObjectReference{
322✔
508
                APIVersion: corev1.SchemeGroupVersion.String(),
322✔
509
                Kind:       "Secret",
322✔
510
                Namespace:  secret.Namespace,
322✔
511
                Name:       secret.Name,
322✔
512
        }
322✔
513

322✔
514
        if err := r.Status().Update(ctx, res); err != nil {
322✔
UNCOV
515
                return nil, errLogAndWrap(log, err, "failed to update status")
×
UNCOV
516
        }
×
517

518
        return secret, nil
322✔
519
}
520

521
func (r *RenderTaskReconciler) configSecretKey(res *solarv1alpha1.RenderTask, jobNS string) client.ObjectKey {
1,157✔
522
        return client.ObjectKey{
1,157✔
523
                Name:      truncateName(fmt.Sprintf("render-%s", res.Name), maxK8sLabelValueLen),
1,157✔
524
                Namespace: jobNS,
1,157✔
525
        }
1,157✔
526
}
1,157✔
527

528
func (r *RenderTaskReconciler) renderJobKey(res *solarv1alpha1.RenderTask, jobNS string) client.ObjectKey {
347✔
529
        return client.ObjectKey{
347✔
530
                Name:      truncateName(fmt.Sprintf("render-%s", res.Name), maxK8sLabelValueLen),
347✔
531
                Namespace: jobNS,
347✔
532
        }
347✔
533
}
347✔
534

535
func (r *RenderTaskReconciler) reference(baseURL, repo, tag string) string {
21✔
536
        base := baseURL
21✔
537
        if !strings.HasPrefix(base, "oci://") {
40✔
538
                base = fmt.Sprintf("oci://%s", base)
19✔
539
        }
19✔
540

541
        base = strings.TrimSuffix(base, "/")
21✔
542

21✔
543
        return fmt.Sprintf("%s/%s:%s", base, repo, tag)
21✔
544
}
545

546
func ttlSeconds(ttl *int32) int32 {
326✔
547
        if ttl != nil {
617✔
548
                return *ttl
291✔
549
        }
291✔
550

551
        return 3600
35✔
552
}
553

554
func shouldCleanupSecrets(res *solarv1alpha1.RenderTask, ttl time.Duration) bool {
289✔
555
        cond := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobFailed)
289✔
556

289✔
557
        return cond != nil && time.Since(cond.LastTransitionTime.Time) >= ttl
289✔
558
}
289✔
559

560
func remainingTTL(res *solarv1alpha1.RenderTask, ttl time.Duration) time.Duration {
4✔
561
        cond := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobFailed)
4✔
562
        if cond == nil {
4✔
563
                return ttl
×
564
        }
×
565

566
        remaining := ttl - time.Since(cond.LastTransitionTime.Time)
4✔
567
        if remaining < 0 {
4✔
568
                return 0
×
569
        }
×
570

571
        return remaining
4✔
572
}
573

574
func cleanupSecrets(ctx context.Context, r *RenderTaskReconciler, res *solarv1alpha1.RenderTask, jobNS string) {
288✔
575
        if err := r.deleteConfigSecret(ctx, res, jobNS); err != nil && !apierrors.IsNotFound(err) {
288✔
576
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete config secret: %s", err)
×
577
        }
×
578
}
579

580
func cleanupRenderResources(ctx context.Context, r *RenderTaskReconciler, res *solarv1alpha1.RenderTask, job *batchv1.Job, jobNS string) {
3✔
581
        cleanupSecrets(ctx, r, res, jobNS)
3✔
582
        if err := r.deleteRenderJob(ctx, res, jobNS); err != nil && !apierrors.IsNotFound(err) {
3✔
583
                r.Recorder.Eventf(res, job, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete job: %s", err)
×
584
        }
×
585
}
586

587
// SetupWithManager sets up the controller with the Manager.
588
func (r *RenderTaskReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
589
        return ctrl.NewControllerManagedBy(mgr).
1✔
590
                For(&solarv1alpha1.RenderTask{}).
1✔
591
                Owns(&batchv1.Job{}).
1✔
592
                Owns(&corev1.Secret{}).
1✔
593
                Complete(r)
1✔
594
}
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