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

opendefensecloud / solution-arsenal / 23453335623

23 Mar 2026 06:22PM UTC coverage: 71.622%. First build
23453335623

Pull #323

github

web-flow
Merge 4a3fb76a4 into 7bddf518b
Pull Request #323: fix(controller): filter reconciliation by namespace and fix EnvTest test errors

13 of 26 new or added lines in 6 files covered. (50.0%)

2173 of 3034 relevant lines covered (71.62%)

13.49 hits per line

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

66.03
/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
        // WatchNamespace restricts reconciliation to this namespace.
41
        // Should be empty in production (watches all namespaces).
42
        // Intended for use in integration tests only.
43
        // See: https://book.kubebuilder.io/reference/envtest#testing-considerations
44
        WatchNamespace string
45
}
46

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

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

66
// Reconcile moves the current state of the cluster closer to the desired state
67
func (r *DiscoveryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
20✔
68
        log := ctrl.LoggerFrom(ctx)
20✔
69
        ctrlResult := ctrl.Result{}
20✔
70

20✔
71
        log.V(1).Info("Discovery is being reconciled", "req", req)
20✔
72

20✔
73
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
20✔
NEW
74
                return ctrlResult, nil
×
NEW
75
        }
×
76

77
        // Fetch the Order instance
78
        res := &solarv1alpha1.Discovery{}
20✔
79
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
21✔
80
                if apierrors.IsNotFound(err) {
2✔
81
                        // Object not found, return. Created objects are automatically garbage collected.
1✔
82
                        return ctrlResult, nil
1✔
83
                }
1✔
84

85
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
86
        }
87

88
        // Handle deletion: cleanup artifact workflows, then remove finalizer
89
        if !res.DeletionTimestamp.IsZero() {
20✔
90
                log.V(1).Info("Discovery is being deleted")
1✔
91
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "Deleting", "Delete", "Discovery is being deleted, cleaning up worker")
1✔
92

1✔
93
                // Cleanup worker resources, if exists
1✔
94
                if err := r.deleteWorkerResources(ctx, res); err != nil {
1✔
95
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up worker resources")
×
96
                }
×
97

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

109
                return ctrlResult, nil
1✔
110
        }
111

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

125
        pod := &corev1.Pod{}
14✔
126
        err := r.Get(ctx, types.NamespacedName{Name: discoveryPrefixed(res.Name), Namespace: res.Namespace}, pod)
14✔
127
        if err != nil && !apierrors.IsNotFound(err) {
14✔
128
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "PodNotFound", "GetPod", "Failed to get pod", err)
×
129
                return ctrlResult, errLogAndWrap(log, err, "failed to get pod information")
×
130
        }
×
131

132
        // No pod yet, create it.
133
        if apierrors.IsNotFound(err) {
18✔
134
                if err := r.createWorkerResources(ctx, res); err != nil {
4✔
135
                        return ctrlResult, errLogAndWrap(log, err, "failed to create pod")
×
136
                }
×
137

138
                return ctrlResult, nil
4✔
139
        }
140

141
        // Pod exists, check if it's up to date with our configuration and if it is healthy.
142
        if res.Status.PodGeneration != res.GetGeneration() {
13✔
143
                // Recreate pod, configuration mismatch
3✔
144
                r.Recorder.Eventf(res, nil, corev1.EventTypeNormal, "ConfigurationChanged", "CompareConfiguration", "Configuration changed. Replacing pod.")
3✔
145
                if err := r.deleteWorkerResources(ctx, res); err != nil {
3✔
146
                        return ctrlResult, errLogAndWrap(log, err, "failed to clean up worker resources")
×
147
                }
×
148

149
                if err := r.createWorkerResources(ctx, res); err != nil {
4✔
150
                        return ctrlResult, errLogAndWrap(log, err, "failed to create pod")
1✔
151
                }
1✔
152

153
                return ctrlResult, nil
2✔
154
        } else {
7✔
155
                log.V(1).Info("Configuration hasn't changed", "podGen", res.Status.PodGeneration, "gen", res.GetGeneration())
7✔
156
        }
