Skip to content
Draft
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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,14 @@ jobs:
int-tests-kind:
name: Integration tests (kind)
runs-on: ubuntu-latest
needs:
- should-run-int-tests-kind
if: needs.should-run-int-tests-kind.outputs.run == 'true'
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0 # necessary for getting the last tag
- name: Setup go
uses: actions/setup-go@v5
with:
Expand All @@ -178,10 +182,14 @@ jobs:
int-tests-kind-ha-registry:
name: Integration tests (kind) HA registry
runs-on: ubuntu-latest
needs:
- should-run-int-tests-kind
if: needs.should-run-int-tests-kind.outputs.run == 'true'
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0 # necessary for getting the last tag
- name: Setup go
uses: actions/setup-go@v5
with:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ jobs:
version=$(gh release list --repo axboe/fio --json name,isLatest | jq -r '.[] | select(.isLatest)|.name' | cut -d- -f2)
echo "fio version: $version"
sed -i "/^FIO_VERSION/c\FIO_VERSION = $version" versions.mk
- name: Helm
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=$(gh release list --repo helm/helm --json tagName,isLatest | jq -r '.[] | select(.isLatest) | .tagName')
echo "helm version: $version"
sed -i "/^HELM_VERSION/c\HELM_VERSION = $version" versions.mk
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
Expand Down
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,23 @@ output/bins/kubectl-support_bundle-%:
rm -rf output/tmp
touch $@

.PHONY: cmd/installer/goods/bins/helm
cmd/installer/goods/bins/helm:
$(MAKE) output/bins/helm-$(HELM_VERSION)-$(ARCH)
cp output/bins/helm-$(HELM_VERSION)-$(ARCH) $@
touch $@

output/bins/helm-%:
mkdir -p output/bins
mkdir -p output/tmp
curl --retry 5 --retry-all-errors -fL -o output/tmp/helm.tar.gz \
https://get.helm.sh/helm-$(call split-hyphen,$*,1)-$(OS)-$(call split-hyphen,$*,2).tar.gz
tar -xzf output/tmp/helm.tar.gz -C output/tmp
mv output/tmp/$(OS)-$(call split-hyphen,$*,2)/helm $@
rm -rf output/tmp
chmod +x $@
touch $@

.PHONY: cmd/installer/goods/bins/kubectl-preflight
cmd/installer/goods/bins/kubectl-preflight:
$(MAKE) output/bins/kubectl-preflight-$(TROUBLESHOOT_VERSION)-$(ARCH)
Expand Down Expand Up @@ -229,6 +246,7 @@ static: cmd/installer/goods/bins/k0s \
cmd/installer/goods/bins/kubectl-support_bundle \
cmd/installer/goods/bins/local-artifact-mirror \
cmd/installer/goods/bins/fio \
cmd/installer/goods/bins/helm \
cmd/installer/goods/internal/bins/kubectl-kots

.PHONY: static-dryrun
Expand All @@ -238,6 +256,7 @@ static-dryrun:
cmd/installer/goods/bins/kubectl-support_bundle \
cmd/installer/goods/bins/local-artifact-mirror \
cmd/installer/goods/bins/fio \
cmd/installer/goods/bins/helm \
cmd/installer/goods/internal/bins/kubectl-kots

