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

opendefensecloud / solution-arsenal / 26037916041

18 May 2026 01:53PM UTC coverage: 72.542% (+1.4%) from 71.112%
26037916041

push

github

web-flow
fix(deps): update module github.com/onsi/gomega to v1.41.0 (#529)

This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [github.com/onsi/gomega](https://redirect.github.com/onsi/gomega) |
`v1.40.0` → `v1.41.0` |
![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fonsi%2fgomega/v1.41.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fonsi%2fgomega/v1.40.0/v1.41.0?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/133) for more information.

---

### Release Notes

<details>
<summary>onsi/gomega (github.com/onsi/gomega)</summary>

###
[`v1.41.0`](https://redirect.github.com/onsi/gomega/compare/v1.40.0...v1.41.0)

[Compare
Source](https://redirect.github.com/onsi/gomega/compare/v1.40.0...v1.41.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:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzkuMyIsInVwZGF0ZWRJblZlciI6IjQzLjE4Mi4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

2383 of 3285 relevant lines covered (72.54%)

28.78 hits per line

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

87.24
/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
        // WatchNamespace restricts reconciliation to this namespace.
51
        // Should be empty in production (watches all namespaces).
52
        // Intended for use in integration tests only.
53
        // See: https://book.kubebuilder.io/reference/envtest#testing-considerations
54
        WatchNamespace string
55
}
56

57
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks,verbs=get;list;watch;create;update;patch;delete
58
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks/status,verbs=get;update;patch
59
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks/finalizers,verbs=update
60
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
61
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
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) {
450✔
66
        log := ctrl.LoggerFrom(ctx)
450✔
67
        ctrlResult := ctrl.Result{}
450✔
68

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

450✔
71
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
477✔
72
                return ctrlResult, nil
27✔
73
        }
27✔
74

75
        // Fetch the RenderTask instance
76
        res := &solarv1alpha1.RenderTask{}
423✔
77
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
424✔
78
                if apierrors.IsNotFound(err) {
2✔
79
                        return ctrlResult, nil
1✔
80
                }
1✔
81

82
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
83
        }
84

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

×
90
                return ctrlResult, nil
×
91
        }
×
92

93
        // Check if renderjob has already completed successfully
94
        sc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobSucceeded)
422✔
95
        if sc != nil && sc.ObservedGeneration >= res.Generation && sc.Status == metav1.ConditionTrue {
424✔
96
                log.V(1).Info("RenderTask has already completed successfully, no further action needed")
2✔
97

2✔
98
                return ctrlResult, nil
2✔
99
        }
2✔
100

101
        // Determine the namespace for Jobs/Secrets — use the RenderTask's namespace
102
        jobNS := r.taskNamespace(res)
420✔
103

420✔
104
        // Reconcile Config Secret
420✔
105
        configSecret := &corev1.Secret{}
420✔
106
        err := r.Get(ctx, r.configSecretKey(res, jobNS), configSecret)
420✔
107
        if err != nil && apierrors.IsNotFound(err) {
695✔
108
                createdSecret, err := r.createConfigSecret(ctx, res, jobNS)
275✔
109
                if err != nil {
275✔
110
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateSecretFailed", "CreateConfigSecret", "Failed to create config secret: %s", err)
×
111

×
112
                        return ctrlResult, errLogAndWrap(log, err, "failed to create secret")
×
113
                }
×
114

115
                configSecret = createdSecret
275✔
116
        } else if err != nil {
145✔
117
                return ctrlResult, errLogAndWrap(log, err, "could not get secret")
×
118
        }
×
119

120
        // Resolve push secret from the RenderTask's PushSecretRef
121
        var pushSecret *corev1.Secret
420✔
122
        if res.Spec.PushSecretRef != nil {
840✔
123
                pushSecret = &corev1.Secret{}
420✔
124
                if err := r.Get(ctx, client.ObjectKey{Name: res.Spec.PushSecretRef.Name, Namespace: jobNS}, pushSecret); err != nil {
497✔
125
                        return ctrlResult, errLogAndWrap(log, err, "failed to get push secret")
77✔
126
                }
77✔
127
        }
128

129
        // Reconcile Job
130
        job := &batchv1.Job{}
343✔
131
        err = r.Get(ctx, r.renderJobKey(res, jobNS), job)