7✔
157

158
        return ctrlResult, nil
7✔
159
}
160

161
// deleteWorkerResources deletes the resources of the worker pod
162
func (r *DiscoveryReconciler) deleteWorkerResources(ctx context.Context, res *solarv1alpha1.Discovery) error {
4✔
163
        log := ctrl.LoggerFrom(ctx)
4✔
164

4✔
165
        if err := r.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: discoveryPrefixed(res.Name), Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
4✔
166
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "ServiceDeletionFailed", "DeleteService", "Failed to delete service", err)
×
167
                return errLogAndWrap(log, err, "service deletion failed")
×
168
        }
×
169

170
        if err := r.Delete(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: discoveryPrefixed(res.Name), Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
4✔
171
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "SecretDeletionFailed", "DeleteSecret", "Failed to delete secret", err)
×
172
                return errLogAndWrap(log, err, "secret deletion failed")
×
173
        }
×
174

175
        if err := r.Delete(ctx, &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: discoveryPrefixed(res.Name), Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
4✔
176
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "PodDeletionFailed", "DeletePod", "Failed to delete pod", err)
×
177
                return errLogAndWrap(log, err, "pod deletion failed")
×
178
        }
×
179

180
        if err := r.Delete(ctx, &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Name: workerRoleName, Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
4✔
181
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleDeletionFailed", "DeleteRole", "Failed to delete role", err)
×
182
                return errLogAndWrap(log, err, "role deletion failed")
×
183
        }
×
184

185
        if err := r.Delete(ctx, &rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Name: workerRoleName, Namespace: res.Namespace}}); err != nil && !apierrors.IsNotFound(err) {
4✔
186
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleBindingDeletionFailed", "DeleteRoleBinding", "Failed to delete rolebinding", err)
×
187
                return errLogAndWrap(log, err, "rolebinding deletion failed")
×
188
        }
×
189

190
        return nil
4✔
191
}
192

193
// createWorkerResources creates the necessary resources for the worker pod
194
func (r *DiscoveryReconciler) createWorkerResources(ctx context.Context, res *solarv1alpha1.Discovery) error {
7✔
195
        log := ctrl.LoggerFrom(ctx)
7✔
196

7✔
197
        // Create or get service account in the discovery's namespace
7✔
198
        workerSA := &corev1.ServiceAccount{
7✔
199
                ObjectMeta: objectMeta(res),
7✔
200
        }
7✔
201

7✔
202
        existingSA := &corev1.ServiceAccount{}
7✔
203
        err := r.Get(ctx, types.NamespacedName{Name: workerSA.Name, Namespace: workerSA.Namespace}, existingSA)
7✔
204
        if err != nil && !apierrors.IsNotFound(err) {
7✔
205
                return errLogAndWrap(log, err, "failed to get service account")
×
206
        }
×
207

208
        if apierrors.IsNotFound(err) {
11✔
209
                if err := r.Create(ctx, workerSA); err != nil {
4✔
210
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "ServiceAccountCreationFailed", "CreateServiceAccount", "Failed to create service account", err)
×
211
                        return errLogAndWrap(log, err, "failed to create service account")
×
212
                }
×
213
                r.Recorder.Eventf(res, workerSA, corev1.EventTypeNormal, "ServiceAccountCreated", "CreateServiceAccount", "ServiceAccount created")
4✔
214
                if err := controllerutil.SetControllerReference(res, workerSA, r.Scheme); err != nil {
4✔
215
                        return errLogAndWrap(log, err, "failed to set controller reference on service account")
×
216
                }
×
217
        }
218

219
        // Create Role to define RBAC permissions required for discovery worker
