Skip to content

Commit

Permalink
feat: adds --set to helm override vars
Browse files Browse the repository at this point in the history
  • Loading branch information
UncleGedd committed Mar 19, 2024
1 parent db61652 commit 132039f
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 34 deletions.
50 changes: 36 additions & 14 deletions docs/overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ The `value` is the value to set at the `path`. Values can be simple values such
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.
```
```yaml
kind: UDSBundle
metadata:
name: export-vars
name: example-bundle
description: Example for using an imported variable to set an overrides value
version: 0.0.1

Expand All @@ -145,7 +145,7 @@ packages:
exports:
- name: COLOR

- name: helm-overrides
- name: helm-overrides-package
path: "../../packages/helm"
ref: 0.0.1

Expand All @@ -160,35 +160,57 @@ packages:
```

### 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`:
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 `variables` key in the following `uds-bundle.yaml`:

```yaml
...
overrides:
helm-overrides-component:
podinfo:
variables:
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:
variables:
- name: UI_COLOR
path: "ui.color"
description: "Set the color for podinfo's UI"
default: "purple"
```

There are 2 ways to override the `UI_COLOR` variable:
There are 3 ways to override the `UI_COLOR` variable:

1. **UDS config**: you can create a `uds-config.yaml` file in the same directory as the bundle and specify the variable to override. For example, to override the `UI_COLOR` variable, you can create a `uds-config.yaml`:

```yaml
variables:
helm-overrides-package:
ui_color: green
ui_color: green # Note that the variable for `UI_COLOR` can be upper or lowercase
```
Note that the variable for `UI_COLOR` can be upper or lowercase.

1. **Environment variables**: you can create an environment variable prefixed with `UDS_` and the name of the variable. For example, to override the `UI_COLOR` variable, you can create an environment variable called `UDS_UI_COLOR` and set it to the desired value. Note that environment variables take precedence over `uds-config.yaml` variables.

1. **--set Flag**: you can also override the variable using the CLI's `--set` flag. For example, to override the `UI_COLOR` variable, you can run one of the following commands:

```bash
# by default ui_color will apply to all packages in the bundle
uds deploy example-bundle --set ui_color=green

# to specify a specific package that the variable should apply to you can prepend th package name to the variable
uds deploy example-bundle --set helm-overrides-package.ui_color=green
```

> **:warning: Warning**: Because Helm override variables and Zarf variables share the same --set syntax, be careful with variable names to avoid conflicts.


#### Variable Precedence
Variable precedence is as follows:
1. The `--set` flag
1. Environment variables
2. `uds-config.yaml` variables
3. Variables `default` in the`uds-bundle.yaml`
1. `uds-config.yaml` variables
1. Variables `default` in the`uds-bundle.yaml`
47 changes: 27 additions & 20 deletions src/pkg/bundle/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,28 +348,35 @@ func (b *Bundle) processOverrideVariables(overrideMap *map[string]map[string]*va
// Ensuring variable name is upper case since comparisons are being done against upper case env and config variables
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, nil); err != nil {
return err
// check for override in --set vars
for k, val := range b.cfg.DeployOpts.SetVariables {
if strings.Contains(k, ".") {
// check for <pkg>.<var> syntax was used in --set and use uppercase for a non-case-sensitive comparison
setVal := strings.Split(k, ".")
if setVal[0] == pkgName && strings.ToUpper(setVal[1]) == v.Name {
overrideVal = val
}
} else if strings.ToUpper(k) == v.Name {
overrideVal = val
}
continue
}
// check for override in config
configFileOverride, existsInConfig := b.cfg.DeployOpts.Variables[pkgName][v.Name]
sharedConfigOverride, existsInSharedConfig := b.cfg.DeployOpts.SharedVariables[v.Name]
if v.Default == nil && !existsInConfig && !existsInSharedConfig {
// no default and not in config, use values from underlying chart
continue
} else if existsInConfig {
// if set in config
overrideVal = configFileOverride
} else if existsInSharedConfig {
// if set in shared config
overrideVal = sharedConfigOverride
} else {
// use default v if no config v is set
overrideVal = v.Default

// check for override in env vars if not in --set
if envVarOverride, exists := os.LookupEnv(strings.ToUpper(config.EnvVarPrefix + v.Name)); overrideVal == nil && exists {
overrideVal = envVarOverride
}

// if not in --set or an env var, use the following precedence: configFile, sharedConfig, default
if overrideVal == nil {
if configFileOverride, existsInConfig := b.cfg.DeployOpts.Variables[pkgName][v.Name]; existsInConfig {
overrideVal = configFileOverride
} else if sharedConfigOverride, existsInSharedConfig := b.cfg.DeployOpts.SharedVariables[v.Name]; existsInSharedConfig {
overrideVal = sharedConfigOverride
} else if v.Default != nil {
overrideVal = v.Default
} else {
continue
}
}

// Add the override to the map, or return an error if the path is invalid
Expand Down
191 changes: 191 additions & 0 deletions src/pkg/bundle/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"testing"

"github.com/defenseunicorns/uds-cli/src/types"
"github.com/stretchr/testify/require"
"helm.sh/helm/v3/pkg/cli/values"
)

func TestLoadVariablesPrecedence(t *testing.T) {
Expand Down Expand Up @@ -239,3 +241,192 @@ func TestLoadVariablesPrecedence(t *testing.T) {
})
}
}

