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

opendefensecloud / solution-arsenal / 24896156140

24 Apr 2026 02:57PM UTC coverage: 72.431% (+0.5%) from 71.891%
24896156140

push

github

web-flow
Feature: Table Convertor (#449)

Fixes #84

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Kubectl-style table output for Solar resources (Component,
ComponentVersion, Profile, Registry, RegistryBinding, Release,
ReleaseBinding, RenderTask, Target).
  * Optional namespace scoping for RenderTask reconciliation.

* **Tests**
* Comprehensive tests validating table columns, rows, and status
derivation across resources.

* **Documentation**
* New/updated developer guides (discovery, controllers, architecture,
etc.) and API reference updates.

* **Chores**
* Script to list resources and inspect table columns; removed
Release.status.chartURL.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

2065 of 2851 relevant lines covered (72.43%)

23.97 hits per line

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

77.78
/pkg/controller/profile_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

10
        corev1 "k8s.io/api/core/v1"
11
        apierrors "k8s.io/apimachinery/pkg/api/errors"
12
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13
        "k8s.io/apimachinery/pkg/labels"
14
        "k8s.io/apimachinery/pkg/runtime"
15
        "k8s.io/client-go/tools/events"
16
        ctrl "sigs.k8s.io/controller-runtime"
17
        "sigs.k8s.io/controller-runtime/pkg/client"
18
        "sigs.k8s.io/controller-runtime/pkg/handler"
19
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
20

21
        solarv1alpha1 "go.opendefense.cloud/solar/api/solar/v1alpha1"
22
)
23

24
// ProfileReconciler reconciles a Profile object.
25
// It evaluates the Profile's TargetSelector against all Targets in the namespace
26
// and creates/deletes ReleaseBindings accordingly.
27
type ProfileReconciler struct {
28
        client.Client
29
        Scheme   *runtime.Scheme
30
        Recorder events.EventRecorder
31
        // WatchNamespace restricts reconciliation to this namespace.
32
        WatchNamespace string
33
}
34

35
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=profiles,verbs=get;list;watch
36
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=profiles/status,verbs=get;update;patch
37
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=releasebindings,verbs=get;list;watch;create;update;patch;delete
38
//+kubebuilder:rbac:groups=solar.opendefense.cloud,resources=targets,verbs=get;list;watch
39
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
40
//+kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch
41

