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

opendefensecloud / solution-arsenal / 22100339794

17 Feb 2026 01:28PM UTC coverage: 70.995% (+0.3%) from 70.744%
22100339794

Pull #157

github

web-flow
Merge e90e61c19 into bbcb7ff8f
Pull Request #157: Implement APIWriter

91 of 104 new or added lines in 5 files covered. (87.5%)

10 existing lines in 2 files now uncovered.

1684 of 2372 relevant lines covered (70.99%)

14.57 hits per line

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

85.63
/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
        "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
        renderTaskFinalizer = "solar.opendefense.cloud/rendertask-finalizer"
29

30
        annotationJobName    = "solar.opendefense.cloud/job-name"
31
        annotationSecretName = "solar.opendefense.cloud/secret-name"
32

33
        // Condition types
34
        ConditionTypeJobScheduled = "JobScheduled"
35
        ConditionTypeJobSucceeded = "JobSucceeded"
36
        ConditionTypeJobFailed    = "JobFailed"
37
        ConditionTypeSecretSynced = "SecretSynced"
38

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

43
// RenderTaskReconciler reconciles a RenderTask object
44
type RenderTaskReconciler struct {
45
        client.Client
46
        Scheme          *runtime.Scheme
47
        Recorder        events.EventRecorder
48
        RendererImage   string
49
        RendererCommand string
50
        RendererArgs    []string
51
}
52

53
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks,verbs=get;list;watch;create;update;patch;delete
54
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks/status,verbs=get;update;patch
55
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=rendertasks/finalizers,verbs=update
56
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
57
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
58
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
59

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

168✔
65
        log.V(1).Info("RenderTask is being reconciled", "req", req)
168✔
66

168✔
67
        // Fetch the RenderTask instance
168✔
68
        res := &solarv1alpha1.RenderTask{}
168✔
69
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
176✔
70
                if apierrors.IsNotFound(err) {
16✔
71
                        // Object not found, return. Created objects are automatically garbage collected.
8✔
72
                        return ctrlResult, nil
8✔
73
                }
8✔
74

75
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
76
        }
77

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

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

88
                if err := r.deleteRenderSecret(ctx, res); err != nil && !apierrors.IsNotFound(err) {
5✔
89
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up render secret")
×
90
                }
×
91

92
                // Remove finalizer
93
                if slices.Contains(res.Finalizers, renderTaskFinalizer) {
10✔
94
                        log.V(1).Info("Removing finalizer from resource")
5✔
95
                        res.Finalizers = slices.DeleteFunc(res.Finalizers, func(f string) bool {
10✔
96
                                return f == renderTaskFinalizer
5✔
97
                        })
5✔
98
                        if err := r.Update(ctx, res); err != nil {
5✔
UNCOV
99
                                return ctrlResult, errLogAndWrap(log, err, "failed to remove finalizer")
×
UNCOV
100
                        }
×
101
                }
102

103
                return ctrlResult, nil
5✔
104
        }
105

106
        // Add finalizer if not present and not deleting
107
        if res.DeletionTimestamp.IsZero() {
310✔
108
                if !slices.Contains(res.Finalizers, renderTaskFinalizer) {
184✔
109
                        log.V(1).Info("Adding finalizer to resource")
29✔
110
                        res.Finalizers = append(res.Finalizers, renderTaskFinalizer)
29✔
111
                        if err := r.Update(ctx, res); err != nil {
29✔
112
                                return ctrlResult, errLogAndWrap(log, err, "failed to add finalizer")
×
113
                        }
×
114
                        // Return without requeue; the Update event will trigger reconciliation again
115
                        return ctrlResult, nil
29✔
116
                }
117
        }
118

119
        // Check if renderjob has already completed successfully
120
        sc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobSucceeded)
126✔
121
        if sc != nil && sc.ObservedGeneration >= res.Generation && sc.Status == metav1.ConditionTrue {
131✔
122
                log.V(1).Info("RenderTask has already completed successfully, no further action needed")
5✔
123
                return ctrlResult, nil
5✔
124
        }
5✔
125

126
        // Check if renderjob has already failed
127
        fc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeJobFailed)
121✔
128
        if fc != nil && fc.ObservedGeneration >= res.Generation && fc.Status == metav1.ConditionTrue {
124✔
129
                log.V(1).Info("RenderTask has already failed, no further action needed")
3✔
130
                return ctrlResult, nil
3✔
131
        }
3✔
132

133
        // Check if secret creation has already failed
134
        ssc := apimeta.FindStatusCondition(res.Status.Conditions, ConditionTypeSecretSynced)