.PHONY: embedded-cluster-linux-amd64
Expand Down
17 changes: 17 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
linuxinstall "github.com/replicatedhq/embedded-cluster/api/controllers/linux/install"
"github.com/replicatedhq/embedded-cluster/api/pkg/logger"
"github.com/replicatedhq/embedded-cluster/api/types"
"github.com/replicatedhq/embedded-cluster/pkg/helm"
"github.com/replicatedhq/embedded-cluster/pkg/metrics"
"github.com/replicatedhq/embedded-cluster/pkg/runtimeconfig"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -37,6 +38,7 @@ import (
type API struct {
cfg types.APIConfig

hcli helm.Client
logger logrus.FieldLogger
metricsReporter metrics.ReporterInterface

Expand Down Expand Up @@ -93,8 +95,19 @@ func WithMetricsReporter(metricsReporter metrics.ReporterInterface) Option {
}
}

// WithHelmClient configures the helm client for the API.
func WithHelmClient(hcli helm.Client) Option {
return func(a *API) {
a.hcli = hcli
}
}

// New creates a new API instance.
func New(cfg types.APIConfig, opts ...Option) (*API, error) {
if cfg.InstallTarget == "" {
return nil, fmt.Errorf("target is required")
}

api := &API{
cfg: cfg,
}
Expand All @@ -115,6 +128,10 @@ func New(cfg types.APIConfig, opts ...Option) (*API, error) {
api.logger = l
}

if err := api.initClients(); err != nil {
return nil, fmt.Errorf("init clients: %w", err)
}

if err := api.initHandlers(); err != nil {
return nil, fmt.Errorf("init handlers: %w", err)
}
Expand Down
88 changes: 88 additions & 0 deletions api/clients.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package api

import (
"fmt"

"github.com/replicatedhq/embedded-cluster/api/internal/clients"
"github.com/replicatedhq/embedded-cluster/api/types"
"github.com/replicatedhq/embedded-cluster/pkg/helm"
"github.com/replicatedhq/embedded-cluster/pkg/versions"
)

func (a *API) initClients() error {
if a.hcli == nil {
if err := a.initHelmClient(); err != nil {
return fmt.Errorf("init helm client: %w", err)
}
}
return nil
}

// initHelmClient initializes the Helm client based on the installation target
func (a *API) initHelmClient() error {
switch a.cfg.InstallTarget {
case types.InstallTargetLinux:
return a.initLinuxHelmClient()
case types.InstallTargetKubernetes:
return a.initKubernetesHelmClient()
default:
return fmt.Errorf("unsupported install target: %s", a.cfg.InstallTarget)
}
}

// initLinuxHelmClient initializes the Helm client for Linux installations
func (a *API) initLinuxHelmClient() error {
airgapPath := ""
if a.cfg.AirgapBundle != "" {
airgapPath = a.cfg.RuntimeConfig.EmbeddedClusterChartsSubDir()
}

hcli, err := helm.NewClient(helm.HelmOptions{
HelmPath: a.cfg.RuntimeConfig.PathToEmbeddedClusterBinary("helm"),
KubernetesEnvSettings: a.cfg.RuntimeConfig.GetKubernetesEnvSettings(),
K8sVersion: versions.K0sVersion,
AirgapPath: airgapPath,
})
if err != nil {
return fmt.Errorf("create linux helm client: %w", err)
}

a.hcli = hcli
return nil
}

// initKubernetesHelmClient initializes the Helm client for Kubernetes installations
func (a *API) initKubernetesHelmClient() error {
// get the kubernetes version
kcli, err := clients.NewDiscoveryClient(clients.KubeClientOptions{
RESTClientGetter: a.cfg.Installation.GetKubernetesEnvSettings().RESTClientGetter(),
})
if err != nil {
return fmt.Errorf("create discovery client: %w", err)
}
k8sVersion, err := kcli.ServerVersion()
if err != nil {
return fmt.Errorf("get server version: %w", err)
}

// get the helm binary path
helmPath, err := a.cfg.Installation.PathToEmbeddedBinary("helm")
if err != nil {
return fmt.Errorf("get helm path: %w", err)
}

// create the helm client
hcli, err := helm.NewClient(helm.HelmOptions{
HelmPath: helmPath,
KubernetesEnvSettings: a.cfg.Installation.GetKubernetesEnvSettings(),
// TODO: how can we support airgap?
AirgapPath: "",
K8sVersion: k8sVersion.String(),
})
if err != nil {
return fmt.Errorf("create kubernetes helm client: %w", err)
}

a.hcli = hcli
return nil
}
12 changes: 11 additions & 1 deletion api/controllers/app/install/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/replicatedhq/embedded-cluster/api/internal/store"
"github.com/replicatedhq/embedded-cluster/api/pkg/logger"
"github.com/replicatedhq/embedded-cluster/api/types"
"github.com/replicatedhq/embedded-cluster/pkg/helm"
"github.com/replicatedhq/embedded-cluster/pkg/release"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
"github.com/sirupsen/logrus"
Expand All @@ -27,7 +28,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, ignoreAppPreflights bool) error
InstallApp(ctx context.Context, opts InstallAppOptions) error
GetAppInstallStatus(ctx context.Context) (types.AppInstall, error)
}

Expand All @@ -47,6 +48,7 @@ type InstallController struct {
clusterID string
airgapBundle string
privateCACertConfigMapName string
hcli helm.Client
}

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

func WithHelmClient(hcli helm.Client) InstallControllerOption {
return func(c *InstallController) {
c.hcli = hcli
}
}

func NewInstallController(opts ...InstallControllerOption) (*InstallController, error) {
controller := &InstallController{
logger: logger.NewDiscardLogger(),
Expand Down Expand Up @@ -190,6 +198,7 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
appreleasemanager.WithReleaseData(controller.releaseData),
appreleasemanager.WithLicense(license),
appreleasemanager.WithPrivateCACertConfigMapName(controller.privateCACertConfigMapName),
appreleasemanager.WithHelmClient(controller.hcli),
)
if err != nil {
return nil, fmt.Errorf("create app release manager: %w", err)
Expand All @@ -205,6 +214,7 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
appinstallmanager.WithClusterID(controller.clusterID),
appinstallmanager.WithAirgapBundle(controller.airgapBundle),
appinstallmanager.WithAppInstallStore(controller.store.AppInstallStore()),
appinstallmanager.WithHelmClient(controller.hcli),
)
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, ignoreAppPreflights bool) error {
args := m.Called(ctx, ignoreAppPreflights)
func (m *MockController) InstallApp(ctx context.Context, opts InstallAppOptions) error {
args := m.Called(ctx, opts)
return args.Error(0)
}

Expand Down
31 changes: 25 additions & 6 deletions api/controllers/app/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ 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, ignoreAppPreflights bool) (finalErr error) {
func (c *InstallController) InstallApp(ctx context.Context, opts InstallAppOptions) (finalErr error) {
lock, err := c.stateMachine.AcquireLock()
if err != nil {
return types.NewConflictError(err)
Expand All @@ -33,7 +40,7 @@ func (c *InstallController) InstallApp(ctx context.Context, ignoreAppPreflights
// 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 !ignoreAppPreflights || !allowIgnoreAppPreflights {
if !opts.IgnoreAppPreflights || !allowIgnoreAppPreflights {
return types.NewBadRequestError(ErrAppPreflightChecksFailed)
}
err = c.stateMachine.Transition(lock, states.StateAppPreflightsFailedBypassed)
Expand All @@ -47,9 +54,15 @@ func (c *InstallController) InstallApp(ctx context.Context, ignoreAppPreflights
}

// Get config values for app installation
configValues, err := c.appConfigManager.GetKotsadmConfigValues()
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()
if err != nil {
return fmt.Errorf("get kotsadm config values for app install: %w", err)
return fmt.Errorf("get kots config values for app install: %w", err)
}

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

// Install the app
err := c.appInstallManager.Install(ctx, configValues)
// 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)
if err != nil {
return fmt.Errorf("install app: %w", err)
}
Expand Down
Loading