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

opendefensecloud / solution-arsenal / 21174958280

20 Jan 2026 02:22PM UTC coverage: 66.89% (-3.9%) from 70.819%
21174958280

Pull #33

github

web-flow
Merge bae3ae4a9 into 4f4db7fd8
Pull Request #33: Adds Discovery API to Solar Core

97 of 166 new or added lines in 3 files covered. (58.43%)

299 of 447 relevant lines covered (66.89%)

1.2 hits per line

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

63.82
/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
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
12
        corev1 "k8s.io/api/core/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/client-go/tools/record"
17
        ctrl "sigs.k8s.io/controller-runtime"
18

19
        "k8s.io/client-go/kubernetes"
20
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
21

22
        "sigs.k8s.io/controller-runtime/pkg/client"
23
)
24

25
const (
26
        discoveryFinalizer = "solar.opendefense.cloud/discovery-finalizer"
27
)
28

29
// DiscoveryReconciler reconciles a Discovery object
30
type DiscoveryReconciler struct {
31
        client.Client
32
        ClientSet     kubernetes.Interface
33
        Scheme        *runtime.Scheme
34
        Recorder      record.EventRecorder
35
        WorkerImage   string
36
        WorkerCommand string
37
        WorkerArgs    []string
38
}
39

40
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=discoveries,verbs=get;list;watch;create;update;patch;delete
41
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=discoveries/status,verbs=get;update;patch
42
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=discoveries/finalizers,verbs=update
43
//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete
44
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
45
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
46

47
// Reconcile moves the current state of the cluster closer to the desired state
48
func (r *DiscoveryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
2✔
49
        log := ctrl.LoggerFrom(ctx)
2✔
50
        ctrlResult := ctrl.Result{}
2✔
51

2✔
52
        // Fetch the Order instance
2✔
53
        res := &solarv1alpha1.Discovery{}
2✔
54
        if err := r.Get(ctx, req.NamespacedName, res); err != nil {
2✔
NEW
55
                if apierrors.IsNotFound(err) {
×
NEW
56
                        // Object not found, return. Created objects are automatically garbage collected.
×
NEW
57
                        return ctrlResult, nil
×
NEW
58
                }
×
NEW
59
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
60
        }
61

62
        // Handle deletion: cleanup artifact workflows, then remove finalizer
63
        if !res.DeletionTimestamp.IsZero() {
2✔
NEW
64
                log.V(1).Info("Discovery is being deleted")
×
NEW
65
                r.Recorder.Event(res, corev1.EventTypeWarning, "Deleting", "Discovery is being deleted, cleaning up worker")
×
NEW
66

×
NEW
67
                // Cleanup worker, if exists
×
NEW
68
                if err := r.Delete(ctx, &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: res.Namespace, Name: res.Name}}); err != nil && !apierrors.IsNotFound(err) {
×
NEW
69
                        return ctrlResult, errLogAndWrap(log, err, "pod deletion failed")
×
NEW
70
                }
×
71

72
                // Remove finalizer
NEW
73
                if slices.Contains(res.Finalizers, discoveryFinalizer) {
×
NEW
74
                        log.V(1).Info("Removing finalizer from resource")
×
NEW
75
                        res.Finalizers = slices.DeleteFunc(res.Finalizers, func(f string) bool {
×
NEW
76
                                return f == discoveryFinalizer
×
NEW
77
                        })
×
NEW
78
                        if err := r.Update(ctx, res); err != nil {
×
NEW
79
                                return ctrlResult, errLogAndWrap(log, err, "failed to remove finalizer")
×
NEW
80
                        }
×
81
                }
82
        }
83

84
        // Add finalizer if not present and not deleting