118✔
135
        if ssc != nil && ssc.ObservedGeneration >= res.Generation && ssc.Status == metav1.ConditionFalse {
122✔
136
                log.V(1).Info("Secret creation failed, aborting reconcile")
4✔
137
                return ctrlResult, nil
4✔
138
        }
4✔
139

140
        // Reconcile Secret
141
        secret := &corev1.Secret{}
114✔
142
        err := r.Get(ctx, client.ObjectKey{Name: renderPrefixed(res.Name), Namespace: res.Namespace}, secret)
114✔
143
        if err != nil && apierrors.IsNotFound(err) {
143✔
144
                err := r.createRenderSecret(ctx, res)
29✔
145
                if err != nil {
33✔
146
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateSecretFailed", "CreateSecret", fmt.Sprintf("Failed to create secret: %s", err))
4✔
147
                        if changed := apimeta.SetStatusCondition(&res.Status.Conditions, metav1.Condition{
4✔
148
                                Type:               ConditionTypeSecretSynced,
4✔
149
                                Status:             metav1.ConditionFalse,
4✔
150
                                ObservedGeneration: res.Generation,
4✔
151
                                Reason:             "SecretCreationFailed",
4✔
152
                                Message:            fmt.Sprintf("Failed to create secret: %v", err),
4✔
153
                        }); changed {
8✔
154
                                if err := r.Status().Update(ctx, res); err != nil {
4✔
155
                                        log.Error(err, "failed to update RenderTask status")
×
156
                                }
×
157
                        }
158

159
                        return ctrlResult, errLogAndWrap(log, err, "failed to create secret")
4✔
160
                }
161
                // update secret after creation
162
                if err := r.Get(ctx, client.ObjectKey{Name: renderPrefixed(res.Name), Namespace: res.Namespace}, secret); err != nil {
25✔
163
                        return ctrlResult, errLogAndWrap(log, err, "could not get secret")
×
164
                }
×
165
        } else if err != nil {
85✔
166
                return ctrlResult, errLogAndWrap(log, err, "could not get secret")
×
167
        }
×
168

169
        // Reconcile Job
170
        job := &batchv1.Job{}
110✔
171
        err = r.Get(ctx, client.ObjectKey{Name: renderPrefixed(res.Name), Namespace: res.Namespace}, job)
110✔
172
        if err != nil && apierrors.IsNotFound(err) {
158✔
173
                err := r.createRenderJob(ctx, res, secret)
48✔
174
                if err != nil {
73✔
175
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreateJobFailed", "CreateJob", fmt.Sprintf("Failed to create job: %s", err))
25✔
176
                        return ctrlResult, errLogAndWrap(log, err, "failed to create job")
25✔
177
                }
25✔
178

179
                // update job after creation
180
                if err := r.Get(ctx, client.ObjectKey{Name: renderPrefixed(res.Name), Namespace: res.Namespace}, job); err != nil {
23✔
181
                        return ctrlResult, errLogAndWrap(log, err, "could not get job")
×
182
                }
×
183
        } else if err != nil {
62✔
184
                return ctrlResult, errLogAndWrap(log, err, "could not get job")
×
185
        }
×
186

187
        if changed := r.updateResourceStatusFromJob(ctx, res, job); changed {
116✔
188
                if err := r.Status().Update(ctx, res); err != nil {
35✔
189
                        return ctrlResult, errLogAndWrap(log, err, "failed to update status")
4✔
190
                }
4✔
191
        }
192

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

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

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

214
        return ctrlResult, nil
×
215
}
216

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

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

×
230
                return changed
×
231
        }
×
232

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

3✔
242
                if res.Status.ChartURL != res.Spec.PushOptions.ReferenceURL {
6✔
243
                        res.Status.ChartURL = res.Spec.PushOptions.ReferenceURL
3✔
244
                        changed = true
3✔
245
                }
3✔
246

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

3✔
250
                return changed
3✔
251
        }
252

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

1✔
264
                return changed
1✔
265
        }
1✔
266

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

276
func (r *RenderTaskReconciler) deleteRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask) error {
8✔
277
        job := &batchv1.Job{}
8✔
278
        if err := r.Get(ctx, client.ObjectKey{Name: renderPrefixed(res.Name), Namespace: res.Namespace}, job); err != nil {
8✔
UNCOV
279
                return err
×
UNCOV
280
        }
×
281

282
        return r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground))
8✔
283
}
284

