Skip to content

Commit e9fc6a3

Browse files
authored
Update the installation page to show app preflight checks (#2636)
* Update the installation page to show app preflight checks and installation
1 parent 9536114 commit e9fc6a3

File tree

86 files changed

+4947
-663
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+4947
-663
lines changed

api/controllers/app/install/controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Controller interface {
2525
GetAppPreflightStatus(ctx context.Context) (types.Status, error)
2626
GetAppPreflightOutput(ctx context.Context) (*types.PreflightsOutput, error)
2727
GetAppPreflightTitles(ctx context.Context) ([]string, error)
28-
InstallApp(ctx context.Context) error
28+
InstallApp(ctx context.Context, ignoreAppPreflights bool) error
2929
GetAppInstallStatus(ctx context.Context) (types.AppInstall, error)
3030
}
3131

@@ -181,6 +181,7 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
181181
appinstallmanager.WithReleaseData(controller.releaseData),
182182
appinstallmanager.WithClusterID(controller.clusterID),
183183
appinstallmanager.WithAirgapBundle(controller.airgapBundle),
184+
appinstallmanager.WithAppInstallStore(controller.store.AppInstallStore()),
184185
)
185186
if err != nil {
186187
return nil, fmt.Errorf("create app install manager: %w", err)

api/controllers/app/install/controller_mock.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ func (m *MockController) GetAppPreflightTitles(ctx context.Context) ([]string, e
6969
}
7070

7171
// InstallApp mocks the InstallApp method
72-
func (m *MockController) InstallApp(ctx context.Context) error {
73-
args := m.Called(ctx)
72+
func (m *MockController) InstallApp(ctx context.Context, ignoreAppPreflights bool) error {
73+
args := m.Called(ctx, ignoreAppPreflights)
7474
return args.Error(0)
7575
}
7676

api/controllers/app/install/install.go

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ package install
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"runtime/debug"
78

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

13+
var (
14+
ErrAppPreflightChecksFailed = errors.New("app preflight checks failed")
15+
)
16+
1217
// InstallApp triggers app installation with proper state transitions and panic handling
13-
func (c *InstallController) InstallApp(ctx context.Context) (finalErr error) {
18+
func (c *InstallController) InstallApp(ctx context.Context, ignoreAppPreflights bool) (finalErr error) {
1419
lock, err := c.stateMachine.AcquireLock()
1520
if err != nil {
1621
return types.NewConflictError(err)
@@ -25,6 +30,18 @@ func (c *InstallController) InstallApp(ctx context.Context) (finalErr error) {
2530
}
2631
}()
2732

33+
// Check if app preflights have failed and if we should ignore them
34+
if c.stateMachine.CurrentState() == states.StateAppPreflightsFailed {
35+
allowIgnoreAppPreflights := true // TODO: implement once we check for strict app preflights
36+
if !ignoreAppPreflights || !allowIgnoreAppPreflights {
37+
return types.NewBadRequestError(ErrAppPreflightChecksFailed)
38+
}
39+
err = c.stateMachine.Transition(lock, states.StateAppPreflightsFailedBypassed)
40+
if err != nil {
41+
return fmt.Errorf("failed to transition states: %w", err)
42+
}
43+
}
44+
2845
if err := c.stateMachine.ValidateTransition(lock, states.StateAppInstalling); err != nil {
2946
return types.NewConflictError(err)
3047
}
@@ -48,21 +65,18 @@ func (c *InstallController) InstallApp(ctx context.Context) (finalErr error) {
4865

4966
defer func() {
5067
if r := recover(); r != nil {
51-
finalErr = fmt.Errorf("panic installing app: %v: %s", r, string(debug.Stack()))
68+
finalErr = fmt.Errorf("panic: %v: %s", r, string(debug.Stack()))
5269
}
53-
// Handle errors from app installation
5470
if finalErr != nil {
5571
c.logger.Error(finalErr)
5672

5773
if err := c.stateMachine.Transition(lock, states.StateAppInstallFailed); err != nil {
5874
c.logger.Errorf("failed to transition states: %w", err)
5975
}
60-
return
61-
}
62-
63-
// Transition to succeeded state on successful app installation
64-
if err := c.stateMachine.Transition(lock, states.StateSucceeded); err != nil {
65-
c.logger.Errorf("failed to transition states: %w", err)
76+
} else {
77+
if err := c.stateMachine.Transition(lock, states.StateSucceeded); err != nil {
78+
c.logger.Errorf("failed to transition states: %w", err)
79+
}
6680
}
6781
}()
6882

@@ -78,23 +92,6 @@ func (c *InstallController) InstallApp(ctx context.Context) (finalErr error) {
7892
return nil
7993
}
8094

81-
// TODO: remove this once we have endpoints to trigger app installation and report status
82-
// and the app installation is decoupled from the infra installation
83-
func (c *InstallController) InstallAppNoState(ctx context.Context) error {
84-
// Get config values for app installation
85-
configValues, err := c.appConfigManager.GetKotsadmConfigValues()
86-
if err != nil {
87-
return fmt.Errorf("get kotsadm config values for app install: %w", err)
88-
}
89-
90-
// Install the app using the app install manager
91-
if err := c.appInstallManager.Install(ctx, configValues); err != nil {
92-
return fmt.Errorf("install app: %w", err)
93-
}
94-
95-
return nil
96-
}
97-
9895
func (c *InstallController) GetAppInstallStatus(ctx context.Context) (types.AppInstall, error) {
9996
return c.appInstallManager.GetStatus()
10097
}

api/controllers/app/install/test_suite.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ func (s *AppInstallControllerTestSuite) TestRunAppPreflights() {
185185
opts: RunAppPreflightOptions{
186186
PreflightBinaryPath: "/usr/bin/preflight",
187187
},
188-
currentState: states.StateSucceeded,
188+
currentState: states.StateInfrastructureInstalled,
189189
expectedState: states.StateAppPreflightsSucceeded,
190190
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
191191
mock.InOrder(
@@ -263,7 +263,7 @@ func (s *AppInstallControllerTestSuite) TestRunAppPreflights() {
263263
opts: RunAppPreflightOptions{
264264
PreflightBinaryPath: "/usr/bin/preflight",
265265
},
266-
currentState: states.StateSucceeded,
266+
currentState: states.StateInfrastructureInstalled,
267267
expectedState: states.StateAppPreflightsFailed,
268268
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
269269
mock.InOrder(
@@ -289,7 +289,7 @@ func (s *AppInstallControllerTestSuite) TestRunAppPreflights() {
289289
opts: RunAppPreflightOptions{
290290
PreflightBinaryPath: "/usr/bin/preflight",
291291
},
292-
currentState: states.StateSucceeded,
292+
currentState: states.StateInfrastructureInstalled,
293293
expectedState: states.StateAppPreflightsExecutionFailed,
294294
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
295295
mock.InOrder(
@@ -308,7 +308,7 @@ func (s *AppInstallControllerTestSuite) TestRunAppPreflights() {
308308
opts: RunAppPreflightOptions{
309309
PreflightBinaryPath: "/usr/bin/preflight",
310310
},
311-
currentState: states.StateSucceeded,
311+
currentState: states.StateInfrastructureInstalled,
312312
expectedState: states.StateAppPreflightsSucceeded,
313313
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
314314
mock.InOrder(
@@ -327,7 +327,7 @@ func (s *AppInstallControllerTestSuite) TestRunAppPreflights() {
327327
opts: RunAppPreflightOptions{
328328
PreflightBinaryPath: "/usr/bin/preflight",
329329
},
330-
currentState: states.StateSucceeded,
330+
currentState: states.StateInfrastructureInstalled,
331331
expectedState: states.StateAppPreflightsSucceeded,
332332
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
333333
mock.InOrder(
@@ -353,8 +353,8 @@ func (s *AppInstallControllerTestSuite) TestRunAppPreflights() {
353353
opts: RunAppPreflightOptions{
354354
PreflightBinaryPath: "/usr/bin/preflight",
355355
},
356-
currentState: states.StateSucceeded,
357-
expectedState: states.StateSucceeded,
356+
currentState: states.StateInfrastructureInstalled,
357+
expectedState: states.StateInfrastructureInstalled,
358358
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
359359
mock.InOrder(
360360
acm.On("GetConfigValues").Return(types.AppConfigValues{"test-item": types.AppConfigValue{Value: "test-value"}}, nil),
@@ -368,7 +368,7 @@ func (s *AppInstallControllerTestSuite) TestRunAppPreflights() {
368368
opts: RunAppPreflightOptions{
369369
PreflightBinaryPath: "/usr/bin/preflight",
370370
},
371-
currentState: states.StateSucceeded,
371+
currentState: states.StateInfrastructureInstalled,
372372
expectedState: states.StateAppPreflightsExecutionFailed,
373373
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
374374
mock.InOrder(
@@ -501,11 +501,12 @@ func (s *AppInstallControllerTestSuite) TestGetAppInstallStatus() {
501501

502502
func (s *AppInstallControllerTestSuite) TestInstallApp() {
503503
tests := []struct {
504-
name string
505-
currentState statemachine.State
506-
expectedState statemachine.State
507-
setupMocks func(*appconfig.MockAppConfigManager, *appinstallmanager.MockAppInstallManager)
508-
expectedErr bool
504+
name string
505+
ignoreAppPreflights bool
506+
currentState statemachine.State
507+
expectedState statemachine.State
508+
setupMocks func(*appconfig.MockAppConfigManager, *appinstallmanager.MockAppInstallManager)
509+
expectedErr bool
509510
}{
510511
{
511512
name: "invalid state transition from succeeded state",
@@ -574,6 +575,37 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
574575
},
575576
expectedErr: true,
576577
},
578+
{
579+
name: "successful app installation with failed preflights - ignored",
580+
ignoreAppPreflights: true,
581+
currentState: states.StateAppPreflightsFailed,
582+
expectedState: states.StateSucceeded,
583+
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
584+
mock.InOrder(
585+
acm.On("GetKotsadmConfigValues").Return(kotsv1beta1.ConfigValues{
586+
Spec: kotsv1beta1.ConfigValuesSpec{
587+
Values: map[string]kotsv1beta1.ConfigValue{
588+
"test-key": {Value: "test-value"},
589+
},
590+
},
591+
}, nil),
592+
aim.On("Install", mock.Anything, mock.MatchedBy(func(cv kotsv1beta1.ConfigValues) bool {
593+
return cv.Spec.Values["test-key"].Value == "test-value"
594+
})).Return(nil),
595+
)
596+
},
597+
expectedErr: false,
598+
},
599+
{
600+
name: "failed app installation with failed preflights - not ignored",
601+
ignoreAppPreflights: false,
602+
currentState: states.StateAppPreflightsFailed,
603+
expectedState: states.StateAppPreflightsFailed,
604+
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
605+
// No mocks needed as method should return early with error
606+
},
607+
expectedErr: true,
608+
},
577609
}
578610

579611
for _, tt := range tests {
@@ -596,7 +628,7 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
596628
require.NoError(t, err, "failed to create install controller")
597629

598630
tt.setupMocks(appConfigManager, appInstallManager)
599-
err = controller.InstallApp(t.Context())
631+
err = controller.InstallApp(t.Context(), tt.ignoreAppPreflights)
600632

601633
if tt.expectedErr {
602634
assert.Error(t, err)

api/controllers/kubernetes/install/controller_mock.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ func (m *MockController) RunAppPreflights(ctx context.Context, opts appcontrolle
109109
}
110110

111111
// InstallApp mocks the InstallApp method
112-
func (m *MockController) InstallApp(ctx context.Context) error {
113-
args := m.Called(ctx)
112+
func (m *MockController) InstallApp(ctx context.Context, ignoreAppPreflights bool) error {
113+
args := m.Called(ctx, ignoreAppPreflights)
114114
return args.Error(0)
115115
}
116116

api/controllers/kubernetes/install/controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ func TestSetupInfra(t *testing.T) {
296296
{
297297
name: "successful setup",
298298
currentState: states.StateInstallationConfigured,
299-
expectedState: states.StateSucceeded,
299+
expectedState: states.StateInfrastructureInstalled,
300300
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
301301
mock.InOrder(
302302
fm.On("Install", mock.Anything, ki).Return(nil),

api/controllers/kubernetes/install/infra.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ func (c *InstallController) SetupInfra(ctx context.Context) (finalErr error) {
4646
c.logger.Errorf("failed to transition states: %w", err)
4747
}
4848
} else {
49-
// TODO: change to StateInfrastructureInstallSucceeded once app installation is decoupled from infra installation
50-
if err := c.stateMachine.Transition(lock, states.StateSucceeded); err != nil {
49+
if err := c.stateMachine.Transition(lock, states.StateInfrastructureInstalled); err != nil {
5150
c.logger.Errorf("failed to transition states: %w", err)
5251
}
5352
}

api/controllers/kubernetes/install/statemachine.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,18 @@ var validStateTransitions = map[statemachine.State][]statemachine.State{
1414
states.StateInstallationConfiguring: {states.StateInstallationConfigured, states.StateInstallationConfigurationFailed},
1515
states.StateInstallationConfigurationFailed: {states.StateApplicationConfiguring, states.StateInstallationConfiguring},
1616
states.StateInstallationConfigured: {states.StateApplicationConfiguring, states.StateInstallationConfiguring, states.StateInfrastructureInstalling},
17-
states.StateInfrastructureInstalling: {states.StateSucceeded, states.StateInfrastructureInstallFailed},
18-
// TODO: only allow running preflights after infra is installed and before installing the app once the app installation is decoupled from infra installation
19-
states.StateAppPreflightsRunning: {states.StateAppPreflightsSucceeded, states.StateAppPreflightsFailed, states.StateAppPreflightsExecutionFailed},
20-
states.StateAppPreflightsExecutionFailed: {states.StateAppPreflightsRunning},
21-
states.StateAppPreflightsFailed: {states.StateAppPreflightsRunning, states.StateAppPreflightsFailedBypassed},
22-
states.StateAppPreflightsSucceeded: {states.StateAppPreflightsRunning, states.StateAppInstalling},
23-
states.StateAppPreflightsFailedBypassed: {states.StateAppPreflightsRunning, states.StateAppInstalling},
24-
states.StateAppInstalling: {states.StateSucceeded, states.StateAppInstallFailed},
17+
states.StateInfrastructureInstalling: {states.StateInfrastructureInstalled, states.StateInfrastructureInstallFailed},
18+
states.StateInfrastructureInstalled: {states.StateAppPreflightsRunning},
19+
states.StateAppPreflightsRunning: {states.StateAppPreflightsSucceeded, states.StateAppPreflightsFailed, states.StateAppPreflightsExecutionFailed},
20+
states.StateAppPreflightsExecutionFailed: {states.StateAppPreflightsRunning},
21+
states.StateAppPreflightsFailed: {states.StateAppPreflightsRunning, states.StateAppPreflightsFailedBypassed},
22+
states.StateAppPreflightsSucceeded: {states.StateAppPreflightsRunning, states.StateAppInstalling},
23+
states.StateAppPreflightsFailedBypassed: {states.StateAppPreflightsRunning, states.StateAppInstalling},
24+
states.StateAppInstalling: {states.StateSucceeded, states.StateAppInstallFailed},
2525
// final states
2626
states.StateInfrastructureInstallFailed: {},
2727
states.StateAppInstallFailed: {},
28-
// TODO: remove StateAppPreflightsRunning once app installation is decoupled from infra installation
29-
states.StateSucceeded: {states.StateAppPreflightsRunning},
28+
states.StateSucceeded: {},
3029
}
3130

3231
type StateMachineOptions struct {

api/controllers/kubernetes/install/statemachine_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,20 @@ func TestStateMachineTransitions(t *testing.T) {
7171
},
7272
},
7373
{
74-
name: `State "InfrastructureInstalling" can transition to "Succeeded" or "InfrastructureInstallFailed"`,
74+
name: `State "InfrastructureInstalling" can transition to "InfrastructureInstalled" or "InfrastructureInstallFailed"`,
7575
startState: states.StateInfrastructureInstalling,
7676
validTransitions: []statemachine.State{
77-
states.StateSucceeded,
77+
states.StateInfrastructureInstalled,
7878
states.StateInfrastructureInstallFailed,
7979
},
8080
},
81+
{
82+
name: `State "InfrastructureInstalled" can transition to "AppPreflightsRunning"`,
83+
startState: states.StateInfrastructureInstalled,
84+
validTransitions: []statemachine.State{
85+
states.StateAppPreflightsRunning,
86+
},
87+
},
8188
{
8289
name: `State "InfrastructureInstallFailed" can not transition to any other state`,
8390
startState: states.StateInfrastructureInstallFailed,
@@ -136,13 +143,6 @@ func TestStateMachineTransitions(t *testing.T) {
136143
startState: states.StateAppInstallFailed,
137144
validTransitions: []statemachine.State{},
138145
},
139-
{
140-
name: `State "Succeeded" can transition to "AppPreflightsRunning"`,
141-
startState: states.StateSucceeded,
142-
validTransitions: []statemachine.State{
143-
states.StateAppPreflightsRunning,
144-
},
145-
},
146146
}
147147

148148
for _, tt := range tests {
@@ -172,8 +172,7 @@ func TestStateMachineTransitions(t *testing.T) {
172172

173173
func TestIsFinalState(t *testing.T) {
174174
finalStates := []statemachine.State{
175-
// TODO: uncomment once app installation is decoupled from infra installation
176-
// states.StateSucceeded,
175+
states.StateSucceeded,
177176
states.StateInfrastructureInstallFailed,
178177
states.StateAppInstallFailed,
179178
}

api/controllers/linux/install/controller.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,6 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
282282
infra.WithReleaseData(controller.releaseData),
283283
infra.WithEndUserConfig(controller.endUserConfig),
284284
infra.WithClusterID(controller.clusterID),
285-
// TODO: remove this one app installation is decoupled from infra installation
286-
infra.WithAppInstaller(controller.InstallAppNoState),
287285
)
288286
}
289287

0 commit comments

Comments
 (0)