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

opendefensecloud / solution-arsenal / 22999901793

12 Mar 2026 11:32AM UTC coverage: 71.674% (+0.4%) from 71.295%
22999901793

push

github

web-flow
Merge pull request #271 from opendefensecloud/fix/236-delete-event-fails

fix: handle empty name in delete event's resource

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

20 existing lines in 4 files now uncovered.

2085 of 2909 relevant lines covered (71.67%)

19.79 hits per line

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

66.35
/pkg/controller/discovery_controller.go
1
// Copyright 2026 BWI GmbH and Artefact Conduit contributors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package controller
5

6
import (
7
        "context"
8
        "fmt"
9
        "slices"
10

11
        corev1 "k8s.io/api/core/v1"
12
        rbacv1 "k8s.io/api/rbac/v1"
13
        apierrors "k8s.io/apimachinery/pkg/api/errors"
14
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15
        "k8s.io/apimachinery/pkg/runtime"
16
        "k8s.io/apimachinery/pkg/types"
17
        "k8s.io/apimachinery/pkg/util/intstr"
18
        "k8s.io/client-go/tools/events"
19
        ctrl "sigs.k8s.io/controller-runtime"
20
        "sigs.k8s.io/controller-runtime/pkg/client"
21
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
22

23
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
24
        "go.opendefense.cloud/solar/pkg/discovery"
25
)
26

27
const (
28
        discoveryFinalizer = "solar.opendefense.cloud/discovery-finalizer"
29
        workerRoleName     = "solar-discovery-worker"
30
)
31

32
// DiscoveryReconciler reconciles a Discovery object
33
type DiscoveryReconciler struct {
34
        client.Client
35
        Scheme        *runtime.Scheme
36
        Recorder      events.EventRecorder
37
        WorkerImage   string
38
        WorkerCommand string
39
        WorkerArgs    []string
40
}
41

42
//nolint:lll
43
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=discoveries,verbs=get;list;watch;create;update;patch;delete
44
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=discoveries/status,verbs=get;update;patch
45
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=discoveries/finalizers,verbs=update
46
//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete
47
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
48
//+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
49
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete
50
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
51
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch;create;update;patch;delete
52
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
53
//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch
54
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=components,verbs=get;list;watch;create;update;patch;delete
55
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=componentversions,verbs=get;list;watch;create;update;patch;delete
56

57
// needed in order to be able to grant permissions
58
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=components,verbs=get;list;watch;create;update;patch;delete
59
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=componentversions,verbs=get;list;watch;create;update;patch;delete
60

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

21✔
66
        log.V(1).Info("Discovery is being reconciled", "req", req)
21✔
67

21✔
68
        // Fetch the Order instance
21✔
69
        res := &solarv1alpha1.Discovery{}
21✔
70
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
22✔
71
                if apierrors.IsNotFound(err) {
2✔
72
                        // Object not found, return. Created objects are automatically garbage collected.
1✔
73
                        return ctrlResult, nil
1✔
74
                }
1✔
75

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

79
        // Handle deletion: cleanup artifact workflows, then remove finalizer
80
        if !res.DeletionTimestamp.IsZero() {
21✔
81
                log.V(1).Info("Discovery is being deleted")
1✔
82
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "Deleting", "Delete", "Discovery is being deleted, cleaning up worker")
1✔
83

1✔
84
                // Cleanup worker resources, if exists
1✔
85
                if err := r.deleteWorkerResources(ctx, res); err != nil {
1✔
86
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up worker resources")
×
87
                }
×
88

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

100
                return ctrlResult, nil
1✔
101
        }
102

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

116
        pod := &corev1.Pod{}
15✔
117
        err := r.Get(ctx, types.NamespacedName{Name: discoveryPrefixed(res.Name), Namespace: res.Namespace}, pod)
15✔
118
        if err != nil && !apierrors.IsNotFound(err) {
15✔
119
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "PodNotFound", "GetPod", "Failed to get pod", err)
×
120
                return ctrlResult, errLogAndWrap(log, err, "failed to get pod information")
×
121
        }
×
122

123
        // No pod yet, create it.
124
        if apierrors.IsNotFound(err) {
19✔
125
                if err := r.createWorkerResources(ctx, res); err != nil {
4✔
126
                        return ctrlResult, errLogAndWrap(log, err, "failed to create pod")
×
127
                }
×
128

129
                return ctrlResult, nil
4✔
130
        }
131

132
        // Pod exists, check if it's up to date with our configuration and if it is healthy.
