Skip to content

Commit 6ed4cef

Browse files
authored
chore: split controllers to separate packages + cover them with unit tests (#404)
Signed-off-by: odubajDT <[email protected]>
1 parent bbeeea2 commit 6ed4cef

File tree

11 files changed

+509
-310
lines changed

11 files changed

+509
-310
lines changed

Makefile

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,7 @@ vet: ## Run go vet against code.
6363

6464
.PHONY: component-test
6565
component-test: manifests generate fmt vet envtest ## Run tests.
66-
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./controllers/... -coverprofile cover-controllers.out
67-
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./webhooks/... -coverprofile cover-webhooks.out
68-
sed -i '/mode: set/d' "cover-controllers.out"
69-
sed -i '/mode: set/d' "cover-webhooks.out"
70-
echo "mode: set" > cover.out
71-
cat cover-controllers.out cover-webhooks.out >> cover.out
72-
rm cover-controllers.out cover-webhooks.out
66+
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./webhooks/... -coverprofile cover.out
7367

7468
.PHONY: unit-test
7569
unit-test: manifests fmt vet generate envtest ## Run tests.

apis/core/v1alpha1/featureflagconfiguration_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,7 @@ func FeatureFlagConfigurationId(namespace, name string) string {
145145
func FeatureFlagConfigurationConfigMapKey(namespace, name string) string {
146146
return fmt.Sprintf("%s.flagd.json", FeatureFlagConfigurationId(namespace, name))
147147
}
148+
149+
func (p *FeatureFlagServiceProvider) IsSet() bool {
150+
return p != nil && p.Name != ""
151+
}

controllers/common/common.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
appsV1 "k8s.io/api/apps/v1"
8+
"sigs.k8s.io/controller-runtime/pkg/client"
9+
)
10+
11+
const (
12+
CrdName = "FeatureFlagConfiguration"
13+
ReconcileErrorInterval = 10 * time.Second
14+
ReconcileSuccessInterval = 120 * time.Second
15+
FinalizerName = "featureflagconfiguration.core.openfeature.dev/finalizer"
16+
OpenFeatureAnnotationPath = "spec.template.metadata.annotations.openfeature.dev/openfeature.dev"
17+
FlagSourceConfigurationAnnotation = "flagsourceconfiguration"
18+
OpenFeatureAnnotationRoot = "openfeature.dev"
19+
)
20+
21+
func FlagSourceConfigurationIndex(o client.Object) []string {
22+
deployment, ok := o.(*appsV1.Deployment)
23+
if !ok {
24+
return []string{
25+
"false",
26+
}
27+
}
28+
29+
if deployment.Spec.Template.ObjectMeta.Annotations == nil {
30+
return []string{
31+
"false",
32+
}
33+
}
34+
if _, ok := deployment.Spec.Template.ObjectMeta.Annotations[fmt.Sprintf("openfeature.dev/%s", FlagSourceConfigurationAnnotation)]; ok {
35+
return []string{
36+
"true",
37+
}
38+
}
39+
return []string{
40+
"false",
41+
}
42+
}

controllers/common/common_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
appsV1 "k8s.io/api/apps/v1"
9+
corev1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
)
13+
14+
func TestFlagSourceConfigurationIndex(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
obj client.Object
18+
out []string
19+
}{
20+
{
21+
name: "non-deployment object",
22+
obj: &appsV1.DaemonSet{},
23+
out: []string{"false"},
24+
},
25+
{
26+
name: "no annotations",
27+
obj: &appsV1.Deployment{},
28+
out: []string{"false"},
29+
},
30+
{
31+
name: "not existing right annotation",
32+
obj: &appsV1.Deployment{
33+
Spec: appsV1.DeploymentSpec{
34+
Template: corev1.PodTemplateSpec{
35+
ObjectMeta: metav1.ObjectMeta{
36+
Annotations: map[string]string{
37+
"silly": "some",
38+
},
39+
},
40+
},
41+
},
42+
},
43+
out: []string{"false"},
44+
},
45+
{
46+
name: "existing annotation",
47+
obj: &appsV1.Deployment{
48+
Spec: appsV1.DeploymentSpec{
49+
Template: corev1.PodTemplateSpec{
50+
ObjectMeta: metav1.ObjectMeta{
51+
Annotations: map[string]string{
52+
fmt.Sprintf("openfeature.dev/%s", FlagSourceConfigurationAnnotation): "true",
53+
},
54+
},
55+
},
56+
},
57+
},
58+
out: []string{"true"},
59+
},
60+
}
61+
62+
for _, tt := range tests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
out := FlagSourceConfigurationIndex(tt.obj)
65+
require.Equal(t, tt.out, out)
66+
})
67+
68+
}
69+
}

