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

opendefensecloud / solution-arsenal / 22868177672

09 Mar 2026 06:17PM UTC coverage: 71.014% (+0.2%) from 70.844%
22868177672

push

github

web-flow
fix: return created secrets from createConfigSecret and copyAuthSecret (#255)

48 of 52 new or added lines in 1 file covered. (92.31%)

7 existing lines in 2 files now uncovered.

2009 of 2829 relevant lines covered (71.01%)

18.96 hits per line

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

88.01
/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
        "slices"
11
        "strings"
12
        "time"
13

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

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

29
const (
30
        renderTaskFinalizer = "solar.opendefense.cloud/rendertask-finalizer"
31

32
        annotationJobName    = "solar.opendefense.cloud/job-name"
33
        annotationSecretName = "solar.opendefense.cloud/secret-name"
34

35
        // Condition types
36
        ConditionTypeJobScheduled = "JobScheduled"
37
        ConditionTypeJobSucceeded = "JobSucceeded"
38
        ConditionTypeJobFailed    = "JobFailed"
39

40
        ConditionTypeTaskCompleted = "TaskCompleted"
41
        ConditionTypeTaskFailed    = "TaskFailed"
42
)
43

44
// RenderTaskReconciler reconciles a RenderTask object
45
type RenderTaskReconciler struct {
46
        client.Client
47
        Scheme          *runtime.Scheme
48
        Recorder        events.EventRecorder
49
        RendererImage   string
50
        RendererCommand string
51
        RendererArgs    []string
52
        PushSecretRef   *corev1.SecretReference
53
        BaseURL         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

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

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

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

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

81
        // Handle deletion: cleanup job and secret, then remove finalizer
82
        if !res.DeletionTimestamp.IsZero() {
433✔
83
                log.V(1).Info("RenderTask is being deleted")
15✔
84
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "Deleting", "Delete", "RenderTask is being deleted, cleaning up secret and job")
15✔
85

15✔
86
                // Cleanup render resources, if exists
15✔
87
                if err := r.deleteRenderJob(ctx, res); err != nil && !apierrors.IsNotFound(err) {
15✔
88
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up render job")
×
89
                }
×
90

91
                if err := r.deleteConfigSecret(ctx, res); err != nil && !apierrors.IsNotFound(err) {
15✔
92
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up render secret")
×
93
                }
×
94

95
                if err := r.deleteAuthSecret(ctx, res); err != nil && !apierrors.IsNotFound(err) {
15✔
96
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up auth secret")
×
97
                }
×
98

99
                // Remove finalizer
100
                if slices.Contains(res.Finalizers, renderTaskFinalizer) {
30✔
101
                        log.V(1).Info("Removing finalizer from resource")
15✔
102
                        res.Finalizers = slices.DeleteFunc(res.Finalizers, func(f string) bool {
30✔
103
                                return f == renderTaskFinalizer
15✔
104
                        })
15✔
105
                        if err := r.Update(ctx, res); err != nil {
19✔
106
                                return ctrlResult, errLogAndWrap(log, err, "failed to remove finalizer")
4✔
107
                        }
4✔
108
                }
109

110
                return ctrlResult, nil
11✔
111
        }
112

113
        // Add finalizer if not present and not deleting
114
        if res.DeletionTimestamp.IsZero() {
806✔
115
                if !slices.Contains(res.Finalizers, renderTaskFinalizer) {
446✔
116
                        log.V(1).Info("Adding finalizer to resource")
43✔
117
                        res.Finalizers = append(res.Finalizers, renderTaskFinalizer)
43✔
118
                        if err := r.Update(ctx, res); err != nil {
43✔
119
                                return ctrlResult, errLogAndWrap(log, err, "failed to add finalizer")
×
120
                        }
×
121
                        // Return without requeue; the Update event will trigger reconciliation again
122
                        return ctrlResult, nil
43✔
123
                }
124
        }
125

126
        // Check if renderjob has already completed successfully
127
        sc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobSucceeded)
360✔
128
        if sc != nil && sc.ObservedGeneration >= res.Generation && sc.Status == metav1.ConditionTrue {
365✔
129
                log.V(1).Info("RenderTask has already completed successfully, no further action needed")
5✔
130
                return ctrlResult, nil
5✔
131
        }
5✔
132

133
        // Check if renderjob has already failed
134
        fc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobFailed)