220
        role := &rbacv1.Role{
7✔
221
                ObjectMeta: metav1.ObjectMeta{
7✔
222
                        Name:      workerRoleName,
7✔
223
                        Namespace: res.Namespace,
7✔
224
                        Labels: map[string]string{
7✔
225
                                "app.kubernetes.io/managed-by": "solar-discovery-controller",
7✔
226
                        },
7✔
227
                },
7✔
228
                Rules: []rbacv1.PolicyRule{
7✔
229
                        {
7✔
230
                                APIGroups: []string{solarv1alpha1.SchemeGroupVersion.Group},
7✔
231
                                Resources: []string{"componentversions", "components"},
7✔
232
                                Verbs:     []string{"get", "list", "watch", "create", "update", "patch", "delete"},
7✔
233
                        },
7✔
234
                },
7✔
235
        }
7✔
236

7✔
237
        existingRole := &rbacv1.Role{}
7✔
238
        err = r.Get(ctx, types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, existingRole)
7✔
239
        if err != nil && !apierrors.IsNotFound(err) {
7✔
240
                return errLogAndWrap(log, err, "failed to get role")
×
241
        }
×
242

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

271
        // Create roleBinding to grant RBAC permissions to the worker service account
272
        roleBinding := &rbacv1.RoleBinding{
7✔
273
                ObjectMeta: metav1.ObjectMeta{
7✔
274
                        Name:      workerRoleName,
7✔
275
                        Namespace: res.Namespace,
7✔
276
                        Labels: map[string]string{
7✔
277
                                "app.kubernetes.io/managed-by": "solar-discovery-controller",
7✔
278
                        },
7✔
279
                },
7✔
280
                RoleRef: rbacv1.RoleRef{
7✔
281
                        APIGroup: "rbac.authorization.k8s.io",
7✔
282
                        Kind:     "Role",
7✔
283
                        Name:     workerRoleName,
7✔
284
                },
7✔
285
                Subjects: []rbacv1.Subject{
7✔
286
                        {
7✔
287
                                Kind:      "ServiceAccount",
7✔
288
                                Name:      workerSA.Name,
7✔
289
                                Namespace: res.Namespace,
7✔
290
                        },
7✔
291
                },
7✔
292
        }
7✔
293

7✔
294
        existingRB := &rbacv1.RoleBinding{}
7✔
295
        err = r.Get(ctx, types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, existingRB)
7✔
296
        if err != nil && !apierrors.IsNotFound(err) {
7✔
297
                return errLogAndWrap(log, err, "failed to get rolebinding")
×
298
        }
×
299

300
        if apierrors.IsNotFound(err) {
14✔
301
                if err := r.Create(ctx, roleBinding); err != nil {
7✔
302
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleBindingCreationFailed", "CreateRoleBinding", "Failed to create rolebinding", err)
×
303
                        return errLogAndWrap(log, err, "failed to create rolebinding")
×
304
                }
×
305
                r.Recorder.Eventf(res, roleBinding, corev1.EventTypeNormal, "RoleBindingCreated", "CreateRoleBinding", "RoleBinding created")
7✔
306
                if err := controllerutil.SetControllerReference(res, roleBinding, r.Scheme); err != nil {
7✔
307
                        return errLogAndWrap(log, err, "failed to set controller reference on rolebinding")
×
308
                }
×
309
        } else {
×
310
                needsUpdate := false
×
311
                if existingRB.RoleRef.Name != workerRoleName {
×
312
                        existingRB.RoleRef.Name = workerRoleName
×
313
                        needsUpdate = true
×
314
                }
×
315
                if len(existingRB.Subjects) != 1 ||
×
316
                        existingRB.Subjects[0].Kind != "ServiceAccount" ||
×
317
                        existingRB.Subjects[0].Name != discoveryPrefixed(res.Name) ||
×
318
                        existingRB.Subjects[0].Namespace != res.Namespace {
×
319
                        existingRB.Subjects = roleBinding.Subjects
×
320
                        needsUpdate = true
×
321
                }
×
322

323
                if needsUpdate {
×
324
                        if err := r.Update(ctx, existingRB); err != nil {
×
325
                                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "RoleBindingUpdateFailed", "UpdateRoleBinding", "Failed to update rolebinding", err)
×
326
                                return errLogAndWrap(log, err, "failed to update rolebinding")
×
327
                        }
×
328
                        r.Recorder.Eventf(res, existingRB, corev1.EventTypeNormal, "RoleBindingUpdated", "UpdateRoleBinding", "RoleBinding updated")
×
329
                }
