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

opendefensecloud / solution-arsenal / 21704757710

05 Feb 2026 08:44AM UTC coverage: 67.473% (+1.1%) from 66.416%
21704757710

Pull #119

github

web-flow
Merge 31e86dd4f into 1bc1b2999
Pull Request #119: Implement dummy target controller

78 of 101 new or added lines in 1 file covered. (77.23%)

697 of 1033 relevant lines covered (67.47%)

6.56 hits per line

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

77.23
/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
        "slices"
9

10
        corev1 "k8s.io/api/core/v1"
11
        apiequality "k8s.io/apimachinery/pkg/api/equality"
12
        apierrors "k8s.io/apimachinery/pkg/api/errors"
13
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
        "k8s.io/apimachinery/pkg/runtime"
15
        "k8s.io/client-go/tools/record"
16
        ctrl "sigs.k8s.io/controller-runtime"
17
        "sigs.k8s.io/controller-runtime/pkg/client"
18

19
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
20
)
21

22
const (
23
        targetFinalizer = "solar.opendefense.cloud/target-finalizer"
24
)
25

26
type TargetReconciler struct {
27
        client.Client
28
        Scheme   *runtime.Scheme
29
        Recorder record.EventRecorder
30
}
31

32
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets/status,verbs=get;update;patch
33
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets/finalizers,verbs=update
34
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=hydratedtargets,verbs=get;list;watch;create;update;patch;delete
35

36
// Reconcile moves the current state of the cluster closer to the desired state
37
func (r *TargetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
13✔
38
        log := ctrl.LoggerFrom(ctx)
13✔
39
        ctrlResult := ctrl.Result{}
13✔
40

13✔
41
        log.V(1).Info("Target is being reconciled", "req", req)
13✔
42

13✔
43
        // Fetch target
13✔
44
        target := &solarv1alpha1.Target{}
13✔
45
        if err := r.Get(ctx, req.NamespacedName, target); err != nil {
14✔
46
                if apierrors.IsNotFound(err) {
2✔
47
                        return ctrlResult, nil
1✔
48
                }
1✔
49

NEW
50
                return ctrlResult, errLogAndWrap(log, err, "failed to get object")
×
51
        }
52

53
        // Handle deletion
54
        if !target.DeletionTimestamp.IsZero() {
13✔
55
                log.V(1).Info("Target is being deleted")
1✔
56
                r.Recorder.Event(target, corev1.EventTypeWarning, "Deleting", "Target is being deleted, cleaning up HydratedTarget")
1✔
57

1✔
58
                // Delete HydratedTarget
1✔
59
                if err := r.Delete(ctx, &solarv1alpha1.HydratedTarget{ObjectMeta: metav1.ObjectMeta{Namespace: target.Namespace, Name: target.Name}}); err != nil && !apierrors.IsNotFound(err) {
1✔
NEW
60
                        return ctrlResult, errLogAndWrap(log, err, "failed to delete HydratedTarget")
×
NEW
61
                }
×
62

63
                // Remove finalizer
64
                if slices.Contains(target.Finalizers, targetFinalizer) {
2✔
65
                        // Re-fetch latest version to avoid conflicts
1✔
66
                        latest := &solarv1alpha1.Target{}
1✔
67
                        if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
1✔
NEW
68
                                return ctrlResult, errLogAndWrap(log, err, "failed to get latest Target for finalizer removal")
×
NEW
69
                        }
×
70
                        log.V(1).Info("Removing finalizer from Target")
1✔
71
                        original := latest.DeepCopy()
1✔
72
                        latest.Finalizers = slices.DeleteFunc(latest.Finalizers, func(s string) bool {
2✔
73
                                return s == targetFinalizer
1✔
74
                        })
1✔
75

76
                        if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
1✔
NEW
77
                                return ctrlResult, errLogAndWrap(log, err, "failed to remove finalizer from Target")
×
NEW
78
                        }
×
79
                }
80

81
                return ctrlResult, nil
1✔
82
        }
83

84
        // Set finalizer if not set already and not currently deleting
85
        if target.DeletionTimestamp.IsZero() && !slices.Contains(target.Finalizers, targetFinalizer) {
14✔
86
                log.V(1).Info("Target does not have finalizer set, adding finalizer")
3✔
87
                latest := &solarv1alpha1.Target{}
3✔
88
                if err := r.Get(ctx, req.NamespacedName, latest); err != nil {
3✔
NEW
89
                        return ctrlResult, errLogAndWrap(log, err, "failed to get latest Target for finalizer addition")
×
NEW
90
                }
×
91
                original := latest.DeepCopy()
3✔
92
                latest.Finalizers = append(latest.Finalizers, targetFinalizer)
3✔
93
                if err := r.Patch(ctx, latest, client.MergeFrom(original)); err != nil {
3✔
NEW
94
                        return ctrlResult, errLogAndWrap(log, err, "failed to add finalizer to Target")
×
NEW
95
                }
×
96

97
                return ctrlResult, nil
3✔
98
        }