355✔
135
        if fc != nil && fc.ObservedGeneration >= res.Generation && fc.Status == metav1.ConditionTrue {
358✔
136
                log.V(1).Info("RenderTask has already failed, no further action needed")
3✔
137
                return ctrlResult, nil
3✔
138
        }
3✔
139

140
        // Reconcile Config Secret
141
        configSecret := &corev1.Secret{}
352✔
142
        err := r.Get(ctx, r.configSecretKey(res), configSecret)
352✔
143
        if err != nil && apierrors.IsNotFound(err) {
431✔
144
                createdSecret, err := r.createConfigSecret(ctx, res)
79✔
145
                if err != nil {
118✔
146
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateSecretFailed", "CreateConfigSecret", fmt.Sprintf("Failed to create config secret: %s", err))
39✔
147
                        return ctrlResult, errLogAndWrap(log, err, "failed to create secret")
39✔
148
                }
39✔
149
                configSecret = createdSecret
40✔
150
        } else if err != nil {
273✔
151
                return ctrlResult, errLogAndWrap(log, err, "could not get secret")
×
152
        }
×
153

154
        // Reconcile Auth Secret
155
        authSecret := &corev1.Secret{}
313✔
156
        err = r.Get(ctx, r.authSecretKey(res), authSecret)
313✔
157
        if err != nil && apierrors.IsNotFound(err) && r.PushSecretRef != nil {
594✔
158
                createdSecret, err := r.copyAuthSecret(ctx, res)
281✔
159
                if err != nil {
549✔
160
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateSecretFailed", "CreateAuthSecret", fmt.Sprintf("Failed to create auth secret: %s", err))
268✔
161
                        return ctrlResult, errLogAndWrap(log, err, "failed to copy auth secret to namespace")
268✔
162
                }
268✔
163
                authSecret = createdSecret
13✔
164
        } else if client.IgnoreNotFound(err) != nil {
32✔
165
                return ctrlResult, errLogAndWrap(log, err, "could not get auth secret")
×
166
        }
×
167

168
        // Reconcile Job
169
        job := &batchv1.Job{}
45✔
170
        err = r.Get(ctx, r.renderJobKey(res), job)
45✔
171
        if err != nil && apierrors.IsNotFound(err) {
58✔
172
                err := r.createRenderJob(ctx, res, configSecret, authSecret)
13✔
173
                if err != nil {
13✔
UNCOV
174
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateJobFailed", "CreateJob", fmt.Sprintf("Failed to create job: %s", err))
×
UNCOV
175
                        return ctrlResult, errLogAndWrap(log, err, "failed to create job")
×
UNCOV
176
                }
×
177
        } else if err != nil {
32✔
178
                return ctrlResult, errLogAndWrap(log, err, "could not get job")
×
179
        }
×
180

181
        // Update Status
182
        if changed := r.updateResourceStatusFromJob(ctx, res, job); changed {
64✔
183
                if err := r.Status().Update(ctx, res); err != nil {
21✔
184
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
2✔
185
                }
2✔
186
        }
187

188
        // Check if we need to clean up
189
        if isJobComplete(job) && job.Status.Succeeded > 0 {
46✔
190
                if err := r.deleteRenderJob(ctx, res); err != nil && !apierrors.IsNotFound(err) {
3✔
191
                        r.Recorder.Eventf(res, job, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete job", err)
×
192
                        return ctrlResult, nil
×
193
                }
×
194
                if err := r.deleteConfigSecret(ctx, res); err != nil && !apierrors.IsNotFound(err) {
3✔
195
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete config secret", err)
×
196
                        return ctrlResult, nil
×
197
                }
×
198
                if err := r.deleteAuthSecret(ctx, res); err != nil && !apierrors.IsNotFound(err) {
3✔
199
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "DeletionFailed", "Delete", "Failed to delete auth secret", err)
×
200
                        return ctrlResult, nil
×
201
                }
×
202
                log.V(1).Info("Cleaned up after successful job")
3✔
203

3✔
204
                return ctrlResult, nil
3✔
205
        }
206

207
        // Check if job is still running
208
        if !isJobComplete(job) {
80✔
209
                log.V(1).Info("Job is still running, requeue after 5 seconds")
40✔
210
                return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
40✔
211
        }
40✔
212

213
        return ctrlResult, nil
×
214
}
215