85
        if res.DeletionTimestamp.IsZero() {
4✔
86
                if !slices.Contains(res.Finalizers, discoveryFinalizer) {
3✔
87
                        log.V(1).Info("Adding finalizer to resource")
1✔
88
                        res.Finalizers = append(res.Finalizers, discoveryFinalizer)
1✔
89
                        if err := r.Update(ctx, res); err != nil {
1✔
NEW
90
                                return ctrlResult, errLogAndWrap(log, err, "failed to add finalizer")
×
NEW
91
                        }
×
92
                        // Return without requeue; the Update event will trigger reconciliation again
93
                        return ctrlResult, nil
1✔
94
                }
95
        }
96

97
        pod, err := r.ClientSet.CoreV1().Pods(res.Namespace).Get(ctx, discoveryPrefixed(res.Name), metav1.GetOptions{})
1✔
98
        if err != nil && !apierrors.IsNotFound(err) {
1✔
NEW
99
                r.Recorder.Eventf(res, corev1.EventTypeWarning, "Reconcile", "Failed to get pod", err)
×
NEW
100
                return ctrlResult, errLogAndWrap(log, err, "failed to get pod information")
×
NEW
101
        }
×
102

103
        // No pod yet, create it.
104
        if pod == nil || pod.Name == "" {
2✔
105
                if err := r.createPod(ctx, res); err != nil {
1✔
NEW
106
                        return ctrlResult, errLogAndWrap(log, err, "failed to create pod")
×
NEW
107
                }
×
108
                return ctrlResult, nil
1✔
109
        }
110

111
        // Pod exists, check if it's up to date with our configuration and if it is healthy.
NEW
112
        if res.Status.PodGeneration != res.GetGeneration() {
×
NEW
113
                // Recreate pod, configuration mismatch
×
NEW
114
                r.Recorder.Eventf(res, corev1.EventTypeNormal, "Reconcile", "Configuration changed. Replacing pod.")
×
NEW
115
                if err := r.ClientSet.CoreV1().Secrets(res.Namespace).Delete(ctx, discoveryPrefixed(res.Name), metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
×
NEW
116
                        r.Recorder.Eventf(res, corev1.EventTypeWarning, "DeletionFailed", "Failed to delete secret", err)
×
NEW
117
                        return ctrlResult, errLogAndWrap(log, err, "secret deletion failed")
×
NEW
118
                }
×
NEW
119
                if err := r.ClientSet.CoreV1().Pods(res.Namespace).Delete(ctx, discoveryPrefixed(res.Name), metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
×
NEW
120
                        r.Recorder.Eventf(res, corev1.EventTypeWarning, "DeletionFailed", "Failed to delete pod", err)
×
NEW
121
                        return ctrlResult, errLogAndWrap(log, err, "pod deletion failed")
×
NEW
122
                }
×
NEW
123
                if err := r.createPod(ctx, res); err != nil {
×
NEW
124
                        return ctrlResult, errLogAndWrap(log, err, "failed to create pod")
×
NEW
125
                }
×
NEW
126
                return ctrlResult, nil
×
127
        }
128

129
        // TODO: Check pods health
130

NEW
131
        return ctrlResult, nil
×
132
}
133

