From 132039f3a031f007412baebf0b0e6bd0fd43b150 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Tue, 19 Mar 2024 15:14:49 -0500 Subject: [PATCH] feat: adds --set to helm override vars --- docs/overrides.md | 50 ++++++--- src/pkg/bundle/deploy.go | 47 +++++---- src/pkg/bundle/deploy_test.go | 191 ++++++++++++++++++++++++++++++++++ src/test/e2e/variable_test.go | 18 ++++ 4 files changed, 272 insertions(+), 34 deletions(-) diff --git a/docs/overrides.md b/docs/overrides.md index 5935af92..a7ec018d 100644 --- a/docs/overrides.md +++ b/docs/overrides.md @@ -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 @@ -145,7 +145,7 @@ packages: exports: - name: COLOR - - name: helm-overrides + - name: helm-overrides-package path: "../../packages/helm" ref: 0.0.1 @@ -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` diff --git a/src/pkg/bundle/deploy.go b/src/pkg/bundle/deploy.go index 96a8f4fe..067130ec 100644 --- a/src/pkg/bundle/deploy.go +++ b/src/pkg/bundle/deploy.go @@ -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 . 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 diff --git a/src/pkg/bundle/deploy_test.go b/src/pkg/bundle/deploy_test.go index f4d7b697..38694851 100644 --- a/src/pkg/bundle/deploy_test.go +++ b/src/pkg/bundle/deploy_test.go @@ -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) { @@ -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)) + } + }) + } +} diff --git a/src/test/e2e/variable_test.go b/src/test/e2e/variable_test.go index 407acecd..49d4d13b 100644 --- a/src/test/e2e/variable_test.go +++ b/src/test/e2e/variable_test.go @@ -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) @@ -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) }