Skip to content

Commit d52e8a9

Browse files
diamonwigginsajp-ioJGAntunes
authored
feat: allow users to bypass host preflights in install wizard (#2335)
* add ignorehostpreflight to api installation config * continue adding ignorehostpreflights flag to API * add integration tests * add modal to confirm skipping of preflights when ignorehostpreflights flag is passed * revert files updated by buildtools * update swagger docs * fix unit tests * restructure unit test * debug issue with unit test * debug issue with unit test * remove test * Update web/src/components/wizard/ValidationStep.tsx Co-authored-by: Alex Parker <7272359+ajp-io@users.noreply.github.com> * store ignorehostpreflight flag value in preflight response instead of installation config * adding preflight validation to be used during infra setup * continue adding preflight validation to infra setup * update web to send request to ignore preflights when we start the installation * add additional validationstep tests * update swagger docs * cleanup unused code * add design doc * simply preflight validatio and move to setupinfra on install controller * consistently use allowignorehostpreflights in api * simplify response for setupinfra * update design doc * revert buildtools files * cleanup * address feedback * address feedback * fix integration test * fix failing test * improve error handling * returning bool for setupinfra no longer necessary * improve setupinfra tests * remove NewHostPreflights function that's not being used * use mock manager with real controller for integration tests * conditional render modal only when needed * remove uneeded comment * Update api/integration/infra_test.go Co-authored-by: João Antunes <joao@replicated.com> * remove modal test * remove unneeded validation step test * consolidate tests * change api error handling to use returned handler from the API * update tests to assert expected error from API * update tests * f * fix tests * fix lint * make property name consistent * revert uneeded changes --------- Co-authored-by: Alex Parker <7272359+ajp-io@users.noreply.github.com> Co-authored-by: João Antunes <joao@replicated.com>
1 parent 725a109 commit d52e8a9

File tree

21 files changed

+1416
-115
lines changed

21 files changed

+1416
-115
lines changed

api/api.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,20 @@ import (
4242
// @externalDocs.description OpenAPI
4343
// @externalDocs.url https://swagger.io/resources/open-api/
4444
type API struct {
45-
authController auth.Controller
46-
consoleController console.Controller
47-
installController install.Controller
48-
rc runtimeconfig.RuntimeConfig
49-
releaseData *release.ReleaseData
50-
tlsConfig types.TLSConfig
51-
license []byte
52-
airgapBundle string
53-
configValues string
54-
endUserConfig *ecv1beta1.Config
55-
logger logrus.FieldLogger
56-
hostUtils hostutils.HostUtilsInterface
57-
metricsReporter metrics.ReporterInterface
45+
authController auth.Controller
46+
consoleController console.Controller
47+
installController install.Controller
48+
rc runtimeconfig.RuntimeConfig
49+
releaseData *release.ReleaseData
50+
tlsConfig types.TLSConfig
51+
license []byte
52+
airgapBundle string
53+
configValues string
54+
endUserConfig *ecv1beta1.Config
55+
allowIgnoreHostPreflights bool
56+
logger logrus.FieldLogger
57+
hostUtils hostutils.HostUtilsInterface
58+
metricsReporter metrics.ReporterInterface
5859
}
5960

6061
type APIOption func(*API)
@@ -137,6 +138,12 @@ func WithEndUserConfig(endUserConfig *ecv1beta1.Config) APIOption {
137138
}
138139
}
139140

141+
func WithAllowIgnoreHostPreflights(allowIgnoreHostPreflights bool) APIOption {
142+
return func(a *API) {
143+
a.allowIgnoreHostPreflights = allowIgnoreHostPreflights
144+
}
145+
}
146+
140147
func New(password string, opts ...APIOption) (*API, error) {
141148
api := &API{}
142149

@@ -192,6 +199,7 @@ func New(password string, opts ...APIOption) (*API, error) {
192199
install.WithAirgapBundle(api.airgapBundle),
193200
install.WithConfigValues(api.configValues),
194201
install.WithEndUserConfig(api.endUserConfig),
202+
install.WithAllowIgnoreHostPreflights(api.allowIgnoreHostPreflights),
195203
)
196204
if err != nil {
197205
return nil, fmt.Errorf("new install controller: %w", err)

api/controllers/install/controller.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type Controller interface {
2828
GetHostPreflightStatus(ctx context.Context) (types.Status, error)
2929
GetHostPreflightOutput(ctx context.Context) (*types.HostPreflightsOutput, error)
3030
GetHostPreflightTitles(ctx context.Context) ([]string, error)
31-
SetupInfra(ctx context.Context) error
31+
SetupInfra(ctx context.Context, ignoreHostPreflights bool) error
3232
GetInfra(ctx context.Context) (types.Infra, error)
3333
SetStatus(ctx context.Context, status types.Status) error
3434
GetStatus(ctx context.Context) (types.Status, error)
@@ -55,12 +55,13 @@ type InstallController struct {
5555
configValues string
5656
endUserConfig *ecv1beta1.Config
5757

58-
install types.Install
59-
store store.Store
60-
rc runtimeconfig.RuntimeConfig
61-
stateMachine statemachine.Interface
62-
logger logrus.FieldLogger
63-
mu sync.RWMutex
58+
install types.Install
59+
store store.Store
60+
rc runtimeconfig.RuntimeConfig
61+
stateMachine statemachine.Interface
62+
logger logrus.FieldLogger
63+
mu sync.RWMutex
64+
allowIgnoreHostPreflights bool
6465
}
6566

6667
type InstallControllerOption func(*InstallController)
@@ -137,6 +138,12 @@ func WithEndUserConfig(endUserConfig *ecv1beta1.Config) InstallControllerOption
137138
}
138139
}
139140

141+
func WithAllowIgnoreHostPreflights(allowIgnoreHostPreflights bool) InstallControllerOption {
142+
return func(c *InstallController) {
143+
c.allowIgnoreHostPreflights = allowIgnoreHostPreflights
144+
}
145+
}
146+
140147
func WithInstallationManager(installationManager installation.InstallationManager) InstallControllerOption {
141148
return func(c *InstallController) {
142149
c.installationManager = installationManager

api/controllers/install/controller_test.go

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -641,27 +641,33 @@ func TestGetInstallationStatus(t *testing.T) {
641641

642642
func TestSetupInfra(t *testing.T) {
643643
tests := []struct {
644-
name string
645-
currentState statemachine.State
646-
expectedState statemachine.State
647-
setupMocks func(runtimeconfig.RuntimeConfig, *preflight.MockHostPreflightManager, *installation.MockInstallationManager, *infra.MockInfraManager, *metrics.MockReporter)
648-
expectedErr bool
644+
name string
645+
clientIgnoreHostPreflights bool // From HTTP request
646+
serverAllowIgnoreHostPreflights bool // From CLI flag
647+
currentState statemachine.State
648+
expectedState statemachine.State
649+
setupMocks func(runtimeconfig.RuntimeConfig, *preflight.MockHostPreflightManager, *installation.MockInstallationManager, *infra.MockInfraManager, *metrics.MockReporter)
650+
expectedErr error
649651
}{
650652
{
651-
name: "successful setup with passed preflights",
652-
currentState: StatePreflightsSucceeded,
653-
expectedState: StateSucceeded,
653+
name: "successful setup with passed preflights",
654+
clientIgnoreHostPreflights: false,
655+
serverAllowIgnoreHostPreflights: true,
656+
currentState: StatePreflightsSucceeded,
657+
expectedState: StateSucceeded,
654658
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, r *metrics.MockReporter) {
655659
mock.InOrder(
656660
fm.On("Install", mock.Anything, rc).Return(nil),
657661
)
658662
},
659-
expectedErr: false,
663+
expectedErr: nil,
660664
},
661665
{
662-
name: "successful setup with failed preflights",
663-
currentState: StatePreflightsFailed,
664-
expectedState: StateSucceeded,
666+
name: "successful setup with failed preflights - ignored with CLI flag",
667+
clientIgnoreHostPreflights: true,
668+
serverAllowIgnoreHostPreflights: true,
669+
currentState: StatePreflightsFailed,
670+
expectedState: StateSucceeded,
665671
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, r *metrics.MockReporter) {
666672
preflightOutput := &types.HostPreflightsOutput{
667673
Fail: []types.HostPreflightsRecord{
@@ -677,37 +683,73 @@ func TestSetupInfra(t *testing.T) {
677683
fm.On("Install", mock.Anything, rc).Return(nil),
678684
)
679685
},
680-
expectedErr: false,
686+
expectedErr: nil,
681687
},
682688
{
683-
name: "preflight output error",
684-
currentState: StatePreflightsFailed,
685-
expectedState: StatePreflightsFailed,
689+
name: "failed setup with failed preflights - not ignored",
690+
clientIgnoreHostPreflights: false,
691+
serverAllowIgnoreHostPreflights: true,
692+
currentState: StatePreflightsFailed,
693+
expectedState: StatePreflightsFailed,
694+
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, r *metrics.MockReporter) {
695+
},
696+
expectedErr: types.NewBadRequestError(ErrPreflightChecksFailed),
697+
},
698+
{
699+
name: "preflight output error",
700+
clientIgnoreHostPreflights: true,
701+
serverAllowIgnoreHostPreflights: true,
702+
currentState: StatePreflightsFailed,
703+
expectedState: StatePreflightsFailed,
686704
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, r *metrics.MockReporter) {
687705
mock.InOrder(
688706
pm.On("GetHostPreflightOutput", t.Context()).Return(nil, errors.New("get output error")),
689707
)
690708
},
691-
expectedErr: true,
709+
expectedErr: errors.New("any error"), // Just check that an error occurs, don't care about exact message
692710
},
693711
{
694-
name: "install infra error",
695-
currentState: StatePreflightsSucceeded,
696-
expectedState: StateFailed,
712+
name: "install infra error",
713+
clientIgnoreHostPreflights: false,
714+
serverAllowIgnoreHostPreflights: true,
715+
currentState: StatePreflightsSucceeded,
716+
expectedState: StateFailed,
697717
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, r *metrics.MockReporter) {
698718
mock.InOrder(
699719
fm.On("Install", mock.Anything, rc).Return(errors.New("install error")),
700720
)
701721
},
702-
expectedErr: false,
722+
expectedErr: nil,
703723
},
704724
{
705-
name: "invalid state transition",
706-
currentState: StateInstallationConfigured,
707-
expectedState: StateInstallationConfigured,
725+
name: "invalid state transition",
726+
clientIgnoreHostPreflights: false,
727+
serverAllowIgnoreHostPreflights: true,
728+
currentState: StateInstallationConfigured,
729+
expectedState: StateInstallationConfigured,
708730
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, r *metrics.MockReporter) {
709731
},
710-
expectedErr: true,
732+
expectedErr: errors.New("invalid transition"), // Just check that an error occurs, don't care about exact message
733+
},
734+
{
735+
name: "failed preflights with ignore flag but CLI flag disabled",
736+
clientIgnoreHostPreflights: true,
737+
serverAllowIgnoreHostPreflights: false,
738+
currentState: StatePreflightsFailed,
739+
expectedState: StatePreflightsFailed,
740+
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, r *metrics.MockReporter) {
741+
},
742+
expectedErr: types.NewBadRequestError(ErrPreflightChecksFailed),
743+
},
744+
{
745+
name: "failed preflights without ignore flag and CLI flag disabled",
746+
clientIgnoreHostPreflights: false,
747+
serverAllowIgnoreHostPreflights: false,
748+
currentState: StatePreflightsFailed,
749+
expectedState: StatePreflightsFailed,
750+
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, r *metrics.MockReporter) {
751+
},
752+
expectedErr: types.NewBadRequestError(ErrPreflightChecksFailed),
711753
},
712754
}
713755