133
        if res.Status.PodGeneration != res.GetGeneration() {
16✔
134
                // Recreate pod, configuration mismatch
5✔
135
                r.Recorder.Eventf(res, nil, corev1.EventTypeNormal, "ConfigurationChanged", "CompareConfiguration", "Configuration changed. Replacing pod.")
5✔
136
                if err := r.deleteWorkerResources(ctx, res); err != nil {
5✔
137
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up worker resources")
×
138
                }
×
139

140
                if err := r.createWorkerResources(ctx, res); err != nil {
8✔
141
                        return ctrlResult, errLogAndWrap(log, err, "failed to create pod")
3✔
142
                }
3✔
143

144
                return ctrlResult, nil
2✔
145
        } else {
6✔
146
                log.V(1).Info("Configuration hasn't changed", "podGen", res.Status.PodGeneration, "gen", res.GetGeneration())
6✔
147
        }
6✔
148

149
        return ctrlResult, nil
6✔
150
}
151

152
// deleteWorkerResources deletes the resources of the worker pod
153
func (r *DiscoveryReconciler) deleteWorkerResources(ctx context.Context, res *solarv1alpha1.Discovery) error {
6✔
154
        log := ctrl.LoggerFrom(ctx)
6✔
155

6✔
156
        if err := r.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: discoveryPrefixed(res.Name), Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
6✔
157
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "ServiceDeletionFailed", "DeleteService", "Failed to delete service", err)
×
158
                return errLogAndWrap(log, err, "service deletion failed")
×
159
        }
×
160

161
        if err := r.Delete(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: discoveryPrefixed(res.Name), Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
6✔
162
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "SecretDeletionFailed", "DeleteSecret", "Failed to delete secret", err)
×
163
                return errLogAndWrap(log, err, "secret deletion failed")
×
164
        }
×
165

166
        if err := r.Delete(ctx, &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: discoveryPrefixed(res.Name), Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
6✔
167
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "PodDeletionFailed", "DeletePod", "Failed to delete pod", err)
×
168
                return errLogAndWrap(log, err, "pod deletion failed")
×
169
        }
×
170

171
        if err := r.Delete(ctx, &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Name: workerRoleName, Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
6✔
172
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleDeletionFailed", "DeleteRole", "Failed to delete role", err)
×
173
                return errLogAndWrap(log, err, "role deletion failed")
×
174
        }
×
175

176
        if err := r.Delete(ctx, &rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Name: workerRoleName, Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
6✔
177
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleBindingDeletionFailed", "DeleteRoleBinding", "Failed to delete rolebinding", err)
×
178
                return errLogAndWrap(log, err, "rolebinding deletion failed")
×
179
        }
×
180

181
        return nil
6✔
182
}
183

184
// createWorkerResources creates the necessary resources for the worker pod
185
func (r *DiscoveryReconciler) createWorkerResources(ctx context.Context, res *solarv1alpha1.Discovery) error {
9✔
186
        log := ctrl.LoggerFrom(ctx)
9✔
187

9✔
188
        // Create or get service account in the discovery's namespace
9✔
189
        workerSA := &corev1.ServiceAccount{
9✔
190
                ObjectMeta: objectMeta(res),
9✔
191
        }
9✔
192

9✔
193
        existingSA := &corev1.ServiceAccount{}
9✔
194
        err := r.Get(ctx, types.NamespacedName{Name: workerSA.Name, Namespace: workerSA.Namespace}, existingSA)
9✔
195
        if err != nil && !apierrors.IsNotFound(err) {
9✔
196
                return errLogAndWrap(log, err, "failed to get service account")
×
197
        }
×
198

199
        if apierrors.IsNotFound(err) {
13✔
200
                if err := r.Create(ctx, workerSA); err != nil {
4✔
201
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "ServiceAccountCreationFailed", "CreateServiceAccount", "Failed to create service account", err)
×
202
                        return errLogAndWrap(log, err, "failed to create service account")
×
203
                }
×
204
                r.Recorder.Eventf(res, workerSA, corev1.EventTypeNormal, "ServiceAccountCreated", "CreateServiceAccount", "ServiceAccount created")
4✔
205
                if err := controllerutil.SetControllerReference(res, workerSA, r.Scheme); err != nil {
4✔
206
                        return errLogAndWrap(log, err, "failed to set controller reference on service account")
×
207
                }
×
208
        }
209

210
        // Create Role to define RBAC permissions required for discovery worker
