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

opendefensecloud / solution-arsenal / 24086668072

07 Apr 2026 02:26PM UTC coverage: 71.24% (+0.06%) from 71.176%
24086668072

Pull #367

github

web-flow
Merge 988a84823 into fbf2782c4
Pull Request #367: feat: Use flake.nix instead of devenv

2212 of 3105 relevant lines covered (71.24%)

27.13 hits per line

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

76.33
/pkg/controller/target_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
        "fmt"
9
        "slices"
10

11
        corev1 "k8s.io/api/core/v1"
12
        apiequality "k8s.io/apimachinery/pkg/api/equality"
13
        apierrors "k8s.io/apimachinery/pkg/api/errors"
14
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15
        "k8s.io/apimachinery/pkg/labels"
16
        "k8s.io/apimachinery/pkg/runtime"
17
        "k8s.io/apimachinery/pkg/types"
18
        "k8s.io/client-go/tools/events"
19
        ctrl "sigs.k8s.io/controller-runtime"
20
        "sigs.k8s.io/controller-runtime/pkg/builder"
21
        "sigs.k8s.io/controller-runtime/pkg/client"
22
        "sigs.k8s.io/controller-runtime/pkg/event"
23
        "sigs.k8s.io/controller-runtime/pkg/handler"
24
        "sigs.k8s.io/controller-runtime/pkg/predicate"
25
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
26

27
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
28
)
29

30
const (
31
        targetFinalizer = "solar.opendefense.cloud/target-finalizer"
32
)
33

34
type TargetReconciler struct {
35
        client.Client
36
        Scheme   *runtime.Scheme
37
        Recorder events.EventRecorder
38
        // WatchNamespace restricts reconciliation to this namespace.
39
        // Should be empty in production (watches all namespaces).
40
        // Intended for use in integration tests only.
41
        // See: https://book.kubebuilder.io/reference/envtest#testing-considerations
42
        WatchNamespace string
43
}
44

45
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets/status,verbs=get;update;patch
46
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets/finalizers,verbs=update
47
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets,verbs=get;list;watch;create;update;patch;delete
48
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=hydratedtargets,verbs=get;list;watch;create;update;patch;delete
49
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=profiles,verbs=get;list;watch
50
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
51
//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch
52

53
// Reconcile moves the current state of the cluster closer to the desired state
54
func (r *TargetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
72✔
55
        log := ctrl.LoggerFrom(ctx)
72✔
56
        ctrlResult := ctrl.Result{}
72✔
57

72✔
58
        log.V(1).Info("Target is being reconciled", "req", req)
72✔
59

72✔
60
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
73✔
61
                return ctrl.Result{}, nil
1✔
62
        }
1✔
63

64
        // Fetch target
65
        target := &solarv1alpha1.Target{}
71✔
66
        if err := r.Get(ctx, req.NamespacedName, target); err != nil {
73✔
67
                if apierrors.IsNotFound(err) {
4✔
68
                        return ctrlResult, nil
2✔
69
                }
2✔
70

71
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
72
        }
73

74
        // Handle deletion
75
        if !target.DeletionTimestamp.IsZero() {
70✔
76
                log.V(1).Info("Target is being deleted")
1✔
77
                r.Recorder.Eventf(target, nil, corev1.EventTypeWarning, "Deleting", "Reconcile", "Target is being deleted, cleaning up HydratedTarget")
1✔
78

1✔
79
                // Delete HydratedTarget
1✔
80
                if err := r.Delete(ctx, &solarv1alpha1.HydratedTarget{ObjectMeta: metav1.ObjectMeta{Namespace: target.Namespace, Name: target.Name}}); err != nil && !apierrors.IsNotFound(err) {
1✔
81
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete HydratedTarget")
×
82
                }
×
83

84
                // Remove finalizer
85
                if slices.Contains(target.Finalizers, targetFinalizer) {
2✔
86
                        // Re-fetch latest version to avoid conflicts
1✔
87
                        latest := &solarv1alpha1.Target{}
1✔
88
                        if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
1✔
89
                                return ctrlResult, errLogAndWrap(log, err, "failed to get latest Target for finalizer removal")
×
90
                        }
×
91
                        log.V(1).Info("Removing finalizer from Target")
1✔
92
                        original := latest.DeepCopy()
1✔
93
                        latest.Finalizers = slices.DeleteFunc(latest.Finalizers, func(s string) bool {
2✔
94
                                return s == targetFinalizer
1✔
95
                        })
1✔
96

97
                        if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
1✔
98
                                return ctrlResult, errLogAndWrap(log, err, "failed to remove finalizer from Target")
×
99
                        }
