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

feat: imported vars as override values #423

Merged
merged 15 commits into from
Feb 22, 2024
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
28 changes: 28 additions & 0 deletions docs/overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,34 @@ The `value` is the value to set at the `path`. Values can be simple values such
value:
customAnnotation: "customValue"
```
If using a variable that has been [exported](../README.md#importingexporting-variables) from another package, that variable can also be used to set a value, using the syntax `${...}`. In the example below the `COLOR` variable is being used to set the `podinfo.ui.color` value.
```
kind: UDSBundle
metadata:
name: export-vars
description: Example for using an imported variable to set an overrides value
version: 0.0.1
packages:
- name: output-var
repository: localhost:888/output-var
ref: 0.0.1
exports:
- name: COLOR
- name: helm-overrides
path: "../../packages/helm"
ref: 0.0.1
overrides:
podinfo-component:
unicorn-podinfo:
values:
- path: "podinfo.replicaCount"
value: 1
- path: "podinfo.ui.color"
value: ${COLOR}
```

### Variables
Variables are similar to [values](#values) in that they allow users to override values in a Zarf package component's underlying Helm chart; they also share a similar syntax. However, unlike `values`, `variables` can be overridden at deploy time. For example, consider the following `variables`:
Expand Down
50 changes: 42 additions & 8 deletions src/pkg/bundle/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/AlecAivazis/survey/v2"
Expand All @@ -30,6 +31,9 @@ import (
// ZarfOverrideMap is a map of Zarf packages -> components -> Helm charts -> values
type ZarfOverrideMap map[string]map[string]map[string]interface{}

// templatedVarRegex is the regex for templated variables
var templatedVarRegex = regexp.MustCompile(`\${([^}]+)}`)

// Deploy deploys a bundle
//
// : create a new provider
Expand Down Expand Up @@ -166,7 +170,7 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle, zarfPackag
SetVariables: pkgVars,
}

valuesOverrides, err := b.loadChartOverrides(pkg)
valuesOverrides, err := b.loadChartOverrides(pkg, pkgVars)
if err != nil {
return err
}
Expand Down Expand Up @@ -201,6 +205,10 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle, zarfPackag
// save exported vars
pkgExportedVars := make(map[string]string)
for _, exp := range pkg.Exports {
// ensure if variable exists in package
if _, ok := pkgCfg.SetVariableMap[exp.Name]; !ok {
return fmt.Errorf("cannot export variable %s because it does not exist in package %s", exp.Name, pkg.Name)
}
pkgExportedVars[strings.ToUpper(exp.Name)] = pkgCfg.SetVariableMap[exp.Name].Value
}
bundleExportedVars[pkg.Name] = pkgExportedVars
Expand Down Expand Up @@ -281,7 +289,7 @@ func (b *Bundle) confirmBundleDeploy() (confirm bool) {
}

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

// Create a nested map to hold the values
overrideMap := make(map[string]map[string]*values.Options)
Expand All @@ -290,7 +298,7 @@ func (b *Bundle) loadChartOverrides(pkg types.Package) (ZarfOverrideMap, error)
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)
err := b.processOverrideValues(&overrideMap, &chartCopy.Values, componentName, chartName, pkgVars)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -332,10 +340,10 @@ func (b *Bundle) loadChartOverrides(pkg types.Package) (ZarfOverrideMap, error)
}

// 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) error {
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 {
// Add the override to the map, or return an error if the path is invalid
if err := addOverrideValue(*overrideMap, componentName, chartName, v.Path, v.Value); err != nil {
if err := addOverrideValue(*overrideMap, componentName, chartName, v.Path, v.Value, pkgVars); err != nil {
return err
}
}
Expand All @@ -350,7 +358,7 @@ func (b *Bundle) processOverrideVariables(overrideMap *map[string]map[string]*va
v.Name = strings.ToUpper(v.Name)
// check for override in env vars
if envVarOverride, exists := os.LookupEnv(strings.ToUpper(config.EnvVarPrefix + v.Name)); exists {
if err := addOverrideValue(*overrideMap, componentName, chartName, v.Path, envVarOverride); err != nil {
if err := addOverrideValue(*overrideMap, componentName, chartName, v.Path, envVarOverride, nil); err != nil {
return err
}
continue
Expand All @@ -373,7 +381,7 @@ func (b *Bundle) processOverrideVariables(overrideMap *map[string]map[string]*va
}

// Add the override to the map, or return an error if the path is invalid
if err := addOverrideValue(*overrideMap, componentName, chartName, v.Path, overrideVal); err != nil {
if err := addOverrideValue(*overrideMap, componentName, chartName, v.Path, overrideVal, nil); err != nil {
return err
}

Expand All @@ -382,7 +390,7 @@ func (b *Bundle) processOverrideVariables(overrideMap *map[string]map[string]*va
}

// addOverrideValue adds a value to a ZarfOverrideMap
func addOverrideValue(overrides map[string]map[string]*values.Options, component string, chart string, valuePath string, value interface{}) error {
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 {
overrides[component] = make(map[string]*values.Options)
Expand All @@ -408,6 +416,9 @@ func addOverrideValue(overrides map[string]map[string]*values.Options, component
}
// use JSONValues because we can easily marshal the YAML to JSON and Helm understands it
jsonVals := fmt.Sprintf("%s=[%s]", valuePath, strings.Join(jsonStrs, ","))
if pkgVars != nil {
jsonVals = setTemplatedVariables(jsonVals, pkgVars)
}
overrides[component][chart].JSONValues = append(overrides[component][chart].JSONValues, jsonVals)
case map[string]interface{}:
// handle objects by parsing them as json and appending to Options.JSONValues
Expand All @@ -417,11 +428,34 @@ func addOverrideValue(overrides map[string]map[string]*values.Options, component
}
// use JSONValues because we can easily marshal the YAML to JSON and Helm understands it
val := fmt.Sprintf("%s=%s", valuePath, j)
if pkgVars != nil {
val = setTemplatedVariables(val, pkgVars)
}
overrides[component][chart].JSONValues = append(overrides[component][chart].JSONValues, val)
default:
// Check for any templated variables if pkgVars set
decleaver marked this conversation as resolved.
Show resolved Hide resolved
if pkgVars != nil {
templatedVariable := fmt.Sprintf("%v", v)
value = setTemplatedVariables(templatedVariable, pkgVars)
}
// handle default case of simple values like strings and numbers
helmVal := fmt.Sprintf("%s=%v", valuePath, value)
overrides[component][chart].Values = append(overrides[component][chart].Values, helmVal)
}
return nil
}

// setTemplatedVariables sets the value for the templated variables
func setTemplatedVariables(templatedVariables string, pkgVars map[string]string) string {
// Use ReplaceAllStringFunc to handle all occurrences of templated variables
replacedValue := templatedVarRegex.ReplaceAllStringFunc(templatedVariables, func(match string) string {
// returns slice with the templated variable and the variable name
variableName := templatedVarRegex.FindStringSubmatch(match)[1]
// If we have a templated variable, get the value from pkgVars
if varValue, ok := pkgVars[variableName]; ok {
return varValue
}
return fmt.Sprintf("${%s_not_found}", variableName)
})
return replacedValue
}
46 changes: 46 additions & 0 deletions src/test/bundles/12-exported-pkg-vars/uds-bundle.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
kind: UDSBundle
metadata:
name: export-vars
description: testing a bundle using exported vars from zarf package
version: 0.0.1

packages:
- name: output-var
repository: localhost:888/output-var
ref: 0.0.1
exports:
- name: COLOR
- name: ANNOTATION
- name: DEFENSE
- name: BOOL

- name: helm-overrides
path: "src/test/packages/helm"
ref: 0.0.1
imports:
- name: COLOR
package: output-var
- name: ANNOTATION
package: output-var

overrides:
podinfo-component:
unicorn-podinfo:
values:
- path: "podinfo.replicaCount"
value: 1
- path: "podinfo.ui.color"
value: ${COLOR}
decleaver marked this conversation as resolved.
Show resolved Hide resolved
decleaver marked this conversation as resolved.
Show resolved Hide resolved
- path: podinfo.podAnnotations
value:
customAnnotation: ${COLOR}${ANNOTATION}
- path: "podinfo.tolerations"
value:
- key: "unicorn"
operator: "Equal"
value: ${DEFENSE}
effect: "NoSchedule"
- key: "uds"
operator: "Equal"
value: ${BOOL}
effect: "NoSchedule"
2 changes: 1 addition & 1 deletion src/test/e2e/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func createLocal(t *testing.T, bundlePath string, arch string) {
require.NoError(t, err)
}

func createLocalError(t *testing.T, bundlePath string, arch string) (stderr string) {
func createLocalError(bundlePath string, arch string) (stderr string) {
cmd := strings.Split(fmt.Sprintf("create %s --insecure --confirm -a %s", bundlePath, arch), " ")
_, stderr, _ = e2e.UDS(cmd...)
return stderr
Expand Down
45 changes: 44 additions & 1 deletion src/test/e2e/variable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestBundleVariables(t *testing.T) {

// Test with bad variable name in import
bundleDir = "src/test/bundles/02-simple-vars/import-all-bad-name"
stderr = createLocalError(t, bundleDir, e2e.Arch)
stderr = createLocalError(bundleDir, e2e.Arch)
require.Contains(t, stderr, "does not have a matching export")

// Test name collisions with exported variables
Expand Down Expand Up @@ -234,3 +234,46 @@ func TestVariablePrecedence(t *testing.T) {

remove(t, bundlePath)
}

func TestZarfPackageExportVarsAsGlobalBundleVars(t *testing.T) {
deployZarfInit(t)
zarfPkgPath1 := "src/test/packages/no-cluster/output-var"
e2e.CreateZarfPkg(t, zarfPkgPath1, false)

e2e.SetupDockerRegistry(t, 888)
defer e2e.TeardownRegistry(t, 888)

pkg := filepath.Join(zarfPkgPath1, fmt.Sprintf("zarf-package-output-var-%s-0.0.1.tar.zst", e2e.Arch))
zarfPublish(t, pkg, "localhost:888")

e2e.HelmDepUpdate(t, "src/test/packages/helm/unicorn-podinfo")
e2e.CreateZarfPkg(t, "src/test/packages/helm", false)
bundleDir := "src/test/bundles/12-exported-pkg-vars"
bundlePath := filepath.Join(bundleDir, fmt.Sprintf("uds-bundle-export-vars-%s-0.0.1.tar.zst", e2e.Arch))

createLocal(t, bundleDir, e2e.Arch)
deploy(t, bundlePath)

// check templated variables overrides in values
cmd := strings.Split("zarf tools kubectl get deploy -n podinfo unicorn-podinfo -o=jsonpath='{.spec.template.spec.containers[0].env[?(@.name==\"PODINFO_UI_COLOR\")].value}'", " ")
outputUIColor, _, err := e2e.UDS(cmd...)
require.Equal(t, "'orange'", outputUIColor)
require.NoError(t, err)

// check multiple templated variables as object overrides in values
cmd = strings.Split("zarf tools kubectl get deployment -n podinfo unicorn-podinfo -o=jsonpath='{.spec.template.metadata.annotations}'", " ")
annotations, _, err := e2e.UDS(cmd...)
require.Contains(t, annotations, "\"customAnnotation\":\"orangeAnnotation\"")
require.NoError(t, err)

// check templated variable list-type overrides in values
cmd = strings.Split("zarf tools kubectl get deployment -n podinfo unicorn-podinfo -o=jsonpath='{.spec.template.spec.tolerations}'", " ")
tolerations, _, err := e2e.UDS(cmd...)
require.Contains(t, tolerations, "\"key\":\"uds\"")
require.Contains(t, tolerations, "\"value\":\"true\"")
require.Contains(t, tolerations, "\"key\":\"unicorn\"")
require.Contains(t, tolerations, "\"value\":\"defense\"")
require.NoError(t, err)

remove(t, bundlePath)
}
12 changes: 12 additions & 0 deletions src/test/packages/no-cluster/output-var/zarf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ components:
echo "shared var in output-var pkg: "${ZARF_VAR_DOMAIN}""
- cmd: |
echo "output-var SPECIFIC_PKG_VAR = "${ZARF_VAR_SPECIFIC_PKG_VAR}""
- cmd: echo "orange"
setVariables:
- name: COLOR
- cmd: echo "Annotation"
setVariables:
- name: ANNOTATION
- cmd: echo "defense"
setVariables:
- name: DEFENSE
- cmd: echo "true"
setVariables:
- name: BOOL
Loading