42
// Reconcile evaluates the Profile's TargetSelector and ensures matching ReleaseBindings exist.
43
func (r *ProfileReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
24✔
44
        log := ctrl.LoggerFrom(ctx)
24✔
45

24✔
46
        log.V(1).Info("Profile is being reconciled", "req", req)
24✔
47

24✔
48
        if r.WatchNamespace != "" && req.Namespace != r.WatchNamespace {
37✔
49
                return ctrl.Result{}, nil
13✔
50
        }
13✔
51

52
        // Fetch Profile
53
        profile := &solarv1alpha1.Profile{}
11✔
54
        if err := r.Get(ctx, req.NamespacedName, profile); err != nil {
11✔
55
                if apierrors.IsNotFound(err) {
×
56
                        return ctrl.Result{}, nil
×
57
                }
×
58

59
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to get Profile")
×
60
        }
61

62
        // Evaluate TargetSelector against all Targets
63
        selector, err := metav1.LabelSelectorAsSelector(&profile.Spec.TargetSelector)
11✔
64
        if err != nil {
11✔
65
                log.Error(err, "invalid targetSelector in Profile")
×
66

×
67
                return ctrl.Result{}, nil
×
68
        }
×
69

70
        targetList := &solarv1alpha1.TargetList{}
11✔
71
        if err := r.List(ctx, targetList,
11✔
72
                client.InNamespace(profile.Namespace),
11✔
73
                client.MatchingLabelsSelector{Selector: selector},
11✔
74
        ); err != nil {
11✔
75
                return ctrl.Result{}, errLogAndWrap(log, err, "failed to list Targets")
×
76
        }
×
77

78
        // Build set of desired ReleaseBindings (one per matching target)
79
        desiredTargets := map[string]bool{}
11✔
80
        for _, target := range targetList.Items {
23✔
81
                desiredTargets[target.Name] = true
12✔
82
        }
12✔
83

84
        // List existing ReleaseBindings owned by this Profile
85
        existingBindings := &solarv1alpha1.ReleaseBindingList{}
11✔
86
        if err := r.List(ctx, existingBindings,
11✔
87
                client.InNamespace(profile.Namespace),
11✔
88
                client.MatchingFields{"metadata.ownerReferences.name": profile.Name},
11✔
89
        ); err != nil {
22✔
90
                // Field index may not be available; fall back to listing all and filtering
11✔
91
                allBindings := &solarv1alpha1.ReleaseBindingList{}
11✔
92
                if err := r.List(ctx, allBindings, client.InNamespace(profile.Namespace)); err != nil {
11✔
93
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to list ReleaseBindings")
×
94
                }
×
95

96
                existingBindings = &solarv1alpha1.ReleaseBindingList{}
11✔
97

11✔
98
                for i := range allBindings.Items {
18✔
99
                        if metav1.IsControlledBy(&allBindings.Items[i], profile) {
14✔
100
                                existingBindings.Items = append(existingBindings.Items, allBindings.Items[i])
7✔
101
                        }
7✔
102
                }
103
        }
104

105
        // Delete ReleaseBindings for targets that no longer match
106
        existingTargets := map[string]*solarv1alpha1.ReleaseBinding{}
11✔
107
        for i := range existingBindings.Items {
18✔
108
                rb := &existingBindings.Items[i]
7✔
109
                existingTargets[rb.Spec.TargetRef.Name] = rb
7✔
110

7✔
111
                if !desiredTargets[rb.Spec.TargetRef.Name] {
8✔
112
                        log.V(1).Info("Deleting ReleaseBinding for unmatched target", "target", rb.Spec.TargetRef.Name)
1✔
113
                        if err := r.Delete(ctx, rb); err != nil && !apierrors.IsNotFound(err) {
1✔
114
                                return ctrl.Result{}, errLogAndWrap(log, err, "failed to delete ReleaseBinding")
×
115
                        }
×
116

117
                        r.Recorder.Eventf(profile, nil, corev1.EventTypeNormal, "Deleted", "Delete",
1✔
118
                                "Deleted ReleaseBinding for target %s", rb.Spec.TargetRef.Name)
1✔
119
                }
120
        }
121

122
        // Create ReleaseBindings for new matching targets
123
        for _, target := range targetList.Items {
23✔
124
                if _, exists := existingTargets[target.Name]; exists {
18✔
125
                        continue
6✔
126
                }
127

128
                rb := &solarv1alpha1.ReleaseBinding{
6✔
129
                        ObjectMeta: metav1.ObjectMeta{
6✔
130
                                // We need to truncated the name: 57 (input) + 1 (-) + 5 (appended by generated) = 63 (max chars allowed)
6✔
131
                                GenerateName: truncateName(fmt.Sprintf("%s-%s", profile.Name, target.Name), 57) + "-",
6✔
132
                                Namespace:    profile.Namespace,
6✔
133
                        },
6✔
134
                        Spec: solarv1alpha1.ReleaseBindingSpec{
6✔
135
                                TargetRef:  corev1.LocalObjectReference{Name: target.Name},
6✔
136
                                ReleaseRef: profile.Spec.ReleaseRef,
6✔
137
                        },
6✔
138
                }
6✔
139
                if err := ctrl.SetControllerReference(profile, rb, r.Scheme); err != nil {
6✔
140
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to set controller reference on ReleaseBinding")
×
141
                }
×
142

143
                if err := r.Create(ctx, rb); err != nil {
6✔
144
                        if apierrors.IsAlreadyExists(err) {
×
145
                                continue
×
146
                        }
147

148
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to create ReleaseBinding")
×
149
                }
150

151
                log.V(1).Info("Created ReleaseBinding for target", "target", target.Name)
6✔
152
                r.Recorder.Eventf(profile, nil, corev1.EventTypeNormal, "Created", "Create",
6✔
153
                        "Created ReleaseBinding for target %s", target.Name)
6✔
154
        }
155

156
        // Update status
157
        original := profile.DeepCopy()
11✔
158
        profile.Status.MatchedTargets = len(targetList.Items)
11✔
159
        if profile.Status.MatchedTargets != original.Status.MatchedTargets {
17✔
160
                if err := r.Status().Update(ctx, profile); err != nil {
6✔
161
                        return ctrl.Result{}, errLogAndWrap(log, err, "failed to update Profile status")
×
162
                }
×
163
        }
164

165
        return ctrl.Result{}, nil
11✔
166
}
167

168
// SetupWithManager sets up the controller with the Manager.
169
func (r *ProfileReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
170
        return ctrl.NewControllerManagedBy(mgr).
1✔
171
                For(&solarv1alpha1.Profile{}).
1✔
172
                Owns(&solarv1alpha1.ReleaseBinding{}).
1✔
173
                Watches(
1✔
174
                        &solarv1alpha1.Target{},
1✔
175
                        handler.EnqueueRequestsFromMapFunc(r.mapTargetToProfiles),
1✔
176
                ).
1✔
177
                Complete(r)
1✔
178
}
1✔
179

180
// mapTargetToProfiles maps a Target to all Profiles in the same namespace that might match it.
181
func (r *ProfileReconciler) mapTargetToProfiles(ctx context.Context, obj client.Object) []reconcile.Request {
130✔
182
        log := ctrl.LoggerFrom(ctx)
130✔
183

130✔
184
        target, ok := obj.(*solarv1alpha1.Target)
130✔
185
        if !ok {
130✔
186
                return nil
×
187
        }
×
188

189
        profileList := &solarv1alpha1.ProfileList{}
130✔
190
        if err := r.List(ctx, profileList, client.InNamespace(target.Namespace)); err != nil {
130✔
191
                log.Error(err, "failed to list Profiles for Target mapping")
×
192

×
193
                return nil
×
194
        }
×
195

196
        targetLabels := labels.Set(target.Labels)
130✔
197
        var requests []reconcile.Request
130✔
198

130✔
199
        for _, profile := range profileList.Items {
175✔
200
                selector, err := metav1.LabelSelectorAsSelector(&profile.Spec.TargetSelector)
45✔
201
                if err != nil {
45✔
202
                        continue
×
203
                }
204

205
                if selector.Matches(targetLabels) {
81✔
206
                        requests = append(requests, reconcile.Request{
36✔
207
                                NamespacedName: client.ObjectKeyFromObject(&profile),
36✔
208
                        })
36✔
209
                }
36✔
210
        }
211

212
        return requests
130✔
213
}
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