343✔
132
        if err != nil && apierrors.IsNotFound(err) {
359✔
133
                err := r.createRenderJob(ctx, res, configSecret, pushSecret, jobNS)
16✔
134
                if err != nil {
16✔
135
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateJobFailed", "CreateJob", "Failed to create job: %s", err)
×
136

×
137
                        return ctrlResult, errLogAndWrap(log, err, "failed to create job")
×
138
                }
×
139
        } else if err != nil {
327✔
140
                return ctrlResult, errLogAndWrap(log, err, "could not get job")
×
141
        }
×
142

143
        // Update Status
144
        if changed := r.updateResourceStatusFromJob(ctx, res, job); changed {
366✔
145
                if err := r.Status().Update(ctx, res); err != nil {
25✔
146
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
2✔
147
                }
2✔
148
        }
149

150
        ttlDuration := time.Duration(ttlSeconds(res.Spec.FailedJobTTL)) * time.Second
341✔
151

341✔
152
        switch {
341✔
153
        case job.Status.Succeeded > 0:
3✔
154
                cleanupRenderResources(ctx, r, res, job, jobNS)
3✔
155
                log.V(1).Info("Cleaned up after successful job")
3✔
156

3✔
157
                return ctrlResult, nil
3✔
158

159
        case job.Status.Failed > 0:
304✔
160
                if shouldCleanupSecrets(res, ttlDuration) {
604✔
161
                        cleanupSecrets(ctx, r, res, jobNS)
300✔
162
                        log.V(1).Info("Cleaned up secrets after failed job TTL")
300✔
163

300✔
164
                        return ctrlResult, nil
300✔
165
                }
300✔
166

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

4✔
170
                return ctrl.Result{RequeueAfter: remaining + time.Second}, nil
4✔
171
        }
172

173
        return ctrlResult, nil
34✔
174
}
175

176
// taskNamespace returns the namespace to use for Jobs/Secrets.
177
func (r *RenderTaskReconciler) taskNamespace(res *solarv1alpha1.RenderTask) string {
420✔
178
        return res.Namespace
420✔
179
}
420✔
180

181
// updateResourceStatusFromJob updates the resource status based on job status
182
func (r *RenderTaskReconciler) updateResourceStatusFromJob(ctx context.Context, res *solarv1alpha1.RenderTask, job *batchv1.Job) (changed bool) {
343✔
183
        log := ctrl.LoggerFrom(ctx)
343✔
184

343✔
185
        if job == nil {
343✔
186
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
×
187
                        Type:               ConditionTypeJobScheduled,
×
188
                        Status:             metav1.ConditionFalse,
×
189
                        ObservedGeneration: res.Generation,
×
190
                        Reason:             "DoesNotExist",
×
191
                        Message:            "Renderer job does not exist",
×
192
                })
×
193

×
194
                return changed
×
195
        }
×
196

197
        if job.Status.Succeeded > 0 {
346✔
198
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
3✔
199
                        Type:               ConditionTypeJobSucceeded,
3✔
200
                        Status:             metav1.ConditionTrue,
3✔
201
                        ObservedGeneration: res.Generation,
3✔
202
                        Reason:             "JobSucceeded",
3✔
203
                        Message:            fmt.Sprintf("Renderer job completed successfully at %v", job.Status.CompletionTime),
3✔
204
                })
3✔
205

3✔
206
                chartURL := r.reference(res.Spec.BaseURL, res.Spec.Repository, res.Spec.Tag)
3✔
207
                if res.Status.ChartURL != chartURL {
6✔
208
                        res.Status.ChartURL = chartURL
3✔
209
                        changed = true
3✔
210
                }
3✔
211

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

3✔
215
                return changed
3✔
216
        }
217

218
        if job.Status.Failed > 0 {
644✔
219
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
304✔
220
                        Type:               ConditionTypeJobFailed,
304✔
221
                        Status:             metav1.ConditionTrue,
304✔
222
                        ObservedGeneration: res.Generation,
304✔
223
                        Reason:             "JobFailed",
304✔
224
                        Message:            "Renderer job failed",
304✔
225
                })
304✔
226
                r.Recorder.Eventf(res, job, corev1.EventTypeWarning, "JobFailed", "RunJob", "Renderer job failed")
304✔
227
                log.V(1).Info("Job failed", "name", job.Name)
304✔
228

304✔
229
                return changed
304✔
230
        }
304✔
231

232
        return apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
36✔
233
                Type:               ConditionTypeJobScheduled,
36✔
234
                Status:             metav1.ConditionTrue,
36✔
235
                ObservedGeneration: res.Generation,
36✔
236
                Reason:             "JobScheduled",
