helm: re-enable timeout flag (#2658)

* Honor (hidden) timeout flag for applying helm charts
* Set only internally used structs to private

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-11-29 14:55:10 +01:00 committed by GitHub
parent e06848c68a
commit b3c734b804
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 137 additions and 129 deletions

View File

@ -126,12 +126,12 @@ func NewApplyCmd() *cobra.Command {
cmd.Flags().Bool("merge-kubeconfig", false, "merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config") cmd.Flags().Bool("merge-kubeconfig", false, "merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config")
cmd.Flags().BoolP("yes", "y", false, "run command without further confirmation\n"+ cmd.Flags().BoolP("yes", "y", false, "run command without further confirmation\n"+
"WARNING: the command might delete or update existing resources without additional checks. Please read the docs.\n") "WARNING: the command might delete or update existing resources without additional checks. Please read the docs.\n")
cmd.Flags().Duration("timeout", 5*time.Minute, "change helm upgrade timeout\n"+ cmd.Flags().Duration("helm-timeout", 10*time.Minute, "change helm install/upgrade timeout\n"+
"Might be useful for slow connections or big clusters.") "Might be useful for slow connections or big clusters.")
cmd.Flags().StringSlice("skip-phases", nil, "comma-separated list of upgrade phases to skip\n"+ cmd.Flags().StringSlice("skip-phases", nil, "comma-separated list of upgrade phases to skip\n"+
fmt.Sprintf("one or multiple of %s", formatSkipPhases())) fmt.Sprintf("one or multiple of %s", formatSkipPhases()))
must(cmd.Flags().MarkHidden("timeout")) must(cmd.Flags().MarkHidden("helm-timeout"))
must(cmd.RegisterFlagCompletionFunc("skip-phases", skipPhasesCompletion)) must(cmd.RegisterFlagCompletionFunc("skip-phases", skipPhasesCompletion))
return cmd return cmd
@ -140,12 +140,12 @@ func NewApplyCmd() *cobra.Command {
// applyFlags defines the flags for the apply command. // applyFlags defines the flags for the apply command.
type applyFlags struct { type applyFlags struct {
rootFlags rootFlags
yes bool yes bool
conformance bool conformance bool
mergeConfigs bool mergeConfigs bool
upgradeTimeout time.Duration helmTimeout time.Duration
helmWaitMode helm.WaitMode helmWaitMode helm.WaitMode
skipPhases skipPhases skipPhases skipPhases
} }
// parse the apply command flags. // parse the apply command flags.
@ -174,9 +174,9 @@ func (f *applyFlags) parse(flags *pflag.FlagSet) error {
return fmt.Errorf("getting 'yes' flag: %w", err) return fmt.Errorf("getting 'yes' flag: %w", err)
} }
f.upgradeTimeout, err = flags.GetDuration("timeout") f.helmTimeout, err = flags.GetDuration("helm-timeout")
if err != nil { if err != nil {
return fmt.Errorf("getting 'timeout' flag: %w", err) return fmt.Errorf("getting 'helm-timeout' flag: %w", err)
} }
f.conformance, err = flags.GetBool("conformance") f.conformance, err = flags.GetBool("conformance")

View File

@ -97,8 +97,8 @@ func TestParseApplyFlags(t *testing.T) {
"default flags": { "default flags": {
flags: defaultFlags(), flags: defaultFlags(),
wantFlags: applyFlags{ wantFlags: applyFlags{
helmWaitMode: helm.WaitModeAtomic, helmWaitMode: helm.WaitModeAtomic,
upgradeTimeout: 5 * time.Minute, helmTimeout: 10 * time.Minute,
}, },
}, },
"skip phases": { "skip phases": {
@ -108,9 +108,9 @@ func TestParseApplyFlags(t *testing.T) {
return flags return flags
}(), }(),
wantFlags: applyFlags{ wantFlags: applyFlags{
skipPhases: newPhases(skipHelmPhase, skipK8sPhase), skipPhases: newPhases(skipHelmPhase, skipK8sPhase),
helmWaitMode: helm.WaitModeAtomic, helmWaitMode: helm.WaitModeAtomic,
upgradeTimeout: 5 * time.Minute, helmTimeout: 10 * time.Minute,
}, },
}, },
"skip helm wait": { "skip helm wait": {
@ -120,8 +120,8 @@ func TestParseApplyFlags(t *testing.T) {
return flags return flags
}(), }(),
wantFlags: applyFlags{ wantFlags: applyFlags{
helmWaitMode: helm.WaitModeNone, helmWaitMode: helm.WaitModeNone,
upgradeTimeout: 5 * time.Minute, helmTimeout: 10 * time.Minute,
}, },
}, },
} }

