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

opendefensecloud / solution-arsenal / 22720535703

05 Mar 2026 01:39PM UTC coverage: 70.788%. First build
22720535703

Pull #195

github

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

232 of 255 new or added lines in 5 files covered. (90.98%)

2004 of 2831 relevant lines covered (70.79%)

22.4 hits per line

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

89.46
/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) {
395✔
65
        log := ctrl.LoggerFrom(ctx)
395✔
66
        ctrlResult := ctrl.Result{}
395✔
67

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

395✔
70
        // Fetch the RenderTask instance
395✔
71
        res := &solarv1alpha1.RenderTask{}
395✔
72
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
407✔
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() {
396✔
83
                log.V(1).Info("RenderTask is being deleted")
13✔
84
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "Deleting", "Delete", "RenderTask is being deleted, cleaning up secret and job")
13✔
85

13✔
86
                // Cleanup render resources, if exists
13✔
87
                if err := r.deleteRenderJob(ctx, res); err != nil && !apierrors.IsNotFound(err) {
13✔
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) {
13✔
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) {
13✔
NEW
96
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up auth secret")
×
NEW
97
                }
×
98

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

110
                return ctrlResult, nil
11✔
111
        }
112

113
        // Add finalizer if not present and not deleting
114
        if res.DeletionTimestamp.IsZero() {
740✔
115
                if !slices.Contains(res.Finalizers, renderTaskFinalizer) {
416✔
116
                        log.V(1).Info("Adding finalizer to resource")
46✔
117
                        res.Finalizers = append(res.Finalizers, renderTaskFinalizer)
46✔
118
                        if err := r.Update(ctx, res); err != nil {
46✔
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
46✔
123
                }
124
        }
125

126
        // Check if renderjob has already completed successfully
127
        sc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobSucceeded)
324✔
128
        if sc != nil && sc.ObservedGeneration >= res.Generation && sc.Status == metav1.ConditionTrue {
329✔
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)
319✔
135
        if fc != nil && fc.ObservedGeneration >= res.Generation && fc.Status == metav1.ConditionTrue {
322✔
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{}
316✔
142
        err := r.Get(ctx, r.configSecretKey(res), configSecret)
316✔
143
        if err != nil && apierrors.IsNotFound(err) {
403✔
144
                err := r.createConfigSecret(ctx, res)
87✔
145
                if err != nil {
132✔
146
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateSecretFailed", "CreateConfigSecret", fmt.Sprintf("Failed to create config secret: %s", err))
45✔
147
                        return ctrlResult, errLogAndWrap(log, err, "failed to create secret")
45✔
148
                }
45✔
149
        } else if err != nil {
229✔
150
                return ctrlResult, errLogAndWrap(log, err, "could not get secret")
×
151
        }
×
152

153
        // Reconcile Auth Secret
154
        authSecret := &corev1.Secret{}
271✔
155
        err = r.Get(ctx, r.authSecretKey(res), authSecret)
271✔
156
        if err != nil && apierrors.IsNotFound(err) && r.PushSecretRef != nil {
333✔
157
                err := r.copyAuthSecret(ctx, res)
62✔
158
                if err != nil {
84✔
159
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateSecretFailed", "CreateAuthSecret", fmt.Sprintf("Failed to create auth secret: %s", err))
22✔
160
                        return ctrlResult, errLogAndWrap(log, err, "failed to copy auth secret to namespace")
22✔
161
                }
22✔
162
        } else if client.IgnoreNotFound(err) != nil {
209✔
NEW
163
                return ctrlResult, errLogAndWrap(log, err, "could not get auth secret")
×
NEW
164
        }
×
165

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

179
        // Update Status
180
        if changed := r.updateResourceStatusFromJob(ctx, res, job); changed {
257✔
181
                if err := r.Status().Update(ctx, res); err != nil {
54✔
182
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
5✔
183
                }
5✔
184
        }
185

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

3✔
202
                return ctrlResult, nil
3✔
203
        }
