Skip to content

Commit

Permalink
Refactor deploy package
Browse files Browse the repository at this point in the history
  • Loading branch information
phillebaba committed Jan 16, 2025
1 parent cb855c0 commit 5906276
Show file tree
Hide file tree
Showing 10 changed files with 2,277 additions and 198 deletions.
594 changes: 396 additions & 198 deletions src/internal/packager2/deploy.go

Large diffs are not rendered by default.

512 changes: 512 additions & 0 deletions src/internal/packager2/helm/chart.go

Large diffs are not rendered by default.

178 changes: 178 additions & 0 deletions src/internal/packager2/helm/common.go
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)
}
75 changes: 75 additions & 0 deletions src/internal/packager2/helm/destroy.go
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))
}
57 changes: 57 additions & 0 deletions src/internal/packager2/helm/images.go
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
}
Loading

0 comments on commit 5906276

Please sign in to comment.