36✔
237
                Message:            fmt.Sprintf("Renderer job is running (active: %d, succeeded: %d, failed: %d)", job.Status.Active, job.Status.Succeeded, job.Status.Failed),
36✔
238
        })
36✔
239
}
240

241
func (r *RenderTaskReconciler) deleteRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask, jobNS string) error {
3✔
242
        job := &batchv1.Job{}
3✔
243
        if err := r.Get(ctx, r.renderJobKey(res, jobNS), job); err != nil {
3✔
244
                return err
×
245
        }
×
246

247
        return r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground))
3✔
248
}
249

250
func (r *RenderTaskReconciler) deleteConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask, jobNS string) error {
303✔
251
        secret := &corev1.Secret{}
303✔
252
        if err := r.Get(ctx, r.configSecretKey(res, jobNS), secret); err != nil {
308✔
253
                return err
5✔
254
        }
5✔
255

256
        return r.Delete(ctx, secret, client.PropagationPolicy(metav1.DeletePropagationBackground))
298✔
257
}
258

259
func (r *RenderTaskReconciler) createRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask, configSecret, pushSecret *corev1.Secret, jobNS string) error {
16✔
260
        log := ctrl.LoggerFrom(ctx)
16✔
261

16✔
262
        jobKey := r.renderJobKey(res, jobNS)
16✔
263
        jobName := jobKey.Name
16✔
264
        backoffLimit := int32(3)
16✔
265
        ttlSecondsAfterFinished := int32(3600)
16✔
266
        if res.Spec.FailedJobTTL != nil {
18✔
267
                ttlSecondsAfterFinished = *res.Spec.FailedJobTTL
2✔
268
        }
2✔
269

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

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

341
        pushURL := r.reference(res.Spec.BaseURL, res.Spec.Repository, res.Spec.Tag)
16✔
342

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

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

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

1✔
420
                        job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
1✔
421
                                Name:      "dockerconfig",
1✔
422
                                MountPath: "/etc/renderer/dockerconfig.json",
1✔
423
                                SubPath:   "dockerconfig.json",
1✔
424
                                ReadOnly:  true,
1✔
425
                        })
1✔
426

1✔
427
                        job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
1✔
428
                                Name:  "DOCKER_CONFIG",
1✔
429
                                Value: "/etc/renderer/dockerconfig.json",
1✔
430
                        })
1✔
431
                default:
14✔
432
                }
433
        }
434

435
        // Set owner references
436
        if err := controllerutil.SetControllerReference(res, job, r.Scheme); err != nil {
16✔
437
                return errLogAndWrap(log, err, "failed to set controller reference")
×
438
        }
×
439

440
        if err := r.Create(ctx, job); err != nil {
16✔
441
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create job: %s", err)
×
442

×
443
                return errLogAndWrap(log, err, "job creation failed")
×
444
        }
×
445

446
        res.Status.JobRef = &corev1.ObjectReference{
16✔
447
                APIVersion: batchv1.SchemeGroupVersion.String(),
16✔
448
                Kind:       "Job",
16✔
449
                Namespace:  job.Namespace,
16✔
450
                Name:       job.Name,
16✔
451
        }
16✔
452

16✔
453
        if err := r.Status().Update(ctx, res); err != nil {
16✔
454
                return errLogAndWrap(log, err, "failed to update status")
×
455
        }
×
456

457
        return nil
16✔
458
}
459

460
func (r *RenderTaskReconciler) createConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask, jobNS string) (*corev1.Secret, error) {
275✔
461
        log := ctrl.LoggerFrom(ctx)
275✔
462

275✔
463
        cfgJson, err := json.Marshal(res.Spec.RendererConfig)
275✔
464
        if err != nil {
275✔
465
                return nil, err
×
466
        }
×
467

468
        secretKey := r.configSecretKey(res, jobNS)
275✔
469
        secret := &corev1.Secret{
275✔
470
                ObjectMeta: metav1.ObjectMeta{
275✔
471
                        Name:      secretKey.Name,
275✔
472
                        Namespace: secretKey.Namespace,
275✔
473
                        Annotations: map[string]string{
275✔
474
                                annotationSecretName: secretKey.Name,
275✔
475
                        },
275✔
476
                },
275✔
477
                Type: corev1.SecretTypeOpaque,
275✔
478
                Data: map[string][]byte{
275✔
479
                        "config.json": cfgJson,
275✔
480
                },
275✔
481
        }
275✔
482

275✔
483
        // Set owner references
275✔
484
        if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
275✔
485
                return nil, errLogAndWrap(log, err, "failed to set controller reference")
×
486
        }
