Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .testcoverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ exclude:
paths:
- ^controller/testSupport # exclude test support files
- mocks # exclude generated mock files
- ^test/openfga

- ^test/
- ^logger/testlogger

1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ func BindConfigToFlags(v *viper.Viper, cmd *cobra.Command, config any) error {
return nil
}

// unmarshalIntoStruct returns a function that unmarshal viper config into cfg and panics on error.
func unmarshalIntoStruct(v *viper.Viper, cfg any) func() {
return func() {
if err := v.Unmarshal(cfg); err != nil {
Expand Down
9 changes: 9 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,12 @@ func TestNewDefaultConfig(t *testing.T) {
err = v.Unmarshal(&config.CommonServiceConfig{})
assert.NoError(t, err)
}

func TestGenerateFlagSetUnsupportedType(t *testing.T) {
type test struct {
UnsupportedField []string `mapstructure:"unsupported-field"`
}
testStruct := test{}
err := config.BindConfigToFlags(viper.New(), &cobra.Command{}, &testStruct)
assert.Error(t, err)
}
7 changes: 2 additions & 5 deletions controller/lifecycle/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,10 @@ type Config struct {
}

type ConditionManager interface {
MustToRuntimeObjectConditionsInterface(instance runtimeobject.RuntimeObject, log *logger.Logger) RuntimeObjectConditions
SetInstanceConditionUnknownIfNotSet(conditions *[]metav1.Condition) bool
SetSubroutineConditionToUnknownIfNotSet(conditions *[]metav1.Condition, subroutine subroutine.Subroutine, isFinalize bool, log *logger.Logger) bool
SetSubroutineCondition(conditions *[]metav1.Condition, subroutine subroutine.Subroutine, subroutineResult ctrl.Result, subroutineErr error, isFinalize bool, log *logger.Logger) bool
SetInstanceConditionReady(conditions *[]metav1.Condition, status metav1.ConditionStatus) bool
ToRuntimeObjectConditionsInterface(instance runtimeobject.RuntimeObject, log *logger.Logger) (RuntimeObjectConditions, error)
}

type RuntimeObjectConditions interface {
Expand All @@ -44,9 +42,8 @@ type RuntimeObjectConditions interface {
}

type SpreadManager interface {
ToRuntimeObjectSpreadReconcileStatusInterface(instance runtimeobject.RuntimeObject, log *logger.Logger) (RuntimeObjectSpreadReconcileStatus, error)
MustToRuntimeObjectSpreadReconcileStatusInterface(instance runtimeobject.RuntimeObject, log *logger.Logger) RuntimeObjectSpreadReconcileStatus
OnNextReconcile(instanceStatusObj RuntimeObjectSpreadReconcileStatus, log *logger.Logger) (ctrl.Result, error)
ReconcileRequired(instance runtimeobject.RuntimeObject, log *logger.Logger) bool
OnNextReconcile(instance runtimeobject.RuntimeObject, log *logger.Logger) (ctrl.Result, error)
RemoveRefreshLabelIfExists(instance runtimeobject.RuntimeObject) bool
SetNextReconcileTime(instanceStatusObj RuntimeObjectSpreadReconcileStatus, log *logger.Logger)
UpdateObservedGeneration(instanceStatusObj RuntimeObjectSpreadReconcileStatus, log *logger.Logger)
Expand Down
74 changes: 74 additions & 0 deletions controller/lifecycle/builder/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package builder

import (
"sigs.k8s.io/controller-runtime/pkg/client"
mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"

"github.com/platform-mesh/golang-commons/controller/lifecycle/controllerruntime"
"github.com/platform-mesh/golang-commons/controller/lifecycle/multicluster"
"github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine"
"github.com/platform-mesh/golang-commons/logger"
)

type Builder struct {
operatorName string
controllerName string
withConditionManagement bool
withSpreadingReconciles bool
withReadOnly bool
subroutines []subroutine.Subroutine
log *logger.Logger
}

func NewBuilder(operatorName, controllerName string, subroutines []subroutine.Subroutine, log *logger.Logger) *Builder {
return &Builder{
operatorName: operatorName,
controllerName: controllerName,
log: log,
withConditionManagement: false,
subroutines: subroutines,
}
}

func (b *Builder) WithConditionManagement() *Builder {
b.withConditionManagement = true
return b
}

func (b *Builder) WithSpreadingReconciles() *Builder {
b.withSpreadingReconciles = true
return b
}

func (b *Builder) WithReadOnly() *Builder {
b.withReadOnly = true
return b
}

func (b *Builder) BuildControllerRuntime(cl client.Client) *controllerruntime.LifecycleManager {
lm := controllerruntime.NewLifecycleManager(b.subroutines, b.operatorName, b.controllerName, cl, b.log)
if b.withConditionManagement {
lm.WithConditionManagement()
}
if b.withSpreadingReconciles {
lm.WithSpreadingReconciles()
}
if b.withReadOnly {
lm.WithReadOnly()
}
return lm
}

func (b *Builder) BuildMultiCluster(mgr mcmanager.Manager) *multicluster.LifecycleManager {
lm := multicluster.NewLifecycleManager(b.subroutines, b.operatorName, b.controllerName, mgr, b.log)
if b.withConditionManagement {
lm.WithConditionManagement()
}
if b.withSpreadingReconciles {
lm.WithSpreadingReconciles()
}
if b.withReadOnly {
lm.WithReadOnly()
}
return lm
}
110 changes: 110 additions & 0 deletions controller/lifecycle/builder/builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package builder

import (
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/client-go/rest"
mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"

pmtesting "github.com/platform-mesh/golang-commons/controller/testSupport"
"github.com/platform-mesh/golang-commons/logger"
)

func TestNewBuilder_Defaults(t *testing.T) {
log := &logger.Logger{}
b := NewBuilder("op", "ctrl", nil, log)
if b.operatorName != "op" {
t.Errorf("expected operatorName 'op', got %s", b.operatorName)
}
if b.controllerName != "ctrl" {
t.Errorf("expected controllerName 'ctrl', got %s", b.controllerName)
}
if b.withConditionManagement {
t.Error("expected withConditionManagement to be false")
}
if b.withSpreadingReconciles {
t.Error("expected withSpreadingReconciles to be false")
}
if b.withReadOnly {
t.Error("expected withReadOnly to be false")
}
if b.log != log {
t.Error("expected log to be set")
}
}

func TestBuilder_WithConditionManagement(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{})
b.WithConditionManagement()
if !b.withConditionManagement {
t.Error("WithConditionManagement should set withConditionManagement to true")
}
}

func TestBuilder_WithSpreadingReconciles(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{})
b.WithSpreadingReconciles()
if !b.withSpreadingReconciles {
t.Error("WithSpreadingReconciles should set withSpreadingReconciles to true")
}
}

func TestBuilder_WithReadOnly(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{})
b.WithReadOnly()
if !b.withReadOnly {
t.Error("WithReadOnly should set withReadOnly to true")
}
}

func TestControllerRuntimeBuilder(t *testing.T) {
t.Run("Minimal setup", func(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{})
fakeClient := pmtesting.CreateFakeClient(t, &pmtesting.TestApiObject{})
lm := b.BuildControllerRuntime(fakeClient)
assert.NotNil(t, lm)
})
t.Run("All Options", func(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{}).WithConditionManagement().WithSpreadingReconciles()
fakeClient := pmtesting.CreateFakeClient(t, &pmtesting.TestApiObject{})
lm := b.BuildControllerRuntime(fakeClient)
assert.NotNil(t, lm)
})
t.Run("ReadOnly", func(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{}).WithReadOnly()
fakeClient := pmtesting.CreateFakeClient(t, &pmtesting.TestApiObject{})
lm := b.BuildControllerRuntime(fakeClient)
assert.NotNil(t, lm)
})
}

