Skip to content

Commit 356a05b

Browse files
authored
chore(api): add app controller (#2572)
* chore: breakout app controller * chore: compose controllers with app controller * chore: breakout integration tests * chore(test): fix app test suite * chore: remove unused field
1 parent 3640c62 commit 356a05b

30 files changed

+1307
-2158
lines changed

api/controllers/app/app_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package app
2+
3+
import (
4+
"testing"
5+
6+
appinstall "github.com/replicatedhq/embedded-cluster/api/controllers/app/install"
7+
kubernetesinstall "github.com/replicatedhq/embedded-cluster/api/controllers/kubernetes/install"
8+
linuxinstall "github.com/replicatedhq/embedded-cluster/api/controllers/linux/install"
9+
"github.com/replicatedhq/embedded-cluster/api/internal/statemachine"
10+
"github.com/stretchr/testify/suite"
11+
)
12+
13+
func TestAppInstallControllerSuite(t *testing.T) {
14+
installTypes := []struct {
15+
name string
16+
installType string
17+
createStateMachine func(initialState statemachine.State) statemachine.Interface
18+
}{
19+
{
20+
name: "linux install",
21+
installType: "linux",
22+
createStateMachine: func(initialState statemachine.State) statemachine.Interface {
23+
return linuxinstall.NewStateMachine(linuxinstall.WithCurrentState(initialState))
24+
},
25+
},
26+
{
27+
name: "kubernetes install",
28+
installType: "kubernetes",
29+
createStateMachine: func(initialState statemachine.State) statemachine.Interface {
30+
return kubernetesinstall.NewStateMachine(kubernetesinstall.WithCurrentState(initialState))
31+
},
32+
},
33+
}
34+
35+
for _, tt := range installTypes {
36+
t.Run(tt.name, func(t *testing.T) {
37+
testSuite := &appinstall.AppInstallControllerTestSuite{
38+
InstallType: tt.installType,
39+
CreateStateMachine: tt.createStateMachine,
40+
}
41+
suite.Run(t, testSuite)
42+
})
43+
}
44+
}

api/controllers/kubernetes/install/app.go renamed to api/controllers/app/install/config.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"runtime/debug"
77

8+
states "github.com/replicatedhq/embedded-cluster/api/internal/states/install"
89
"github.com/replicatedhq/embedded-cluster/api/types"
910
)
1011

@@ -15,12 +16,12 @@ func (c *InstallController) PatchAppConfigValues(ctx context.Context, values typ
1516
}
1617
defer lock.Release()
1718

18-
err = c.stateMachine.ValidateTransition(lock, StateApplicationConfiguring, StateApplicationConfigured)
19+
err = c.stateMachine.ValidateTransition(lock, states.StateApplicationConfiguring, states.StateApplicationConfigured)
1920
if err != nil {
2021
return types.NewConflictError(err)
2122
}
2223

23-
err = c.stateMachine.Transition(lock, StateApplicationConfiguring)
24+
err = c.stateMachine.Transition(lock, states.StateApplicationConfiguring)
2425
if err != nil {
2526
return fmt.Errorf("failed to transition states: %w", err)
2627
}
@@ -31,7 +32,7 @@ func (c *InstallController) PatchAppConfigValues(ctx context.Context, values typ
3132
}
3233

3334
if finalErr != nil {
34-
if err := c.stateMachine.Transition(lock, StateApplicationConfigurationFailed); err != nil {
35+
if err := c.stateMachine.Transition(lock, states.StateApplicationConfigurationFailed); err != nil {
3536
c.logger.Errorf("failed to transition states: %w", err)
3637
}
3738
}
@@ -47,18 +48,18 @@ func (c *InstallController) PatchAppConfigValues(ctx context.Context, values typ
4748
return fmt.Errorf("patch app config values: %w", err)
4849
}
4950

50-
err = c.stateMachine.Transition(lock, StateApplicationConfigured)
51+
err = c.stateMachine.Transition(lock, states.StateApplicationConfigured)
5152
if err != nil {
5253
return fmt.Errorf("failed to transition states: %w", err)
5354
}
5455

5556
return nil
5657
}
5758

