Skip to content

Commit

Permalink
feat: enables setting namespaces in bundled Helm charts (#539)
Browse files Browse the repository at this point in the history
  • Loading branch information
UncleGedd committed Apr 1, 2024
1 parent ba671aa commit 6726b8f
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 17 deletions.
23 changes: 23 additions & 0 deletions docs/overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Bundle overrides provide a mechanism to customize Helm charts inside of Zarf pac
- [Syntax](#syntax)
- [Values](#values)
- [Variables](#variables)
- [Namespace](#namespace)

## Quickstart

Expand Down Expand Up @@ -214,3 +215,25 @@ Variable precedence is as follows:
1. Environment variables
1. `uds-config.yaml` variables
1. Variables `default` in the`uds-bundle.yaml`

### Namespace
It's also possible to specify a namespace for a packaged Helm chart to be installed in. For example, to deploy the a chart in the `custom-podinfo` namespace, you can specify the `namespace` in the `overrides` block:

```yaml
kind: UDSBundle
metadata:
name: example-bundle
version: 0.0.1

packages:
- name: helm-overrides-package
path: "../../packages/helm"
ref: 0.0.1"
overrides:
podinfo-component:
unicorn-podinfo:
namespace: custom-podinfo # custom namespace!
values:
- path: "podinfo.replicaCount"
value: 1
```
40 changes: 27 additions & 13 deletions src/pkg/bundle/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import (
"helm.sh/helm/v3/pkg/getter"
)

// ZarfOverrideMap is a map of Zarf packages -> components -> Helm charts -> values
type ZarfOverrideMap map[string]map[string]map[string]interface{}
// PkgOverrideMap is a map of Zarf packages -> components -> Helm charts -> values/namespace
type PkgOverrideMap map[string]map[string]map[string]interface{}

// templatedVarRegex is the regex for templated variables
var templatedVarRegex = regexp.MustCompile(`\${([^}]+)}`)
Expand Down Expand Up @@ -109,7 +109,7 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle) error {
Retries: b.cfg.DeployOpts.Retries,
}

valuesOverrides, err := b.loadChartOverrides(pkg, pkgVars)
valuesOverrides, nsOverrides, err := b.loadChartOverrides(pkg, pkgVars)
if err != nil {
return err
}
Expand All @@ -128,7 +128,7 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle) error {
// Automatically confirm the package deployment
zarfConfig.CommonOptions.Confirm = true

source, err := sources.New(b.cfg.DeployOpts.Source, b.cfg.DeployOpts.ZarfPackageNameMap[pkg.Name], opts, sha)
source, err := sources.New(b.cfg.DeployOpts.Source, b.cfg.DeployOpts.ZarfPackageNameMap[pkg.Name], opts, sha, nsOverrides)
if err != nil {
return err
}
Expand Down Expand Up @@ -231,29 +231,31 @@ func (b *Bundle) ConfirmBundleDeploy() (confirm bool) {
}

// loadChartOverrides converts a helm path to a ValuesOverridesMap config for Zarf
func (b *Bundle) loadChartOverrides(pkg types.Package, pkgVars map[string]string) (ZarfOverrideMap, error) {
func (b *Bundle) loadChartOverrides(pkg types.Package, pkgVars map[string]string) (PkgOverrideMap, sources.NamespaceOverrideMap, error) {

// Create a nested map to hold the values
// Create nested maps to hold the overrides
overrideMap := make(map[string]map[string]*values.Options)
nsOverrides := make(sources.NamespaceOverrideMap)

// Loop through each package component's charts and process overrides
for componentName, component := range pkg.Overrides {
for chartName, chart := range component {
chartCopy := chart // Create a copy of the chart
err := b.processOverrideValues(&overrideMap, &chartCopy.Values, componentName, chartName, pkgVars)
if err != nil {
return nil, err
return nil, nil, err
}
err = b.processOverrideVariables(&overrideMap, pkg.Name, &chartCopy.Variables, componentName, chartName)
if err != nil {
return nil, err
return nil, nil, err
}
b.processOverrideNamespaces(nsOverrides, chartCopy.Namespace, componentName, chartName)
}
}

processed := make(ZarfOverrideMap)
processed := make(PkgOverrideMap)

// Convert the options.Values map to the ZarfOverrideMap format
// Convert the options.Values map (located in chart.MergeValues) to the PkgOverrideMap format
for componentName, component := range overrideMap {
// Create a map to hold all the charts in the component
componentMap := make(map[string]map[string]interface{})
Expand All @@ -267,7 +269,7 @@ func (b *Bundle) loadChartOverrides(pkg types.Package, pkgVars map[string]string
// Merge the chart values with Helm
data, err := chart.MergeValues(getter.Providers{})
if err != nil {
return nil, err
return nil, nil, err
}

// Add the chart values to the component map
Expand All @@ -278,7 +280,7 @@ func (b *Bundle) loadChartOverrides(pkg types.Package, pkgVars map[string]string
processed[componentName] = componentMap
}

return processed, nil
return processed, nsOverrides, nil
}

// PreDeployValidation validates the bundle before deployment
Expand Down Expand Up @@ -336,6 +338,18 @@ func (b *Bundle) PreDeployValidation() (string, string, string, error) {
return bundleName, string(bundleYAML), source, err
}

// processOverrideNamespaces processes a bundles namespace overrides and adds them to the override map
func (b *Bundle) processOverrideNamespaces(overrideMap sources.NamespaceOverrideMap, ns string, componentName string, chartName string) {
if ns == "" {
return // no namespace override
}
// check if component exists in override map
if _, ok := overrideMap[componentName]; !ok {
overrideMap[componentName] = make(map[string]string)
}
overrideMap[componentName][chartName] = ns
}

// processOverrideValues processes a bundles values overrides and adds them to the override map
func (b *Bundle) processOverrideValues(overrideMap *map[string]map[string]*values.Options, values *[]types.BundleChartValue, componentName string, chartName string, pkgVars map[string]string) error {
for _, v := range *values {
Expand Down Expand Up @@ -394,7 +408,7 @@ func (b *Bundle) processOverrideVariables(overrideMap *map[string]map[string]*va
return nil
}

// addOverrideValue adds a value to a ZarfOverrideMap
// addOverrideValue adds a value to a PkgOverrideMap
func addOverrideValue(overrides map[string]map[string]*values.Options, component string, chart string, valuePath string, value interface{}, pkgVars map[string]string) error {
// Create the component map if it doesn't exist
if _, ok := overrides[component]; !ok {
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/bundle/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func removePackages(packagesToRemove []types.Package, b *Bundle, zarfPackageName
}

sha := strings.Split(pkg.Ref, "sha256:")[1]
source, err := sources.New(b.cfg.RemoveOpts.Source, zarfPackageName, opts, sha)
source, err := sources.New(b.cfg.RemoveOpts.Source, zarfPackageName, opts, sha, nil)
if err != nil {
return err
}
Expand Down
23 changes: 23 additions & 0 deletions src/pkg/sources/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The UDS Authors

// Package sources contains Zarf packager sources
package sources

import zarfTypes "github.com/defenseunicorns/zarf/src/types"

// addNamespaceOverrides checks if pkg components have charts with namespace overrides and adds them
func addNamespaceOverrides(pkg *zarfTypes.ZarfPackage, nsOverrides NamespaceOverrideMap) {
if len(nsOverrides) == 0 {
return
}
for i, comp := range pkg.Components {
if _, exists := nsOverrides[comp.Name]; exists {
for j, chart := range comp.Charts {
if _, exists = nsOverrides[comp.Name][chart.Name]; exists {
pkg.Components[i].Charts[j].Namespace = nsOverrides[comp.Name][comp.Charts[j].Name]
}
}
}
}
}
4 changes: 3 additions & 1 deletion src/pkg/sources/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

// New creates a new package source based on pkgLocation
func New(pkgLocation string, pkgName string, opts zarfTypes.ZarfPackageOptions, sha string) (zarfSources.PackageSource, error) {
func New(pkgLocation string, pkgName string, opts zarfTypes.ZarfPackageOptions, sha string, nsOverrides NamespaceOverrideMap) (zarfSources.PackageSource, error) {
var source zarfSources.PackageSource
if strings.Contains(pkgLocation, "tar.zst") {
source = &TarballBundle{
Expand All @@ -25,6 +25,7 @@ func New(pkgLocation string, pkgName string, opts zarfTypes.ZarfPackageOptions,
PkgManifestSHA: sha,
TmpDir: opts.PackageSource,
BundleLocation: pkgLocation,
nsOverrides: nsOverrides,
}
} else {
platform := ocispec.Platform{
Expand All @@ -41,6 +42,7 @@ func New(pkgLocation string, pkgName string, opts zarfTypes.ZarfPackageOptions,
PkgManifestSHA: sha,
TmpDir: opts.PackageSource,
Remote: remote.OrasRemote,
nsOverrides: nsOverrides,
}
}
return source, nil
Expand Down
2 changes: 2 additions & 0 deletions src/pkg/sources/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type RemoteBundle struct {
TmpDir string
Remote *oci.OrasRemote
isPartial bool
nsOverrides NamespaceOverrideMap
}

// LoadPackage loads a Zarf package from a remote bundle
Expand Down Expand Up @@ -88,6 +89,7 @@ func (r *RemoteBundle) LoadPackage(dst *layout.PackagePaths, filter filters.Comp
}
}
}
addNamespaceOverrides(&pkg, r.nsOverrides)
return pkg, nil, err
}

Expand Down
5 changes: 5 additions & 0 deletions src/pkg/sources/tarball.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import (
"github.com/defenseunicorns/uds-cli/src/pkg/utils"
)

// NamespaceOverrideMap is a map of component names to a map of chart names to namespace overrides
type NamespaceOverrideMap = map[string]map[string]string

// TarballBundle is a package source for local tarball bundles that implements Zarf's packager.PackageSource
type TarballBundle struct {
PkgOpts *zarfTypes.ZarfPackageOptions
Expand All @@ -36,6 +39,7 @@ type TarballBundle struct {
BundleLocation string
PkgName string
isPartial bool
nsOverrides NamespaceOverrideMap
}

// LoadPackage loads a Zarf package from a local tarball bundle
Expand Down Expand Up @@ -88,6 +92,7 @@ func (t *TarballBundle) LoadPackage(dst *layout.PackagePaths, filter filters.Com
}
}
}
addNamespaceOverrides(&pkg, t.nsOverrides)
packageSpinner.Successf("Loaded bundled Zarf package: %s", t.PkgName)
return pkg, nil, err
}
Expand Down
17 changes: 17 additions & 0 deletions src/test/bundles/07-helm-overrides/namespace/uds-bundle.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
kind: UDSBundle
metadata:
name: override-namespace
description: testing a bundle with a specified namespace
version: 0.0.1

packages:
- name: helm-overrides
path: "../../../packages/helm"
ref: 0.0.1
overrides:
podinfo-component:
unicorn-podinfo:
namespace: "override-namespace"
values:
- path: "podinfo.replicaCount"
value: 1
2 changes: 1 addition & 1 deletion src/test/e2e/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func TestBundleWithLocalAndRemotePkgs(t *testing.T) {
publishInsecure(t, bundlePath, bundleRef.Registry)
pull(t, bundleRef.String(), tarballPath) // note that pull pulls the bundle into the build dir
publishInsecure(t, filepath.Join("build", filepath.Base(bundlePath)), "oci://localhost:889")
deployAndRemoveRemoteInsecure(t, bundleRef.String())
deployInsecure(t, bundleRef.String())
})
}

Expand Down
8 changes: 7 additions & 1 deletion src/test/e2e/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,18 @@ func removePackagesFlag(tarballPath string, packages string) (stdout string, std
return stdout, stderr
}

func deployAndRemoveRemoteInsecure(t *testing.T, ref string) {
func deployInsecure(t *testing.T, ref string) {
cmd := strings.Split(fmt.Sprintf("deploy %s --insecure --confirm --no-tea", ref), " ")
_, _, err := e2e.UDS(cmd...)
require.NoError(t, err)
}

func removeInsecure(t *testing.T, remote string) {
cmd := strings.Split(fmt.Sprintf("remove %s --insecure --confirm", remote), " ")
_, _, err := e2e.UDS(cmd...)
require.NoError(t, err)
}

func deployAndRemoveLocalAndRemoteInsecure(t *testing.T, ref string, tarballPath string) {
var cmd []string
// test both paths because we want to test that the pulled tarball works as well
Expand Down
35 changes: 35 additions & 0 deletions src/test/e2e/variable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,41 @@ func TestBundleWithHelmOverrides(t *testing.T) {
remove(t, bundlePath)
}

func TestBundleWithOverridenNamespace(t *testing.T) {
deployZarfInit(t)
e2e.HelmDepUpdate(t, "src/test/packages/helm/unicorn-podinfo")
e2e.CreateZarfPkg(t, "src/test/packages/helm", false)
name := "override-namespace"
bundleDir := "src/test/bundles/07-helm-overrides/namespace"
bundlePath := filepath.Join(bundleDir, fmt.Sprintf("uds-bundle-%s-%s-0.0.1.tar.zst", name, e2e.Arch))
e2e.SetupDockerRegistry(t, 888)
defer e2e.TeardownRegistry(t, 888)
// remove namespace after tests
defer func() {
cmd := strings.Split("zarf tools kubectl delete ns override-namespace", " ")
_, _, _ = e2e.UDS(cmd...)
}()

createLocal(t, bundleDir, e2e.Arch)

t.Run("test namespace override in local bundle", func(t *testing.T) {
deploy(t, bundlePath)
cmd := strings.Split("zarf tools kubectl get deploy -n override-namespace unicorn-podinfo -o=jsonpath='{.metadata.name}'", " ")
deployments, _, _ := e2e.UDS(cmd...)
require.Contains(t, deployments, "unicorn-podinfo")
remove(t, bundlePath)
})

t.Run("test namespace override in remote bundle", func(t *testing.T) {
publishInsecure(t, bundlePath, "localhost:888")
deployInsecure(t, fmt.Sprintf("localhost:888/%s:0.0.1", name))
cmd := strings.Split("zarf tools kubectl get deploy -n override-namespace unicorn-podinfo -o=jsonpath='{.metadata.name}'", " ")
deployments, _, _ := e2e.UDS(cmd...)
require.Contains(t, deployments, "unicorn-podinfo")
removeInsecure(t, fmt.Sprintf("localhost:888/%s:0.0.1", name))
})
}

func TestBundleWithEnvVarHelmOverrides(t *testing.T) {
// set up configs and env vars
deployZarfInit(t)
Expand Down
1 change: 1 addition & 0 deletions src/types/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Package struct {
type BundleChartOverrides struct {
Values []BundleChartValue `json:"values,omitempty" jsonschema:"description=List of Helm chart values to set statically"`
Variables []BundleChartVariable `json:"variables,omitempty" jsonschema:"description=List of Helm chart variables to set via UDS variables"`
Namespace string `json:"namespace,omitempty" jsonschema:"description=The namespace to deploy the Helm chart to"`

// EXPERIMENTAL, not yet implemented
//ValueFiles []BundleChartValueFile `json:"value-files,omitempty" jsonschema:"description=List of Helm chart value files to set statically"`
Expand Down
4 changes: 4 additions & 0 deletions uds.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
},
"type": "array",
"description": "List of Helm chart variables to set via UDS variables"
},
"namespace": {
"type": "string",
"description": "The namespace to deploy the Helm chart to"
}
},
"additionalProperties": false,
Expand Down

0 comments on commit 6726b8f

Please sign in to comment.