View File

@ -37,6 +37,7 @@ func (a *applyCmd) runHelmApply(
Force: a.flags.force, Force: a.flags.force,
Conformance: a.flags.conformance, Conformance: a.flags.conformance,
HelmWaitMode: a.flags.helmWaitMode, HelmWaitMode: a.flags.helmWaitMode,
ApplyTimeout: a.flags.helmTimeout,
AllowDestructive: helm.DenyDestructive, AllowDestructive: helm.DenyDestructive,
} }
helmApplier, err := a.newHelmClient(constants.AdminConfFilename, a.log) helmApplier, err := a.newHelmClient(constants.AdminConfFilename, a.log)

View File

@ -19,11 +19,7 @@ import (
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
) )
const ( const applyRetryInterval = 30 * time.Second
// timeout is the maximum time given per helm action.
timeout = 10 * time.Minute
applyRetryInterval = 30 * time.Second
)
type applyAction interface { type applyAction interface {
Apply(context.Context) error Apply(context.Context) error
@ -45,12 +41,12 @@ func newActionConfig(kubeconfig string, logger debugLog) (*action.Configuration,
return actionConfig, nil return actionConfig, nil
} }
func newHelmInstallAction(config *action.Configuration, release Release) *action.Install { func newHelmInstallAction(config *action.Configuration, release release, timeout time.Duration) *action.Install {
action := action.NewInstall(config) action := action.NewInstall(config)
action.Namespace = constants.HelmNamespace action.Namespace = constants.HelmNamespace
action.Timeout = timeout action.Timeout = timeout
action.ReleaseName = release.ReleaseName action.ReleaseName = release.releaseName
setWaitMode(action, release.WaitMode) setWaitMode(action, release.waitMode)
return action return action
} }
@ -73,7 +69,7 @@ func setWaitMode(a *action.Install, waitMode WaitMode) {
// installAction is an action that installs a helm chart. // installAction is an action that installs a helm chart.
type installAction struct { type installAction struct {
preInstall func(context.Context) error preInstall func(context.Context) error
release Release release release
helmAction *action.Install helmAction *action.Install
postInstall func(context.Context) error postInstall func(context.Context) error
log debugLog log debugLog
@ -104,13 +100,13 @@ func (a *installAction) SaveChart(chartsDir string, fileHandler file.Handler) er
} }
func (a *installAction) apply(ctx context.Context) error { func (a *installAction) apply(ctx context.Context) error {
_, err := a.helmAction.RunWithContext(ctx, a.release.Chart, a.release.Values) _, err := a.helmAction.RunWithContext(ctx, a.release.chart, a.release.values)
return err return err
} }
// ReleaseName returns the release name. // ReleaseName returns the release name.
func (a *installAction) ReleaseName() string { func (a *installAction) ReleaseName() string {
return a.release.ReleaseName return a.release.releaseName
} }
// IsAtomic returns true if the action is atomic. // IsAtomic returns true if the action is atomic.
@ -118,7 +114,7 @@ func (a *installAction) IsAtomic() bool {
return a.helmAction.Atomic return a.helmAction.Atomic
} }
func newHelmUpgradeAction(config *action.Configuration) *action.Upgrade { func newHelmUpgradeAction(config *action.Configuration, timeout time.Duration) *action.Upgrade {
action := action.NewUpgrade(config) action := action.NewUpgrade(config)
action.Namespace = constants.HelmNamespace action.Namespace = constants.HelmNamespace
action.Timeout = timeout action.Timeout = timeout
@ -131,7 +127,7 @@ func newHelmUpgradeAction(config *action.Configuration) *action.Upgrade {
type upgradeAction struct { type upgradeAction struct {
preUpgrade func(context.Context) error preUpgrade func(context.Context) error
postUpgrade func(context.Context) error postUpgrade func(context.Context) error
release Release release release
helmAction *action.Upgrade helmAction *action.Upgrade
log debugLog log debugLog
} }
@ -160,13 +156,13 @@ func (a *upgradeAction) SaveChart(chartsDir string, fileHandler file.Handler) er
} }
func (a *upgradeAction) apply(ctx context.Context) error { func (a *upgradeAction) apply(ctx context.Context) error {
_, err := a.helmAction.RunWithContext(ctx, a.release.ReleaseName, a.release.Chart, a.release.Values) _, err := a.helmAction.RunWithContext(ctx, a.release.releaseName, a.release.chart, a.release.values)
return err return err
} }
// ReleaseName returns the release name. // ReleaseName returns the release name.
func (a *upgradeAction) ReleaseName() string { func (a *upgradeAction) ReleaseName() string {
return a.release.ReleaseName return a.release.releaseName
} }
// IsAtomic returns true if the action is atomic. // IsAtomic returns true if the action is atomic.
@ -174,12 +170,12 @@ func (a *upgradeAction) IsAtomic() bool {
return a.helmAction.Atomic return a.helmAction.Atomic
} }
func saveChart(release Release, chartsDir string, fileHandler file.Handler) error { func saveChart(release release, chartsDir string, fileHandler file.Handler) error {
if err := saveChartToDisk(release.Chart, chartsDir, fileHandler); err != nil { if err := saveChartToDisk(release.chart, chartsDir, fileHandler); err != nil {
return fmt.Errorf("saving chart %s to %q: %w", release.ReleaseName, chartsDir, err) return fmt.Errorf("saving chart %s to %q: %w", release.releaseName, chartsDir, err)
} }
if err := fileHandler.WriteYAML(filepath.Join(chartsDir, release.Chart.Metadata.Name, "overrides.yaml"), release.Values); err != nil { if err := fileHandler.WriteYAML(filepath.Join(chartsDir, release.chart.Metadata.Name, "overrides.yaml"), release.values); err != nil {
return fmt.Errorf("saving override values for chart %s to %q: %w", release.ReleaseName, filepath.Join(chartsDir, release.Chart.Metadata.Name), err) return fmt.Errorf("saving override values for chart %s to %q: %w", release.releaseName, filepath.Join(chartsDir, release.chart.Metadata.Name), err)
} }
return nil return nil

View File

@ -11,6 +11,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/semver" "github.com/edgelesssys/constellation/v2/internal/semver"
@ -45,17 +46,19 @@ func newActionFactory(kubeClient crdClient, lister releaseVersionLister, actionC
} }
// GetActions returns a list of actions to apply the given releases. // GetActions returns a list of actions to apply the given releases.
func (a actionFactory) GetActions(releases []Release, configTargetVersion semver.Semver, force, allowDestructive bool) (actions []applyAction, includesUpgrade bool, err error) { func (a actionFactory) GetActions(
releases []release, configTargetVersion semver.Semver, force, allowDestructive bool, timeout time.Duration,
) (actions []applyAction, includesUpgrade bool, err error) {
upgradeErrs := []error{} upgradeErrs := []error{}
for _, release := range releases { for _, release := range releases {
err := a.appendNewAction(release, configTargetVersion, force, allowDestructive, &actions) err := a.appendNewAction(release, configTargetVersion, force, allowDestructive, timeout, &actions)
var invalidUpgrade *compatibility.InvalidUpgradeError var invalidUpgrade *compatibility.InvalidUpgradeError
if errors.As(err, &invalidUpgrade) { if errors.As(err, &invalidUpgrade) {
upgradeErrs = append(upgradeErrs, err) upgradeErrs = append(upgradeErrs, err)
continue continue
} }
if err != nil { if err != nil {
return actions, includesUpgrade, fmt.Errorf("creating action for %s: %w", release.ReleaseName, err) return actions, includesUpgrade, fmt.Errorf("creating action for %s: %w", release.releaseName, err)
} }
} }
for _, action := range actions { for _, action := range actions {
@ -67,17 +70,19 @@ func (a actionFactory) GetActions(releases []Release, configTargetVersion semver
return actions, includesUpgrade, errors.Join(upgradeErrs...) return actions, includesUpgrade, errors.Join(upgradeErrs...)
} }
func (a actionFactory) appendNewAction(release Release, configTargetVersion semver.Semver, force, allowDestructive bool, actions *[]applyAction) error { func (a actionFactory) appendNewAction(
newVersion, err := semver.New(release.Chart.Metadata.Version) release release, configTargetVersion semver.Semver, force, allowDestructive bool, timeout time.Duration, actions *[]applyAction,
) error {
newVersion, err := semver.New(release.chart.Metadata.Version)
if err != nil { if err != nil {
return fmt.Errorf("parsing chart version: %w", err) return fmt.Errorf("parsing chart version: %w", err)
} }
cliSupportsConfigVersion := configTargetVersion.Compare(newVersion) != 0 cliSupportsConfigVersion := configTargetVersion.Compare(newVersion) != 0
currentVersion, err := a.versionLister.currentVersion(release.ReleaseName) currentVersion, err := a.versionLister.currentVersion(release.releaseName)
if errors.Is(err, errReleaseNotFound) { if errors.Is(err, errReleaseNotFound) {
// Don't install a new release if the user's config specifies a different version than the CLI offers. // Don't install a new release if the user's config specifies a different version than the CLI offers.
if !force && isCLIVersionedRelease(release.ReleaseName) && cliSupportsConfigVersion { if !force && isCLIVersionedRelease(release.releaseName) && cliSupportsConfigVersion {
return compatibility.NewInvalidUpgradeError( return compatibility.NewInvalidUpgradeError(
currentVersion.String(), currentVersion.String(),
configTargetVersion.String(), configTargetVersion.String(),
@ -85,23 +90,23 @@ func (a actionFactory) appendNewAction(release Release, configTargetVersion semv
) )
} }
a.log.Debugf("Release %s not found, adding to new releases...", release.ReleaseName) a.log.Debugf("release %s not found, adding to new releases...", release.releaseName)
*actions = append(*actions, a.newInstall(release)) *actions = append(*actions, a.newInstall(release, timeout))
return nil return nil
} }
if err != nil { if err != nil {
return fmt.Errorf("getting version for %s: %w", release.ReleaseName, err) return fmt.Errorf("getting version for %s: %w", release.releaseName, err)
} }
a.log.Debugf("Current %s version: %s", release.ReleaseName, currentVersion) a.log.Debugf("Current %s version: %s", release.releaseName, currentVersion)
a.log.Debugf("New %s version: %s", release.ReleaseName, newVersion) a.log.Debugf("New %s version: %s", release.releaseName, newVersion)
if !force { if !force {
// For charts we package ourselves, the version is equal to the CLI version (charts are embedded in the binary). // For charts we package ourselves, the version is equal to the CLI version (charts are embedded in the binary).
// We need to make sure this matches with the version in a user's config, if an upgrade should be applied. // We need to make sure this matches with the version in a user's config, if an upgrade should be applied.
if isCLIVersionedRelease(release.ReleaseName) { if isCLIVersionedRelease(release.releaseName) {
// If target version is not a valid upgrade, don't upgrade any charts. // If target version is not a valid upgrade, don't upgrade any charts.
if err := configTargetVersion.IsUpgradeTo(currentVersion); err != nil { if err := configTargetVersion.IsUpgradeTo(currentVersion); err != nil {
return fmt.Errorf("invalid upgrade for %s: %w", release.ReleaseName, err) return fmt.Errorf("invalid upgrade for %s: %w", release.releaseName, err)
} }
// Target version is newer than current version, so we should perform an upgrade. // Target version is newer than current version, so we should perform an upgrade.
// Now make sure the target version is equal to the the CLI version. // Now make sure the target version is equal to the the CLI version.
@ -117,32 +122,32 @@ func (a actionFactory) appendNewAction(release Release, configTargetVersion semv
if err := newVersion.IsUpgradeTo(currentVersion); err != nil { if err := newVersion.IsUpgradeTo(currentVersion); err != nil {
// TODO(3u13r): Remove when Constellation v2.14 is released. // TODO(3u13r): Remove when Constellation v2.14 is released.
// We need to ignore that we jump from Cilium v1.12 to v1.15-pre. We have verified that this works. // We need to ignore that we jump from Cilium v1.12 to v1.15-pre. We have verified that this works.
if !(errors.Is(err, compatibility.ErrMinorDrift) && release.ReleaseName == "cilium") { if !(errors.Is(err, compatibility.ErrMinorDrift) && release.releaseName == "cilium") {
return fmt.Errorf("invalid upgrade for %s: %w", release.ReleaseName, err) return fmt.Errorf("invalid upgrade for %s: %w", release.releaseName, err)
} }
} }
} }
} }
if !allowDestructive && if !allowDestructive &&
release.ReleaseName == certManagerInfo.releaseName { release.releaseName == certManagerInfo.releaseName {
return ErrConfirmationMissing return ErrConfirmationMissing
} }
a.log.Debugf("Upgrading %s from %s to %s", release.ReleaseName, currentVersion, newVersion) a.log.Debugf("Upgrading %s from %s to %s", release.releaseName, currentVersion, newVersion)
*actions = append(*actions, a.newUpgrade(release)) *actions = append(*actions, a.newUpgrade(release, timeout))
return nil return nil
} }
func (a actionFactory) newInstall(release Release) *installAction { func (a actionFactory) newInstall(release release, timeout time.Duration) *installAction {
action := &installAction{helmAction: newHelmInstallAction(a.cfg, release), release: release, log: a.log} action := &installAction{helmAction: newHelmInstallAction(a.cfg, release, timeout), release: release, log: a.log}
return action return action
} }
func (a actionFactory) newUpgrade(release Release) *upgradeAction { func (a actionFactory) newUpgrade(release release, timeout time.Duration) *upgradeAction {
action := &upgradeAction{helmAction: newHelmUpgradeAction(a.cfg), release: release, log: a.log} action := &upgradeAction{helmAction: newHelmUpgradeAction(a.cfg, timeout), release: release, log: a.log}
if release.ReleaseName == constellationOperatorsInfo.releaseName { if release.releaseName == constellationOperatorsInfo.releaseName {
action.preUpgrade = func(ctx context.Context) error { action.preUpgrade = func(ctx context.Context) error {
if err := a.updateCRDs(ctx, release.Chart); err != nil { if err := a.updateCRDs(ctx, release.chart); err != nil {
return fmt.Errorf("updating operator CRDs: %w", err) return fmt.Errorf("updating operator CRDs: %w", err)
} }
return nil return nil

View File

@ -9,6 +9,7 @@ package helm
import ( import (
"errors" "errors"
"testing" "testing"
"time"
"github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
@ -26,7 +27,7 @@ func TestAppendNewAction(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
lister stubLister lister stubLister
release Release release release
configTargetVersion semver.Semver configTargetVersion semver.Semver
force bool force bool
allowDestructive bool allowDestructive bool
@ -35,9 +36,9 @@ func TestAppendNewAction(t *testing.T) {
}{ }{
"upgrade release": { "upgrade release": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
ReleaseName: "test", releaseName: "test",
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.0", Version: "1.1.0",
}, },
@ -47,9 +48,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"upgrade to same version": { "upgrade to same version": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
ReleaseName: "test", releaseName: "test",
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.0.0", Version: "1.0.0",
}, },
@ -61,9 +62,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"upgrade to older version": { "upgrade to older version": {
lister: stubLister{version: semver.NewFromInt(1, 1, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 1, 0, "")},
release: Release{ release: release{
ReleaseName: "test", releaseName: "test",
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.0.0", Version: "1.0.0",
}, },
@ -75,9 +76,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"upgrade to older version can be forced": { "upgrade to older version can be forced": {
lister: stubLister{version: semver.NewFromInt(1, 1, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 1, 0, "")},
release: Release{ release: release{
ReleaseName: "test", releaseName: "test",
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.0.0", Version: "1.0.0",
}, },
@ -88,9 +89,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"non semver in chart metadata": { "non semver in chart metadata": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
ReleaseName: "test", releaseName: "test",
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "some-version", Version: "some-version",
}, },
@ -100,9 +101,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"listing release fails": { "listing release fails": {
lister: stubLister{err: assert.AnError}, lister: stubLister{err: assert.AnError},
release: Release{ release: release{
ReleaseName: "test", releaseName: "test",
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.0", Version: "1.1.0",
}, },
@ -113,9 +114,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"release not installed": { "release not installed": {
lister: stubLister{err: errReleaseNotFound}, lister: stubLister{err: errReleaseNotFound},
release: Release{ release: release{
ReleaseName: "test", releaseName: "test",
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.0", Version: "1.1.0",
}, },
@ -125,13 +126,13 @@ func TestAppendNewAction(t *testing.T) {
}, },
"destructive release upgrade requires confirmation": { "destructive release upgrade requires confirmation": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.0", Version: "1.1.0",
}, },
}, },
ReleaseName: certManagerInfo.releaseName, releaseName: certManagerInfo.releaseName,
}, },
configTargetVersion: semver.NewFromInt(1, 1, 0, ""), configTargetVersion: semver.NewFromInt(1, 1, 0, ""),
wantErr: true, wantErr: true,
@ -141,22 +142,22 @@ func TestAppendNewAction(t *testing.T) {
}, },
"destructive release upgrade can be accepted": { "destructive release upgrade can be accepted": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.0", Version: "1.1.0",
}, },
}, },
ReleaseName: certManagerInfo.releaseName, releaseName: certManagerInfo.releaseName,
}, },
configTargetVersion: semver.NewFromInt(1, 1, 0, ""), configTargetVersion: semver.NewFromInt(1, 1, 0, ""),
allowDestructive: true, allowDestructive: true,
}, },
"config version takes precedence over CLI version": { "config version takes precedence over CLI version": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
ReleaseName: constellationServicesInfo.releaseName, releaseName: constellationServicesInfo.releaseName,
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.0", Version: "1.1.0",
}, },
@ -168,9 +169,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"error if CLI version does not match config version on upgrade": { "error if CLI version does not match config version on upgrade": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
ReleaseName: constellationServicesInfo.releaseName, releaseName: constellationServicesInfo.releaseName,
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.5", Version: "1.1.5",
}, },
@ -182,9 +183,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"config version matches CLI version on upgrade": { "config version matches CLI version on upgrade": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
ReleaseName: constellationServicesInfo.releaseName, releaseName: constellationServicesInfo.releaseName,
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.5", Version: "1.1.5",
}, },
@ -194,9 +195,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"config - CLI version mismatch can be forced through": { "config - CLI version mismatch can be forced through": {
lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")}, lister: stubLister{version: semver.NewFromInt(1, 0, 0, "")},
release: Release{ release: release{
ReleaseName: constellationServicesInfo.releaseName, releaseName: constellationServicesInfo.releaseName,
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.5", Version: "1.1.5",
}, },
@ -207,9 +208,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"installing new release requires matching config and CLI version": { "installing new release requires matching config and CLI version": {
lister: stubLister{err: errReleaseNotFound}, lister: stubLister{err: errReleaseNotFound},
release: Release{ release: release{
ReleaseName: constellationServicesInfo.releaseName, releaseName: constellationServicesInfo.releaseName,
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.0", Version: "1.1.0",
}, },
@ -221,9 +222,9 @@ func TestAppendNewAction(t *testing.T) {
}, },
"config - CLI version mismatch for new releases can be forced through": { "config - CLI version mismatch for new releases can be forced through": {
lister: stubLister{err: errReleaseNotFound}, lister: stubLister{err: errReleaseNotFound},
release: Release{ release: release{
ReleaseName: constellationServicesInfo.releaseName, releaseName: constellationServicesInfo.releaseName,
Chart: &chart.Chart{ chart: &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Version: "1.1.0", Version: "1.1.0",
}, },
@ -241,7 +242,7 @@ func TestAppendNewAction(t *testing.T) {
actions := []applyAction{} actions := []applyAction{}
actionFactory := newActionFactory(nil, tc.lister, &action.Configuration{}, logger.NewTest(t)) actionFactory := newActionFactory(nil, tc.lister, &action.Configuration{}, logger.NewTest(t))
err := actionFactory.appendNewAction(tc.release, tc.configTargetVersion, tc.force, tc.allowDestructive, &actions) err := actionFactory.appendNewAction(tc.release, tc.configTargetVersion, tc.force, tc.allowDestructive, time.Second, &actions)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
if tc.assertErr != nil { if tc.assertErr != nil {

View File

@ -31,6 +31,7 @@ package helm
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -81,6 +82,7 @@ type Options struct {
HelmWaitMode WaitMode HelmWaitMode WaitMode
AllowDestructive bool AllowDestructive bool
Force bool Force bool
ApplyTimeout time.Duration
} }
// PrepareApply loads the charts and returns the executor to apply them. // PrepareApply loads the charts and returns the executor to apply them.
@ -93,15 +95,18 @@ func (h Client) PrepareApply(
if err != nil { if err != nil {
return nil, false, fmt.Errorf("loading Helm releases: %w", err) return nil, false, fmt.Errorf("loading Helm releases: %w", err)
} }
h.log.Debugf("Loaded Helm releases") h.log.Debugf("Loaded Helm releases")
actions, includesUpgrades, err := h.factory.GetActions(releases, conf.MicroserviceVersion, flags.Force, flags.AllowDestructive) actions, includesUpgrades, err := h.factory.GetActions(
releases, conf.MicroserviceVersion, flags.Force, flags.AllowDestructive, flags.ApplyTimeout,
)
return &ChartApplyExecutor{actions: actions, log: h.log}, includesUpgrades, err return &ChartApplyExecutor{actions: actions, log: h.log}, includesUpgrades, err
} }
func (h Client) loadReleases( func (h Client) loadReleases(
conf *config.Config, secret uri.MasterSecret, conf *config.Config, secret uri.MasterSecret,
stateFile *state.State, flags Options, serviceAccURI string, stateFile *state.State, flags Options, serviceAccURI string,
) ([]Release, error) { ) ([]release, error) {
helmLoader := newLoader(conf, stateFile, h.cliVersion) helmLoader := newLoader(conf, stateFile, h.cliVersion)
h.log.Debugf("Created new Helm loader") h.log.Debugf("Created new Helm loader")
return helmLoader.loadReleases(flags.Conformance, flags.HelmWaitMode, secret, serviceAccURI) return helmLoader.loadReleases(flags.Conformance, flags.HelmWaitMode, secret, serviceAccURI)

View File

@ -115,7 +115,7 @@ func newLoader(config *config.Config, stateFile *state.State, cliVersion semver.
// makes sure if a release was removed as a dependency from one chart, // makes sure if a release was removed as a dependency from one chart,
// and then added as a new standalone chart (or as a dependency of another chart), // and then added as a new standalone chart (or as a dependency of another chart),
// that the new release is installed after the existing one to avoid name conflicts. // that the new release is installed after the existing one to avoid name conflicts.
type releaseApplyOrder []Release type releaseApplyOrder []release
// loadReleases loads the embedded helm charts and returns them as a HelmReleases object. // loadReleases loads the embedded helm charts and returns them as a HelmReleases object.
func (i *chartLoader) loadReleases(conformanceMode bool, helmWaitMode WaitMode, masterSecret uri.MasterSecret, func (i *chartLoader) loadReleases(conformanceMode bool, helmWaitMode WaitMode, masterSecret uri.MasterSecret,
@ -126,7 +126,7 @@ func (i *chartLoader) loadReleases(conformanceMode bool, helmWaitMode WaitMode,
return nil, fmt.Errorf("loading cilium: %w", err) return nil, fmt.Errorf("loading cilium: %w", err)
} }
ciliumVals := extraCiliumValues(i.config.GetProvider(), conformanceMode, i.stateFile.Infrastructure) ciliumVals := extraCiliumValues(i.config.GetProvider(), conformanceMode, i.stateFile.Infrastructure)
ciliumRelease.Values = mergeMaps(ciliumRelease.Values, ciliumVals) ciliumRelease.values = mergeMaps(ciliumRelease.values, ciliumVals)
certManagerRelease, err := i.loadRelease(certManagerInfo, helmWaitMode) certManagerRelease, err := i.loadRelease(certManagerInfo, helmWaitMode)
if err != nil { if err != nil {
@ -137,7 +137,7 @@ func (i *chartLoader) loadReleases(conformanceMode bool, helmWaitMode WaitMode,
if err != nil { if err != nil {
return nil, fmt.Errorf("loading operators: %w", err) return nil, fmt.Errorf("loading operators: %w", err)
} }
operatorRelease.Values = mergeMaps(operatorRelease.Values, extraOperatorValues(i.stateFile.Infrastructure.UID)) operatorRelease.values = mergeMaps(operatorRelease.values, extraOperatorValues(i.stateFile.Infrastructure.UID))
conServicesRelease, err := i.loadRelease(constellationServicesInfo, helmWaitMode) conServicesRelease, err := i.loadRelease(constellationServicesInfo, helmWaitMode)
if err != nil { if err != nil {
@ -148,7 +148,7 @@ func (i *chartLoader) loadReleases(conformanceMode bool, helmWaitMode WaitMode,
if err != nil { if err != nil {
return nil, fmt.Errorf("extending constellation-services values: %w", err) return nil, fmt.Errorf("extending constellation-services values: %w", err)
} }
conServicesRelease.Values = mergeMaps(conServicesRelease.Values, svcVals) conServicesRelease.values = mergeMaps(conServicesRelease.values, svcVals)
releases := releaseApplyOrder{ciliumRelease, conServicesRelease, certManagerRelease} releases := releaseApplyOrder{ciliumRelease, conServicesRelease, certManagerRelease}
if i.config.DeployCSIDriver() { if i.config.DeployCSIDriver() {
@ -160,7 +160,7 @@ func (i *chartLoader) loadReleases(conformanceMode bool, helmWaitMode WaitMode,
if err != nil { if err != nil {
return nil, fmt.Errorf("extending CSI values: %w", err) return nil, fmt.Errorf("extending CSI values: %w", err)
} }
csiRelease.Values = mergeMaps(csiRelease.Values, extraCSIvals) csiRelease.values = mergeMaps(csiRelease.values, extraCSIvals)
releases = append(releases, csiRelease) releases = append(releases, csiRelease)
} }
if i.config.HasProvider(cloudprovider.AWS) { if i.config.HasProvider(cloudprovider.AWS) {
@ -177,10 +177,10 @@ func (i *chartLoader) loadReleases(conformanceMode bool, helmWaitMode WaitMode,
// loadRelease loads the embedded chart and values depending on the given info argument. // loadRelease loads the embedded chart and values depending on the given info argument.
// IMPORTANT: .helmignore rules specifying files in subdirectories are not applied (e.g. crds/kustomization.yaml). // IMPORTANT: .helmignore rules specifying files in subdirectories are not applied (e.g. crds/kustomization.yaml).
func (i *chartLoader) loadRelease(info chartInfo, helmWaitMode WaitMode) (Release, error) { func (i *chartLoader) loadRelease(info chartInfo, helmWaitMode WaitMode) (release, error) {
chart, err := loadChartsDir(helmFS, info.path) chart, err := loadChartsDir(helmFS, info.path)
if err != nil { if err != nil {
return Release{}, fmt.Errorf("loading %s chart: %w", info.releaseName, err) return release{}, fmt.Errorf("loading %s chart: %w", info.releaseName, err)
} }
var values map[string]any var values map[string]any
@ -190,7 +190,7 @@ func (i *chartLoader) loadRelease(info chartInfo, helmWaitMode WaitMode) (Releas
var ok bool var ok bool
values, ok = ciliumVals[i.csp.String()] values, ok = ciliumVals[i.csp.String()]
if !ok { if !ok {
return Release{}, fmt.Errorf("cilium values for csp %q not found", i.csp.String()) return release{}, fmt.Errorf("cilium values for csp %q not found", i.csp.String())
} }
case certManagerInfo.releaseName: case certManagerInfo.releaseName:
values = i.loadCertManagerValues() values = i.loadCertManagerValues()
@ -210,7 +210,7 @@ func (i *chartLoader) loadRelease(info chartInfo, helmWaitMode WaitMode) (Releas
updateVersions(chart, i.cliVersion) updateVersions(chart, i.cliVersion)
} }
return Release{Chart: chart, Values: values, ReleaseName: info.releaseName, WaitMode: helmWaitMode}, nil return release{chart: chart, values: values, releaseName: info.releaseName, waitMode: helmWaitMode}, nil
} }
func (i *chartLoader) loadAWSLBControllerValues() map[string]any { func (i *chartLoader) loadAWSLBControllerValues() map[string]any {

View File

@ -84,8 +84,8 @@ func TestLoadReleases(t *testing.T) {
) )
require.NoError(err) require.NoError(err)
for _, release := range helmReleases { for _, release := range helmReleases {
if release.ReleaseName == constellationServicesInfo.releaseName { if release.releaseName == constellationServicesInfo.releaseName {
assert.NotNil(release.Chart.Dependencies()) assert.NotNil(release.chart.Dependencies())
} }
} }
} }

View File

@ -10,11 +10,11 @@ package helm
import "helm.sh/helm/v3/pkg/chart" import "helm.sh/helm/v3/pkg/chart"
// Release bundles all information necessary to create a helm release. // Release bundles all information necessary to create a helm release.
type Release struct { type release struct {
Chart *chart.Chart chart *chart.Chart
Values map[string]any values map[string]any
ReleaseName string releaseName string
WaitMode WaitMode waitMode WaitMode
} }
// WaitMode specifies the wait mode for a helm release. // WaitMode specifies the wait mode for a helm release.

View File

@ -12,7 +12,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/semver" "github.com/edgelesssys/constellation/v2/internal/semver"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/release" helmrelease "helm.sh/helm/v3/pkg/release"
"k8s.io/client-go/util/retry" "k8s.io/client-go/util/retry"
) )
@ -34,7 +34,7 @@ func NewReleaseVersionClient(kubeConfigPath string, log debugLog) (*ReleaseVersi
// listAction execute a List action by wrapping helm's action package. // listAction execute a List action by wrapping helm's action package.
// It creates the action, runs it at returns results and errors. // It creates the action, runs it at returns results and errors.
func (c ReleaseVersionClient) listAction(release string) (res []*release.Release, err error) { func (c ReleaseVersionClient) listAction(release string) (res []*helmrelease.Release, err error) {
action := action.NewList(c.config) action := action.NewList(c.config)
action.Filter = release action.Filter = release
// during init, the kube API might not yet be reachable, so we retry // during init, the kube API might not yet be reachable, so we retry