Skip to content

Commit 7cdd5f4

Browse files
committed
chore(v3): headless install dryrun tests
1 parent 8fe7073 commit 7cdd5f4

File tree

36 files changed

+1570
-505
lines changed

36 files changed

+1570
-505
lines changed

api/api.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
linuxupgrade "github.com/replicatedhq/embedded-cluster/api/controllers/linux/upgrade"
1212
"github.com/replicatedhq/embedded-cluster/api/pkg/logger"
1313
"github.com/replicatedhq/embedded-cluster/api/types"
14+
"github.com/replicatedhq/embedded-cluster/pkg-new/preflights"
1415
"github.com/replicatedhq/embedded-cluster/pkg/helm"
1516
"github.com/replicatedhq/embedded-cluster/pkg/metrics"
1617
"github.com/replicatedhq/embedded-cluster/pkg/runtimeconfig"
@@ -45,6 +46,7 @@ type API struct {
4546
hcli helm.Client
4647
kcli client.Client
4748
mcli metadata.Interface
49+
preflightRunner preflights.PreflightRunnerInterface
4850
logger logrus.FieldLogger
4951
metricsReporter metrics.ReporterInterface
5052

@@ -138,6 +140,13 @@ func WithMetadataClient(mcli metadata.Interface) Option {
138140
}
139141
}
140142