330
        }
331

332
        // Create secret
333
        secret := &corev1.Secret{
7✔
334
                ObjectMeta: objectMeta(res),
7✔
335
        }
7✔
336

7✔
337
        rp := discovery.NewRegistryProvider()
7✔
338
        reg := &discovery.Registry{
7✔
339
                Name:      res.Name,
7✔
340
                PlainHTTP: res.Spec.Registry.PlainHTTP,
7✔
341
                Hostname:  res.Spec.Registry.RegistryURL,
7✔
342
        }
7✔
343
        if res.Spec.Webhook != nil {
7✔
344
                reg.WebhookPath = res.Spec.Webhook.Path
×
345
                reg.Flavor = res.Spec.Webhook.Flavor
×
346
        }
×
347
        if res.Spec.DiscoveryInterval != nil {
11✔
348
                reg.ScanInterval = res.Spec.DiscoveryInterval.Duration
4✔
349
        }
4✔
350
        if err := rp.Register(reg); err != nil {
7✔
351
                return errLogAndWrap(log, err, "failed to register registry")
×
352
        }
×
353

354
        // Add credentials if specified
355
        if res.Spec.Registry.SecretRef.Name != "" {
7✔
356
                sec := &corev1.Secret{}
×
357
                if err := r.Get(ctx, types.NamespacedName{Name: res.Spec.Registry.SecretRef.Name, Namespace: res.Namespace}, sec); err != nil {
×
358
                        return errLogAndWrap(log, err, "failed to get registry secret")
×
359
                } else {
×
360
                        username, okUser := sec.Data["username"]
×
361
                        password, okPass := sec.Data["password"]
×
362
                        if okUser && okPass {
×
363
                                reg.Credentials = &discovery.RegistryCredentials{
×
364
                                        Username: string(username),
×
365
                                        Password: string(password),
×
366
                                }
×
367
                        } else {
×
368
                                return fmt.Errorf("registry secret is missing username or password fields")
×
369
                        }
×
370
                }
371
        }
372

373
        confData, err := rp.Marshal()
7✔
374
        if err != nil {
7✔
375
                return errLogAndWrap(log, err, "failed to marshal registry configuration")
×
376
        }
×
377
        secret.StringData = map[string]string{
7✔
378
                "config.yaml": string(confData),
7✔
379
        }
7✔
380

7✔
381
        existingSecret := &corev1.Secret{}
7✔
382
        err = r.Get(ctx, types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, existingSecret)
7✔
383
        if err != nil && !apierrors.IsNotFound(err) {
7✔
384
                return errLogAndWrap(log, err, "failed to get secret")
×
385
        }
×
386

387
        if apierrors.IsNotFound(err) {
14✔
388
                if err := r.Create(ctx, secret); err != nil {
7✔
389
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "SecretCreationFailed", "CreateSecret", "Failed to create secret", err)
×
390
                        return errLogAndWrap(log, err, "failed to create secret")
×
391
                }
×
392
                r.Recorder.Eventf(res, secret, corev1.EventTypeNormal, "SecretCreated", "CreateSecret", "Secret created")
7✔
393

7✔
394
                if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
7✔
395
                        return errLogAndWrap(log, err, "failed to set controller reference")
×
396
                }
×
397
        } else {
×
398
                existingSecret.StringData = secret.StringData
×
399
                if err := r.Update(ctx, existingSecret); err != nil {
×
400
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "SecretUpdateFailed", "UpdateSecret", "Failed to update secret", err)
×
401
                        return errLogAndWrap(log, err, "failed to update secret")
×
402
                }