216
// updateResourceStatusFromJob updates the resource status based on job status
217
func (r *RenderTaskReconciler) updateResourceStatusFromJob(ctx context.Context, res *solarv1alpha1.RenderTask, job *batchv1.Job) (changed bool) {
45✔
218
        log := ctrl.LoggerFrom(ctx)
45✔
219

45✔
220
        if job == nil {
45✔
221
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
×
222
                        Type:               ConditionTypeJobScheduled,
×
223
                        Status:             metav1.ConditionFalse,
×
224
                        ObservedGeneration: res.Generation,
×
225
                        Reason:             "DoesNotExist",
×
226
                        Message:            "Renderer job does not exist",
×
227
                })
×
228

×
229
                return changed
×
230
        }
×
231

232
        if job.Status.Succeeded > 0 {
48✔
233
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
3✔
234
                        Type:               ConditionTypeJobSucceeded,
3✔
235
                        Status:             metav1.ConditionTrue,
3✔
236
                        ObservedGeneration: res.Generation,
3✔
237
                        Reason:             "JobSucceeded",
3✔
238
                        Message:            fmt.Sprintf("Renderer job completed successfully at %v", job.Status.CompletionTime),
3✔
239
                })
3✔
240

3✔
241
                if res.Status.ChartURL != r.referenceURL(res.Spec.Repository, res.Spec.Tag) {
6✔
242
                        res.Status.ChartURL = r.referenceURL(res.Spec.Repository, res.Spec.Tag)
3✔
243
                        changed = true
3✔
244
                }
3✔
245

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

3✔
249
                return changed
3✔
250
        }
251

252
        if job.Status.Failed > 0 {
43✔
253
                changed = apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
1✔
254
                        Type:               ConditionTypeJobFailed,
1✔
255
                        Status:             metav1.ConditionTrue,
1✔
256
                        ObservedGeneration: res.Generation,
1✔
257
                        Reason:             "JobFailed",
1✔
258
                        Message:            "Renderer job failed",
1✔
259
                })
1✔
260
                r.Recorder.Eventf(res, job, corev1.EventTypeWarning, "JobFailed", "RunJob", "Renderer job failed")
1✔
261
                log.V(1).Info("Job failed", "name", job.Name)
1✔
262

1✔
263
                return changed
1✔
264
        }
1✔
265

266
        return apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
41✔
267
                Type:               ConditionTypeJobScheduled,
41✔
268
                Status:             metav1.ConditionTrue,
41✔
269
                ObservedGeneration: res.Generation,
41✔
270
                Reason:             "JobScheduled",
41✔
271
                Message:            fmt.Sprintf("Renderer job is running (active: %d, succeeded: %d, failed: %d)", job.Status.Active, job.Status.Succeeded, job.Status.Failed),
41✔
272
        })
41✔
273
}
274

275
func (r *RenderTaskReconciler) deleteAuthSecret(ctx context.Context, res *solarv1alpha1.RenderTask) error {
18✔
276
        secret := &corev1.Secret{}
18✔
277
        if err := r.Get(ctx, r.authSecretKey(res), secret); err != nil {
32✔
278
                return err
14✔
279
        }
14✔
280

281
        return r.Delete(ctx, secret, client.PropagationPolicy(metav1.DeletePropagationBackground))
4✔
282
}
283

284
func (r *RenderTaskReconciler) deleteRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask) error {
18✔
285
        job := &batchv1.Job{}
18✔
286
        if err := r.Get(ctx, r.renderJobKey(res), job); err != nil {
32✔
287
                return err
14✔
288
        }
14✔
289

290
        return r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground))
4✔
291
}
292

293
func (r *RenderTaskReconciler) deleteConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask) error {
18✔
294
        secret := &corev1.Secret{}
18✔
295
        if err := r.Get(ctx, r.configSecretKey(res), secret); err != nil {
22✔
296
                return err
4✔
297
        }
4✔
298

299
        return r.Delete(ctx, secret, client.PropagationPolicy(metav1.DeletePropagationBackground))
14✔
300
}
301

