Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 1 addition & 18 deletions api/controllers/app/install/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/replicatedhq/embedded-cluster/pkg/release"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
"github.com/sirupsen/logrus"
"k8s.io/cli-runtime/pkg/genericclioptions"
kyaml "sigs.k8s.io/yaml"
)

Expand All @@ -28,7 +27,7 @@ type Controller interface {
GetAppPreflightStatus(ctx context.Context) (types.Status, error)
GetAppPreflightOutput(ctx context.Context) (*types.PreflightsOutput, error)
GetAppPreflightTitles(ctx context.Context) ([]string, error)
InstallApp(ctx context.Context, opts InstallAppOptions) error
InstallApp(ctx context.Context, ignoreAppPreflights bool) error
GetAppInstallStatus(ctx context.Context) (types.AppInstall, error)
}

Expand All @@ -48,8 +47,6 @@ type InstallController struct {
clusterID string
airgapBundle string
privateCACertConfigMapName string
restClientGetter genericclioptions.RESTClientGetter
kubeConfigPath string
}

type InstallControllerOption func(*InstallController)
Expand Down Expand Up @@ -132,18 +129,6 @@ func WithPrivateCACertConfigMapName(configMapName string) InstallControllerOptio
}
}

func WithRESTClientGetter(restClientGetter genericclioptions.RESTClientGetter) InstallControllerOption {
return func(c *InstallController) {
c.restClientGetter = restClientGetter
}
}

func WithKubeConfigPath(kubeConfigPath string) InstallControllerOption {
return func(c *InstallController) {
c.kubeConfigPath = kubeConfigPath
}
}