×
403
                r.Recorder.Eventf(res, existingSecret, corev1.EventTypeNormal, "SecretUpdated", "UpdateSecret", "Secret updated")
×
404

×
405
                if err := controllerutil.SetControllerReference(res, existingSecret, r.Scheme); err != nil {
×
406
                        return errLogAndWrap(log, err, "failed to set controller reference")
×
407
                }
×
408
        }
409

410
        // Create pod
411
        var args = r.WorkerArgs
7✔
412
        args = append(args, "--config", "/etc/worker/config.yaml", "--namespace", res.Namespace)
7✔
413
        pod := &corev1.Pod{
7✔
414
                ObjectMeta: objectMeta(res),
7✔
415
                Spec: corev1.PodSpec{
7✔
416
                        ServiceAccountName: workerSA.Name,
7✔
417
                        Volumes: []corev1.Volume{
7✔
418
                                {
7✔
419
                                        Name: "config",
7✔
420
                                        VolumeSource: corev1.VolumeSource{
7✔
421
                                                Secret: &corev1.SecretVolumeSource{
7✔
422
                                                        SecretName: discoveryPrefixed(res.Name),
7✔
423
                                                },
7✔
424
                                        },
7✔
425
                                },
7✔
426
                        },
7✔
427
                },
7✔
428
        }
7✔
429

7✔
430
        container := corev1.Container{
7✔
431

7✔
432
                Name:    "worker",
7✔
433
                Image:   r.WorkerImage,
7✔
434
                Command: []string{r.WorkerCommand},
7✔
435
                Args:    args,
7✔
436
                VolumeMounts: []corev1.VolumeMount{
7✔
437
                        {
7✔
438
                                Name:      "config",
7✔
439
                                ReadOnly:  true,
7✔
440
                                MountPath: "/etc/worker"},
7✔
441
                },
7✔
442
                Ports: []corev1.ContainerPort{
7✔
443
                        {
7✔
444
                                Name:          "webhook",
7✔
445
                                ContainerPort: 8080,
7✔
446
                        },
7✔
447
                },
7✔
448
        }
7✔
449

7✔
450
        if cmName := res.Spec.Registry.CAConfigMapRef.Name; cmName != "" {
8✔
451
                pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
1✔
452
                        Name: "ca-bundle",
1✔
453
                        VolumeSource: corev1.VolumeSource{
1✔
454
                                ConfigMap: &corev1.ConfigMapVolumeSource{
1✔
455
                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
456
                                                Name: cmName,
1✔
457
                                        },
1✔
458
                                        Items: []corev1.KeyToPath{
1✔
459
                                                {
1✔
460
                                                        Key:  "trust-bundle.pem",
1✔
461
                                                        Path: "ca-bundle.pem",
1✔
462
                                                },
1✔
463
                                        },
1✔
464
                                },
1✔
465
                        },
1✔
466
                })
1✔
467
                container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
1✔
468
                        Name:      "ca-bundle",
1✔
469
                        MountPath: "/etc/ssl/certs",
1✔
470
                        ReadOnly:  true,
1✔
471
                })
1✔
472
                container.Env = append(container.Env, corev1.EnvVar{
1✔
473
                        Name:  "SSL_CERT_FILE",
1✔
474
                        Value: "/etc/ssl/certs/ca-bundle.pem",
1✔
475
                })
1✔
476
        }
1✔
477

478
        pod.Spec.Containers = []corev1.Container{container}
7✔
479

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

485
        // Create pod in cluster
486
        if err := r.Create(ctx, pod); err != nil {
7✔
487
                r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "PodCreationFailed", "CreatePod", "Failed to create pod", err)
×
488
                return errLogAndWrap(log, err, "failed to create pod")
×
489
        }
×
490
        r.Recorder.Eventf(res, pod, corev1.EventTypeNormal, "PodCreated", "CreatePod", "Pod created")
7✔
491
        log.V(1).Info("Pod created", "podGen", res.GetGeneration())