func TestMulticontrollerRuntimeBuilder(t *testing.T) {
t.Run("Minimal setup", func(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{})
cfg := &rest.Config{}
provider := pmtesting.NewFakeProvider(cfg)
mgr, err := mcmanager.New(cfg, provider, mcmanager.Options{})
assert.NoError(t, err)
lm := b.BuildMultiCluster(mgr)
assert.NotNil(t, lm)
})
t.Run("All Options", func(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{}).WithConditionManagement().WithSpreadingReconciles()
cfg := &rest.Config{}
provider := pmtesting.NewFakeProvider(cfg)
mgr, err := mcmanager.New(cfg, provider, mcmanager.Options{})
assert.NoError(t, err)
lm := b.BuildMultiCluster(mgr)
assert.NotNil(t, lm)
})
t.Run("ReadOnly", func(t *testing.T) {
b := NewBuilder("op", "ctrl", nil, &logger.Logger{}).WithReadOnly()
cfg := &rest.Config{}
provider := pmtesting.NewFakeProvider(cfg)
mgr, err := mcmanager.New(cfg, provider, mcmanager.Options{})
assert.NoError(t, err)
lm := b.BuildMultiCluster(mgr)
assert.NotNil(t, lm)
})
}
22 changes: 0 additions & 22 deletions controller/lifecycle/conditions/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"

"github.com/platform-mesh/golang-commons/controller/lifecycle/api"
"github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject"
"github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine"
"github.com/platform-mesh/golang-commons/logger"
"github.com/platform-mesh/golang-commons/sentry"
)

const (
Expand Down Expand Up @@ -118,22 +115,3 @@ func (c *ConditionManager) SetSubroutineCondition(conditions *[]metav1.Condition
}
return changed
}

func (c *ConditionManager) ToRuntimeObjectConditionsInterface(instance runtimeobject.RuntimeObject, log *logger.Logger) (api.RuntimeObjectConditions, error) {
if obj, ok := instance.(api.RuntimeObjectConditions); ok {
return obj, nil
}
err := fmt.Errorf("ManageConditions is enabled, but instance does not implement RuntimeObjectConditions interface. This is a programming error")
log.Error().Err(err).Msg("instance does not implement RuntimeObjectConditions interface")
sentry.CaptureError(err, nil)
return nil, err
}