302
func (r *RenderTaskReconciler) copyAuthSecret(ctx context.Context, res *solarv1alpha1.RenderTask) (*corev1.Secret, error) {
281✔
303
        log := ctrl.LoggerFrom(ctx)
281✔
304

281✔
305
        controllerSecret := &corev1.Secret{}
281✔
306
        if err := r.Get(ctx, client.ObjectKey{Name: r.PushSecretRef.Name, Namespace: r.PushSecretRef.Namespace}, controllerSecret); err != nil {
531✔
307
                return nil, err
250✔
308
        }
250✔
309

310
        authSecret := &corev1.Secret{
31✔
311
                ObjectMeta: metav1.ObjectMeta{
31✔
312
                        Name:      r.authSecretKey(res).Name,
31✔
313
                        Namespace: r.authSecretKey(res).Namespace,
31✔
314
                },
31✔
315
                Type:       controllerSecret.Type,
31✔
316
                Data:       controllerSecret.Data,
31✔
317
                StringData: controllerSecret.StringData,
31✔
318
        }
31✔
319

31✔
320
        if err := r.Create(ctx, authSecret); err != nil {
49✔
321
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create secret: %s", err)
18✔
322
                return nil, errLogAndWrap(log, err, "secret creation failed")
18✔
323
        }
18✔
324

325
        // Set owner references
326
        if err := controllerutil.SetControllerReference(res, authSecret, r.Scheme); err != nil {
13✔
NEW
327
                return nil, errLogAndWrap(log, err, "failed to set controller reference")
×
328
        }
×
329

330
        return authSecret, nil
13✔
331
}
332

333
func (r *RenderTaskReconciler) createRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask, configSecret, authSecret *corev1.Secret) error {
13✔
334
        log := ctrl.LoggerFrom(ctx)
13✔
335

13✔
336
        jobName := r.renderJobKey(res).Name
13✔
337
        backoffLimit := int32(3)
13✔
338
        ttlSecondsAfterFinished := int32(3600) // Clean up after 1 hour
13✔
339

13✔
340
        job := &batchv1.Job{
13✔
341
                ObjectMeta: metav1.ObjectMeta{
13✔
342
                        Name:      jobName,
13✔
343
                        Namespace: r.renderJobKey(res).Namespace,
13✔
344
                        OwnerReferences: []metav1.OwnerReference{
13✔
345
                                {
13✔
346
                                        APIVersion:         solarv1alpha1.SchemeGroupVersion.String(),
13✔
347
                                        Kind:               res.Kind,
13✔
348
                                        Name:               res.Name,
13✔
349
                                        UID:                res.GetUID(),
13✔
350
                                        Controller:         ptr.To(true),
13✔
351
                                        BlockOwnerDeletion: ptr.To(true),
13✔
352
                                },
13✔
353
                        },
13✔
354
                        Annotations: map[string]string{
13✔
355
                                annotationJobName: jobName,
13✔
356
                        },
13✔
357
                },
13✔
358
                Spec: batchv1.JobSpec{
13✔
359
                        BackoffLimit:            &backoffLimit,
13✔
360
                        TTLSecondsAfterFinished: &ttlSecondsAfterFinished,
13✔
361
                        Template: corev1.PodTemplateSpec{
13✔
362
                                Spec: corev1.PodSpec{
13✔
363
                                        RestartPolicy: corev1.RestartPolicyNever,
13✔
364
                                        Containers: []corev1.Container{
13✔
365
                                                {
13✔
366
                                                        Name:    "renderer",
13✔
367
                                                        Image:   r.RendererImage,
13✔
368
                                                        Command: []string{r.RendererCommand},
13✔
369
                                                        Args: append(r.RendererArgs,
13✔
370
                                                                "/etc/renderer/config.json",
13✔
371
                                                                fmt.Sprintf(`--url="%s"`, r.referenceURL(res.Spec.Repository, res.Spec.Tag)),
13✔
372
                                                        ),
13✔
373
                                                        Env: []corev1.EnvVar{
13✔
374
                                                                {
13✔
375
                                                                        Name: "POD_NAMESPACE",
13✔
376
                                                                        ValueFrom: &corev1.EnvVarSource{
13✔
377
                                                                                FieldRef: &corev1.ObjectFieldSelector{
13✔
378
                                                                                        FieldPath: "metadata.namespace",
13✔
379
                                                                                },
13✔
380
                                                                        },
13✔
381
                                                                },
13✔
382
                                                                {
13✔
383
                                                                        Name: "POD_NAME",
13✔
384
                                                                        ValueFrom: &corev1.EnvVarSource{
13✔
385
                                                                                FieldRef: &corev1.ObjectFieldSelector{
13✔
386
                                                                                        FieldPath: "metadata.name",
13✔
387
                                                                                },
13✔
388
                                                                        },
13✔
389
                                                                },
13✔
390
                                                        },
13✔
391
                                                        VolumeMounts: []corev1.VolumeMount{
13✔
392
                                                                {
13✔
393
                                                                        Name:      "config",
13✔
394
                                                                        MountPath: "/etc/renderer/config.json",
13✔
395
                                                                        SubPath:   "config.json",
13✔
396
                                                                        ReadOnly:  true,
13✔
397
                                                                },
13✔
398
                                                        },
13✔
399
                                                },
13✔
400
                                        },
13✔
401
                                        Volumes: []corev1.Volume{
13✔
402
                                                {
13✔
403
                                                        Name: "config",
13✔
404
                                                        VolumeSource: corev1.VolumeSource{
13✔
405
                                                                Secret: &corev1.SecretVolumeSource{
13✔
406
                                                                        SecretName: configSecret.Name,
13✔
407
                                                                        Items: []corev1.KeyToPath{
13✔
408
                                                                                {
13✔
409
                                                                                        Key:  "config.json",
13✔
410
                                                                                        Path: "config.json",
13✔
411
                                                                                },
13✔
412
                                                                        },
13✔
413
                                                                },
13✔
414
                                                        },
13✔
415
                                                },
13✔
416
                                        },
13✔
417
                                },
13✔
418
                        },
13✔
419
                },
13✔
420
        }
13✔
421

13✔
422
        if authSecret != nil {
26✔
423
                switch authSecret.Type {
13✔
424
                case corev1.SecretTypeBasicAuth:
1✔
425
                        job.Spec.Template.Spec.Containers[0].EnvFrom = append(job.Spec.Template.Spec.Containers[0].EnvFrom, corev1.EnvFromSource{
1✔
426
                                SecretRef: &corev1.SecretEnvSource{
1✔
427
                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
428
                                                Name: authSecret.Name,
1✔
429
                                        },
1✔
430
                                },
1✔
431
                        })
1✔
432
                        job.Spec.Template.Spec.Containers[0].Args = append(job.Spec.Template.Spec.Containers[0].Args,
1✔
433
                                `--username="$username"`, `--password="$password"`)
1✔
434

435
                case corev1.SecretTypeDockerConfigJson:
1✔
436
                        job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
1✔
437
                                Name: "dockerconfig",
1✔
438
                                VolumeSource: corev1.VolumeSource{
1✔
439
                                        Secret: &corev1.SecretVolumeSource{
1✔
440
                                                SecretName: authSecret.Name,
1✔
441
                                                Items: []corev1.KeyToPath{
1✔
442
                                                        {
1✔
443
                                                                Key:  ".dockerconfigjson",
1✔
444
                                                                Path: "dockerconfig.json",
1✔
445
                                                        },
1✔
446
                                                },
1✔
447
                                        },
1✔
448
                                },
1✔
449
                        })