143+
// WithPreflightRunner configures the preflight runner for the API.
144+
func WithPreflightRunner(preflightRunner preflights.PreflightRunnerInterface) Option {
145+
return func(a *API) {
146+
a.preflightRunner = preflightRunner
147+
}
148+
}
149+
141150
// New creates a new API instance.
142151
func New(cfg types.APIConfig, opts ...Option) (*API, error) {
143152
if cfg.InstallTarget == "" {

api/controllers/app/apppreflight.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,31 @@ func (c *AppController) RunAppPreflights(ctx context.Context, opts RunAppPreflig
117117
}
118118

119119
func (c *AppController) getStateFromAppPreflightsOutput(ctx context.Context) statemachine.State {
120-
output, err := c.GetAppPreflightOutput(ctx)
121-
// If there was an error getting the state we assume preflight execution failed
120+
status, err := c.GetAppPreflightStatus(ctx)
122121
if err != nil {
123-
c.logger.WithError(err).Error("error getting app preflight output")
122+
c.logger.WithError(err).Error("error getting preflight status")
124123
return states.StateAppPreflightsExecutionFailed
125124
}
126-
// If there is no output, we assume preflights succeeded
127-
if output == nil || !output.HasFail() {
125+
switch status.State {
126+
case types.StateSucceeded:
128127
return states.StateAppPreflightsSucceeded
128+
case types.StateFailed:
129+
output, err := c.GetAppPreflightOutput(ctx)
130+
// If there was an error getting the state we assume preflight execution failed
131+
if err != nil {
132+
c.logger.WithError(err).Error("error getting preflight output")
133+
return states.StateAppPreflightsExecutionFailed
134+
}
135+
// If there are failures, we return the failed state
136+
if output != nil && output.HasFail() {
137+
return states.StateAppPreflightsFailed
138+
}
139+
// Otherwise, we assume preflight execution failed
140+
return states.StateAppPreflightsExecutionFailed
141+
default:
142+
c.logger.Errorf("unexpected preflight status: %s", status.State)
143+
return states.StateAppPreflightsExecutionFailed
129144
}
130-
return states.StateAppPreflightsFailed
131145
}
132146

133147
func (c *AppController) GetAppPreflightStatus(ctx context.Context) (types.Status, error) {
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package app
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
apppreflightmanager "github.com/replicatedhq/embedded-cluster/api/internal/managers/app/preflight"
8+
"github.com/replicatedhq/embedded-cluster/api/internal/managers/app/release"
9+
"github.com/replicatedhq/embedded-cluster/api/internal/statemachine"
10+
"github.com/replicatedhq/embedded-cluster/api/internal/states"
11+
"github.com/replicatedhq/embedded-cluster/api/internal/store"
12+
"github.com/replicatedhq/embedded-cluster/api/types"
13+
pkgrelease "github.com/replicatedhq/embedded-cluster/pkg/release"
14+
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/mock"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
var (
21+
failedPreflightOutput = &types.PreflightsOutput{
22+
Pass: []types.PreflightsRecord{},
23+
Warn: []types.PreflightsRecord{},
24+
Fail: []types.PreflightsRecord{
25+
{
26+
Title: "Test Check Failed",
27+
Message: "This check failed",
28+
},
29+
},
30+
}
31+
32+
successfulPreflightOutput = &types.PreflightsOutput{
33+
Pass: []types.PreflightsRecord{
34+
{
35+
Title: "Test Check Passed",
36+
Message: "This check passed",
37+
},
38+
},
39+
Warn: []types.PreflightsRecord{},
40+
Fail: []types.PreflightsRecord{},
41+
}
42+
)
43+
44+
func Test_AppController_getStateFromAppPreflightsOutput(t *testing.T) {
45+
tests := []struct {
46+
name string
47+
setupMocks func(*apppreflightmanager.MockAppPreflightManager)
48+
expectedState statemachine.State
49+
}{
50+
{
51+
name: "status succeeded returns StateAppPreflightsSucceeded",
52+
setupMocks: func(m *apppreflightmanager.MockAppPreflightManager) {
53+
m.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
54+
State: types.StateSucceeded,
55+
}, nil)
56+
},
57+
expectedState: states.StateAppPreflightsSucceeded,
58+
},
59+
{
60+
name: "status failed with failures returns StateAppPreflightsFailed",
61+
setupMocks: func(m *apppreflightmanager.MockAppPreflightManager) {
62+
m.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
63+
State: types.StateFailed,
64+
}, nil)
65+
m.On("GetAppPreflightOutput", mock.Anything).Return(failedPreflightOutput, nil)
66+
},
67+
expectedState: states.StateAppPreflightsFailed,
68+
},
69+
{
70+
name: "status failed with nil output returns StateAppPreflightsExecutionFailed",
71+
setupMocks: func(m *apppreflightmanager.MockAppPreflightManager) {
72+
m.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
73+
State: types.StateFailed,
74+
}, nil)
75+
m.On("GetAppPreflightOutput", mock.Anything).Return(nil, nil)
76+
},
77+
expectedState: states.StateAppPreflightsExecutionFailed,
78+
},
79+
{
80+
name: "status failed with no failures returns StateAppPreflightsExecutionFailed",
81+
setupMocks: func(m *apppreflightmanager.MockAppPreflightManager) {
82+
m.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
83+
State: types.StateFailed,
84+
}, nil)
85+
m.On("GetAppPreflightOutput", mock.Anything).Return(successfulPreflightOutput, nil)
86+
},
87+
expectedState: states.StateAppPreflightsExecutionFailed,
88+
},
89+
{
90+
name: "status failed with output error returns StateAppPreflightsExecutionFailed",
91+
setupMocks: func(m *apppreflightmanager.MockAppPreflightManager) {
92+
m.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
93+
State: types.StateFailed,
94+
}, nil)
95+
m.On("GetAppPreflightOutput", mock.Anything).Return(nil, errors.New("get output error"))
96+
},
97+
expectedState: states.StateAppPreflightsExecutionFailed,
98+
},
99+
{
100+
name: "get status error returns StateAppPreflightsExecutionFailed",
101+
setupMocks: func(m *apppreflightmanager.MockAppPreflightManager) {
102+
m.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{}, errors.New("get status error"))
103+
},
104+
expectedState: states.StateAppPreflightsExecutionFailed,
105+
},
106+
{
107+
name: "unexpected state returns StateAppPreflightsExecutionFailed",
108+
setupMocks: func(m *apppreflightmanager.MockAppPreflightManager) {
109+
m.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
110+
State: types.StateRunning, // Unexpected state
111+
}, nil)
112+
},
113+
expectedState: states.StateAppPreflightsExecutionFailed,
114+
},
115+
}
116+
117+
for _, tt := range tests {
118+
t.Run(tt.name, func(t *testing.T) {
119+
mockPreflightManager := &apppreflightmanager.MockAppPreflightManager{}
120+
tt.setupMocks(mockPreflightManager)
121+
122+
// Create a simple state machine with minimal transitions
123+
sm := statemachine.New(states.StateNew, map[statemachine.State][]statemachine.State{
124+
states.StateNew: {states.StateApplicationConfigured},
125+
})
126+
mockStore := &store.MockStore{}
127+
// Mock GetConfigValues call that happens during controller initialization
128+
mockStore.AppConfigMockStore.On("GetConfigValues").Return(types.AppConfigValues{}, nil)
129+
130+
controller, err := NewAppController(
131+
WithAppPreflightManager(mockPreflightManager),
132+
WithAppReleaseManager(&release.MockAppReleaseManager{}),
133+
WithStateMachine(sm),
134+
WithStore(mockStore),
135+
WithReleaseData(&pkgrelease.ReleaseData{
136+
AppConfig: &kotsv1beta1.Config{},
137+
}),
138+
)
139+
require.NoError(t, err)
140+
141+
state := controller.getStateFromAppPreflightsOutput(t.Context())
142+
143+
assert.Equal(t, tt.expectedState, state, "expected state %s but got %s", tt.expectedState, state)
144+
145+
mockPreflightManager.AssertExpectations(t)
146+
})
147+
}
148+
}