211
        role := &rbacv1.Role{
9✔
212
                ObjectMeta: metav1.ObjectMeta{
9✔
213
                        Name:      workerRoleName,
9✔
214
                        Namespace: res.Namespace,
9✔
215
                        Labels: map[string]string{
9✔
216
                                "app.kubernetes.io/managed-by": "solar-discovery-controller",
9✔
217
                        },
9✔
218
                },
9✔
219
                Rules: []rbacv1.PolicyRule{
9✔
220
                        {
9✔
221
                                APIGroups: []string{solarv1alpha1.SchemeGroupVersion.Group},
9✔
222
                                Resources: []string{"componentversions", "components"},
9✔
223
                                Verbs:     []string{"get", "list", "watch", "create", "update", "patch", "delete"},
9✔
224
                        },
9✔
225
                },
9✔
226
        }
9✔
227

9✔
228
        existingRole := &rbacv1.Role{}
9✔
229
        err = r.Get(ctx, types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, existingRole)
9✔
230
        if err != nil && !apierrors.IsNotFound(err) {
9✔
231
                return errLogAndWrap(log, err, "failed to get role")
×
232
        }
×
233

234
        if apierrors.IsNotFound(err) {
18✔
235
                if err := r.Create(ctx, role); err != nil {
9✔
236
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleCreationFailed", "CreateRole", "Failed to create role", err)
×
237
                        return errLogAndWrap(log, err, "failed to create role")
×
238
                }
×
239
                r.Recorder.Eventf(res, role, corev1.EventTypeNormal, "RoleCreated", "CreateRole", "Role created")
9✔
240
                if err := controllerutil.SetControllerReference(res, role, r.Scheme); err != nil {
9✔
241
                        return errLogAndWrap(log, err, "failed to set controller reference on role")
×
242
                }
×
243
        } else {
×
244
                // check if out of sync
×
245
                needsUpdate := false
×
246
                if len(existingRole.Rules) != len(role.Rules) ||
×
247
                        !slices.Equal(existingRole.Rules[0].Verbs, role.Rules[0].Verbs) ||
×
248
                        !slices.Equal(existingRole.Rules[0].APIGroups, role.Rules[0].APIGroups) ||
×
249
                        !slices.Equal(existingRole.Rules[0].Resources, role.Rules[0].Resources) {
×
250
                        existingRole.Rules = role.Rules
×
251
                        needsUpdate = true
×
252
                }
×
253
                if needsUpdate {
×
254
                        if err := r.Update(ctx, existingRole); err != nil {
×
255
                                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleUpdateFailed", "UpdateRole", "Failed to update role", err)
×
256
                                return errLogAndWrap(log, err, "failed to update role")
×
257
                        }
×
258
                        r.Recorder.Eventf(res, existingRole, corev1.EventTypeNormal, "RoleUpdated", "UpdateRole", "Role updated")
×
259
                }
260
        }
261

262
        // Create roleBinding to grant RBAC permissions to the worker service account
263
        roleBinding := &rbacv1.RoleBinding{
9✔
264
                ObjectMeta: metav1.ObjectMeta{
9✔
265
                        Name:      workerRoleName,
9✔
266
                        Namespace: res.Namespace,
9✔
267
                        Labels: map[string]string{
9✔
268
                                "app.kubernetes.io/managed-by": "solar-discovery-controller",
9✔
269
                        },
9✔
270
                },
9✔
271
                RoleRef: rbacv1.RoleRef{
9✔
272
                        APIGroup: "rbac.authorization.k8s.io",
9✔
273
                        Kind:     "Role",
9✔
274
                        Name:     workerRoleName,
9✔
275
                },
9✔
276
                Subjects: []rbacv1.Subject{
9✔
277
                        {
9✔
278
                                Kind:      "ServiceAccount",
9✔
279
                                Name:      workerSA.Name,
9✔
280
                                Namespace: res.Namespace,
9✔
281
                        },
9✔
282
                },
9✔
283
        }
9✔
284

9✔
285
        existingRB := &rbacv1.RoleBinding{}
9✔
286
        err = r.Get(ctx, types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, existingRB)
9✔
287
        if err != nil && !apierrors.IsNotFound(err) {
9✔
288
                return errLogAndWrap(log, err, "failed to get rolebinding")
×
289
        }
×
290