func TestHelmOverrideVariablePrecedence(t *testing.T) {
// args for b.processOverrideVariables fn
type args struct {
pkgName string
variables *[]types.BundleChartVariable
componentName string
chartName string
}
testCases := []struct {
name string
bundle Bundle
args args
loadEnvVar bool
expectedVal string
}{
{
name: "--set flag precedence",
loadEnvVar: true,
bundle: Bundle{
cfg: &types.BundleConfig{
DeployOpts: types.BundleDeployOptions{
SetVariables: map[string]string{
"foo": "set using --set flag",
},
SharedVariables: map[string]interface{}{
"FOO": "set from shared key in uds-config.yaml",
},
Variables: map[string]map[string]interface{}{
"FOO": {
"foo": "set from variables key in uds-config.yaml",
},
},
},
},
},
args: args{
pkgName: "fooPkg",
variables: &[]types.BundleChartVariable{
{
Name: "foo",
Default: "default value",
},
},
componentName: "component",
chartName: "chart",
},
expectedVal: "=set using --set flag",
},
{
name: "env var precedence",
loadEnvVar: true,
bundle: Bundle{
cfg: &types.BundleConfig{
DeployOpts: types.BundleDeployOptions{
SharedVariables: map[string]interface{}{
"FOO": "set from shared key in uds-config.yaml",
},
Variables: map[string]map[string]interface{}{
"fooPkg": {
"FOO": "set from variables key in uds-config.yaml",
},
},
},
},
},
args: args{
pkgName: "fooPkg",
variables: &[]types.BundleChartVariable{
{
Name: "foo",
Default: "default value",
},
},
componentName: "component",
chartName: "chart",
},
expectedVal: "=set using env var",
},
{
name: "uds-config variables key precedence",
bundle: Bundle{
cfg: &types.BundleConfig{
DeployOpts: types.BundleDeployOptions{
SharedVariables: map[string]interface{}{
"FOO": "set from shared key in uds-config.yaml",
},
Variables: map[string]map[string]interface{}{
"fooPkg": {
"FOO": "set from variables key in uds-config.yaml",
},
},
},
},
},
args: args{
pkgName: "fooPkg",
variables: &[]types.BundleChartVariable{
{
Name: "foo",
Default: "default value",
},
},
componentName: "component",
chartName: "chart",
},
expectedVal: "=set from variables key in uds-config.yaml",
},
{
name: "uds-config shared key precedence",
bundle: Bundle{
cfg: &types.BundleConfig{
DeployOpts: types.BundleDeployOptions{
SharedVariables: map[string]interface{}{
"FOO": "set from shared key in uds-config.yaml",
},
},
},
},
args: args{
pkgName: "fooPkg",
variables: &[]types.BundleChartVariable{
{
Name: "foo",
Default: "default value",
},
},
componentName: "component",
chartName: "chart",
},
expectedVal: "=set from shared key in uds-config.yaml",
},
{
name: "use variable default",
bundle: Bundle{
cfg: &types.BundleConfig{},
},
args: args{
pkgName: "fooPkg",
variables: &[]types.BundleChartVariable{
{
Name: "foo",
Default: "default value",
},
},
componentName: "component",
chartName: "chart",
},
expectedVal: "=default value",
},
{
name: "no variable overrides",
bundle: Bundle{
cfg: &types.BundleConfig{},
},
args: args{
pkgName: "fooPkg",
variables: &[]types.BundleChartVariable{
{
Name: "foo",
},
},
componentName: "component",
chartName: "chart",
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b := &Bundle{
cfg: tc.bundle.cfg,
bundle: tc.bundle.bundle,
tmp: tc.bundle.tmp,
}
// Set for select test cases to test precedence of env vars
os.Unsetenv("UDS_FOO")
if tc.loadEnvVar {
os.Setenv("UDS_FOO", "set using env var")
}
overrideMap := map[string]map[string]*values.Options{}
err := b.processOverrideVariables(&overrideMap, tc.args.pkgName, tc.args.variables, tc.args.componentName, tc.args.chartName)
require.NoError(t, err)
if tc.expectedVal == "" {
require.Equal(t, 0, len(overrideMap))
}
})
}
}
18 changes: 18 additions & 0 deletions src/test/e2e/variable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ func TestBundleWithEnvVarHelmOverrides(t *testing.T) {
require.NoError(t, err)
err = os.Setenv("UDS_UI_COLOR", color)
require.NoError(t, err)
err = os.Setenv("UDS_UI_MSG", "im set by an env var")
require.NoError(t, err)
err = os.Setenv("UDS_SECRET_VAL", b64Secret)
require.NoError(t, err)

Expand All @@ -191,6 +193,22 @@ func TestBundleWithEnvVarHelmOverrides(t *testing.T) {
require.NoError(t, err)
})

t.Run("ensure --set overrides take precedence over env vars", func(t *testing.T) {
deployCmd := fmt.Sprintf("deploy %s --set UI_COLOR=orange --set helm-overrides.ui_msg=foo --confirm", bundlePath)
_, _, err := e2e.UDS(strings.Split(deployCmd, " ")...)
require.NoError(t, err)

cmd := strings.Split("z 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.NoError(t, err)
require.Equal(t, "'orange'", outputUIColor)

cmd = strings.Split("z tools kubectl get deploy -n podinfo unicorn-podinfo -o=jsonpath='{.spec.template.spec.containers[0].env[?(@.name==\"PODINFO_UI_MESSAGE\")].value}'", " ")
outputMsg, _, err := e2e.UDS(cmd...)
require.NoError(t, err)
require.Equal(t, "'foo'", outputMsg)
})

remove(t, bundlePath)
}

Expand Down

0 comments on commit 132039f

Please sign in to comment.