204

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

211
        return ctrlResult, nil
×
212
}
213

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

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

×
227
                return changed
×
228
        }
×
229

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

3✔
239
                if res.Status.ChartURL != r.referenceURL(res.Spec.Reference) {
6✔
240
                        res.Status.ChartURL = r.referenceURL(res.Spec.Reference)
3✔
241
                        changed = true
3✔
242
                }
3✔
243

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

3✔
247
                return changed
3✔
248
        }
249

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

1✔
261
                return changed
1✔
262
        }
1✔
263

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

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

279
        return r.Delete(ctx, secret, client.PropagationPolicy(metav1.DeletePropagationBackground))
14✔
280
}
281

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

288
        return r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground))
14✔
289
}
290

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

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

300
func (r *RenderTaskReconciler) copyAuthSecret(ctx context.Context, res *solarv1alpha1.RenderTask) error {
62✔
301
        log := ctrl.LoggerFrom(ctx)
62✔
302

62✔
303
        controllerSecret := &corev1.Secret{}
62✔
304
        if err := r.Get(ctx, client.ObjectKey{Name: r.PushSecretRef.Name, Namespace: r.PushSecretRef.Namespace}, controllerSecret); err != nil {
62✔
NEW
305
                return err
×
NEW
306
        }
×
307

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

62✔
318
        if err := r.Create(ctx, authSecret); err != nil {
84✔
319
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create secret: %s", err)
22✔
320
                return errLogAndWrap(log, err, "secret creation failed")
22✔
321
        }
22✔
322

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

328
        return nil
40✔
329
}
330

331
func (r *RenderTaskReconciler) createRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask, secret *corev1.Secret) error {
80✔
332
        log := ctrl.LoggerFrom(ctx)
80✔
333

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

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

80✔
420
        authSecret := &corev1.Secret{}
80✔
421
        if r.PushSecretRef != nil {
160✔
422
                if err := r.Get(ctx, r.authSecretKey(res), authSecret); err != nil {
85✔
423
                        if apierrors.IsNotFound(err) {
10✔
424
                                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "SecretNotFound", "GetAuthCredentials", "the configured auth secret was not found")
5✔
425
                        }
5✔
426

427
                        return errLogAndWrap(log, err, "failed to get auth secret from secret ref")
5✔
428
                }
429
        }
430

431
        switch authSecret.Type {
75✔
432
        case corev1.SecretTypeBasicAuth:
2✔
433
                job.Spec.Template.Spec.Containers[0].EnvFrom = append(job.Spec.Template.Spec.Containers[0].EnvFrom, corev1.EnvFromSource{
2✔
434
                        SecretRef: &corev1.SecretEnvSource{
2✔
435
                                LocalObjectReference: corev1.LocalObjectReference{
2✔
436
                                        Name: authSecret.Name,
2✔
437
                                },
2✔
438
                        },
2✔
439
                })
2✔
440
                job.Spec.Template.Spec.Containers[0].Args = append(job.Spec.Template.Spec.Containers[0].Args,
2✔
441
                        `--username="$username"`, `--password="$password"`)
2✔
442

443
        case corev1.SecretTypeDockerConfigJson:
54✔
444
                job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
54✔
445
                        Name: "dockerconfig",
54✔
446
                        VolumeSource: corev1.VolumeSource{
54✔
447
                                Secret: &corev1.SecretVolumeSource{
54✔
448
                                        SecretName: authSecret.Name,
54✔
449
                                        Items: []corev1.KeyToPath{
54✔
450
                                                {
54✔
451
                                                        Key:  ".dockerconfigjson",
54✔
452
                                                        Path: "dockerconfig.json",
54✔
453
                                                },
54✔
454
                                        },
54✔
455
                                },
54✔
456
                        },
54✔
457
                })
54✔
458

54✔
459
                job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