×
487

488
        if err := r.Create(ctx, secret); err != nil {
275✔
489
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create secret: %s", err)
×
490

×
491
                return nil, errLogAndWrap(log, err, "secret creation failed")
×
492
        }
×
493

494
        res.Status.ConfigSecretRef = &corev1.ObjectReference{
275✔
495
                APIVersion: corev1.SchemeGroupVersion.String(),
275✔
496
                Kind:       "Secret",
275✔
497
                Namespace:  secret.Namespace,
275✔
498
                Name:       secret.Name,
275✔
499
        }
275✔
500

275✔
501
        if err := r.Status().Update(ctx, res); err != nil {
275✔
502
                return nil, errLogAndWrap(log, err, "failed to update status")
×
503
        }
×
504

505
        return secret, nil
275✔
506
}
507

508
func (r *RenderTaskReconciler) configSecretKey(res *solarv1alpha1.RenderTask, jobNS string) client.ObjectKey {
998✔
509
        return client.ObjectKey{
998✔
510
                Name:      truncateName(fmt.Sprintf("render-%s", res.Name), maxK8sLabelValueLen),
998✔
511
                Namespace: jobNS,
998✔
512
        }
998✔
513
}
998✔
514

515
func (r *RenderTaskReconciler) renderJobKey(res *solarv1alpha1.RenderTask, jobNS string) client.ObjectKey {
362✔
516
        return client.ObjectKey{
362✔
517
                Name:      truncateName(fmt.Sprintf("render-%s", res.Name), maxK8sLabelValueLen),
362✔
518
                Namespace: jobNS,
362✔
519
        }
362✔
520
}
362✔
521

522
func (r *RenderTaskReconciler) reference(baseURL, repo, tag string) string {
19✔
523
        base := baseURL
19✔
524
        if !strings.HasPrefix(base, "oci://") {
38✔
525
                base = fmt.Sprintf("oci://%s", base)
19✔
526
        }
19✔
527

528
        base = strings.TrimSuffix(base, "/")
19✔
529

19✔
530
        return fmt.Sprintf("%s/%s:%s", base, repo, tag)
19✔
531
}
532

533
func ttlSeconds(ttl *int32) int32 {
341✔
534
        if ttl != nil {
647✔
535
                return *ttl
306✔
536
        }
306✔
537

538
        return 3600
35✔
539
}
540

541
func shouldCleanupSecrets(res *solarv1alpha1.RenderTask, ttl time.Duration) bool {
304✔
542
        cond := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobFailed)
304✔
543

304✔
544
        return cond != nil && time.Since(cond.LastTransitionTime.Time) >= ttl
304✔
545
}
304✔
546

547
func remainingTTL(res *solarv1alpha1.RenderTask, ttl time.Duration) time.Duration {
4✔
548
        cond := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobFailed)
4✔
549
        if cond == nil {
4✔
550
                return ttl
×
551
        }
×
552

553
        remaining := ttl - time.Since(cond.LastTransitionTime.Time)
4✔
554
        if remaining < 0 {
4✔
555
                return 0
×
556
        }
×
557

558
        return remaining
4✔
559
}
560

561
func cleanupSecrets(ctx context.Context, r *RenderTaskReconciler, res *solarv1alpha1.RenderTask, jobNS string) {
303✔
562
        if err := r.deleteConfigSecret(ctx, res, jobNS); err != nil && !apierrors.IsNotFound(err) {
303✔
563
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete config secret: %s", err)
×
564
        }
×
565
}
566

567
func cleanupRenderResources(ctx context.Context, r *RenderTaskReconciler, res *solarv1alpha1.RenderTask, job *batchv1.Job, jobNS string) {
3✔
568
        cleanupSecrets(ctx, r, res, jobNS)
3✔
569
        if err := r.deleteRenderJob(ctx, res, jobNS); err != nil && !apierrors.IsNotFound(err) {
3✔
570
                r.Recorder.Eventf(res, job, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete job: %s", err)
×
571
        }
×
572
}
573

574
// SetupWithManager sets up the controller with the Manager.
575
func (r *RenderTaskReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
576
        return ctrl.NewControllerManagedBy(mgr).
1✔
577
                For(&solarv1alpha1.RenderTask{}).
1✔
578
                Owns(&batchv1.Job{}).
1✔
579
                Owns(&corev1.Secret{}).
1✔
580
                Complete(r)
1✔
581
}
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