285
func (r *RenderTaskReconciler) deleteRenderSecret(ctx context.Context, res *solarv1alpha1.RenderTask) error {
8✔
286
        secret := &corev1.Secret{}
8✔
287
        if err := r.Get(ctx, client.ObjectKey{Name: renderPrefixed(res.Name), Namespace: res.Namespace}, secret); err != nil {
8✔
UNCOV
288
                return err
×
UNCOV
289
        }
×
290

291
        return r.Delete(ctx, secret, client.PropagationPolicy(metav1.DeletePropagationBackground))
8✔
292
}
293

294
func (r *RenderTaskReconciler) createRenderJob(ctx context.Context, res *solarv1alpha1.RenderTask, secret *corev1.Secret) error {
48✔
295
        log := ctrl.LoggerFrom(ctx)
48✔
296

48✔
297
        jobName := renderPrefixed(res.Name)
48✔
298
        backoffLimit := int32(3)
48✔
299
        ttlSecondsAfterFinished := int32(3600) // Clean up after 1 hour
48✔
300

48✔
301
        job := &batchv1.Job{
48✔
302
                ObjectMeta: metav1.ObjectMeta{
48✔
303
                        Name:      jobName,
48✔
304
                        Namespace: res.Namespace,
48✔
305
                        OwnerReferences: []metav1.OwnerReference{
48✔
306
                                {
48✔
307
                                        APIVersion:         solarv1alpha1.SchemeGroupVersion.String(),
48✔
308
                                        Kind:               res.Kind,
48✔
309
                                        Name:               res.Name,
48✔
310
                                        UID:                res.GetUID(),
48✔
311
                                        Controller:         boolPtr(true),
48✔
312
                                        BlockOwnerDeletion: boolPtr(true),
48✔
313
                                },
48✔
314
                        },
48✔
315
                        Annotations: map[string]string{
48✔
316
                                annotationJobName: jobName,
48✔
317
                        },
48✔
318
                },
48✔
319
                Spec: batchv1.JobSpec{
48✔
320
                        BackoffLimit:            &backoffLimit,
48✔
321
                        TTLSecondsAfterFinished: &ttlSecondsAfterFinished,
48✔
322
                        Template: corev1.PodTemplateSpec{
48✔
323
                                Spec: corev1.PodSpec{
48✔
324
                                        RestartPolicy: corev1.RestartPolicyNever,
48✔
325
                                        Containers: []corev1.Container{
48✔
326
                                                {
48✔
327
                                                        Name:    "renderer",
48✔
328
                                                        Image:   r.RendererImage,
48✔
329
                                                        Command: []string{r.RendererCommand},
48✔
330
                                                        Args:    append(r.RendererArgs, "/etc/renderer/config.json"),
48✔
331
                                                        Env: []corev1.EnvVar{
48✔
332
                                                                {
48✔
333
                                                                        Name: "POD_NAMESPACE",
48✔
334
                                                                        ValueFrom: &corev1.EnvVarSource{
48✔
335
                                                                                FieldRef: &corev1.ObjectFieldSelector{
48✔
336
                                                                                        FieldPath: "metadata.namespace",
48✔
337
                                                                                },
48✔
338
                                                                        },
48✔
339
                                                                },
48✔
340
                                                                {
48✔
341
                                                                        Name: "POD_NAME",
48✔
342
                                                                        ValueFrom: &corev1.EnvVarSource{
48✔
343
                                                                                FieldRef: &corev1.ObjectFieldSelector{
48✔
344
                                                                                        FieldPath: "metadata.name",
48✔
345
                                                                                },
48✔
346
                                                                        },
48✔
347
                                                                },
48✔
348
                                                        },
48✔
349
                                                        VolumeMounts: []corev1.VolumeMount{
48✔
350
                                                                {
48✔
351
                                                                        Name:      "config",
48✔
352
                                                                        MountPath: "/etc/renderer",
48✔
353
                                                                        ReadOnly:  true,
48✔
354
                                                                },
48✔
355
                                                        },
48✔
356
                                                },
48✔
357
                                        },
48✔
358
                                        Volumes: []corev1.Volume{
48✔
359
                                                {
48✔
360
                                                        Name: "config",
48✔
361
                                                        VolumeSource: corev1.VolumeSource{
48✔
362
                                                                Secret: &corev1.SecretVolumeSource{
48✔
363
                                                                        SecretName: secret.Name,
48✔
364
                                                                        Items: []corev1.KeyToPath{
48✔
365
                                                                                {
48✔
366
                                                                                        Key:  "config.json",
48✔
367
                                                                                        Path: "config.json",
48✔
368
                                                                                },
48✔
369
                                                                        },
48✔
370
                                                                },
48✔
371
                                                        },
48✔
372
                                                },
48✔
373
                                        },
48✔
374
                                },
48✔
375
                        },
48✔
376
                },
48✔
377
        }
48✔
378

48✔
379
        if err := r.Create(ctx, job); err != nil {
73✔
380
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create job: %s", err)
25✔
381
                return errLogAndWrap(log, err, "job creation failed")
25✔
382
        }
25✔
383

384
        // Set owner references
385
        if err := controllerutil.SetControllerReference(res, job, r.Scheme); err != nil {
23✔
386
                return errLogAndWrap(log, err, "failed to set controller reference")
×
387
        }
×
388

389
        res.Status.JobRef = &corev1.ObjectReference{
23✔
390
                APIVersion: batchv1.SchemeGroupVersion.String(),
23✔
391
                Kind:       "Job",
23✔
392
                Namespace:  job.Namespace,
23✔
393
                Name:       job.Name,
23✔
394
        }
23✔
395

23✔
396
        if err := r.Status().Update(ctx, res); err != nil {
23✔
397
                return errLogAndWrap(log, err, "failed to update status")
×
398
        }
×
399

400
        return nil
23✔
401
}
402