func NewInstallController(opts ...InstallControllerOption) (*InstallController, error) {
controller := &InstallController{
logger: logger.NewDiscardLogger(),
Expand Down Expand Up @@ -220,8 +205,6 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
appinstallmanager.WithClusterID(controller.clusterID),
appinstallmanager.WithAirgapBundle(controller.airgapBundle),
appinstallmanager.WithAppInstallStore(controller.store.AppInstallStore()),
appinstallmanager.WithRESTClientGetter(controller.restClientGetter),
appinstallmanager.WithKubeConfigPath(controller.kubeConfigPath),
)
if err != nil {
return nil, fmt.Errorf("create app install manager: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions api/controllers/app/install/controller_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ func (m *MockController) GetAppPreflightTitles(ctx context.Context) ([]string, e
}

// InstallApp mocks the InstallApp method
func (m *MockController) InstallApp(ctx context.Context, opts InstallAppOptions) error {
args := m.Called(ctx, opts)
func (m *MockController) InstallApp(ctx context.Context, ignoreAppPreflights bool) error {
args := m.Called(ctx, ignoreAppPreflights)
return args.Error(0)
}

Expand Down
31 changes: 6 additions & 25 deletions api/controllers/app/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,14 @@ import (

states "github.com/replicatedhq/embedded-cluster/api/internal/states/install"
"github.com/replicatedhq/embedded-cluster/api/types"
ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1"
)

var (
ErrAppPreflightChecksFailed = errors.New("app preflight checks failed")
)

type InstallAppOptions struct {
IgnoreAppPreflights bool
ProxySpec *ecv1beta1.ProxySpec
RegistrySettings *types.RegistrySettings
}

// InstallApp triggers app installation with proper state transitions and panic handling
func (c *InstallController) InstallApp(ctx context.Context, opts InstallAppOptions) (finalErr error) {
func (c *InstallController) InstallApp(ctx context.Context, ignoreAppPreflights bool) (finalErr error) {
lock, err := c.stateMachine.AcquireLock()
if err != nil {
return types.NewConflictError(err)
Expand All @@ -40,7 +33,7 @@ func (c *InstallController) InstallApp(ctx context.Context, opts InstallAppOptio
// Check if app preflights have failed and if we should ignore them
if c.stateMachine.CurrentState() == states.StateAppPreflightsFailed {
allowIgnoreAppPreflights := true // TODO: implement once we check for strict app preflights
if !opts.IgnoreAppPreflights || !allowIgnoreAppPreflights {
if !ignoreAppPreflights || !allowIgnoreAppPreflights {
return types.NewBadRequestError(ErrAppPreflightChecksFailed)
}
err = c.stateMachine.Transition(lock, states.StateAppPreflightsFailedBypassed)
Expand All @@ -54,15 +47,9 @@ func (c *InstallController) InstallApp(ctx context.Context, opts InstallAppOptio
}

// Get config values for app installation
appConfigValues, err := c.GetAppConfigValues(ctx)
if err != nil {
return fmt.Errorf("get app config values for app install: %w", err)
}

// Get KOTS config values for the KOTS CLI
kotsConfigValues, err := c.appConfigManager.GetKotsadmConfigValues()
configValues, err := c.appConfigManager.GetKotsadmConfigValues()
if err != nil {
return fmt.Errorf("get kots config values for app install: %w", err)
return fmt.Errorf("get kotsadm config values for app install: %w", err)
}

err = c.stateMachine.Transition(lock, states.StateAppInstalling)
Expand Down Expand Up @@ -93,14 +80,8 @@ func (c *InstallController) InstallApp(ctx context.Context, opts InstallAppOptio
}
}()

// Extract installable Helm charts from release manager
installableCharts, err := c.appReleaseManager.ExtractInstallableHelmCharts(ctx, appConfigValues, opts.ProxySpec, opts.RegistrySettings)
if err != nil {
return fmt.Errorf("extract installable helm charts: %w", err)
}

// Install the app with installable charts and kots config values
err = c.appInstallManager.Install(ctx, installableCharts, kotsConfigValues)
// Install the app
err := c.appInstallManager.Install(ctx, configValues)
if err != nil {
return fmt.Errorf("install app: %w", err)
}
Expand Down
155 changes: 41 additions & 114 deletions api/controllers/app/install/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
states "github.com/replicatedhq/embedded-cluster/api/internal/states/install"
"github.com/replicatedhq/embedded-cluster/api/internal/store"
"github.com/replicatedhq/embedded-cluster/api/types"
ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1"
"github.com/replicatedhq/embedded-cluster/pkg/release"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
Expand Down Expand Up @@ -504,18 +503,16 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
tests := []struct {
name string
ignoreAppPreflights bool
proxySpec *ecv1beta1.ProxySpec
registrySettings *types.RegistrySettings
currentState statemachine.State
expectedState statemachine.State
setupMocks func(*appconfig.MockAppConfigManager, *appreleasemanager.MockAppReleaseManager, *appinstallmanager.MockAppInstallManager)
setupMocks func(*appconfig.MockAppConfigManager, *appinstallmanager.MockAppInstallManager)
expectedErr bool
}{
{
name: "invalid state transition from succeeded state",
currentState: states.StateSucceeded,
expectedState: states.StateSucceeded,
setupMocks: func(acm *appconfig.MockAppConfigManager, arm *appreleasemanager.MockAppReleaseManager, aim *appinstallmanager.MockAppInstallManager) {
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
// No mocks needed for invalid state transition
},
expectedErr: true,
Expand All @@ -524,37 +521,27 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
name: "invalid state transition from infrastructure installing state",
currentState: states.StateInfrastructureInstalling,
expectedState: states.StateInfrastructureInstalling,
setupMocks: func(acm *appconfig.MockAppConfigManager, arm *appreleasemanager.MockAppReleaseManager, aim *appinstallmanager.MockAppInstallManager) {
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
// No mocks needed for invalid state transition
},
expectedErr: true,
},
{
name: "successful app installation from app preflights succeeded state with helm charts",
name: "successful app installation from app preflights succeeded state",
currentState: states.StateAppPreflightsSucceeded,
expectedState: states.StateSucceeded,
setupMocks: func(acm *appconfig.MockAppConfigManager, arm *appreleasemanager.MockAppReleaseManager, aim *appinstallmanager.MockAppInstallManager) {
configValues := kotsv1beta1.ConfigValues{
Spec: kotsv1beta1.ConfigValuesSpec{
Values: map[string]kotsv1beta1.ConfigValue{
"test-key": {Value: "test-value"},
},
},
}
expectedCharts := []types.InstallableHelmChart{
{
Archive: []byte("chart-archive-data"),
Values: map[string]any{"key": "value"},
},
}
appConfigValues := types.AppConfigValues{
"test-key": types.AppConfigValue{Value: "test-value"},
}
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
mock.InOrder(
acm.On("GetConfigValues").Return(appConfigValues, nil),
acm.On("GetKotsadmConfigValues").Return(configValues, nil),
arm.On("ExtractInstallableHelmCharts", mock.Anything, appConfigValues, mock.AnythingOfType("*v1beta1.ProxySpec"), mock.AnythingOfType("*types.RegistrySettings")).Return(expectedCharts, nil),
aim.On("Install", mock.Anything, expectedCharts, configValues).Return(nil),
acm.On("GetKotsadmConfigValues").Return(kotsv1beta1.ConfigValues{
Spec: kotsv1beta1.ConfigValuesSpec{
Values: map[string]kotsv1beta1.ConfigValue{
"test-key": {Value: "test-value"},
},
},
}, nil),
aim.On("Install", mock.Anything, mock.MatchedBy(func(cv kotsv1beta1.ConfigValues) bool {
return cv.Spec.Values["test-key"].Value == "test-value"
})).Return(nil),
)
},
expectedErr: false,
Expand All @@ -563,22 +550,18 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
name: "successful app installation from app preflights failed bypassed state",
currentState: states.StateAppPreflightsFailedBypassed,
expectedState: states.StateSucceeded,
setupMocks: func(acm *appconfig.MockAppConfigManager, arm *appreleasemanager.MockAppReleaseManager, aim *appinstallmanager.MockAppInstallManager) {
configValues := kotsv1beta1.ConfigValues{
Spec: kotsv1beta1.ConfigValuesSpec{
Values: map[string]kotsv1beta1.ConfigValue{
"test-key": {Value: "test-value"},
},
},
}
appConfigValues := types.AppConfigValues{
"test-key": types.AppConfigValue{Value: "test-value"},
}
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
mock.InOrder(
acm.On("GetConfigValues").Return(appConfigValues, nil),
acm.On("GetKotsadmConfigValues").Return(configValues, nil),
arm.On("ExtractInstallableHelmCharts", mock.Anything, appConfigValues, mock.AnythingOfType("*v1beta1.ProxySpec"), mock.AnythingOfType("*types.RegistrySettings")).Return([]types.InstallableHelmChart{}, nil),
aim.On("Install", mock.Anything, []types.InstallableHelmChart{}, configValues).Return(nil),
acm.On("GetKotsadmConfigValues").Return(kotsv1beta1.ConfigValues{
Spec: kotsv1beta1.ConfigValuesSpec{
Values: map[string]kotsv1beta1.ConfigValue{
"test-key": {Value: "test-value"},
},
},
}, nil),
aim.On("Install", mock.Anything, mock.MatchedBy(func(cv kotsv1beta1.ConfigValues) bool {
return cv.Spec.Values["test-key"].Value == "test-value"
})).Return(nil),
)
},
expectedErr: false,
Expand All @@ -587,11 +570,7 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
name: "get config values error",
currentState: states.StateAppPreflightsSucceeded,
expectedState: states.StateAppPreflightsSucceeded,
setupMocks: func(acm *appconfig.MockAppConfigManager, arm *appreleasemanager.MockAppReleaseManager, aim *appinstallmanager.MockAppInstallManager) {
appConfigValues := types.AppConfigValues{
"test-key": types.AppConfigValue{Value: "test-value"},
}
acm.On("GetConfigValues").Return(appConfigValues, nil)
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
acm.On("GetKotsadmConfigValues").Return(kotsv1beta1.ConfigValues{}, errors.New("config values error"))
},
expectedErr: true,
Expand All @@ -601,22 +580,18 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
ignoreAppPreflights: true,
currentState: states.StateAppPreflightsFailed,
expectedState: states.StateSucceeded,
setupMocks: func(acm *appconfig.MockAppConfigManager, arm *appreleasemanager.MockAppReleaseManager, aim *appinstallmanager.MockAppInstallManager) {
configValues := kotsv1beta1.ConfigValues{
Spec: kotsv1beta1.ConfigValuesSpec{
Values: map[string]kotsv1beta1.ConfigValue{
"test-key": {Value: "test-value"},
},
},
}
appConfigValues := types.AppConfigValues{
"test-key": types.AppConfigValue{Value: "test-value"},
}
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
mock.InOrder(
acm.On("GetConfigValues").Return(appConfigValues, nil),
acm.On("GetKotsadmConfigValues").Return(configValues, nil),
arm.On("ExtractInstallableHelmCharts", mock.Anything, appConfigValues, mock.AnythingOfType("*v1beta1.ProxySpec"), mock.AnythingOfType("*types.RegistrySettings")).Return([]types.InstallableHelmChart{}, nil),
aim.On("Install", mock.Anything, []types.InstallableHelmChart{}, configValues).Return(nil),
acm.On("GetKotsadmConfigValues").Return(kotsv1beta1.ConfigValues{
Spec: kotsv1beta1.ConfigValuesSpec{
Values: map[string]kotsv1beta1.ConfigValue{
"test-key": {Value: "test-value"},
},
},
}, nil),
aim.On("Install", mock.Anything, mock.MatchedBy(func(cv kotsv1beta1.ConfigValues) bool {
return cv.Spec.Values["test-key"].Value == "test-value"
})).Return(nil),
)
},
expectedErr: false,
Expand All @@ -626,54 +601,11 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
ignoreAppPreflights: false,
currentState: states.StateAppPreflightsFailed,
expectedState: states.StateAppPreflightsFailed,
setupMocks: func(acm *appconfig.MockAppConfigManager, arm *appreleasemanager.MockAppReleaseManager, aim *appinstallmanager.MockAppInstallManager) {
setupMocks: func(acm *appconfig.MockAppConfigManager, aim *appinstallmanager.MockAppInstallManager) {
// No mocks needed as method should return early with error
},
expectedErr: true,
},
{
name: "successful app installation with proxy spec passed to helm chart extraction",
currentState: states.StateAppPreflightsSucceeded,
expectedState: states.StateSucceeded,
proxySpec: &ecv1beta1.ProxySpec{
HTTPProxy: "http://proxy.example.com:8080",
HTTPSProxy: "https://proxy.example.com:8080",
NoProxy: "localhost,127.0.0.1",
},
registrySettings: &types.RegistrySettings{
HasLocalRegistry: true,
Host: "10.128.0.11:5000",
},
setupMocks: func(acm *appconfig.MockAppConfigManager, arm *appreleasemanager.MockAppReleaseManager, aim *appinstallmanager.MockAppInstallManager) {
configValues := kotsv1beta1.ConfigValues{
Spec: kotsv1beta1.ConfigValuesSpec{
Values: map[string]kotsv1beta1.ConfigValue{
"test-key": {Value: "test-value"},
},
},
}
appConfigValues := types.AppConfigValues{
"test-key": types.AppConfigValue{Value: "test-value"},
}
expectedCharts := []types.InstallableHelmChart{
{
Archive: []byte("chart-with-proxy-template"),
Values: map[string]any{"proxy_url": "http://proxy.example.com:8080"},
},
}
mock.InOrder(
acm.On("GetConfigValues").Return(appConfigValues, nil),
acm.On("GetKotsadmConfigValues").Return(configValues, nil),
arm.On("ExtractInstallableHelmCharts", mock.Anything, appConfigValues, mock.MatchedBy(func(proxySpec *ecv1beta1.ProxySpec) bool {
return proxySpec != nil
}), mock.MatchedBy(func(registrySettings *types.RegistrySettings) bool {
return registrySettings != nil
})).Return(expectedCharts, nil),
aim.On("Install", mock.Anything, expectedCharts, configValues).Return(nil),
)
},
expectedErr: false,
},
}

for _, tt := range tests {
Expand All @@ -695,12 +627,8 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
)
require.NoError(t, err, "failed to create install controller")

tt.setupMocks(appConfigManager, appReleaseManager, appInstallManager)
err = controller.InstallApp(t.Context(), InstallAppOptions{
IgnoreAppPreflights: tt.ignoreAppPreflights,
ProxySpec: tt.proxySpec,
RegistrySettings: tt.registrySettings,
})
tt.setupMocks(appConfigManager, appInstallManager)
err = controller.InstallApp(t.Context(), tt.ignoreAppPreflights)

if tt.expectedErr {
assert.Error(t, err)
Expand All @@ -715,7 +643,6 @@ func (s *AppInstallControllerTestSuite) TestInstallApp() {
assert.False(t, sm.IsLockAcquired(), "state machine should not be locked after app installation")

appConfigManager.AssertExpectations(s.T())
appReleaseManager.AssertExpectations(s.T())
appInstallManager.AssertExpectations(s.T())
})
}
Expand Down
1 change: 0 additions & 1 deletion api/controllers/kubernetes/install/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
appcontroller.WithConfigValues(controller.configValues),
appcontroller.WithAirgapBundle(controller.airgapBundle),
appcontroller.WithPrivateCACertConfigMapName(""), // Private CA ConfigMap functionality not yet implemented for Kubernetes installations
appcontroller.WithRESTClientGetter(controller.restClientGetter),
)
if err != nil {
return nil, fmt.Errorf("create app install controller: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions api/controllers/kubernetes/install/controller_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ func (m *MockController) RunAppPreflights(ctx context.Context, opts appcontrolle
}

// InstallApp mocks the InstallApp method
func (m *MockController) InstallApp(ctx context.Context, opts appcontroller.InstallAppOptions) error {
args := m.Called(ctx, opts)
func (m *MockController) InstallApp(ctx context.Context, ignoreAppPreflights bool) error {
args := m.Called(ctx, ignoreAppPreflights)
return args.Error(0)
}

Expand Down
1 change: 0 additions & 1 deletion api/controllers/linux/install/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
appcontroller.WithClusterID(controller.clusterID),
appcontroller.WithAirgapBundle(controller.airgapBundle),
appcontroller.WithPrivateCACertConfigMapName(adminconsole.PrivateCASConfigMapName), // Linux installations use the ConfigMap
appcontroller.WithKubeConfigPath(controller.rc.PathToKubeConfig()),
)
if err != nil {
return nil, fmt.Errorf("create app install controller: %w", err)
Expand Down
Loading
Loading