Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: save Helm charts to disk before running upgrades #2305

Merged
merged 1 commit into from
Sep 8, 2023
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
4 changes: 4 additions & 0 deletions cli/internal/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ func (s stubRunner) Apply(_ context.Context) error {
return nil
}

func (s stubRunner) SaveCharts(_ string, _ file.Handler) error {
return nil
}

func TestGetLogs(t *testing.T) {
someErr := errors.New("failed")

Expand Down
7 changes: 7 additions & 0 deletions cli/internal/cmd/upgradeapply.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,13 @@ func (u *upgradeApplyCmd) handleServiceUpgrade(
}
}

// Save the Helm charts for the upgrade to disk
chartDir := filepath.Join(upgradeDir, "helm-charts")
if err := executor.SaveCharts(chartDir, u.fileHandler); err != nil {
return fmt.Errorf("saving Helm charts to disk: %w", err)
}
u.log.Debugf("Helm charts saved to %s", chartDir)

if includesUpgrades {
u.log.Debugf("Creating backup of CRDs and CRs")
crds, err := u.kubeUpgrader.BackupCRDs(cmd.Context(), upgradeDir)
Expand Down
3 changes: 3 additions & 0 deletions cli/internal/helm/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go_library(
srcs = [
"action.go",
"actionfactory.go",
"chartutil.go",
"ciliumhelper.go",
"helm.go",
"loader.go",
Expand Down Expand Up @@ -429,6 +430,7 @@ go_library(
"//internal/compatibility",
"//internal/config",
"//internal/constants",
"//internal/file",
"//internal/kms/uri",
"//internal/kubernetes/kubectl",
"//internal/retry",
Expand All @@ -444,6 +446,7 @@ go_library(
"@sh_helm_helm_v3//pkg/action",
"@sh_helm_helm_v3//pkg/chart",
"@sh_helm_helm_v3//pkg/chart/loader",
"@sh_helm_helm_v3//pkg/chartutil",
"@sh_helm_helm_v3//pkg/cli",
"@sh_helm_helm_v3//pkg/release",
],
Expand Down
24 changes: 24 additions & 0 deletions cli/internal/helm/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ package helm
import (
"context"
"fmt"
"path/filepath"
"time"

"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"

"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli"
Expand All @@ -24,6 +26,7 @@ const (

type applyAction interface {
Apply(context.Context) error
SaveChart(chartsDir string, fileHandler file.Handler) error
ReleaseName() string
IsAtomic() bool
}
Expand Down Expand Up @@ -94,6 +97,11 @@ func (a *installAction) Apply(ctx context.Context) error {
return nil
}

// SaveChart saves the chart to the given directory under the `install/<chart-name>` subdirectory.
func (a *installAction) SaveChart(chartsDir string, fileHandler file.Handler) error {
return saveChart(a.release, chartsDir, fileHandler)
}

func (a *installAction) apply(ctx context.Context) error {
_, err := a.helmAction.RunWithContext(ctx, a.release.Chart, a.release.Values)
return err
Expand Down Expand Up @@ -145,6 +153,11 @@ func (a *upgradeAction) Apply(ctx context.Context) error {
return nil
}

// SaveChart saves the chart to the given directory under the `upgrade/<chart-name>` subdirectory.
func (a *upgradeAction) SaveChart(chartsDir string, fileHandler file.Handler) error {
return saveChart(a.release, chartsDir, fileHandler)
}

func (a *upgradeAction) apply(ctx context.Context) error {
_, err := a.helmAction.RunWithContext(ctx, a.release.ReleaseName, a.release.Chart, a.release.Values)
return err
Expand All @@ -159,3 +172,14 @@ func (a *upgradeAction) ReleaseName() string {
func (a *upgradeAction) IsAtomic() bool {
return a.helmAction.Atomic
}

func saveChart(release Release, chartsDir string, fileHandler file.Handler) error {
if err := saveChartToDisk(release.Chart, chartsDir, fileHandler); err != nil {
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 {
return fmt.Errorf("saving override values for chart %s to %q: %w", release.ReleaseName, filepath.Join(chartsDir, release.Chart.Metadata.Name), err)
}

return nil
}
86 changes: 86 additions & 0 deletions cli/internal/helm/chartutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

package helm

import (
"fmt"
"path/filepath"

"github.com/edgelesssys/constellation/v2/internal/file"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
)

// saveChartToDisk saves a chart as files in a directory.
//
// This takes the chart name, and creates a new subdirectory inside of the given dest
// directory, writing the chart's contents to that subdirectory.
// Dependencies are written using the same format, instead of writing them as tar files
//
// View the SaveDir implementation in chartutil as reference: https://github.com/helm/helm/blob/3a31588ad33fe3b89af5a2a54ee1d25bfe6eaa5e/pkg/chartutil/save.go#L40
func saveChartToDisk(c *chart.Chart, dest string, fileHandler file.Handler) error {
// Create the chart directory
outdir := filepath.Join(dest, c.Name())
if fi, err := fileHandler.Stat(outdir); err == nil && !fi.IsDir() {
return fmt.Errorf("file %s already exists and is not a directory", outdir)
}
if err := fileHandler.MkdirAll(outdir); err != nil {
return err
}

// Save the chart file.
if err := chartutil.SaveChartfile(filepath.Join(outdir, chartutil.ChartfileName), c.Metadata); err != nil {
return err
}

// Save values.yaml
for _, f := range c.Raw {
if f.Name == chartutil.ValuesfileName {
vf := filepath.Join(outdir, chartutil.ValuesfileName)
if err := writeFile(vf, f.Data, fileHandler); err != nil {
return err
}
}
}

// Save values.schema.json if it exists
if c.Schema != nil {
filename := filepath.Join(outdir, chartutil.SchemafileName)
if err := writeFile(filename, c.Schema, fileHandler); err != nil {
return err
}
}

// Save templates and files
for _, o := range [][]*chart.File{c.Templates, c.Files} {
for _, f := range o {
n := filepath.Join(outdir, f.Name)
if err := writeFile(n, f.Data, fileHandler); err != nil {
return err
}
}
}

// Save dependencies
base := filepath.Join(outdir, chartutil.ChartsDir)
for _, dep := range c.Dependencies() {
// Don't write dependencies as tar files
// Instead recursively use saveChartToDisk
if err := saveChartToDisk(dep, base, fileHandler); err != nil {
return fmt.Errorf("saving %s: %w", dep.ChartFullPath(), err)
}
}

return nil
}

func writeFile(name string, content []byte, fileHandler file.Handler) error {
if err := fileHandler.MkdirAll(filepath.Dir(name)); err != nil {
return err
}
return fileHandler.Write(name, content)
}
23 changes: 20 additions & 3 deletions cli/internal/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl"
"github.com/edgelesssys/constellation/v2/internal/semver"
Expand All @@ -51,7 +52,6 @@ const (

type debugLog interface {
Debugf(format string, args ...any)
Sync()
}

// Client is a Helm client to apply charts.
Expand Down Expand Up @@ -87,7 +87,10 @@ type Options struct {

// PrepareApply loads the charts and returns the executor to apply them.
// TODO(elchead): remove validK8sVersion by putting ValidK8sVersion into config.Config, see AB#3374.
func (h Client) PrepareApply(conf *config.Config, validK8sversion versions.ValidK8sVersion, idFile clusterid.File, flags Options, tfOutput terraform.ApplyOutput, serviceAccURI string, masterSecret uri.MasterSecret) (Applier, bool, error) {
func (h Client) PrepareApply(
conf *config.Config, validK8sversion versions.ValidK8sVersion, idFile clusterid.File,
flags Options, tfOutput terraform.ApplyOutput, serviceAccURI string, masterSecret uri.MasterSecret,
) (Applier, bool, error) {
releases, err := h.loadReleases(conf, masterSecret, validK8sversion, idFile, flags, tfOutput, serviceAccURI)
if err != nil {
return nil, false, fmt.Errorf("loading Helm releases: %w", err)
Expand All @@ -97,7 +100,10 @@ func (h Client) PrepareApply(conf *config.Config, validK8sversion versions.Valid
return &ChartApplyExecutor{actions: actions, log: h.log}, includesUpgrades, err
}

func (h Client) loadReleases(conf *config.Config, secret uri.MasterSecret, validK8sVersion versions.ValidK8sVersion, idFile clusterid.File, flags Options, tfOutput terraform.ApplyOutput, serviceAccURI string) ([]Release, error) {
func (h Client) loadReleases(
conf *config.Config, secret uri.MasterSecret, validK8sVersion versions.ValidK8sVersion,
idFile clusterid.File, flags Options, tfOutput terraform.ApplyOutput, serviceAccURI string,
) ([]Release, error) {
helmLoader := newLoader(conf, idFile, validK8sVersion, h.cliVersion)
h.log.Debugf("Created new Helm loader")
return helmLoader.loadReleases(flags.Conformance, flags.HelmWaitMode, secret,
Expand All @@ -107,6 +113,7 @@ func (h Client) loadReleases(conf *config.Config, secret uri.MasterSecret, valid
// Applier runs the Helm actions.
type Applier interface {
Apply(ctx context.Context) error
SaveCharts(chartsDir string, fileHandler file.Handler) error
}

// ChartApplyExecutor is a Helm action executor that applies all actions.
Expand All @@ -126,6 +133,16 @@ func (c ChartApplyExecutor) Apply(ctx context.Context) error {
return nil
}

// SaveCharts saves all Helm charts and their values to the given directory.
func (c ChartApplyExecutor) SaveCharts(chartsDir string, fileHandler file.Handler) error {
for _, action := range c.actions {
if err := action.SaveChart(chartsDir, fileHandler); err != nil {
return fmt.Errorf("saving chart %s: %w", action.ReleaseName(), err)
}
}
return nil
}

// mergeMaps returns a new map that is the merger of it's inputs.
// Key collisions are resolved by taking the value of the second argument (map b).
// Taken from: https://github.com/helm/helm/blob/dbc6d8e20fe1d58d50e6ed30f09a04a77e4c68db/pkg/cli/values/options.go#L91-L108.
Expand Down