134
// createPod creates a new pod for the given discovery resource
135
func (r *DiscoveryReconciler) createPod(ctx context.Context, res *solarv1alpha1.Discovery) error {
1✔
136
        log := ctrl.LoggerFrom(ctx)
1✔
137

1✔
138
        // Create secret
1✔
139
        // TODO: Use the actual configuration file instead of a dummy one
1✔
140
        secret := &corev1.Secret{
1✔
141
                ObjectMeta: metav1.ObjectMeta{
1✔
142
                        Namespace: res.Namespace,
1✔
143
                        Name:      discoveryPrefixed(res.Name),
1✔
144
                },
1✔
145
                StringData: map[string]string{
1✔
146
                        "config.yaml": "not implemented",
1✔
147
                },
1✔
148
        }
1✔
149
        _, err := r.ClientSet.CoreV1().Secrets(res.Namespace).Create(ctx, secret, metav1.CreateOptions{})
1✔
150
        if err != nil {
1✔
NEW
151
                r.Recorder.Eventf(res, corev1.EventTypeWarning, "CreationFailed", "Failed to create secret", err)
×
NEW
152
                return errLogAndWrap(log, err, "failed to create secret")
×
NEW
153
        }
×
154
        r.Recorder.Eventf(res, corev1.EventTypeNormal, "PodCreate", "Secret created")
1✔
155

1✔
156
        // Set owner references
1✔
157
        if err := controllerutil.SetControllerReference(res, secret, r.Scheme); err != nil {
1✔
NEW
158
                return errLogAndWrap(log, err, "failed to set controller reference")
×
NEW
159
        }
×
160

161
        // Create pod
162
        var args []string
1✔
163
        args = append(args, r.WorkerArgs...)
1✔
164
        args = append(args, "--config", "/etc/worker/config.yaml")
1✔
165
        pod := &corev1.Pod{
1✔
166
                ObjectMeta: metav1.ObjectMeta{
1✔
167
                        Name:        discoveryPrefixed(res.Name),
1✔
168
                        Namespace:   res.Namespace,
1✔
169
                        Labels:      res.Labels,
1✔
170
                        Annotations: res.Annotations,
1✔
171
                },
1✔
172
                Spec: corev1.PodSpec{
1✔
173
                        Containers: []corev1.Container{
1✔
174
                                {
1✔
175
                                        Name:    "worker",
1✔
176
                                        Image:   r.WorkerImage,
1✔
177
                                        Command: []string{r.WorkerCommand},
1✔
178
                                        Args:    args,
1✔
179
                                        VolumeMounts: []corev1.VolumeMount{
1✔
180
                                                {
1✔
181
                                                        Name:      "config",
1✔
182
                                                        ReadOnly:  true,
1✔
183
                                                        MountPath: "/etc/worker"},
1✔
184
                                        },
1✔
185
                                },
1✔
186
                        },
1✔
187
                        Volumes: []corev1.Volume{
1✔
188
                                {
1✔
189
                                        Name: "config",
1✔
190
                                        VolumeSource: corev1.VolumeSource{
1✔
191
                                                Secret: &corev1.SecretVolumeSource{
1✔
192
                                                        SecretName: res.Name,
1✔
193
                                                },
1✔
194
                                        },
1✔
195
                                },
1✔
196
                        },
1✔
197
                },
1✔
198
        }
1✔
199

1✔
200
        // Set owner references
1✔
201
        if err := controllerutil.SetControllerReference(res, pod, r.Scheme); err != nil {
1✔
NEW
202
                return errLogAndWrap(log, err, "failed to set controller reference")
×
NEW
203
        }
×
204

205
        _, err = r.ClientSet.CoreV1().Pods(res.Namespace).Create(ctx, pod, metav1.CreateOptions{})
1✔
206
        if err != nil {
1✔
NEW
207
                r.Recorder.Eventf(res, corev1.EventTypeWarning, "PodCreate", "Failed to create pod", err)
×
NEW
208
                return errLogAndWrap(log, err, "failed to create pod")
×
NEW
209
        }
×
210
        r.Recorder.Eventf(res, corev1.EventTypeNormal, "PodCreate", "Worker pod created")
1✔
211

1✔
212
        // Update discovery version in status
1✔
213
        res.Status.PodGeneration = res.GetGeneration()
1✔
214
        if err := r.Status().Update(ctx, res); err != nil {
1✔
NEW
215
                return errLogAndWrap(log, err, "failed to update status")
×
NEW
216
        }
×
217

218
        return nil
1✔
219
}
220

221
// SetupWithManager sets up the controller with the Manager.
222
func (r *DiscoveryReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
223
        return ctrl.NewControllerManagedBy(mgr).
1✔
224
                For(&solarv1alpha1.Discovery{}).
1✔
225
                Owns(&corev1.Pod{}).
1✔
226
                Owns(&corev1.Secret{}).
1✔
227
                Complete(r)
1✔
228
}
1✔
229

230
func discoveryPrefixed(discoveryName string) string {
5✔
231
        return fmt.Sprintf("discovery-%s", discoveryName)
5✔
232
}
5✔
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