403
func (r *RenderTaskReconciler) createRenderSecret(ctx context.Context, res *solarv1alpha1.RenderTask) error {
29✔
404
        log := ctrl.LoggerFrom(ctx)
29✔
405

29✔
406
        // FIXME: this seems to be a false positive
29✔
407
        // nolint:musttag
29✔
408
        cfgJson, err := json.Marshal(res.Spec.RendererConfig)
29✔
409
        if err != nil {
29✔
410
                return err
×
411
        }
×
412

413
        secret := &corev1.Secret{
29✔
414
                ObjectMeta: metav1.ObjectMeta{
29✔
415
                        Name:      renderPrefixed(res.Name),
29✔
416
                        Namespace: res.Namespace,
29✔
417
                        OwnerReferences: []metav1.OwnerReference{
29✔
418
                                {
29✔
419
                                        APIVersion:         solarv1alpha1.SchemeGroupVersion.String(),
29✔
420
                                        Kind:               res.Kind,
29✔
421
                                        Name:               res.Name,
29✔
422
                                        UID:                res.UID,
29✔
423
                                        Controller:         boolPtr(true),
29✔
424
                                        BlockOwnerDeletion: boolPtr(true),
29✔
425
                                },
29✔
426
                        },
29✔
427
                        Annotations: map[string]string{
29✔
428
                                annotationSecretName: renderPrefixed(res.Name),
29✔
429
                        },
29✔
430
                },
29✔
431
                Type: corev1.SecretTypeOpaque,
29✔
432
                Data: map[string][]byte{
29✔
433
                        "config.json": cfgJson,
29✔
434
                },
29✔
435
        }
29✔
436

29✔
437
        if err := r.Create(ctx, secret); err != nil {
33✔
438
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "CreationFailed", "Create", "Failed to create secret: %s", err)
4✔
439
                return errLogAndWrap(log, err, "secret creation failed")
4✔
440
        }
4✔
441

442
        // Set owner references
443
        if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
25✔
444
                return errLogAndWrap(log, err, "failed to set controller reference")
×
445
        }
×
446

447
        res.Status.ConfigSecretRef = &corev1.ObjectReference{
25✔
448
                APIVersion: corev1.SchemeGroupVersion.String(),
25✔
449
                Kind:       "Secret",
25✔
450
                Namespace:  secret.Namespace,
25✔
451
                Name:       secret.Name,
25✔
452
        }
25✔
453

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

458
        return nil
25✔
459
}
460

461
// renderPrefixed returns the "render-" prefixed string
462
func renderPrefixed(r string) string {
394✔
463
        return fmt.Sprintf("render-%s", r)
394✔
464
}
394✔
465

466
// isJobComplete returns true if the Job is complete
467
func isJobComplete(job *batchv1.Job) bool {
159✔
468
        return job.Status.CompletionTime != nil
159✔
469
}
159✔
470

471
func boolPtr(b bool) *bool {
154✔
472
        return &b
154✔
473
}
154✔
474

475
// SetupWithManager sets up the controller with the Manager.
476
func (r *RenderTaskReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
477
        return ctrl.NewControllerManagedBy(mgr).
1✔
478
                For(&solarv1alpha1.RenderTask{}).
1✔
479
                Owns(&batchv1.Job{}).
1✔
480
                Owns(&corev1.Secret{}).
1✔
481
                Complete(r)
1✔
482
}
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