291
        if apierrors.IsNotFound(err) {
18✔
292
                if err := r.Create(ctx, roleBinding); err != nil {
9✔
293
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleBindingCreationFailed", "CreateRoleBinding", "Failed to create rolebinding", err)
×
294
                        return errLogAndWrap(log, err, "failed to create rolebinding")
×
295
                }
×
296
                r.Recorder.Eventf(res, roleBinding, corev1.EventTypeNormal, "RoleBindingCreated", "CreateRoleBinding", "RoleBinding created")
9✔
297
                if err := controllerutil.SetControllerReference(res, roleBinding, r.Scheme); err != nil {
9✔
298
                        return errLogAndWrap(log, err, "failed to set controller reference on rolebinding")
×
299
                }
×
300
        } else {
×
301
                needsUpdate := false
×
302
                if existingRB.RoleRef.Name != workerRoleName {
×
303
                        existingRB.RoleRef.Name = workerRoleName
×
304
                        needsUpdate = true
×
305
                }
×
306
                if len(existingRB.Subjects) != 1 ||
×
307
                        existingRB.Subjects[0].Kind != "ServiceAccount" ||
×
308
                        existingRB.Subjects[0].Name != discoveryPrefixed(res.Name) ||
×
309
                        existingRB.Subjects[0].Namespace != res.Namespace {
×
310
                        existingRB.Subjects = roleBinding.Subjects
×
311
                        needsUpdate = true
×
312
                }
×
313

314
                if needsUpdate {
×
315
                        if err := r.Update(ctx, existingRB); err != nil {
×
316
                                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleBindingUpdateFailed", "UpdateRoleBinding", "Failed to update rolebinding", err)
×
317
                                return errLogAndWrap(log, err, "failed to update rolebinding")
×
318
                        }
×
319
                        r.Recorder.Eventf(res, existingRB, corev1.EventTypeNormal, "RoleBindingUpdated", "UpdateRoleBinding", "RoleBinding updated")
×
320
                }
321
        }
322

323
        // Create secret
324
        secret := &corev1.Secret{
9✔
325
                ObjectMeta: objectMeta(res),
9✔
326
        }
9✔
327

9✔
328
        rp := discovery.NewRegistryProvider()
9✔
329
        reg := &discovery.Registry{
9✔
330
                Name:      res.Name,
9✔
331
                PlainHTTP: res.Spec.Registry.PlainHTTP,
9✔
332
                Hostname:  res.Spec.Registry.RegistryURL,
9✔
333
        }
9✔
334
        if res.Spec.Webhook != nil {
9✔
335
                reg.WebhookPath = res.Spec.Webhook.Path
×
336
                reg.Flavor = res.Spec.Webhook.Flavor
×
337
        }
×
338
        if res.Spec.DiscoveryInterval != nil {
14✔
339
                reg.ScanInterval = res.Spec.DiscoveryInterval.Duration
5✔
340
        }
5✔
341
        if err := rp.Register(reg); err != nil {
9✔
342
                return errLogAndWrap(log, err, "failed to register registry")
×
343
        }
×
344

345
        // Add credentials if specified
346
        if res.Spec.Registry.SecretRef.Name != "" {
9✔
347
                sec := &corev1.Secret{}
×
348
                if err := r.Get(ctx, types.NamespacedName{Name: res.Spec.Registry.SecretRef.Name, Namespace: res.Namespace}, sec); err != nil {
×
349
                        return errLogAndWrap(log, err, "failed to get registry secret")
×
350
                } else {
×
351
                        username, okUser := sec.Data["username"]
×
352
                        password, okPass := sec.Data["password"]
×
353
                        if okUser && okPass {
×
354
                                reg.Credentials = &discovery.RegistryCredentials{
×
355
                                        Username: string(username),
×
356
                                        Password: string(password),
×
357
                                }
×
358
                        } else {
×
359
                                return fmt.Errorf("registry secret is missing username or password fields")
×
360
                        }
×
361
                }
362
        }
363

364
        confData, err := rp.Marshal()
9✔
365
        if err != nil {
9✔
366
                return errLogAndWrap(log, err, "failed to marshal registry configuration")
×
367
        }
×
368
        secret.StringData = map[string]string{
9✔
369
                "config.yaml": string(confData),
9✔
370
        }
9✔
371

9✔
372
        existingSecret := &corev1.Secret{}
9✔
373
        err = r.Get(ctx, types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, existingSecret)
9✔
374
        if err != nil && !apierrors.IsNotFound(err) {
9✔
375
                return errLogAndWrap(log, err, "failed to get secret")
×
376
        }
×
377