1✔
450

1✔
451
                        job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
1✔
452
                                Name:      "dockerconfig",
1✔
453
                                MountPath: "/etc/renderer/dockerconfig.json",
1✔
454
                                SubPath:   "dockerconfig.json",
1✔
455
                                ReadOnly:  true,
1✔
456
                        })
1✔
457

1✔
458
                        job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
1✔
459
                                Name:  "DOCKER_CONFIG",
1✔
460
                                Value: "/etc/renderer/dockerconfig.json",
1✔
461
                        })
1✔
462
                default:
11✔
463
                }
464
        }
465

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

471
        // Set owner references
472
        if err := controllerutil.SetControllerReference(res, job, r.Scheme); err != nil {
13✔
473
                return errLogAndWrap(log, err, "failed to set controller reference")
×
474
        }
×
475

476
        res.Status.JobRef = &corev1.ObjectReference{
13✔
477
                APIVersion: batchv1.SchemeGroupVersion.String(),
13✔
478
                Kind:       "Job",
13✔
479
                Namespace:  job.Namespace,
13✔
480
                Name:       job.Name,
13✔
481
        }
13✔
482

13✔
483
        if err := r.Status().Update(ctx, res); err != nil {
13✔
484
                return errLogAndWrap(log, err, "failed to update status")
×
485
        }
×
486

487
        return nil
13✔
488
}
489