7✔
492

7✔
493
        // Create or update service
7✔
494
        svc := &corev1.Service{
7✔
495
                ObjectMeta: objectMeta(res),
7✔
496
                Spec: corev1.ServiceSpec{
7✔
497
                        Type:     corev1.ServiceTypeClusterIP,
7✔
498
                        Ports:    []corev1.ServicePort{{Name: "webhook", Port: 8080, TargetPort: intstr.FromString("webhook")}},
7✔
499
                        Selector: map[string]string{"app.kubernetes.io/name": discoveryPrefixed(res.Name)},
7✔
500
                },
7✔
501
        }
7✔
502

7✔
503
        existingSvc := &corev1.Service{}
7✔
504
        err = r.Get(ctx, types.NamespacedName{Name: svc.Name, Namespace: svc.Namespace}, existingSvc)
7✔
505
        if err != nil && !apierrors.IsNotFound(err) {
7✔
506
                return errLogAndWrap(log, err, "failed to get service")
×
507
        }
×
508

509
        if apierrors.IsNotFound(err) {
14✔
510
                if err := r.Create(ctx, svc); err != nil {
7✔
511
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "ServiceCreationFailed", "CreateService", "Failed to create service", err)
×
512
                        return errLogAndWrap(log, err, "failed to create service")
×
513
                }
×
514
                r.Recorder.Eventf(res, svc, corev1.EventTypeNormal, "ServiceCreated", "CreateService", "Service created")
7✔
515
        } else {
×
516
                existingSvc.Spec = svc.Spec
×
517
                if err := r.Update(ctx, existingSvc); err != nil {
×
518
                        r.Recorder.Eventf(res, nil, corev1.EventTypeWarning, "ServiceUpdateFailed", "UpdateService", "Failed to update service", err)
×
519
                        return errLogAndWrap(log, err, "failed to update service")
×
520
                }
×
521
                r.Recorder.Eventf(res, existingSvc, corev1.EventTypeNormal, "ServiceUpdated", "UpdateService", "Service updated")
×
522
        }
523

524
        // Update discovery version in status
525
        res.Status.PodGeneration = res.GetGeneration()
7✔
526
        if err := r.Status().Update(ctx, res); err != nil {
8✔
527
                return errLogAndWrap(log, err, "failed to update status")
1✔
528
        }
1✔
529

530
        return nil
6✔
531
}
532

533
func objectMeta(res *solarv1alpha1.Discovery) metav1.ObjectMeta {
28✔
534
        labels := res.Labels
28✔
535
        if labels == nil {
56✔
536
                labels = make(map[string]string)
28✔
537
        }
28✔
538
        labels["app.kubernetes.io/managed-by"] = "solar-discovery-controller"
28✔
539
        labels["app.kubernetes.io/component"] = "discovery-worker"
28✔
540
        labels["app.kubernetes.io/instance"] = res.Name
28✔
541
        labels["app.kubernetes.io/name"] = discoveryPrefixed(res.Name)
28✔
542

28✔
543
        return metav1.ObjectMeta{
28✔
544
                Name:        discoveryPrefixed(res.Name),
28✔
545
                Namespace:   res.Namespace,
28✔
546
                Labels:      labels,
28✔
547
                Annotations: res.Annotations,
28✔
548
        }
28✔
549
}
550

551
// discoveryPrefixed returns the name of the discovery prefixed resource
552
func discoveryPrefixed(discoveryName string) string {
114✔
553
        return fmt.Sprintf("discovery-%s", discoveryName)
114✔
554
}
114✔
555

556
// SetupWithManager sets up the controller with the Manager.
557
func (r *DiscoveryReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
558
        return ctrl.NewControllerManagedBy(mgr).
1✔
559
                For(&solarv1alpha1.Discovery{}).
1✔
560
                Owns(&corev1.Pod{}).
1✔
561
                Owns(&corev1.Secret{}).
1✔
562
                Complete(r)
1✔
563
}
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