@@ -732,13 +774,24 @@ func TestSetupInfra(t *testing.T) {
732774
WithInstallationManager(mockInstallationManager),
733775
WithInfraManager(mockInfraManager),
734776
WithMetricsReporter(mockMetricsReporter),
777+
WithAllowIgnoreHostPreflights(tt.serverAllowIgnoreHostPreflights),
735778
)
736779
require.NoError(t, err)
737780

738-
err = controller.SetupInfra(t.Context())
781+
err = controller.SetupInfra(t.Context(), tt.clientIgnoreHostPreflights)
739782

740-
if tt.expectedErr {
783+
if tt.expectedErr != nil {
741784
require.Error(t, err)
785+
786+
// Check for specific error types
787+
var expectedAPIErr *types.APIError
788+
if errors.As(tt.expectedErr, &expectedAPIErr) {
789+
// For API errors, check the exact type and status code
790+
var actualAPIErr *types.APIError
791+
require.True(t, errors.As(err, &actualAPIErr), "expected error to be of type *types.APIError, got %T", err)
792+
assert.Equal(t, expectedAPIErr.StatusCode, actualAPIErr.StatusCode, "status codes should match")
793+
assert.Contains(t, actualAPIErr.Error(), expectedAPIErr.Unwrap().Error(), "error messages should contain expected content")
794+
}
742795
} else {
743796
require.NoError(t, err)
744797

api/controllers/install/infra.go

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

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

89
"github.com/replicatedhq/embedded-cluster/api/types"
910
)
1011

11-
func (c *InstallController) SetupInfra(ctx context.Context) (finalErr error) {
12+
var (
13+
ErrPreflightChecksFailed = errors.New("preflight checks failed")
14+
ErrPreflightChecksNotComplete = errors.New("preflight checks not complete")
15+
)
16+
17+
func (c *InstallController) SetupInfra(ctx context.Context, ignoreHostPreflights bool) (finalErr error) {
1218
if c.stateMachine.CurrentState() == StatePreflightsFailed {
13-
err := c.bypassPreflights(ctx)
19+
err := c.bypassPreflights(ctx, ignoreHostPreflights)
1420
if err != nil {
1521
return fmt.Errorf("bypass preflights: %w", err)
1622
}
@@ -68,7 +74,11 @@ func (c *InstallController) SetupInfra(ctx context.Context) (finalErr error) {
6874
return nil
6975
}
7076

71-
func (c *InstallController) bypassPreflights(ctx context.Context) error {
77+
func (c *InstallController) bypassPreflights(ctx context.Context, ignoreHostPreflights bool) error {
78+
if !ignoreHostPreflights || !c.allowIgnoreHostPreflights {
79+
return types.NewBadRequestError(ErrPreflightChecksFailed)
80+
}
81+
7282
lock, err := c.stateMachine.AcquireLock()
7383
if err != nil {
7484
return types.NewConflictError(err)
@@ -85,6 +95,8 @@ func (c *InstallController) bypassPreflights(ctx context.Context) error {
8595
if err != nil {
8696
return fmt.Errorf("get install host preflight output: %w", err)
8797
}
98+
99+
// Report that preflights were bypassed
88100
if preflightOutput != nil {
89101
c.metricsReporter.ReportPreflightsBypassed(ctx, preflightOutput)
90102
}

0 commit comments

Comments
 (0)