Skip to content

Commit

Permalink
feat: uds config validation (#618)
Browse files Browse the repository at this point in the history
Co-authored-by: UncleGedd <42304551+UncleGedd@users.noreply.github.com>
  • Loading branch information
decleaver and UncleGedd committed May 28, 2024
1 parent cf0eb0f commit 80c5663
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 44 deletions.
24 changes: 24 additions & 0 deletions src/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,30 @@ import (
"github.com/spf13/cobra"
)

type configOption string

// Valid values for options in uds_config.yaml
const (
confirm configOption = "confirm"
insecure configOption = "insecure"
cachePath configOption = "uds_cache"
tempDirectory configOption = "tmp_dir"
logLevelOption configOption = "log_level"
architecture configOption = "architecture"
noLogFile configOption = "no_log_file"
noProgress configOption = "no_progress"
)

// isValidConfigOption checks if a string is a valid config option
func isValidConfigOption(str string) bool {
switch configOption(str) {
case confirm, insecure, cachePath, tempDirectory, logLevelOption, architecture, noLogFile, noProgress:
return true
default:
return false
}
}

// deploy performs validation, confirmation and deployment of a bundle
func deploy(bndlClient *bundle.Bundle) {
_, _, _, err := bndlClient.PreDeployValidation()
Expand Down
59 changes: 59 additions & 0 deletions src/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/config/lang"
"github.com/defenseunicorns/uds-cli/src/types"
zarfCommon "github.com/defenseunicorns/zarf/src/cmd/common"
"github.com/defenseunicorns/zarf/src/pkg/message"
goyaml "github.com/goccy/go-yaml"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -67,6 +69,14 @@ func init() {

initViper()

// load uds-config if it exists
if v.ConfigFileUsed() != "" {
if err := loadViperConfig(); err != nil {
message.Fatalf(err, "Failed to load uds-config: %s", err.Error())
return
}
}

v.SetDefault(V_LOG_LEVEL, "info")
v.SetDefault(V_ARCHITECTURE, "")
v.SetDefault(V_NO_LOG_FILE, false)
Expand All @@ -87,3 +97,52 @@ func init() {
rootCmd.PersistentFlags().BoolVar(&config.CommonOptions.Insecure, "insecure", v.GetBool(V_INSECURE), lang.RootCmdFlagInsecure)
rootCmd.PersistentFlags().IntVar(&config.CommonOptions.OCIConcurrency, "oci-concurrency", v.GetInt(V_BNDL_OCI_CONCURRENCY), lang.CmdBundleFlagConcurrency)
}

// loadViperConfig reads the config file and unmarshals the relevant config into DeployOpts.Variables
func loadViperConfig() error {
// get config file from Viper
configFile, err := os.ReadFile(v.ConfigFileUsed())
if err != nil {
return err
}

err = unmarshalAndValidateConfig(configFile, &bundleCfg)
if err != nil {
return err
}

// ensure the DeployOpts.Variables pkg vars are uppercase
for pkgName, pkgVar := range bundleCfg.DeployOpts.Variables {
for varName, varValue := range pkgVar {
// delete the lowercase var and replace with uppercase
delete(bundleCfg.DeployOpts.Variables[pkgName], varName)
bundleCfg.DeployOpts.Variables[pkgName][strings.ToUpper(varName)] = varValue
}
}

// ensure the DeployOpts.SharedVariables vars are uppercase
for varName, varValue := range bundleCfg.DeployOpts.SharedVariables {
// delete the lowercase var and replace with uppercase
delete(bundleCfg.DeployOpts.SharedVariables, varName)
bundleCfg.DeployOpts.SharedVariables[strings.ToUpper(varName)] = varValue
}

return nil
}

func unmarshalAndValidateConfig(configFile []byte, bundleCfg *types.BundleConfig) error {
// read relevant config into DeployOpts.Variables
// need to use goyaml because Viper doesn't preserve case: https://github.com/spf13/viper/issues/1014
// unmarshalling into DeployOpts because we want to check all of the top level config keys which are currently defined in DeployOpts
err := goyaml.UnmarshalWithOptions(configFile, &bundleCfg.DeployOpts, goyaml.Strict())
if err != nil {
return err
}
// validate config options
for optionName := range bundleCfg.DeployOpts.Options {
if !isValidConfigOption(optionName) {
return fmt.Errorf("invalid config option: %s", optionName)
}
}
return nil
}
44 changes: 0 additions & 44 deletions src/cmd/uds.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ import (
"io"
"os"
"path/filepath"
"strings"

"github.com/AlecAivazis/survey/v2"
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/config/lang"
"github.com/defenseunicorns/uds-cli/src/pkg/bundle"
"github.com/defenseunicorns/zarf/src/pkg/message"
goyaml "github.com/goccy/go-yaml"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -58,14 +56,6 @@ var deployCmd = &cobra.Command{
bundleCfg.DeployOpts.Source = chooseBundle(args)
configureZarf()

// load uds-config if it exists
if v.ConfigFileUsed() != "" {
if err := loadViperConfig(); err != nil {
message.Fatalf(err, "Failed to load uds-config: %s", err.Error())
return
}
}

// create new bundle client and deploy
bndlClient := bundle.NewOrDie(&bundleCfg)
defer bndlClient.ClearPaths()
Expand Down Expand Up @@ -185,40 +175,6 @@ var logsCmd = &cobra.Command{
},
}

// loadViperConfig reads the config file and unmarshals the relevant config into DeployOpts.Variables
func loadViperConfig() error {
// get config file from Viper
configFile, err := os.ReadFile(v.ConfigFileUsed())
if err != nil {
return err
}

// read relevant config into DeployOpts.Variables
// need to use goyaml because Viper doesn't preserve case: https://github.com/spf13/viper/issues/1014
err = goyaml.Unmarshal(configFile, &bundleCfg.DeployOpts)
if err != nil {
return err
}

// ensure the DeployOpts.Variables pkg vars are uppercase
for pkgName, pkgVar := range bundleCfg.DeployOpts.Variables {
for varName, varValue := range pkgVar {
// delete the lowercase var and replace with uppercase
delete(bundleCfg.DeployOpts.Variables[pkgName], varName)
bundleCfg.DeployOpts.Variables[pkgName][strings.ToUpper(varName)] = varValue
}
}

// ensure the DeployOpts.SharedVariables vars are uppercase
for varName, varValue := range bundleCfg.DeployOpts.SharedVariables {
// delete the lowercase var and replace with uppercase
delete(bundleCfg.DeployOpts.SharedVariables, varName)
bundleCfg.DeployOpts.SharedVariables[strings.ToUpper(varName)] = varValue
}

return nil
}

func init() {
initViper()

Expand Down
61 changes: 61 additions & 0 deletions src/cmd/uds_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The UDS Authors

// Package cmd contains the CLI commands for UDS.
package cmd

import (
"testing"

"github.com/defenseunicorns/uds-cli/src/types"
"github.com/stretchr/testify/require"
)

func TestUnmarshalAndValidateConfig(t *testing.T) {
type args struct {
configFile []byte
bundleCfg *types.BundleConfig
}
tests := []struct {
name string
args args
wantErr bool
errContains string
}{
{
name: "Invalid option key",
args: args{
configFile: []byte(`
options:
log_levelx: debug
`),
bundleCfg: &types.BundleConfig{},
},
wantErr: true,
errContains: "invalid config option: log_levelx",
},
{
name: "Option typo",
args: args{
configFile: []byte(`
optionx:
log_level: debug
`),
bundleCfg: &types.BundleConfig{},
},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := unmarshalAndValidateConfig(tt.args.configFile, tt.args.bundleCfg)
if tt.wantErr {
require.NotNil(t, err, "Expected error")
require.Contains(t, err.Error(), tt.errContains, "Error message should contain the expected string")
} else {
require.Nil(t, err, "Expected no error")
}
})
}
}
6 changes: 6 additions & 0 deletions src/test/bundles/07-helm-overrides/uds-config-invalid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
options:
log_levelx: debug

variables:
helm-overrides:
ui_color: "orange"
11 changes: 11 additions & 0 deletions src/test/e2e/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,17 @@ func TestBundleTmpDir(t *testing.T) {
require.NoError(t, err)
}

func TestInvalidConfig(t *testing.T) {
os.Setenv("UDS_CONFIG", filepath.Join("src/test/bundles/07-helm-overrides", "uds-config-invalid.yaml"))
zarfPkgPath := "src/test/packages/helm"
e2e.HelmDepUpdate(t, fmt.Sprintf("%s/unicorn-podinfo", zarfPkgPath))
args := strings.Split(fmt.Sprintf("zarf package create %s -o %s --confirm", zarfPkgPath, zarfPkgPath), " ")
_, stdErr, err := e2e.UDS(args...)
require.Error(t, err)
require.Contains(t, stdErr, "invalid config option: log_levelx")
os.Unsetenv("UDS_CONFIG")
}

func TestInvalidBundle(t *testing.T) {
deployZarfInit(t)
zarfPkgPath := "src/test/packages/helm"
Expand Down
1 change: 1 addition & 0 deletions src/types/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type BundleDeployOptions struct {
Variables map[string]map[string]interface{} `yaml:"variables,omitempty"`
SharedVariables map[string]interface{} `yaml:"shared,omitempty"`
Retries int `yaml:"retries"`
Options map[string]interface{} `yaml:"options,omitempty"`
}

// BundleInspectOptions is the options for the bundler.Inspect() function
Expand Down

0 comments on commit 80c5663

Please sign in to comment.