58-
func (c *InstallController) GetAppConfigValues(ctx context.Context) (types.AppConfigValues, error) {
59-
return c.appConfigManager.GetConfigValues()
60-
}
61-
6259
func (c *InstallController) TemplateAppConfig(ctx context.Context, values types.AppConfigValues, maskPasswords bool) (types.AppConfig, error) {
6360
return c.appConfigManager.TemplateConfig(values, maskPasswords)
6461
}
62+
63+
func (c *InstallController) GetAppConfigValues(ctx context.Context) (types.AppConfigValues, error) {
64+
return c.appConfigManager.GetConfigValues()
65+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package install
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
appconfig "github.com/replicatedhq/embedded-cluster/api/internal/managers/app/config"
8+
"github.com/replicatedhq/embedded-cluster/api/internal/statemachine"
9+
"github.com/replicatedhq/embedded-cluster/api/pkg/logger"
10+
"github.com/replicatedhq/embedded-cluster/api/types"
11+
"github.com/sirupsen/logrus"
12+
)
13+
14+
type Controller interface {
15+
TemplateAppConfig(ctx context.Context, values types.AppConfigValues, maskPasswords bool) (types.AppConfig, error)
16+
PatchAppConfigValues(ctx context.Context, values types.AppConfigValues) error
17+
GetAppConfigValues(ctx context.Context) (types.AppConfigValues, error)
18+
}
19+
20+
var _ Controller = (*InstallController)(nil)
21+
22+
type InstallController struct {
23+
appConfigManager appconfig.AppConfigManager
24+
stateMachine statemachine.Interface
25+
logger logrus.FieldLogger
26+
}
27+
28+
type InstallControllerOption func(*InstallController)
29+
30+
func WithLogger(logger logrus.FieldLogger) InstallControllerOption {
31+
return func(c *InstallController) {
32+
c.logger = logger
33+
}
34+
}
35+
36+
func WithAppConfigManager(appConfigManager appconfig.AppConfigManager) InstallControllerOption {
37+
return func(c *InstallController) {
38+
c.appConfigManager = appConfigManager
39+
}
40+
}
41+
42+
func WithStateMachine(stateMachine statemachine.Interface) InstallControllerOption {
43+
return func(c *InstallController) {
44+
c.stateMachine = stateMachine
45+
}
46+
}
47+
48+
func NewInstallController(opts ...InstallControllerOption) (*InstallController, error) {
49+
controller := &InstallController{
50+
logger: logger.NewDiscardLogger(),
51+
}
52+
53+
for _, opt := range opts {
54+
opt(controller)
55+
}
56+
57+
if err := controller.validateInit(); err != nil {
58+
return nil, err
59+
}
60+
61+
return controller, nil
62+
}
63+
64+
func (c *InstallController) validateInit() error {
65+
if c.appConfigManager == nil {
66+
return errors.New("appConfigManager is required for App Install Controller")
67+
}
68+
if c.stateMachine == nil {
69+
return errors.New("stateMachine is required for App Install Controller")
70+
}
71+
return nil
72+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package install
2+
3+
import (
4+
"context"
5+
6+
"github.com/replicatedhq/embedded-cluster/api/types"
7+
"github.com/stretchr/testify/mock"
8+
)
9+
10+
var _ Controller = (*MockController)(nil)
11+
12+
// MockController is a mock implementation of the Controller interface
13+
type MockController struct {
14+
mock.Mock
15+
}
16+
17+
// TemplateAppConfig mocks the TemplateAppConfig method
18+
func (m *MockController) TemplateAppConfig(ctx context.Context, values types.AppConfigValues, maskPasswords bool) (types.AppConfig, error) {
19+
args := m.Called(ctx, values, maskPasswords)
20+
return args.Get(0).(types.AppConfig), args.Error(1)
21+
}
22+
23+
// PatchAppConfigValues mocks the PatchAppConfigValues method
24+
func (m *MockController) PatchAppConfigValues(ctx context.Context, values types.AppConfigValues) error {
25+
args := m.Called(ctx, values)
26+
return args.Error(0)
27+
}
28+
29+
// GetAppConfigValues mocks the GetAppConfigValues method
30+
func (m *MockController) GetAppConfigValues(ctx context.Context) (types.AppConfigValues, error) {
31+
args := m.Called(ctx)
32+
if args.Get(0) == nil {
33+
return nil, args.Error(1)
34+
}
35+
return args.Get(0).(types.AppConfigValues), args.Error(1)
36+
}

api/controllers/kubernetes/install/app_test.go renamed to api/controllers/app/install/test_suite.go

Lines changed: 34 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,53 +7,37 @@ import (
77

88
appconfig "github.com/replicatedhq/embedded-cluster/api/internal/managers/app/config"
99
"github.com/replicatedhq/embedded-cluster/api/internal/statemachine"
10-
"github.com/replicatedhq/embedded-cluster/api/internal/store"
10+
states "github.com/replicatedhq/embedded-cluster/api/internal/states/install"
1111
"github.com/replicatedhq/embedded-cluster/api/types"
12-
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
13-
"github.com/replicatedhq/kotskinds/multitype"
1412
"github.com/stretchr/testify/assert"
1513
"github.com/stretchr/testify/mock"
1614
"github.com/stretchr/testify/require"
15+
"github.com/stretchr/testify/suite"
1716
)
1817

19-
func TestInstallController_PatchAppConfigValues(t *testing.T) {
20-
// Create an app config for testing
21-
appConfig := kotsv1beta1.Config{
22-
Spec: kotsv1beta1.ConfigSpec{
23-
Groups: []kotsv1beta1.ConfigGroup{
24-
{
25-
Name: "test-group",
26-
Title: "Test Group",
27-
Items: []kotsv1beta1.ConfigItem{
28-
{
29-
Name: "test-item",
30-
Type: "text",
31-
Title: "Test Item",
32-
Default: multitype.FromString("default"),
33-
Value: multitype.FromString("value"),
34-
},
35-
},
36-
},
37-
},
38-
},
39-
}
18+
type AppInstallControllerTestSuite struct {
19+
suite.Suite
20+
InstallType string
21+
CreateStateMachine func(initialState statemachine.State) statemachine.Interface
22+
}
4023

24+
func (s *AppInstallControllerTestSuite) TestPatchAppConfigValues() {
4125
tests := []struct {
4226
name string
4327
values types.AppConfigValues
4428
currentState statemachine.State
4529
expectedState statemachine.State
46-
setupMocks func(*appconfig.MockAppConfigManager, *store.MockStore)
30+
setupMocks func(*appconfig.MockAppConfigManager)
4731
expectedErr bool
4832
}{
4933
{
5034
name: "successful set app config values",
5135
values: types.AppConfigValues{
5236
"test-item": types.AppConfigValue{Value: "new-item"},
5337
},
54-
currentState: StateNew,
55-
expectedState: StateApplicationConfigured,
56-
setupMocks: func(am *appconfig.MockAppConfigManager, st *store.MockStore) {
38+
currentState: states.StateNew,
39+
expectedState: states.StateApplicationConfigured,
40+
setupMocks: func(am *appconfig.MockAppConfigManager) {
5741
mock.InOrder(
5842
am.On("ValidateConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "new-item"}}).Return(nil),
5943
am.On("PatchConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "new-item"}}).Return(nil),
@@ -66,9 +50,9 @@ func TestInstallController_PatchAppConfigValues(t *testing.T) {
6650
values: types.AppConfigValues{
6751
"test-item": types.AppConfigValue{Value: "new-item"},
6852
},
69-
currentState: StateApplicationConfigurationFailed,
70-
expectedState: StateApplicationConfigured,
71-
setupMocks: func(am *appconfig.MockAppConfigManager, st *store.MockStore) {
53+
currentState: states.StateApplicationConfigurationFailed,
54+
expectedState: states.StateApplicationConfigured,
55+
setupMocks: func(am *appconfig.MockAppConfigManager) {
7256
mock.InOrder(
7357
am.On("ValidateConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "new-item"}}).Return(nil),
7458
am.On("PatchConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "new-item"}}).Return(nil),
@@ -81,9 +65,9 @@ func TestInstallController_PatchAppConfigValues(t *testing.T) {
8165
values: types.AppConfigValues{
8266
"test-item": types.AppConfigValue{Value: "new-item"},
8367
},
84-
currentState: StateApplicationConfigured,
85-
expectedState: StateApplicationConfigured,
86-
setupMocks: func(am *appconfig.MockAppConfigManager, st *store.MockStore) {
68+
currentState: states.StateApplicationConfigured,
69+
expectedState: states.StateApplicationConfigured,
70+
setupMocks: func(am *appconfig.MockAppConfigManager) {
8771
mock.InOrder(
8872
am.On("ValidateConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "new-item"}}).Return(nil),
8973
am.On("PatchConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "new-item"}}).Return(nil),
@@ -96,9 +80,9 @@ func TestInstallController_PatchAppConfigValues(t *testing.T) {
9680
values: types.AppConfigValues{
9781
"test-item": types.AppConfigValue{Value: "invalid-value"},
9882
},
99-
currentState: StateNew,
100-
expectedState: StateApplicationConfigurationFailed,
101-
setupMocks: func(am *appconfig.MockAppConfigManager, st *store.MockStore) {
83+
currentState: states.StateNew,
84+
expectedState: states.StateApplicationConfigurationFailed,
85+
setupMocks: func(am *appconfig.MockAppConfigManager) {
10286
mock.InOrder(
10387
am.On("ValidateConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "invalid-value"}}).Return(errors.New("validation error")),
10488
)
@@ -110,9 +94,9 @@ func TestInstallController_PatchAppConfigValues(t *testing.T) {
11094
values: types.AppConfigValues{
11195
"test-item": types.AppConfigValue{Value: "new-item"},
11296
},
113-
currentState: StateNew,
114-
expectedState: StateApplicationConfigurationFailed,
115-
setupMocks: func(am *appconfig.MockAppConfigManager, st *store.MockStore) {
97+
currentState: states.StateNew,
98+
expectedState: states.StateApplicationConfigurationFailed,
99+
setupMocks: func(am *appconfig.MockAppConfigManager) {
116100
mock.InOrder(
117101
am.On("ValidateConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "new-item"}}).Return(nil),
118102
am.On("PatchConfigValues", types.AppConfigValues{"test-item": types.AppConfigValue{Value: "new-item"}}).Return(errors.New("set config error")),
@@ -125,31 +109,26 @@ func TestInstallController_PatchAppConfigValues(t *testing.T) {
125109
values: types.AppConfigValues{
126110
"test-item": types.AppConfigValue{Value: "new-item"},
127111
},
128-
currentState: StateInfrastructureInstalling,
129-
expectedState: StateInfrastructureInstalling,
130-
setupMocks: func(am *appconfig.MockAppConfigManager, st *store.MockStore) {
112+
currentState: states.StateInfrastructureInstalling,
113+
expectedState: states.StateInfrastructureInstalling,
114+
setupMocks: func(am *appconfig.MockAppConfigManager) {
131115
},
132116
expectedErr: true,
133117
},
134118
}
135119

136120
for _, tt := range tests {
137-
t.Run(tt.name, func(t *testing.T) {
138-
sm := NewStateMachine(WithCurrentState(tt.currentState))
139-
140-
mockAppConfigManager := &appconfig.MockAppConfigManager{}
141-
mockStore := &store.MockStore{}
142-
143-
tt.setupMocks(mockAppConfigManager, mockStore)
121+
s.T().Run(tt.name, func(t *testing.T) {
144122

123+
manager := &appconfig.MockAppConfigManager{}
124+
sm := s.CreateStateMachine(tt.currentState)
145125
controller, err := NewInstallController(
146126
WithStateMachine(sm),
147-
WithAppConfigManager(mockAppConfigManager),
148-
WithReleaseData(getTestReleaseData(&appConfig)),
149-
WithStore(mockStore),
127+
WithAppConfigManager(manager),
150128
)
151-
require.NoError(t, err)
129+
require.NoError(t, err, "failed to create install controller")
152130

131+
tt.setupMocks(manager)
153132
err = controller.PatchAppConfigValues(t.Context(), tt.values)
154133

155134
if tt.expectedErr {
@@ -162,12 +141,8 @@ func TestInstallController_PatchAppConfigValues(t *testing.T) {
162141
return sm.CurrentState() == tt.expectedState
163142
}, time.Second, 100*time.Millisecond, "state should be %s but is %s", tt.expectedState, sm.CurrentState())
164143
assert.False(t, sm.IsLockAcquired(), "state machine should not be locked after setting app config values")
144+
manager.AssertExpectations(s.T())
165145

166-
mockAppConfigManager.AssertExpectations(t)
167-
mockStore.LinuxInfraMockStore.AssertExpectations(t)
168-
mockStore.LinuxInstallationMockStore.AssertExpectations(t)
169-
mockStore.LinuxPreflightMockStore.AssertExpectations(t)
170-
mockStore.AppConfigMockStore.AssertExpectations(t)
171146
})
172147
}
173148
}

0 commit comments

Comments
 (0)