api/controllers/app/controller.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/replicatedhq/embedded-cluster/api/internal/store"
1515
"github.com/replicatedhq/embedded-cluster/api/pkg/logger"
1616
"github.com/replicatedhq/embedded-cluster/api/types"
17+
"github.com/replicatedhq/embedded-cluster/pkg-new/preflights"
1718
"github.com/replicatedhq/embedded-cluster/pkg/helm"
1819
"github.com/replicatedhq/embedded-cluster/pkg/release"
1920
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
@@ -51,6 +52,7 @@ type AppController struct {
5152
releaseData *release.ReleaseData
5253
hcli helm.Client
5354
kcli client.Client
55+
preflightRunner preflights.PreflightRunnerInterface
5456
kubernetesEnvSettings *helmcli.EnvSettings
5557
store store.Store
5658
configValues types.AppConfigValues
@@ -163,6 +165,12 @@ func WithAppUpgradeManager(appUpgradeManager appupgrademanager.AppUpgradeManager
163165
}
164166
}
165167

168+
func WithPreflightRunner(preflightRunner preflights.PreflightRunnerInterface) AppControllerOption {
169+
return func(c *AppController) {
170+
c.preflightRunner = preflightRunner
171+
}
172+
}
173+
166174
func NewAppController(opts ...AppControllerOption) (*AppController, error) {
167175
controller := &AppController{
168176
logger: logger.NewDiscardLogger(),
@@ -224,6 +232,7 @@ func NewAppController(opts ...AppControllerOption) (*AppController, error) {
224232
controller.appPreflightManager = apppreflightmanager.NewAppPreflightManager(
225233
apppreflightmanager.WithLogger(controller.logger),
226234
apppreflightmanager.WithAppPreflightStore(controller.store.AppPreflightStore()),
235+
apppreflightmanager.WithPreflightRunner(controller.preflightRunner),
227236
)
228237
}
229238

api/controllers/app/tests/test_suite.go

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,8 @@ func (s *AppControllerTestSuite) TestRunAppPreflights() {
225225
apm.On("RunAppPreflights", mock.Anything, mock.MatchedBy(func(opts apppreflightmanager.RunAppPreflightOptions) bool {
226226
return expectedAPF == opts.AppPreflightSpec
227227
})).Return(nil),
228-
apm.On("GetAppPreflightOutput", mock.Anything).Return(&types.PreflightsOutput{
229-
Pass: []types.PreflightsRecord{
230-
{
231-
Title: "Test Check",
232-
Message: "Test check passed",
233-
},
234-
},
228+
apm.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
229+
State: types.StateSucceeded,
235230
}, nil),
236231
)
237232
},
@@ -251,13 +246,8 @@ func (s *AppControllerTestSuite) TestRunAppPreflights() {
251246
apm.On("RunAppPreflights", mock.Anything, mock.MatchedBy(func(opts apppreflightmanager.RunAppPreflightOptions) bool {
252247
return expectedAPF == opts.AppPreflightSpec
253248
})).Return(nil),
254-
apm.On("GetAppPreflightOutput", mock.Anything).Return(&types.PreflightsOutput{
255-
Pass: []types.PreflightsRecord{
256-
{
257-
Title: "Test Check",
258-
Message: "Test check passed",
259-
},
260-
},
249+
apm.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
250+
State: types.StateSucceeded,
261251
}, nil),
262252
)
263253
},
@@ -277,13 +267,8 @@ func (s *AppControllerTestSuite) TestRunAppPreflights() {
277267
apm.On("RunAppPreflights", mock.Anything, mock.MatchedBy(func(opts apppreflightmanager.RunAppPreflightOptions) bool {
278268
return expectedAPF == opts.AppPreflightSpec
279269
})).Return(nil),
280-
apm.On("GetAppPreflightOutput", mock.Anything).Return(&types.PreflightsOutput{
281-
Pass: []types.PreflightsRecord{
282-
{
283-
Title: "Test Check",
284-
Message: "Test check passed",
285-
},
286-
},
270+
apm.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
271+
State: types.StateSucceeded,
287272
}, nil),
288273
)
289274
},
@@ -303,6 +288,9 @@ func (s *AppControllerTestSuite) TestRunAppPreflights() {
303288
apm.On("RunAppPreflights", mock.Anything, mock.MatchedBy(func(opts apppreflightmanager.RunAppPreflightOptions) bool {
304289
return expectedAPF == opts.AppPreflightSpec
305290
})).Return(nil),
291+
apm.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
292+
State: types.StateFailed,
293+
}, nil),
306294
apm.On("GetAppPreflightOutput", mock.Anything).Return(&types.PreflightsOutput{
307295
Fail: []types.PreflightsRecord{
308296
{
@@ -316,7 +304,7 @@ func (s *AppControllerTestSuite) TestRunAppPreflights() {
316304
expectedErr: false,
317305
},
318306
{
319-
name: "execution succeeded but failed to get preflight output",
307+
name: "failed run preflights with get preflight output error",
320308
opts: appcontroller.RunAppPreflightOptions{
321309
PreflightBinaryPath: "/usr/bin/preflight",
322310
},
@@ -329,52 +317,32 @@ func (s *AppControllerTestSuite) TestRunAppPreflights() {
329317
apm.On("RunAppPreflights", mock.Anything, mock.MatchedBy(func(opts apppreflightmanager.RunAppPreflightOptions) bool {
330318
return expectedAPF == opts.AppPreflightSpec
331319
})).Return(nil),
320+
apm.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
321+
State: types.StateFailed,
322+
}, nil),
332323
apm.On("GetAppPreflightOutput", mock.Anything).Return(nil, errors.New("get output error")),
333324
)
334325
},
335326
expectedErr: false,
336327
},
337328
{
338-
name: "successful execution with nil preflight output",
329+
name: "failed run preflights with nil preflight output",
339330
opts: appcontroller.RunAppPreflightOptions{
340331
PreflightBinaryPath: "/usr/bin/preflight",
341332
},
342333
currentState: states.StateInfrastructureInstalled,
343-
expectedState: states.StateAppPreflightsSucceeded,
344-
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
345-
mock.InOrder(
346-
acm.On("GetConfigValues").Return(types.AppConfigValues{"test-item": types.AppConfigValue{Value: "test-value"}}, nil),
347-
arm.On("ExtractAppPreflightSpec", mock.Anything, types.AppConfigValues{"test-item": types.AppConfigValue{Value: "test-value"}}, mock.Anything, mock.Anything).Return(expectedAPF, nil),
348-
apm.On("RunAppPreflights", mock.Anything, mock.MatchedBy(func(opts apppreflightmanager.RunAppPreflightOptions) bool {
349-
return expectedAPF == opts.AppPreflightSpec
350-
})).Return(nil),
351-
apm.On("GetAppPreflightOutput", mock.Anything).Return(nil, nil),
352-
)
353-
},
354-
expectedErr: false,
355-
},
356-
{
357-
name: "successful execution with preflight warnings",
358-
opts: appcontroller.RunAppPreflightOptions{
359-
PreflightBinaryPath: "/usr/bin/preflight",
360-
},
361-
currentState: states.StateInfrastructureInstalled,
362-
expectedState: states.StateAppPreflightsSucceeded,
334+
expectedState: states.StateAppPreflightsExecutionFailed,
363335
setupMocks: func(apm *apppreflightmanager.MockAppPreflightManager, arm *appreleasemanager.MockAppReleaseManager, acm *appconfig.MockAppConfigManager) {
364336
mock.InOrder(
365337
acm.On("GetConfigValues").Return(types.AppConfigValues{"test-item": types.AppConfigValue{Value: "test-value"}}, nil),
366338
arm.On("ExtractAppPreflightSpec", mock.Anything, types.AppConfigValues{"test-item": types.AppConfigValue{Value: "test-value"}}, mock.Anything, mock.Anything).Return(expectedAPF, nil),
367339
apm.On("RunAppPreflights", mock.Anything, mock.MatchedBy(func(opts apppreflightmanager.RunAppPreflightOptions) bool {
368340
return expectedAPF == opts.AppPreflightSpec
369341
})).Return(nil),
370-
apm.On("GetAppPreflightOutput", mock.Anything).Return(&types.PreflightsOutput{
371-
Warn: []types.PreflightsRecord{
372-
{
373-
Title: "Test Check",
374-
Message: "Test check warning",
375-
},
376-
},
342+
apm.On("GetAppPreflightStatus", mock.Anything).Return(types.Status{
343+
State: types.StateFailed,
377344
}, nil),
345+
apm.On("GetAppPreflightOutput", mock.Anything).Return(nil, nil),
378346
)
379347
},
380348
expectedErr: false,

0 commit comments

Comments
 (0)