-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cb855c0
commit 5906276
Showing
10 changed files
with
2,277 additions
and
198 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors | ||
|
||
// Package helm contains operations for working with helm charts. | ||
package helm | ||
|
||
import ( | ||
"crypto/sha1" | ||
"encoding/hex" | ||
"fmt" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/zarf-dev/zarf/src/api/v1alpha1" | ||
"github.com/zarf-dev/zarf/src/config" | ||
"github.com/zarf-dev/zarf/src/pkg/cluster" | ||
"github.com/zarf-dev/zarf/src/pkg/message" | ||
"github.com/zarf-dev/zarf/src/pkg/variables" | ||
"github.com/zarf-dev/zarf/src/types" | ||
"helm.sh/helm/v3/pkg/action" | ||
"helm.sh/helm/v3/pkg/chart" | ||
"helm.sh/helm/v3/pkg/cli" | ||
) | ||
|
||
// Helm is a config object for working with helm charts. | ||
type Helm struct { | ||
chart v1alpha1.ZarfChart | ||
chartPath string | ||
valuesPath string | ||
|
||
adoptExistingResources bool | ||
yolo bool | ||
cluster *cluster.Cluster | ||
timeout time.Duration | ||
retries int | ||
|
||
kubeVersion string | ||
|
||
chartOverride *chart.Chart | ||
valuesOverrides map[string]any | ||
|
||
settings *cli.EnvSettings | ||
actionConfig *action.Configuration | ||
variableConfig *variables.VariableConfig | ||
state *types.ZarfState | ||
} | ||
|
||
// Modifier is a function that modifies the Helm config. | ||
type Modifier func(*Helm) | ||
|
||
// New returns a new Helm config struct. | ||
func New(chart v1alpha1.ZarfChart, chartPath string, valuesPath string, mods ...Modifier) *Helm { | ||
h := &Helm{ | ||
chart: chart, | ||
chartPath: chartPath, | ||
valuesPath: valuesPath, | ||
timeout: config.ZarfDefaultTimeout, | ||
} | ||
|
||
for _, mod := range mods { | ||
mod(h) | ||
} | ||
|
||
return h | ||
} | ||
|
||
// NewClusterOnly returns a new Helm config struct geared toward interacting with the cluster (not packages) | ||
func NewClusterOnly(variableConfig *variables.VariableConfig, state *types.ZarfState, cluster *cluster.Cluster, adoptExistingResources, yolo bool) *Helm { | ||
return &Helm{ | ||
adoptExistingResources: adoptExistingResources, | ||
yolo: yolo, | ||
variableConfig: variableConfig, | ||
state: state, | ||
cluster: cluster, | ||
timeout: config.ZarfDefaultTimeout, | ||
retries: config.ZarfDefaultRetries, | ||
} | ||
} | ||
|
||
// NewFromZarfManifest generates a helm chart and config from a given Zarf manifest. | ||
func NewFromZarfManifest(manifest v1alpha1.ZarfManifest, manifestPath, packageName, componentName string, mods ...Modifier) (h *Helm, err error) { | ||
spinner := message.NewProgressSpinner("Starting helm chart generation %s", manifest.Name) | ||
defer spinner.Stop() | ||
|
||
// Generate a new chart. | ||
tmpChart := new(chart.Chart) | ||
tmpChart.Metadata = new(chart.Metadata) | ||
|
||
// Generate a hashed chart name. | ||
rawChartName := fmt.Sprintf("raw-%s-%s-%s", packageName, componentName, manifest.Name) | ||
hasher := sha1.New() | ||
hasher.Write([]byte(rawChartName)) | ||
tmpChart.Metadata.Name = rawChartName | ||
sha1ReleaseName := hex.EncodeToString(hasher.Sum(nil)) | ||
|
||
// This is fun, increment forward in a semver-way using epoch so helm doesn't cry. | ||
tmpChart.Metadata.Version = fmt.Sprintf("0.1.%d", config.GetStartTime()) | ||
tmpChart.Metadata.APIVersion = chart.APIVersionV1 | ||
|
||
// Add the manifest files so helm does its thing. | ||
for _, file := range manifest.Files { | ||
spinner.Updatef("Processing %s", file) | ||
manifest := path.Join(manifestPath, file) | ||
data, err := os.ReadFile(manifest) | ||
if err != nil { | ||
return h, fmt.Errorf("unable to read manifest file %s: %w", manifest, err) | ||
} | ||
|
||
// Escape all chars and then wrap in {{ }}. | ||
txt := strconv.Quote(string(data)) | ||
data = []byte("{{" + txt + "}}") | ||
|
||
tmpChart.Templates = append(tmpChart.Templates, &chart.File{Name: manifest, Data: data}) | ||
} | ||
|
||
// Generate the struct to pass to InstallOrUpgradeChart(). | ||
h = &Helm{ | ||
chart: v1alpha1.ZarfChart{ | ||
Name: tmpChart.Metadata.Name, | ||
// Preserve the zarf prefix for chart names to match v0.22.x and earlier behavior. | ||
ReleaseName: fmt.Sprintf("zarf-%s", sha1ReleaseName), | ||
Version: tmpChart.Metadata.Version, | ||
Namespace: manifest.Namespace, | ||
NoWait: manifest.NoWait, | ||
}, | ||
chartOverride: tmpChart, | ||
timeout: config.ZarfDefaultTimeout, | ||
} | ||
|
||
for _, mod := range mods { | ||
mod(h) | ||
} | ||
|
||
spinner.Success() | ||
|
||
return h, nil | ||
} | ||
|
||
// WithDeployInfo adds the necessary information to deploy a given chart | ||
func WithDeployInfo(variableConfig *variables.VariableConfig, state *types.ZarfState, cluster *cluster.Cluster, valuesOverrides map[string]any, adoptExistingResources, yolo bool, timeout time.Duration, retries int) Modifier { | ||
return func(h *Helm) { | ||
h.adoptExistingResources = adoptExistingResources | ||
h.yolo = yolo | ||
h.variableConfig = variableConfig | ||
h.state = state | ||
h.cluster = cluster | ||
h.valuesOverrides = valuesOverrides | ||
h.timeout = timeout | ||
h.retries = retries | ||
} | ||
} | ||
|
||
// WithKubeVersion sets the Kube version for templating the chart | ||
func WithKubeVersion(kubeVersion string) Modifier { | ||
return func(h *Helm) { | ||
h.kubeVersion = kubeVersion | ||
} | ||
} | ||
|
||
// WithVariableConfig sets the variable config for the chart | ||
func WithVariableConfig(variableConfig *variables.VariableConfig) Modifier { | ||
return func(h *Helm) { | ||
h.variableConfig = variableConfig | ||
} | ||
} | ||
|
||
// StandardName generates a predictable full path for a helm chart for Zarf. | ||
func StandardName(destination string, chart v1alpha1.ZarfChart) string { | ||
return filepath.Join(destination, chart.Name+"-"+chart.Version) | ||
} | ||
|
||
// StandardValuesName generates a predictable full path for the values file for a helm chart for zarf | ||
func StandardValuesName(destination string, chart v1alpha1.ZarfChart, idx int) string { | ||
return fmt.Sprintf("%s-%d", StandardName(destination, chart), idx) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors | ||
|
||
// Package helm contains operations for working with helm charts. | ||
package helm | ||
|
||
import ( | ||
"context" | ||
"regexp" | ||
"time" | ||
|
||
"github.com/zarf-dev/zarf/src/pkg/cluster" | ||
"github.com/zarf-dev/zarf/src/pkg/logger" | ||
"github.com/zarf-dev/zarf/src/pkg/message" | ||
"helm.sh/helm/v3/pkg/action" | ||
) | ||
|
||
// Destroy removes ZarfInitPackage charts from the cluster and optionally all Zarf-installed charts. | ||
func Destroy(ctx context.Context, purgeAllZarfInstallations bool) { | ||
start := time.Now() | ||
l := logger.From(ctx) | ||
spinner := message.NewProgressSpinner("Removing Zarf-installed charts") | ||
defer spinner.Stop() | ||
l.Info("removing Zarf-installed charts") | ||
|
||
h := Helm{} | ||
|
||
// Initially load the actionConfig without a namespace | ||
err := h.createActionConfig(ctx, "", spinner) | ||
if err != nil { | ||
// Don't fatal since this is a removal action | ||
spinner.Errorf(err, "Unable to initialize the K8s client") | ||
l.Error("unable to initialize the K8s client", "error", err.Error()) | ||
return | ||
} | ||
|
||
// Match a name that begins with "zarf-" | ||
// Explanation: https://regex101.com/r/3yzKZy/1 | ||
zarfPrefix := regexp.MustCompile(`(?m)^zarf-`) | ||
|
||
// Get a list of all releases in all namespaces | ||
list := action.NewList(h.actionConfig) | ||
list.All = true | ||
list.AllNamespaces = true | ||
// Uninstall in reverse order | ||
list.ByDate = true | ||
list.SortReverse = true | ||
releases, err := list.Run() | ||
if err != nil { | ||
// Don't fatal since this is a removal action | ||
spinner.Errorf(err, "Unable to get the list of installed charts") | ||
l.Error("unable to get the list of installed charts", "error", err.Error()) | ||
} | ||
|
||
// Iterate over all releases | ||
for _, release := range releases { | ||
if !purgeAllZarfInstallations && release.Namespace != cluster.ZarfNamespaceName { | ||
// Don't process releases outside the zarf namespace unless purge all is true | ||
continue | ||
} | ||
// Filter on zarf releases | ||
if zarfPrefix.MatchString(release.Name) { | ||
spinner.Updatef("Uninstalling helm chart %s/%s", release.Namespace, release.Name) | ||
l.Info("uninstalling helm chart", "namespace", release.Namespace, "name", release.Name) | ||
if err = h.RemoveChart(ctx, release.Namespace, release.Name, spinner); err != nil { | ||
// Don't fatal since this is a removal action | ||
spinner.Errorf(err, "Unable to uninstall the chart") | ||
l.Error("unable to uninstall the chart", "error", err.Error()) | ||
} | ||
} | ||
} | ||
|
||
spinner.Success() | ||
l.Debug("done uninstalling charts", "duration", time.Since(start)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors | ||
|
||
package helm | ||
|
||
import ( | ||
"github.com/defenseunicorns/pkg/helpers/v2" | ||
"github.com/goccy/go-yaml" | ||
"helm.sh/helm/v3/pkg/chart/loader" | ||
"helm.sh/helm/v3/pkg/chartutil" | ||
) | ||
|
||
// ChartImages captures the structure of the helm.sh/images annotation within the Helm chart. | ||
type ChartImages []struct { | ||
// Name of the image. | ||
Name string `yaml:"name"` | ||
// Image with tag. | ||
Image string `yaml:"image"` | ||
// Condition specifies the values to determine if the image is included or not. | ||
Condition string `yaml:"condition"` | ||
// Dependency is the subchart that contains the image, if empty its the parent chart. | ||
Dependency string `yaml:"dependency"` | ||
} | ||
|
||
// FindAnnotatedImagesForChart attempts to parse any image annotations found in a chart archive or directory. | ||
func FindAnnotatedImagesForChart(chartPath string, values chartutil.Values) (images []string, err error) { | ||
// Load a new chart. | ||
chart, err := loader.Load(chartPath) | ||
if err != nil { | ||
return images, err | ||
} | ||
values = helpers.MergeMapRecursive(chart.Values, values) | ||
|
||
imageAnnotation := chart.Metadata.Annotations["helm.sh/images"] | ||
|
||
var chartImages ChartImages | ||
|
||
err = yaml.Unmarshal([]byte(imageAnnotation), &chartImages) | ||
if err != nil { | ||
return images, err | ||
} | ||
|
||
for _, i := range chartImages { | ||
// Only include the image if the current values/condition specify it should be included | ||
if i.Condition != "" { | ||
value, err := values.PathValue(i.Condition) | ||
// We intentionally ignore the error here because the key could be missing from the values.yaml | ||
if err == nil && value == true { | ||
images = append(images, i.Image) | ||
} | ||
} else { | ||
images = append(images, i.Image) | ||
} | ||
} | ||
|
||
return images, nil | ||
} |
Oops, something went wrong.