×
100
                }
101

102
                return ctrlResult, nil
1✔
103
        }
104

105
        // Set finalizer if not set already and not currently deleting
106
        if target.DeletionTimestamp.IsZero() && !slices.Contains(target.Finalizers, targetFinalizer) {
80✔
107
                log.V(1).Info("Target does not have finalizer set, adding finalizer")
12✔
108
                latest := &solarv1alpha1.Target{}
12✔
109
                if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
12✔
110
                        return ctrlResult, errLogAndWrap(log, err, "failed to get latest Target for finalizer addition")
×
111
                }
×
112
                original := latest.DeepCopy()
12✔
113
                latest.Finalizers = append(latest.Finalizers, targetFinalizer)
12✔
114
                if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
12✔
115
                        return ctrlResult, errLogAndWrap(log, err, "failed to add finalizer to Target")
×
116
                }
×
117

118
                return ctrlResult, nil
12✔
119
        }
120

121
        // Get matching profiles
122
        profileList := &solarv1alpha1.ProfileList{}
56✔
123
        if err := r.List(ctx, profileList, client.InNamespace(target.Namespace)); err != nil {
56✔
124
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to list Profiles")
×
125
        }
×
126

127
        matchingProfiles := make(map[string]corev1.LocalObjectReference)
56✔
128
        targetLabels := labels.Set(target.Labels)
56✔
129

56✔
130
        for _, profile := range profileList.Items {
91✔
131
                selector, err := metav1.LabelSelectorAsSelector(&profile.Spec.TargetSelector)
35✔
132
                if err != nil {
35✔
133
                        log.Error(err, "invalid targetSelector in Profile; skipping")
×
134
                        continue
×
135
                }
136

137
                if selector.Matches(targetLabels) {
58✔
138
                        matchingProfiles[profile.Name] = corev1.LocalObjectReference{Name: profile.Name}
23✔
139
                }
23✔
140
        }
141

142
        // Check if hydrated target exists, if not create and make sure to SetControllerReference...
143
        hydratedTarget := &solarv1alpha1.HydratedTarget{}
56✔
144
        err := r.Get(ctx, req.NamespacedName, hydratedTarget)
56✔
145

56✔
146
        if err != nil && !apierrors.IsNotFound(err) {
56✔
147
                return ctrlResult, errLogAndWrap(log, err, "failed to get HydratedTarget")
×
148
        }
×
149

150
        // Create HydratedTarget if not exists or update/override spec
151
        if apierrors.IsNotFound(err) {
68✔
152
                log.V(1).Info("Creating HydratedTarget for Target", "target", req.NamespacedName)
12✔
153
                hydratedTarget = &solarv1alpha1.HydratedTarget{
12✔
154
                        ObjectMeta: metav1.ObjectMeta{
12✔
155
                                Name:      target.Name,
12✔
156
                                Namespace: target.Namespace,
12✔
157
                        },
12✔
158
                        Spec: solarv1alpha1.HydratedTargetSpec{
12✔
159
                                Releases: target.Spec.Releases,
12✔
160
                                Profiles: matchingProfiles,
12✔
161
                                Userdata: target.Spec.Userdata,
12✔
162
                        },
12✔
163
                }
12✔
164
                if err := ctrl.SetControllerReference(target, hydratedTarget, r.Scheme); err != nil {
12✔
165
                        return ctrlResult, errLogAndWrap(log, err, "failed to set controller reference on HydratedTarget")
×
166
                }
×
167
                if err := r.Create(ctx, hydratedTarget); err != nil {
12✔
168
                        if !apierrors.IsAlreadyExists(err) {
×
169
                                return ctrlResult, errLogAndWrap(log, err, "failed to create HydratedTarget")
×
170
                        }
×
171
                        log.V(1).Info("HydratedTarget already exists, will update", "hydratedTarget", req.NamespacedName)
×
172
                } else {
12✔
173
                        r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Created", "Create", "Created HydratedTarget %s/%s", hydratedTarget.Namespace, hydratedTarget.Name)
12✔
174
                        return ctrlResult, nil
12✔
175
                }
12✔
176
        }
177

178
        // Update if out of sync
179
        // re-fetch target and hydratedTarget to avoid conflicts
180
        hydratedTarget = &solarv1alpha1.HydratedTarget{}
44✔
181
        if err := r.Get(ctx, req.NamespacedName, hydratedTarget); err != nil {
44✔
182
                return ctrlResult, errLogAndWrap(log, err, "failed to re-fetch HydratedTarget for update check")
×
183
        }
×
184
        target = &solarv1alpha1.Target{}