func (c *ConditionManager) MustToRuntimeObjectConditionsInterface(instance runtimeobject.RuntimeObject, log *logger.Logger) api.RuntimeObjectConditions {
obj, err := c.ToRuntimeObjectConditionsInterface(instance, log)
if err == nil {
return obj
}
log.Panic().Err(err).Msg("instance does not implement RuntimeObjectConditions interface")
return nil
}
41 changes: 0 additions & 41 deletions controller/lifecycle/conditions/conditions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,44 +240,3 @@ func TestSubroutineCondition(t *testing.T) {
assert.Equal(t, metav1.ConditionFalse, condition[0].Status)
})
}

// Dummy types for testing interface conversion

func TestToRuntimeObjectConditionsInterface(t *testing.T) {
log, err := logger.New(logger.DefaultConfig())
require.NoError(t, err)
cm := NewConditionManager()

t.Run("Implements interface", func(t *testing.T) {
obj := pmtesting.DummyRuntimeObjectWithConditions{}
res, err := cm.ToRuntimeObjectConditionsInterface(obj, log)
assert.NoError(t, err)
assert.NotNil(t, res)
})

t.Run("Does not implement interface", func(t *testing.T) {
obj := pmtesting.DummyRuntimeObject{}
res, err := cm.ToRuntimeObjectConditionsInterface(obj, log)
assert.Error(t, err)
assert.Nil(t, res)
})
}

func TestMustToRuntimeObjectConditionsInterface(t *testing.T) {
log, err := logger.New(logger.DefaultConfig())
require.NoError(t, err)
cm := NewConditionManager()

t.Run("Implements interface", func(t *testing.T) {
obj := pmtesting.DummyRuntimeObjectWithConditions{}
res := cm.MustToRuntimeObjectConditionsInterface(obj, log)
assert.NotNil(t, res)
})

t.Run("Does not implement interface panics", func(t *testing.T) {
obj := pmtesting.DummyRuntimeObject{}
assert.Panics(t, func() {
cm.MustToRuntimeObjectConditionsInterface(obj, log)
})
})
}
28 changes: 4 additions & 24 deletions controller/lifecycle/controllerruntime/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type LifecycleManager struct {
prepareContextFunc api.PrepareContextFunc
}

func NewLifecycleManager(log *logger.Logger, operatorName string, controllerName string, client client.Client, subroutines []subroutine.Subroutine) *LifecycleManager {
func NewLifecycleManager(subroutines []subroutine.Subroutine, operatorName string, controllerName string, client client.Client, log *logger.Logger) *LifecycleManager {
log = log.MustChildLoggerWithAttributes("operator", operatorName, "controller", controllerName)
return &LifecycleManager{
log: log,
Expand Down Expand Up @@ -63,37 +63,18 @@ func (l *LifecycleManager) ConditionsManager() api.ConditionManager {
}
return l.conditionsManager
}

func (l *LifecycleManager) Spreader() api.SpreadManager {
// it is important to return nil unsted of a nil pointer to the interface to avoid misbehaving nil checks
// it is important to return nil instead of a nil pointer to the interface to avoid misbehaving nil checks
if l.spreader == nil {
return nil
}
return l.spreader
}

func (l *LifecycleManager) Reconcile(ctx context.Context, req ctrl.Request, instance runtimeobject.RuntimeObject) (ctrl.Result, error) {
return lifecycle.Reconcile(ctx, req, instance, l.client, l)
return lifecycle.Reconcile(ctx, req.NamespacedName, instance, l.client, l)
}

func (l *LifecycleManager) validateInterfaces(instance runtimeobject.RuntimeObject, log *logger.Logger) error {
if l.Spreader() != nil {
_, err := l.Spreader().ToRuntimeObjectSpreadReconcileStatusInterface(instance, log)
if err != nil {
return err
}
}
if l.ConditionsManager() != nil {
_, err := l.ConditionsManager().ToRuntimeObjectConditionsInterface(instance, log)
if err != nil {
return err
}
}
return nil
}

func (l *LifecycleManager) SetupWithManagerBuilder(mgr ctrl.Manager, maxReconciles int, reconcilerName string, instance runtimeobject.RuntimeObject, debugLabelValue string, log *logger.Logger, eventPredicates ...predicate.Predicate) (*builder.Builder, error) {
if err := l.validateInterfaces(instance, log); err != nil {
if err := lifecycle.ValidateInterfaces(instance, log, l); err != nil {
return nil, err
}

Expand All @@ -108,7 +89,6 @@ func (l *LifecycleManager) SetupWithManagerBuilder(mgr ctrl.Manager, maxReconcil
WithOptions(controller.Options{MaxConcurrentReconciles: maxReconciles}).
WithEventFilter(predicate.And(eventPredicates...)), nil
}

func (l *LifecycleManager) SetupWithManager(mgr ctrl.Manager, maxReconciles int, reconcilerName string, instance runtimeobject.RuntimeObject, debugLabelValue string, r reconcile.Reconciler, log *logger.Logger, eventPredicates ...predicate.Predicate) error {
b, err := l.SetupWithManagerBuilder(mgr, maxReconciles, reconcilerName, instance, debugLabelValue, log, eventPredicates...)
if err != nil {
Expand Down
Loading