controllers/controllers_test.go

Lines changed: 0 additions & 100 deletions
This file was deleted.

controllers/featureflagconfiguration_controller.go renamed to controllers/core/featureflagconfiguration/controller.go

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package controllers
17+
package featureflagconfiguration
1818

1919
import (
2020
"context"
21-
"time"
2221

2322
"github.com/go-logr/logr"
23+
"github.com/open-feature/open-feature-operator/controllers/common"
2424
"github.com/open-feature/open-feature-operator/pkg/utils"
2525
corev1 "k8s.io/api/core/v1"
2626
"k8s.io/apimachinery/pkg/api/errors"
27-
"k8s.io/client-go/tools/record"
27+
"sigs.k8s.io/controller-runtime/pkg/builder"
2828
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
29+
"sigs.k8s.io/controller-runtime/pkg/predicate"
2930

3031
"k8s.io/apimachinery/pkg/runtime"
3132
ctrl "sigs.k8s.io/controller-runtime"
3233
"sigs.k8s.io/controller-runtime/pkg/client"
33-
"sigs.k8s.io/controller-runtime/pkg/log"
3434

3535
corev1alpha1 "github.com/open-feature/open-feature-operator/apis/core/v1alpha1"
3636
)
@@ -41,8 +41,6 @@ type FeatureFlagConfigurationReconciler struct {
4141

4242
// Scheme contains the scheme of this controller
4343
Scheme *runtime.Scheme
44-
// Recorder contains the Recorder of this controller
45-
Recorder record.EventRecorder
4644
// ReqLogger contains the Logger of this controller
4745
Log logr.Logger
4846
}
@@ -61,42 +59,36 @@ type FeatureFlagConfigurationReconciler struct {
6159
// For more details, check Reconcile and its Result here:
6260
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
6361

64-
const (
65-
crdName = "FeatureFlagConfiguration"
66-
reconcileErrorInterval = 10 * time.Second
67-
reconcileSuccessInterval = 120 * time.Second
68-
finalizerName = "featureflagconfiguration.core.openfeature.dev/finalizer"
69-
)
62+
const CrdName = "FeatureFlagConfiguration"
7063

7164
func (r *FeatureFlagConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
72-
r.Log = log.FromContext(ctx)
73-
r.Log.Info("Reconciling" + crdName)
65+
r.Log.Info("Reconciling" + CrdName)
7466

7567
ffconf := &corev1alpha1.FeatureFlagConfiguration{}
7668
if err := r.Client.Get(ctx, req.NamespacedName, ffconf); err != nil {
7769
if errors.IsNotFound(err) {
7870
// taking down all associated K8s resources is handled by K8s
79-
r.Log.Info(crdName + " resource not found. Ignoring since object must be deleted")
71+
r.Log.Info(CrdName + " resource not found. Ignoring since object must be deleted")
8072
return r.finishReconcile(nil, false)
8173
}
82-
r.Log.Error(err, "Failed to get the "+crdName)
74+
r.Log.Error(err, "Failed to get the "+CrdName)
8375
return r.finishReconcile(err, false)
8476
}
8577

8678
if ffconf.ObjectMeta.DeletionTimestamp.IsZero() {
8779
// The object is not being deleted, so if it does not have our finalizer,
8880
// then lets add the finalizer and update the object. This is equivalent
8981
// registering our finalizer.
90-
if !utils.ContainsString(ffconf.GetFinalizers(), finalizerName) {
91-
controllerutil.AddFinalizer(ffconf, finalizerName)
82+
if !utils.ContainsString(ffconf.GetFinalizers(), common.FinalizerName) {
83+
controllerutil.AddFinalizer(ffconf, common.FinalizerName)
9284
if err := r.Update(ctx, ffconf); err != nil {
9385
return r.finishReconcile(err, false)
9486
}
9587
}
9688
} else {
9789
// The object is being deleted
98-
if utils.ContainsString(ffconf.GetFinalizers(), finalizerName) {
99-
controllerutil.RemoveFinalizer(ffconf, finalizerName)
90+
if utils.ContainsString(ffconf.GetFinalizers(), common.FinalizerName) {
91+
controllerutil.RemoveFinalizer(ffconf, common.FinalizerName)
10092
if err := r.Update(ctx, ffconf); err != nil {
10193
return ctrl.Result{}, err
10294
}
@@ -106,7 +98,7 @@ func (r *FeatureFlagConfigurationReconciler) Reconcile(ctx context.Context, req
10698
}
10799

108100
// Check the provider on the FeatureFlagConfiguration
109-
if ffconf.Spec.ServiceProvider == nil {
101+
if !ffconf.Spec.ServiceProvider.IsSet() {
110102
r.Log.Info("No service provider specified for FeatureFlagConfiguration, using FlagD")
111103
ffconf.Spec.ServiceProvider = &corev1alpha1.FeatureFlagServiceProvider{
112104
Name: "flagd",
@@ -161,28 +153,20 @@ func (r *FeatureFlagConfigurationReconciler) Reconcile(ctx context.Context, req
161153
return r.finishReconcile(nil, false)
162154
}
163155

164-
// SetupWithManager sets up the controller with the Manager.
165-
func (r *FeatureFlagConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error {
166-
return ctrl.NewControllerManagedBy(mgr).
167-
For(&corev1alpha1.FeatureFlagConfiguration{}).
168-
Owns(&corev1.ConfigMap{}).
169-
Complete(r)
170-
}
171-
172156
func (r *FeatureFlagConfigurationReconciler) finishReconcile(err error, requeueImmediate bool) (ctrl.Result, error) {
173157
if err != nil {
174-
interval := reconcileErrorInterval
158+
interval := common.ReconcileErrorInterval
175159
if requeueImmediate {
176160
interval = 0
177161
}
178-
r.Log.Error(err, "Finished Reconciling "+crdName+" with error: %w")
162+
r.Log.Error(err, "Finished Reconciling "+CrdName+" with error: %w")
179163
return ctrl.Result{Requeue: true, RequeueAfter: interval}, err
180164
}
181-
interval := reconcileSuccessInterval
165+
interval := common.ReconcileSuccessInterval
182166
if requeueImmediate {
183167
interval = 0
184168
}
185-
r.Log.Info("Finished Reconciling " + crdName)
169+
r.Log.Info("Finished Reconciling " + CrdName)
186170
return ctrl.Result{Requeue: true, RequeueAfter: interval}, nil
187171
}
188172

@@ -194,3 +178,11 @@ func (r *FeatureFlagConfigurationReconciler) featureFlagResourceIsOwner(ff *core
194178
}
195179
return false
196180
}
181+
182+
// SetupWithManager sets up the controller with the Manager.
183+
func (r *FeatureFlagConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error {
184+
return ctrl.NewControllerManagedBy(mgr).
185+
For(&corev1alpha1.FeatureFlagConfiguration{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
186+
Owns(&corev1.ConfigMap{}).
187+
Complete(r)
188+
}

0 commit comments

Comments
 (0)