378
        if apierrors.IsNotFound(err) {
18✔
379
                if err := r.Create(ctx, secret); err != nil {
9✔
380
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "SecretCreationFailed", "CreateSecret", "Failed to create secret", err)
×
381
                        return errLogAndWrap(log, err, "failed to create secret")
×
382
                }
×
383
                r.Recorder.Eventf(res, secret, corev1.EventTypeNormal, "SecretCreated", "CreateSecret", "Secret created")
9✔
384

9✔
385
                if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
9✔
386
                        return errLogAndWrap(log, err, "failed to set controller reference")
×
387
                }
×
388
        } else {
×
389
                existingSecret.StringData = secret.StringData
×
390
                if err := r.Update(ctx, existingSecret); err != nil {
×
391
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "SecretUpdateFailed", "UpdateSecret", "Failed to update secret", err)
×
392
                        return errLogAndWrap(log, err, "failed to update secret")
×
393
                }
×
394
                r.Recorder.Eventf(res, existingSecret, corev1.EventTypeNormal, "SecretUpdated", "UpdateSecret", "Secret updated")
×
395

×
396
                if err := controllerutil.SetControllerReference(res, existingSecret, r.Scheme); err != nil {
×
397
                        return errLogAndWrap(log, err, "failed to set controller reference")
×
398
                }
×
399
        }
400

401
        // Create pod
402
        var args = r.WorkerArgs
9✔
403
        args = append(args, "--config", "/etc/worker/config.yaml")
9✔
404
        pod := &corev1.Pod{
9✔
405
                ObjectMeta: objectMeta(res),
9✔
406
                Spec: corev1.PodSpec{
9✔
407
                        ServiceAccountName: workerSA.Name,
9✔
408
                        Volumes: []corev1.Volume{
9✔
409
                                {
9✔
410
                                        Name: "config",
9✔
411
                                        VolumeSource: corev1.VolumeSource{
9✔
412
                                                Secret: &corev1.SecretVolumeSource{
9✔
413
                                                        SecretName: discoveryPrefixed(res.Name),
9✔
414
                                                },
9✔
415
                                        },
9✔
416
                                },
9✔
417
                        },
9✔
418
                },
9✔
419
        }
9✔
420

9✔
421
        container := corev1.Container{
9✔
422

9✔
423
                Name:    "worker",
9✔
424
                Image:   r.WorkerImage,
9✔
425
                Command: []string{r.WorkerCommand},
9✔
426
                Args:    args,
9✔
427
                VolumeMounts: []corev1.VolumeMount{
9✔
428
                        {
9✔
429
                                Name:      "config",
9✔
430
                                ReadOnly:  true,
9✔
431
                                MountPath: "/etc/worker"},
9✔
432
                },
9✔
433
                Ports: []corev1.ContainerPort{
9✔
434
                        {
9✔
435
                                Name:          "webhook",
9✔
436
                                ContainerPort: 8080,
9✔
437
                        },
9✔
438
                },
9✔
439
        }
9✔
440

9✔
441
        if cmName := res.Spec.Registry.CAConfigMapRef.Name; cmName != "" {
11✔
442
                pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
2✔
443
                        Name: "ca-bundle",
2✔
444
                        VolumeSource: corev1.VolumeSource{
2✔
445
                                ConfigMap: &corev1.ConfigMapVolumeSource{
2✔
446
                                        LocalObjectReference: corev1.LocalObjectReference{
2✔
447
                                                Name: cmName,
2✔
448
                                        },
2✔
449
                                        Items: []corev1.KeyToPath{
2✔
450
                                                {
2✔
451
                                                        Key:  "trust-bundle.pem",
2✔
452
                                                        Path: "ca-bundle.pem",
2✔
453
                                                },
2✔
454
                                        },
2✔
455
                                },
2✔
456
                        },
2✔
457
                })
2✔
458
                container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
2✔
459
                        Name:      "ca-bundle",
2✔
460
                        MountPath: "/etc/ssl/certs",
2✔
461
                        ReadOnly:  true,
2✔
462
                })
2✔
463
                container.Env = append(container.Env, corev1.EnvVar{
2✔
464
                        Name:  "SSL_CERT_FILE",
2✔
465
                        Value: "/etc/ssl/certs/ca-bundle.pem",
2✔
466
                })
2✔
467
        }
2✔
468

469
        pod.Spec.Containers = []corev1.Container{container}
9✔
470

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

476
        // Create pod in cluster