99

100
        // Check if hydrated target exists, if not create and make sure to SetControllerReference...
101
        hydratedTarget := &solarv1alpha1.HydratedTarget{}
8✔
102
        err := r.Get(ctx, req.NamespacedName, hydratedTarget)
8✔
103

8✔
104
        if err != nil && !apierrors.IsNotFound(err) {
8✔
NEW
105
                return ctrlResult, errLogAndWrap(log, err, "failed to get HydratedTarget")
×
NEW
106
        }
×
107

108
        // Create HydratedTarget if not exists or update/override spec
109
        if apierrors.IsNotFound(err) {
11✔
110
                log.V(1).Info("Creating HydratedTarget for Target", "target", req.NamespacedName)
3✔
111
                // FIXME: Just copy over releases and userdata (for now)
3✔
112
                hydratedTarget = &solarv1alpha1.HydratedTarget{
3✔
113
                        ObjectMeta: metav1.ObjectMeta{
3✔
114
                                Name:      target.Name,
3✔
115
                                Namespace: target.Namespace,
3✔
116
                        },
3✔
117
                        Spec: solarv1alpha1.HydratedTargetSpec{
3✔
118
                                Releases: target.Spec.Releases,
3✔
119
                                Userdata: target.Spec.Userdata,
3✔
120
                        },
3✔
121
                }
3✔
122
                if err := ctrl.SetControllerReference(target, hydratedTarget, r.Scheme); err != nil {
3✔
NEW
123
                        return ctrlResult, errLogAndWrap(log, err, "failed to set controller reference on HydratedTarget")
×
NEW
124
                }
×
125
                if err := r.Create(ctx, hydratedTarget); err != nil {
3✔
NEW
126
                        return ctrlResult, errLogAndWrap(log, err, "failed to create HydratedTarget")
×
NEW
127
                }
×
128
                r.Recorder.Eventf(target, corev1.EventTypeNormal, "Created", "Created HydratedTarget %s/%s", hydratedTarget.Namespace, hydratedTarget.Name)
3✔
129

3✔
130
                return ctrlResult, nil
3✔
131
        }
132

133
        // Update if out of sync
134
        // re-fetch target and hydratedTarget to avoid conflicts
135
        hydratedTarget = &solarv1alpha1.HydratedTarget{}
5✔
136
        if err := r.Get(ctx, req.NamespacedName, hydratedTarget); err != nil {
5✔
NEW
137
                return ctrlResult, errLogAndWrap(log, err, "failed to re-fetch HydratedTarget for update check")
×
NEW
138
        }
×
139
        target = &solarv1alpha1.Target{}
5✔
140
        if err := r.Get(ctx, req.NamespacedName, target); err != nil {
5✔
NEW
141
                return ctrlResult, errLogAndWrap(log, err, "failed to re-fetch Target for update check")
×
NEW
142
        }
×
143

144
        original := hydratedTarget.DeepCopy()
5✔
145

5✔
146
        if !apiequality.Semantic.DeepEqual(hydratedTarget.Spec, target.Spec) {
10✔
147
                hydratedTarget.Spec.Releases = target.Spec.Releases
5✔
148
                hydratedTarget.Spec.Userdata = target.Spec.Userdata
5✔
149
                log.V(1).Info("Updating HydratedTarget for Target", "target", req.NamespacedName)
5✔
150
                if err := r.Patch(ctx, hydratedTarget, client.MergeFrom(original)); err != nil {
5✔
NEW
151
                        return ctrlResult, errLogAndWrap(log, err, "failed to update HydratedTarget")
×
NEW
152
                }
×
153
                r.Recorder.Eventf(target, corev1.EventTypeNormal, "Updated", "Updated HydratedTarget %s/%s", hydratedTarget.Namespace, hydratedTarget.Name)
5✔
154
        }
155

156
        return ctrl.Result{}, nil
5✔
157
}
158

159
// SetupWithManager sets up the controller with the Manager.
160
func (r *TargetReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
161
        return ctrl.NewControllerManagedBy(mgr).
1✔
162
                For(&solarv1alpha1.Target{}).
1✔
163
                Owns(&solarv1alpha1.HydratedTarget{}).
1✔
164
                Complete(r)
1✔
165
}
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