44✔
185
        if err := r.Get(ctx, req.NamespacedName, target); err != nil {
44✔
186
                return ctrlResult, errLogAndWrap(log, err, "failed to re-fetch Target for update check")
×
187
        }
×
188

189
        original := hydratedTarget.DeepCopy()
44✔
190

44✔
191
        hydratedTarget.Spec.Releases = target.Spec.Releases
44✔
192
        hydratedTarget.Spec.Profiles = matchingProfiles
44✔
193
        hydratedTarget.Spec.Userdata = target.Spec.Userdata
44✔
194

44✔
195
        if !apiequality.Semantic.DeepEqual(original.Spec, hydratedTarget.Spec) {
52✔
196
                log.V(1).Info("Updating HydratedTarget for Target", "target", req.NamespacedName)
8✔
197
                if err := r.Patch(ctx, hydratedTarget, client.MergeFrom(original)); err != nil {
8✔
198
                        return ctrlResult, errLogAndWrap(log, err, "failed to update HydratedTarget")
×
199
                }
×
200
                r.Recorder.Eventf(target, nil, corev1.EventTypeNormal, "Updated", "Update", "Updated HydratedTarget %s/%s", hydratedTarget.Namespace, hydratedTarget.Name)
8✔
201
        }
202

203
        return ctrl.Result{}, nil
44✔
204
}
205

206
// SetupWithManager sets up the controller with the Manager.
207
func (r *TargetReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
208
        return ctrl.NewControllerManagedBy(mgr).
1✔
209
                For(&solarv1alpha1.Target{}).
1✔
210
                Owns(&solarv1alpha1.HydratedTarget{}).
1✔
211
                Watches(
1✔
212
                        &solarv1alpha1.Profile{},
1✔
213
                        handler.EnqueueRequestsFromMapFunc(r.mapProfileToTargets),
1✔
214
                        builder.WithPredicates(profileSelectionPredicate()),
1✔
215
                ).
1✔
216
                Complete(r)
1✔
217
}
1✔
218

219
// profileSelectionPredicate filters events to only trigger reconciles when the target selector of a profile changes.
220
func profileSelectionPredicate() predicate.Predicate {
5✔
221
        return predicate.Funcs{
5✔
222
                UpdateFunc: func(e event.UpdateEvent) bool {
8✔
223
                        oldObj, ok1 := e.ObjectOld.(*solarv1alpha1.Profile)
3✔
224
                        newObj, ok2 := e.ObjectNew.(*solarv1alpha1.Profile)
3✔
225
                        if !ok1 || !ok2 {
3✔
226
                                return false
×
227
                        }
×
228

229
                        return !apiequality.Semantic.DeepEqual(oldObj.Spec.TargetSelector, newObj.Spec.TargetSelector)
3✔
230
                },
231
        }
232
}
233

234
// mapProfileToTargets maps a Profile to a list of Target reconcile requests.
235
func (r *TargetReconciler) mapProfileToTargets(ctx context.Context, obj client.Object) []reconcile.Request {
23✔
236
        log := ctrl.LoggerFrom(ctx)
23✔
237

23✔
238
        profile, ok := obj.(*solarv1alpha1.Profile)
23✔
239
        if !ok {
23✔
240
                log.Error(nil, "Object is not a Profile", "type", fmt.Sprintf("%T", obj))
×
241
                return nil
×
242
        }
×
243

244
        selector, err := metav1.LabelSelectorAsSelector(&profile.Spec.TargetSelector)
23✔
245
        if err != nil {
23✔
246
                log.Error(err, "Invalid targetSelector in Profile", "profile", profile.Name, "targetSelector", profile.Spec.TargetSelector.String())
×
247
                return nil
×
248
        }
×
249

250
        targetList := &solarv1alpha1.TargetList{}
23✔
251
        err = r.List(ctx, targetList,
23✔
252
                client.InNamespace(profile.GetNamespace()),
23✔
253
                client.MatchingLabelsSelector{Selector: selector},
23✔
254
        )
23✔
255
        if err != nil {
23✔
256
                log.V(1).Error(err, "Failed to list Targets for Profile", "profile", profile.Name)
×
257
                return nil
×
258
        }
×
259

260
        requests := make([]reconcile.Request, 0, len(targetList.Items))
23✔
261
        for _, target := range targetList.Items {
30✔
262
                requests = append(requests, reconcile.Request{
7✔
263
                        NamespacedName: types.NamespacedName{
7✔
264
                                Name:      target.Name,
7✔
265
                                Namespace: target.Namespace,
7✔
266
                        },
7✔
267
                })
7✔
268
        }
7✔
269

270
        return requests
23✔
271
}
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