Skip to content

Commit f72a2ac

Browse files
authored
Add logic to run app preflight checks from a preflight spec (#2595)
* Refactor pkg-new/preflights package to allow running app preflight checks
1 parent 15d96ec commit f72a2ac

Some content is hidden

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

41 files changed

+1410
-228
lines changed

api/controllers/linux/install/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type Controller interface {
3030
GetInstallationStatus(ctx context.Context) (types.Status, error)
3131
RunHostPreflights(ctx context.Context, opts RunHostPreflightsOptions) error
3232
GetHostPreflightStatus(ctx context.Context) (types.Status, error)
33-
GetHostPreflightOutput(ctx context.Context) (*types.HostPreflightsOutput, error)
33+
GetHostPreflightOutput(ctx context.Context) (*types.PreflightsOutput, error)
3434
GetHostPreflightTitles(ctx context.Context) ([]string, error)
3535
SetupInfra(ctx context.Context, ignoreHostPreflights bool) error
3636
GetInfra(ctx context.Context) (types.Infra, error)

api/controllers/linux/install/controller_mock.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ func (m *MockController) GetHostPreflightStatus(ctx context.Context) (types.Stat
5454
}
5555

5656
// GetHostPreflightOutput mocks the GetHostPreflightOutput method
57-
func (m *MockController) GetHostPreflightOutput(ctx context.Context) (*types.HostPreflightsOutput, error) {
57+
func (m *MockController) GetHostPreflightOutput(ctx context.Context) (*types.PreflightsOutput, error) {
5858
args := m.Called(ctx)
5959
if args.Get(0) == nil {
6060
return nil, args.Error(1)
6161
}
62-
return args.Get(0).(*types.HostPreflightsOutput), args.Error(1)
62+
return args.Get(0).(*types.PreflightsOutput), args.Error(1)
6363
}
6464

6565
// GetHostPreflightTitles mocks the GetHostPreflightTitles method

api/controllers/linux/install/controller_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,26 @@ import (
2626
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
2727
)
2828

29-
var failedPreflightOutput = &types.HostPreflightsOutput{
30-
Fail: []types.HostPreflightsRecord{
29+
var failedPreflightOutput = &types.PreflightsOutput{
30+
Fail: []types.PreflightsRecord{
3131
{
3232
Title: "Test Check",
3333
Message: "Test check failed",
3434
},
3535
},
3636
}
3737

38-
var successfulPreflightOutput = &types.HostPreflightsOutput{
39-
Pass: []types.HostPreflightsRecord{
38+
var successfulPreflightOutput = &types.PreflightsOutput{
39+
Pass: []types.PreflightsRecord{
4040
{
4141
Title: "Test Check",
4242
Message: "Test check passed",
4343
},
4444
},
4545
}
4646

47-
var warnPreflightOutput = &types.HostPreflightsOutput{
48-
Warn: []types.HostPreflightsRecord{
47+
var warnPreflightOutput = &types.PreflightsOutput{
48+
Warn: []types.PreflightsRecord{
4949
{
5050
Title: "Test Check",
5151
Message: "Test check warning",
@@ -874,7 +874,7 @@ func TestGetHostPreflightOutput(t *testing.T) {
874874
name string
875875
setupMock func(*preflight.MockHostPreflightManager)
876876
expectedErr bool
877-
expectedValue *types.HostPreflightsOutput
877+
expectedValue *types.PreflightsOutput
878878
}{
879879
{
880880
name: "successful get output",

api/controllers/linux/install/hostpreflight.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (c *InstallController) GetHostPreflightStatus(ctx context.Context) (types.S
120120
return c.hostPreflightManager.GetHostPreflightStatus(ctx)
121121
}
122122

123-
func (c *InstallController) GetHostPreflightOutput(ctx context.Context) (*types.HostPreflightsOutput, error) {
123+
func (c *InstallController) GetHostPreflightOutput(ctx context.Context) (*types.PreflightsOutput, error) {
124124
return c.hostPreflightManager.GetHostPreflightOutput(ctx)
125125
}
126126

api/docs/docs.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/docs/swagger.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

api/docs/swagger.yaml

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -63,31 +63,6 @@ components:
6363
type: array
6464
uniqueItems: false
6565
type: object
66-
types.HostPreflightsOutput:
67-
properties:
68-
fail:
69-
items:
70-
$ref: '#/components/schemas/types.HostPreflightsRecord'
71-
type: array
72-
uniqueItems: false
73-
pass:
74-
items:
75-
$ref: '#/components/schemas/types.HostPreflightsRecord'
76-
type: array
77-
uniqueItems: false
78-
warn:
79-
items:
80-
$ref: '#/components/schemas/types.HostPreflightsRecord'
81-
type: array
82-
uniqueItems: false
83-
type: object
84-
types.HostPreflightsRecord:
85-
properties:
86-
message:
87-
type: string
88-
title:
89-
type: string
90-
type: object
9166
types.Infra:
9267
properties:
9368
components:
@@ -112,7 +87,7 @@ components:
11287
allowIgnoreHostPreflights:
11388
type: boolean
11489
output:
115-
$ref: '#/components/schemas/types.HostPreflightsOutput'
90+
$ref: '#/components/schemas/types.PreflightsOutput'
11691
status:
11792
$ref: '#/components/schemas/types.Status'
11893
titles:
@@ -170,6 +145,31 @@ components:
170145
isUi:
171146
type: boolean
172147
type: object
148+
types.PreflightsOutput:
149+
properties:
150+
fail:
151+
items:
152+
$ref: '#/components/schemas/types.PreflightsRecord'
153+
type: array
154+
uniqueItems: false
155+
pass:
156+
items:
157+
$ref: '#/components/schemas/types.PreflightsRecord'
158+
type: array
159+
uniqueItems: false
160+
warn:
161+
items:
162+
$ref: '#/components/schemas/types.PreflightsRecord'
163+
type: array
164+
uniqueItems: false
165+
type: object
166+
types.PreflightsRecord:
167+
properties:
168+
message:
169+
type: string
170+
title:
171+
type: string
172+
type: object
173173
types.State:
174174
type: string
175175
x-enum-varnames:

api/integration/linux/install/hostpreflight_test.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ import (
3636
// Test the getHostPreflightsStatus endpoint returns host preflights status correctly
3737
func TestGetHostPreflightsStatus(t *testing.T) {
3838
hpf := types.HostPreflights{
39-
Output: &types.HostPreflightsOutput{
40-
Pass: []types.HostPreflightsRecord{
39+
Output: &types.PreflightsOutput{
40+
Pass: []types.PreflightsRecord{
4141
{
4242
Title: "Some Preflight",
4343
Message: "All good",
4444
},
4545
},
46-
Fail: []types.HostPreflightsRecord{
46+
Fail: []types.PreflightsRecord{
4747
{
4848
Title: "Another Preflight",
4949
Message: "Oh no!",
@@ -136,7 +136,7 @@ func TestGetHostPreflightsStatus(t *testing.T) {
136136
// Create a mock controller that returns an error
137137
mockController := &linuxinstall.MockController{}
138138
mockController.On("GetHostPreflightTitles", mock.Anything).Return([]string{}, nil)
139-
mockController.On("GetHostPreflightOutput", mock.Anything).Return(&types.HostPreflightsOutput{}, nil)
139+
mockController.On("GetHostPreflightOutput", mock.Anything).Return(&types.PreflightsOutput{}, nil)
140140
mockController.On("GetHostPreflightStatus", mock.Anything).Return(types.Status{}, assert.AnError)
141141

142142
// Create the API with the mock controller
@@ -194,8 +194,8 @@ func TestGetHostPreflightsStatusWithIgnoreFlag(t *testing.T) {
194194
for _, tt := range tests {
195195
t.Run(tt.name, func(t *testing.T) {
196196
hpf := types.HostPreflights{
197-
Output: &types.HostPreflightsOutput{
198-
Pass: []types.HostPreflightsRecord{
197+
Output: &types.PreflightsOutput{
198+
Pass: []types.PreflightsRecord{
199199
{
200200
Title: "Some Preflight",
201201
Message: "All good",
@@ -316,7 +316,7 @@ func TestPostRunHostPreflights(t *testing.T) {
316316
hpfc := &troubleshootv1beta2.HostPreflightSpec{}
317317

318318
mock.InOrder(
319-
runner.On("Prepare", mock.Anything, preflights.PrepareOptions{
319+
runner.On("PrepareHostPreflights", mock.Anything, preflights.PrepareHostPreflightOptions{
320320
DataDir: rc.EmbeddedClusterHomeDirectory(),
321321
K0sDataDir: rc.EmbeddedClusterK0sSubDir(),
322322
OpenEBSDataDir: rc.EmbeddedClusterOpenEBSLocalSubDir(),
@@ -329,7 +329,11 @@ func TestPostRunHostPreflights(t *testing.T) {
329329
IsUI: true,
330330
}).Return(hpfc, nil),
331331
// For a successful run, we expect the runner to return an output without any errors or warnings
332-
runner.On("Run", mock.Anything, hpfc, rc).Return(&types.HostPreflightsOutput{}, "", nil),
332+
runner.On("RunHostPreflights", mock.Anything, hpfc, preflights.RunOptions{
333+
PreflightBinaryPath: rc.PathToEmbeddedClusterBinary("kubectl-preflight"),
334+
ProxySpec: rc.ProxySpec(),
335+
ExtraPaths: []string{rc.EmbeddedClusterBinsSubDir()},
336+
}).Return(&types.PreflightsOutput{}, "", nil),
333337
runner.On("SaveToDisk", mock.Anything, mock.Anything).Return(nil),
334338
runner.On("CopyBundleTo", mock.Anything, mock.Anything).Return(nil),
335339
)
@@ -437,7 +441,7 @@ func TestPostRunHostPreflights(t *testing.T) {
437441
t.Run("Controller error", func(t *testing.T) {
438442
// Mock preflight runner that returns an error
439443
runner := &preflights.MockPreflightRunner{}
440-
runner.On("Prepare", mock.Anything, mock.Anything).Return(nil, assert.AnError)
444+
runner.On("PrepareHostPreflights", mock.Anything, mock.Anything).Return(nil, assert.AnError)
441445

442446
// Create a host preflights manager with the failing mock runner
443447
manager := linuxpreflight.NewHostPreflightManager(
@@ -494,8 +498,8 @@ func TestPostRunHostPreflights(t *testing.T) {
494498
// Mock preflight runner that returns an error
495499
runner := &preflights.MockPreflightRunner{}
496500
mock.InOrder(
497-
runner.On("Prepare", mock.Anything, mock.Anything).Return(hpfc, nil),
498-
runner.On("Run", mock.Anything, hpfc, mock.Anything).Return(nil, "this is an error", assert.AnError),
501+
runner.On("PrepareHostPreflights", mock.Anything, mock.Anything).Return(hpfc, nil),
502+
runner.On("RunHostPreflights", mock.Anything, hpfc, mock.Anything).Return(nil, "this is an error", assert.AnError),
499503
)
500504
// Create a host preflights manager with the failing mock runner
501505
manager := linuxpreflight.NewHostPreflightManager(
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package preflight
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"runtime/debug"
7+
"time"
8+
9+
"github.com/replicatedhq/embedded-cluster/api/types"
10+
"github.com/replicatedhq/embedded-cluster/pkg-new/preflights"
11+
troubleshootanalyze "github.com/replicatedhq/troubleshoot/pkg/analyze"
12+
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
13+
)
14+
15+
type RunAppPreflightOptions struct {
16+
AppPreflightSpec *troubleshootv1beta2.PreflightSpec
17+
RunOptions preflights.RunOptions
18+
}
19+
20+
func (m *appPreflightManager) RunAppPreflights(ctx context.Context, opts RunAppPreflightOptions) (finalErr error) {
21+
defer func() {
22+
if r := recover(); r != nil {
23+
finalErr = fmt.Errorf("panic: %v: %s", r, string(debug.Stack()))
24+
25+
if err := m.setFailedStatus("App preflights failed to run: panic"); err != nil {
26+
m.logger.WithError(err).Error("set failed status")
27+
}
28+
}
29+
}()
30+
31+
if err := m.setRunningStatus(opts.AppPreflightSpec); err != nil {
32+
return fmt.Errorf("set running status: %w", err)
33+
}
34+
35+
// Run the app preflights using the shared core function
36+
output, stderr, err := m.runner.RunAppPreflights(ctx, opts.AppPreflightSpec, opts.RunOptions)
37+
if err != nil {
38+
errMsg := fmt.Sprintf("App preflights failed to run: %v", err)
39+
if stderr != "" {
40+
errMsg += fmt.Sprintf(" (stderr: %s)", stderr)
41+
}
42+
m.logger.Error(errMsg)
43+
if err := m.setFailedStatus(errMsg); err != nil {
44+
return fmt.Errorf("set failed status: %w", err)
45+
}
46+
return
47+
}
48+
49+
// Set final status based on results
50+
if output.HasFail() {
51+
if err := m.setCompletedStatus(types.StateFailed, "App preflights failed", output); err != nil {
52+
return fmt.Errorf("set failed status: %w", err)
53+
}
54+
} else {
55+
if err := m.setCompletedStatus(types.StateSucceeded, "App preflights passed", output); err != nil {
56+
return fmt.Errorf("set succeeded status: %w", err)
57+
}
58+
}
59+
60+
return nil
61+
}
62+
63+
func (m *appPreflightManager) GetAppPreflightStatus(ctx context.Context) (types.Status, error) {
64+
return m.appPreflightStore.GetStatus()
65+
}
66+
67+
func (m *appPreflightManager) GetAppPreflightOutput(ctx context.Context) (*types.PreflightsOutput, error) {
68+
return m.appPreflightStore.GetOutput()
69+
}
70+
71+
func (m *appPreflightManager) GetAppPreflightTitles(ctx context.Context) ([]string, error) {
72+
return m.appPreflightStore.GetTitles()
73+
}
74+
75+
func (m *appPreflightManager) setRunningStatus(apf *troubleshootv1beta2.PreflightSpec) error {
76+
titles, err := m.getTitles(apf)
77+
if err != nil {
78+
return fmt.Errorf("get titles: %w", err)
79+
}
80+
81+
if err := m.appPreflightStore.SetTitles(titles); err != nil {
82+
return fmt.Errorf("set titles: %w", err)
83+
}
84+
85+
if err := m.appPreflightStore.SetOutput(nil); err != nil {
86+
return fmt.Errorf("reset output: %w", err)
87+
}
88+
89+
if err := m.appPreflightStore.SetStatus(types.Status{
90+
State: types.StateRunning,
91+
Description: "Running app preflights",
92+
LastUpdated: time.Now(),
93+
}); err != nil {
94+
return fmt.Errorf("set status: %w", err)
95+
}
96+
97+
return nil
98+
}
99+
100+
func (m *appPreflightManager) setFailedStatus(description string) error {
101+
return m.appPreflightStore.SetStatus(types.Status{
102+
State: types.StateFailed,
103+
Description: description,
104+
LastUpdated: time.Now(),
105+
})
106+
}
107+
108+
func (m *appPreflightManager) setCompletedStatus(state types.State, description string, output *types.PreflightsOutput) error {
109+
if err := m.appPreflightStore.SetOutput(output); err != nil {
110+
return fmt.Errorf("set output: %w", err)
111+
}
112+
113+
return m.appPreflightStore.SetStatus(types.Status{
114+
State: state,
115+
Description: description,
116+
LastUpdated: time.Now(),
117+
})
118+
}
119+
120+
func (m *appPreflightManager) getTitles(apf *troubleshootv1beta2.PreflightSpec) ([]string, error) {
121+
if apf == nil || apf.Analyzers == nil {
122+
return nil, nil
123+
}
124+
125+
titles := []string{}
126+
for _, a := range apf.Analyzers {
127+
analyzer := troubleshootanalyze.GetAnalyzer(a)
128+
excluded, err := analyzer.IsExcluded()
129+
if err != nil {
130+
return nil, fmt.Errorf("check if analyzer is excluded: %w", err)
131+
}
132+
if !excluded {
133+
titles = append(titles, analyzer.Title())
134+
}
135+
}
136+
137+
return titles, nil
138+
}

0 commit comments

Comments
 (0)