477
        if err := r.Create(ctx, pod); err != nil {
9✔
478
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "PodCreationFailed", "CreatePod", "Failed to create pod", err)
×
479
                return errLogAndWrap(log, err, "failed to create pod")
×
480
        }
×
481
        r.Recorder.Eventf(res, pod, corev1.EventTypeNormal, "PodCreated", "CreatePod", "Pod created")
9✔
482
        log.V(1).Info("Pod created", "podGen", res.GetGeneration())
9✔
483

9✔
484
        // Create or update service
9✔
485
        svc := &corev1.Service{
9✔
486
                ObjectMeta: objectMeta(res),
9✔
487
                Spec: corev1.ServiceSpec{
9✔
488
                        Type:     corev1.ServiceTypeClusterIP,
9✔
489
                        Ports:    []corev1.ServicePort{{Name: "webhook", Port: 8080, TargetPort: intstr.FromString("webhook")}},
9✔
490
                        Selector: map[string]string{"app.kubernetes.io/name": discoveryPrefixed(res.Name)},
9✔
491
                },
9✔
492
        }
9✔
493

9✔
494
        existingSvc := &corev1.Service{}
9✔
495
        err = r.Get(ctx, types.NamespacedName{Name: svc.Name, Namespace: svc.Namespace}, existingSvc)
9✔
496
        if err != nil && !apierrors.IsNotFound(err) {
9✔
UNCOV
497
                return errLogAndWrap(log, err, "failed to get service")
×
UNCOV
498
        }
×
499

500
        if apierrors.IsNotFound(err) {
18✔
501
                if err := r.Create(ctx, svc); err != nil {
9✔
UNCOV
502
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "ServiceCreationFailed", "CreateService", "Failed to create service", err)
×
UNCOV
503
                        return errLogAndWrap(log, err, "failed to create service")
×
UNCOV
504
                }
×
505
                r.Recorder.Eventf(res, svc, corev1.EventTypeNormal, "ServiceCreated", "CreateService", "Service created")
9✔
UNCOV
506
        } else {
×
UNCOV
507
                existingSvc.Spec = svc.Spec
×
UNCOV
508
                if err := r.Update(ctx, existingSvc); err != nil {
×
UNCOV
509
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "ServiceUpdateFailed", "UpdateService", "Failed to update service", err)
×
UNCOV
510
                        return errLogAndWrap(log, err, "failed to update service")
×
UNCOV
511
                }
×
UNCOV
512
                r.Recorder.Eventf(res, existingSvc, corev1.EventTypeNormal, "ServiceUpdated", "UpdateService", "Service updated")
×
513
        }
514

515
        // Update discovery version in status
516
        res.Status.PodGeneration = res.GetGeneration()
9✔
517
        if err := r.Status().Update(ctx, res); err != nil {
12✔
518
                return errLogAndWrap(log, err, "failed to update status")
3✔
519
        }
3✔
520

521
        return nil
6✔
522
}
523

524
func objectMeta(res *solarv1alpha1.Discovery) metav1.ObjectMeta {
36✔
525
        labels := res.Labels
36✔
526
        if labels == nil {
72✔
527
                labels = make(map[string]string)
36✔
528
        }
36✔
529
        labels["app.kubernetes.io/managed-by"] = "solar-discovery-controller"
36✔
530
        labels["app.kubernetes.io/component"] = "discovery-worker"
36✔
531
        labels["app.kubernetes.io/instance"] = res.Name
36✔
532
        labels["app.kubernetes.io/name"] = discoveryPrefixed(res.Name)
36✔
533

36✔
534
        return metav1.ObjectMeta{
36✔
535
                Name:        discoveryPrefixed(res.Name),
36✔
536
                Namespace:   res.Namespace,
36✔
537
                Labels:      labels,
36✔
538
                Annotations: res.Annotations,
36✔
539
        }
36✔
540
}
541

542
// discoveryPrefixed returns the name of the discovery prefixed resource
543
func discoveryPrefixed(discoveryName string) string {
141✔
544
        return fmt.Sprintf("discovery-%s", discoveryName)
141✔
545
}
141✔
546

547
// SetupWithManager sets up the controller with the Manager.
548
func (r *DiscoveryReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
549
        return ctrl.NewControllerManagedBy(mgr).
1✔
550
                For(&solarv1alpha1.Discovery{}).
1✔
551
                Owns(&corev1.Pod{}).
1✔
552
                Owns(&corev1.Secret{}).
1✔
553
                Complete(r)
1✔
554
}
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