490
func (r *RenderTaskReconciler) createConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask) (*corev1.Secret, error) {
79✔
491
        log := ctrl.LoggerFrom(ctx)
79✔
492

79✔
493
        cfgJson, err := json.Marshal(res.Spec.RendererConfig)
79✔
494
        if err != nil {
79✔
NEW
495
                return nil, err
×
496
        }
×
497

498
        secret := &corev1.Secret{
79✔
499
                ObjectMeta: metav1.ObjectMeta{
79✔
500
                        Name:      r.configSecretKey(res).Name,
79✔
501
                        Namespace: r.configSecretKey(res).Namespace,
79✔
502
                        OwnerReferences: []metav1.OwnerReference{
79✔
503
                                {
79✔
504
                                        APIVersion:         solarv1alpha1.SchemeGroupVersion.String(),
79✔
505
                                        Kind:               res.Kind,
79✔
506
                                        Name:               res.Name,
79✔
507
                                        UID:                res.UID,
79✔
508
                                        Controller:         ptr.To(true),
79✔
509
                                        BlockOwnerDeletion: ptr.To(true),
79✔
510
                                },
79✔
511
                        },
79✔
512
                        Annotations: map[string]string{
79✔
513
                                annotationSecretName: r.configSecretKey(res).Name,
79✔
514
                        },
79✔
515
                },
79✔
516
                Type: corev1.SecretTypeOpaque,
79✔
517
                Data: map[string][]byte{
79✔
518
                        "config.json": cfgJson,
79✔
519
                },
79✔
520
        }
79✔
521

79✔
522
        if err := r.Create(ctx, secret); err != nil {
118✔
523
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create secret: %s", err)
39✔
524
                return nil, errLogAndWrap(log, err, "secret creation failed")
39✔
525
        }
39✔
526

527
        // Set owner references
528
        if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
40✔
NEW
529
                return nil, errLogAndWrap(log, err, "failed to set controller reference")
×
530
        }
×
531

532
        res.Status.ConfigSecretRef = &corev1.ObjectReference{
40✔
533
                APIVersion: corev1.SchemeGroupVersion.String(),
40✔
534
                Kind:       "Secret",
40✔
535
                Namespace:  secret.Namespace,
40✔
536
                Name:       secret.Name,
40✔
537
        }
40✔
538

40✔
539
        if err := r.Status().Update(ctx, res); err != nil {
40✔
NEW
540
                return nil, errLogAndWrap(log, err, "failed to update status")
×
541
        }
×
542

543
        return secret, nil
40✔
544
}
545

546
func (r *RenderTaskReconciler) configSecretKey(res *solarv1alpha1.RenderTask) client.ObjectKey {
607✔
547
        return client.ObjectKey{
607✔
548
                Name:      fmt.Sprintf("render-%s", res.Name),
607✔
549
                Namespace: res.Namespace,
607✔
550
        }
607✔
551
}
607✔
552

553
func (r *RenderTaskReconciler) authSecretKey(res *solarv1alpha1.RenderTask) client.ObjectKey {
393✔
554
        return client.ObjectKey{
393✔
555
                Name:      fmt.Sprintf("auth-%s", res.Name),
393✔
556
                Namespace: res.Namespace,
393✔
557
        }
393✔
558
}
393✔
559

560
func (r *RenderTaskReconciler) renderJobKey(res *solarv1alpha1.RenderTask) client.ObjectKey {
89✔
561
        return client.ObjectKey{
89✔
562
                Name:      fmt.Sprintf("render-%s", res.Name),
89✔
563
                Namespace: res.Namespace,
89✔
564
        }
89✔
565
}
89✔
566

567
// isJobComplete returns true if the Job is complete
568
func isJobComplete(job *batchv1.Job) bool {
83✔
569
        return job.Status.CompletionTime != nil
83✔
570
}
83✔
571

572
func (r *RenderTaskReconciler) referenceURL(repo string, tag string) string {
19✔
573
        base := r.BaseURL
19✔
574
        if !strings.HasPrefix(base, "oci://") {
38✔
575
                base = fmt.Sprintf("oci://%s", base)
19✔
576
        }
19✔
577
        base = strings.TrimSuffix(base, "/")
19✔
578
        url := fmt.Sprintf("%s/%s:%s", base, repo, tag)
19✔
579

19✔
580
        return url
19✔
581
}
582

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