54✔
460
                        Name:      "dockerconfig",
54✔
461
                        MountPath: "/etc/renderer/dockerconfig.json",
54✔
462
                        SubPath:   "dockerconfig.json",
54✔
463
                        ReadOnly:  true,
54✔
464
                })
54✔
465

54✔
466
                job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
54✔
467
                        Name:  "DOCKER_CONFIG",
54✔
468
                        Value: "/etc/renderer/dockerconfig.json",
54✔
469
                })
54✔
470
        default:
19✔
471
        }
472

473
        if err := r.Create(ctx, job); err != nil {
110✔
474
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create job: %s", err)
35✔
475
                return errLogAndWrap(log, err, "job creation failed")
35✔
476
        }
35✔
477

478
        // Set owner references
479
        if err := controllerutil.SetControllerReference(res, job, r.Scheme); err != nil {
40✔
480
                return errLogAndWrap(log, err, "failed to set controller reference")
×
481
        }
×
482

483
        res.Status.JobRef = &corev1.ObjectReference{
40✔
484
                APIVersion: batchv1.SchemeGroupVersion.String(),
40✔
485
                Kind:       "Job",
40✔
486
                Namespace:  job.Namespace,
40✔
487
                Name:       job.Name,
40✔
488
        }
40✔
489

40✔
490
        if err := r.Status().Update(ctx, res); err != nil {
41✔
491
                return errLogAndWrap(log, err, "failed to update status")
1✔
492
        }
1✔
493

494
        return nil
39✔
495
}
496

497
func (r *RenderTaskReconciler) createConfigSecret(ctx context.Context, res *solarv1alpha1.RenderTask) error {
87✔
498
        log := ctrl.LoggerFrom(ctx)
87✔
499

87✔
500
        cfgJson, err := json.Marshal(res.Spec.RendererConfig)
87✔
501
        if err != nil {
87✔
502
                return err
×
503
        }
×
504

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

87✔
529
        if err := r.Create(ctx, secret); err != nil {
132✔
530
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create secret: %s", err)
45✔
531
                return errLogAndWrap(log, err, "secret creation failed")
45✔
532
        }
45✔
533

534
        // Set owner references
535
        if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
42✔
536
                return errLogAndWrap(log, err, "failed to set controller reference")
×
537
        }
×
538

539
        res.Status.ConfigSecretRef = &corev1.ObjectReference{
42✔
540
                APIVersion: corev1.SchemeGroupVersion.String(),
42✔
541
                Kind:       "Secret",
42✔
542
                Namespace:  secret.Namespace,
42✔
543
                Name:       secret.Name,
42✔
544
        }
42✔
545

42✔
546
        if err := r.Status().Update(ctx, res); err != nil {
42✔
547
                return errLogAndWrap(log, err, "failed to update status")
×
548
        }
×
549

550
        return nil
42✔
551
}
552

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

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

567
func (r *RenderTaskReconciler) renderJobKey(res *solarv1alpha1.RenderTask) client.ObjectKey {
425✔
568
        return client.ObjectKey{
425✔
569
                Name:      fmt.Sprintf("render-%s", res.Name),
425✔
570
                Namespace: res.Namespace,
425✔
571
        }
425✔
572
}
425✔
573

574
// isJobComplete returns true if the Job is complete
575
func isJobComplete(job *batchv1.Job) bool {
403✔
576
        return job.Status.CompletionTime != nil
403✔
577
}
403✔
578

579
func (r *RenderTaskReconciler) referenceURL(ref string) string {
86✔
580
        base := r.BaseURL
86✔
581
        if !strings.HasPrefix(base, "oci://") {
172✔
582
                base = fmt.Sprintf("oci://%s", base)
86✔
583
        }
86✔
584
        base = strings.TrimSuffix(base, "/")
86✔
585
        url := fmt.Sprintf("%s/%s", base, ref)
86✔
586

86✔
587
        return url
86✔
588
}
589

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