diff --git a/api/internal/managers/app/install/install.go b/api/internal/managers/app/install/install.go index acd20a852c..97b73646c1 100644 --- a/api/internal/managers/app/install/install.go +++ b/api/internal/managers/app/install/install.go @@ -49,14 +49,8 @@ func (m *appInstallManager) Install(ctx context.Context, installableCharts []typ } func (m *appInstallManager) install(ctx context.Context, installableCharts []types.InstallableHelmChart, kotsConfigValues kotsv1beta1.ConfigValues) error { - license := &kotsv1beta1.License{} - if err := kyaml.Unmarshal(m.license, license); err != nil { - return fmt.Errorf("parse license: %w", err) - } - - // Setup Helm client - if err := m.setupHelmClient(); err != nil { - return fmt.Errorf("setup helm client: %w", err) + if err := m.installKots(kotsConfigValues); err != nil { + return fmt.Errorf("install kots: %w", err) } // Install Helm charts @@ -64,6 +58,15 @@ func (m *appInstallManager) install(ctx context.Context, installableCharts []typ return fmt.Errorf("install helm charts: %w", err) } + return nil +} + +func (m *appInstallManager) installKots(kotsConfigValues kotsv1beta1.ConfigValues) error { + license := &kotsv1beta1.License{} + if err := kyaml.Unmarshal(m.license, license); err != nil { + return fmt.Errorf("parse license: %w", err) + } + ecDomains := utils.GetDomains(m.releaseData) installOpts := kotscli.InstallOptions{ @@ -83,11 +86,25 @@ func (m *appInstallManager) install(ctx context.Context, installableCharts []typ } installOpts.ConfigValuesFile = configValuesFile + logFn := m.logFn("app") + + logFn("preparing the app for installation") + if m.kotsCLI != nil { - return m.kotsCLI.Install(installOpts) + err := m.kotsCLI.Install(installOpts) + if err != nil { + return fmt.Errorf("install kots: %w", err) + } + } else { + err := kotscli.Install(installOpts) + if err != nil { + return fmt.Errorf("install kots: %w", err) + } } - return kotscli.Install(installOpts) + logFn("successfully prepared the app for installation") + + return nil } // createConfigValuesFile creates a temporary file with the config values @@ -119,6 +136,11 @@ func (m *appInstallManager) installHelmCharts(ctx context.Context, installableCh return fmt.Errorf("no helm charts found") } + // Setup Helm client + if err := m.setupHelmClient(); err != nil { + return fmt.Errorf("setup helm client: %w", err) + } + logFn("installing %d helm charts", len(installableCharts)) for _, installableChart := range installableCharts { @@ -132,6 +154,8 @@ func (m *appInstallManager) installHelmCharts(ctx context.Context, installableCh logFn("successfully installed %s chart", chartName) } + logFn("successfully installed all %d helm charts", len(installableCharts)) + return nil } diff --git a/api/internal/managers/app/install/install_test.go b/api/internal/managers/app/install/install_test.go index bf089e294e..9ea185f898 100644 --- a/api/internal/managers/app/install/install_test.go +++ b/api/internal/managers/app/install/install_test.go @@ -257,7 +257,11 @@ func TestAppInstallManager_Install(t *testing.T) { return opts.ChartPath != "" && opts.ReleaseName == "fluentd" && opts.Namespace == "logging" })).Return((*helmrelease.Release)(nil), assert.AnError) - // Create manager with initialized store (no need for KOTS installer mock since Helm fails first) + // Create mock installer that succeeds (so we get to Helm charts) + mockInstaller := &MockKotsCLIInstaller{} + mockInstaller.On("Install", mock.Anything).Return(nil) + + // Create manager with initialized store store := appinstallstore.NewMemoryStore(appinstallstore.WithAppInstall(types.AppInstall{ Status: types.Status{State: types.StatePending}, })) @@ -266,6 +270,7 @@ func TestAppInstallManager_Install(t *testing.T) { WithClusterID("test-cluster"), WithReleaseData(releaseData), WithK8sVersion("v1.33.0"), + WithKotsCLI(mockInstaller), WithHelmClient(mockHelmClient), WithLogger(logger.NewDiscardLogger()), WithAppInstallStore(store), @@ -282,6 +287,7 @@ func TestAppInstallManager_Install(t *testing.T) { assert.Equal(t, types.StateFailed, appInstall.Status.State) assert.Contains(t, appInstall.Status.Description, "install helm charts") + mockInstaller.AssertExpectations(t) mockHelmClient.AssertExpectations(t) }) @@ -491,6 +497,10 @@ func TestComponentStatusTracking(t *testing.T) { return opts.ReleaseName == "failing-app" })).Return((*helmrelease.Release)(nil), errors.New("helm install failed")) + // Create mock installer that succeeds (so we get to Helm charts) + mockInstaller := &MockKotsCLIInstaller{} + mockInstaller.On("Install", mock.Anything).Return(nil) + // Create manager with in-memory store appInstallStore := appinstallstore.NewMemoryStore(appinstallstore.WithAppInstall(types.AppInstall{ Status: types.Status{State: types.StatePending}, @@ -501,6 +511,7 @@ func TestComponentStatusTracking(t *testing.T) { WithK8sVersion("v1.33.0"), WithLicense([]byte(`{"spec":{"appSlug":"test-app"}}`)), WithClusterID("test-cluster"), + WithKotsCLI(mockInstaller), WithHelmClient(mockHelmClient), ) require.NoError(t, err) @@ -525,6 +536,7 @@ func TestComponentStatusTracking(t *testing.T) { // Overall status should be failed assert.Equal(t, types.StateFailed, appInstall.Status.State) + mockInstaller.AssertExpectations(t) mockHelmClient.AssertExpectations(t) }) }