diff --git a/Makefile b/Makefile index 1924780d4c..62bbf3b859 100644 --- a/Makefile +++ b/Makefile @@ -223,5 +223,5 @@ cve-report: ## Create a CVE report for the current project (must `brew install g @test -d ./build || mkdir ./build go run main.go tools sbom scan . -o json --exclude './site' --exclude './examples' | grype -o template -t hack/grype.tmpl > build/zarf-known-cves.csv -lint-go: ## Run golang-ci-lint to lint the go code (must `brew install golang-ci-lint` first) +lint-go: ## Run golang-ci-lint to lint the go code (must `brew install golangci-lint` first) golangci-lint run diff --git a/src/pkg/variables/templates_test.go b/src/pkg/variables/templates_test.go new file mode 100644 index 0000000000..7dfb540029 --- /dev/null +++ b/src/pkg/variables/templates_test.go @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package variables + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +var start = ` +This is a test file for templating. + + ###PREFIX_VAR_REPLACE_ME### + ###PREFIX_CONST_REPLACE_ME### + ###PREFIX_APP_REPLACE_ME### + ###PREFIX_NON_EXIST### +` +var simple = ` +This is a test file for templating. + + VAR_REPLACED + CONST_REPLACED + APP_REPLACED + ###PREFIX_NON_EXIST### +` +var multiline = ` +This is a test file for templating. + + VAR_REPLACED +VAR_SECOND + CONST_REPLACED +CONST_SECOND + APP_REPLACED +APP_SECOND + ###PREFIX_NON_EXIST### +` +var autoIndent = ` +This is a test file for templating. + + VAR_REPLACED + VAR_SECOND + CONST_REPLACED + CONST_SECOND + APP_REPLACED + APP_SECOND + ###PREFIX_NON_EXIST### +` +var file = ` +This is a test file for templating. + + The contents of this file become the template value + CONSTs Don't Support File + The contents of this file become the template value + ###PREFIX_NON_EXIST### +` + +func TestReplaceTextTemplate(t *testing.T) { + type test struct { + vc VariableConfig + path string + wantErr bool + wantContents string + } + + tests := []test{ + { + vc: VariableConfig{setVariableMap: SetVariableMap{}, applicationTemplates: map[string]*TextTemplate{}}, + path: "non-existent.test", + wantErr: true, + wantContents: start, + }, + { + vc: VariableConfig{ + templatePrefix: "PREFIX", + setVariableMap: SetVariableMap{ + "REPLACE_ME": {Value: "VAR_REPLACED"}, + }, + constants: []Constant{{Name: "REPLACE_ME", Value: "CONST_REPLACED"}}, + applicationTemplates: map[string]*TextTemplate{ + "###PREFIX_APP_REPLACE_ME###": {Value: "APP_REPLACED"}, + }, + }, + wantContents: simple, + }, + { + vc: VariableConfig{ + templatePrefix: "PREFIX", + setVariableMap: SetVariableMap{ + "REPLACE_ME": {Value: "VAR_REPLACED\nVAR_SECOND"}, + }, + constants: []Constant{{Name: "REPLACE_ME", Value: "CONST_REPLACED\nCONST_SECOND"}}, + applicationTemplates: map[string]*TextTemplate{ + "###PREFIX_APP_REPLACE_ME###": {Value: "APP_REPLACED\nAPP_SECOND"}, + }, + }, + wantContents: multiline, + }, + { + vc: VariableConfig{ + templatePrefix: "PREFIX", + setVariableMap: SetVariableMap{ + "REPLACE_ME": {Value: "VAR_REPLACED\nVAR_SECOND", Variable: Variable{AutoIndent: true}}, + }, + constants: []Constant{{Name: "REPLACE_ME", Value: "CONST_REPLACED\nCONST_SECOND", AutoIndent: true}}, + applicationTemplates: map[string]*TextTemplate{ + "###PREFIX_APP_REPLACE_ME###": {Value: "APP_REPLACED\nAPP_SECOND", AutoIndent: true}, + }, + }, + wantContents: autoIndent, + }, + { + vc: VariableConfig{ + templatePrefix: "PREFIX", + setVariableMap: SetVariableMap{ + "REPLACE_ME": {Value: "testdata/file.txt", Variable: Variable{Type: FileVariableType}}, + }, + constants: []Constant{{Name: "REPLACE_ME", Value: "CONSTs Don't Support File"}}, + applicationTemplates: map[string]*TextTemplate{ + "###PREFIX_APP_REPLACE_ME###": {Value: "testdata/file.txt", Type: FileVariableType}, + }, + }, + wantContents: file, + }, + } + + for _, tc := range tests { + if tc.path == "" { + tmpDir := t.TempDir() + tc.path = filepath.Join(tmpDir, "templates.test") + + f, _ := os.Create(tc.path) + defer f.Close() + + f.WriteString(start) + } + + gotErr := tc.vc.ReplaceTextTemplate(tc.path) + if tc.wantErr { + require.Error(t, gotErr) + } else { + require.NoError(t, gotErr) + gotContents, _ := os.ReadFile(tc.path) + require.Equal(t, tc.wantContents, string(gotContents)) + } + } +} diff --git a/src/pkg/variables/testdata/file.txt b/src/pkg/variables/testdata/file.txt new file mode 100644 index 0000000000..c5710b7a29 --- /dev/null +++ b/src/pkg/variables/testdata/file.txt @@ -0,0 +1 @@ +The contents of this file become the template value \ No newline at end of file diff --git a/src/pkg/variables/variables.go b/src/pkg/variables/variables.go index cdd3bc4c94..1f2e6a8480 100644 --- a/src/pkg/variables/variables.go +++ b/src/pkg/variables/variables.go @@ -76,9 +76,18 @@ func (vc *VariableConfig) SetVariable(name, value string, sensitive bool, autoIn // CheckVariablePattern checks to see if a current variable is set to a value that matches its pattern func (vc *VariableConfig) CheckVariablePattern(name, pattern string) error { - if regexp.MustCompile(pattern).MatchString(vc.setVariableMap[name].Value) { - return nil + if variable, ok := vc.setVariableMap[name]; ok { + r, err := regexp.Compile(pattern) + if err != nil { + return err + } + + if r.MatchString(variable.Value) { + return nil + } + + return fmt.Errorf("provided value for variable %q does not match pattern %q", name, pattern) } - return fmt.Errorf("provided value for variable %q does not match pattern %q", name, pattern) + return fmt.Errorf("variable %q was not found in the current variable map", name) } diff --git a/src/pkg/variables/variables_test.go b/src/pkg/variables/variables_test.go new file mode 100644 index 0000000000..901fdf4ba9 --- /dev/null +++ b/src/pkg/variables/variables_test.go @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package variables + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPopulateVariables(t *testing.T) { + type test struct { + vc VariableConfig + vars []InteractiveVariable + presets map[string]string + wantErr bool + wantVars SetVariableMap + } + + prompt := func(_ InteractiveVariable) (value string, err error) { return "Prompt", nil } + + tests := []test{ + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{{Variable: Variable{Name: "NAME"}}}, + presets: map[string]string{}, + wantVars: SetVariableMap{"NAME": {Variable: Variable{Name: "NAME"}}}, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Default: "Default"}, + }, + presets: map[string]string{}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Default"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Default: "Default"}, + }, + presets: map[string]string{"NAME": "Set"}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Set"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME", Sensitive: true, AutoIndent: true, Type: FileVariableType}}, + }, + presets: map[string]string{}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME", Sensitive: true, AutoIndent: true, Type: FileVariableType}}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME", Sensitive: true, AutoIndent: true, Type: FileVariableType}}, + }, + presets: map[string]string{"NAME": "Set"}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME", Sensitive: true, AutoIndent: true, Type: FileVariableType}, Value: "Set"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}, prompt: prompt}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Prompt: true}, + }, + presets: map[string]string{}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Prompt"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}, prompt: prompt}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Default: "Default", Prompt: true}, + }, + presets: map[string]string{}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Prompt"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}, prompt: prompt}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Prompt: true}, + }, + presets: map[string]string{"NAME": "Set"}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Set"}, + }, + }, + } + + for _, tc := range tests { + gotErr := tc.vc.PopulateVariables(tc.vars, tc.presets) + if tc.wantErr { + require.Error(t, gotErr) + } else { + require.NoError(t, gotErr) + } + + gotVars := tc.vc.setVariableMap + + require.Equal(t, len(gotVars), len(tc.wantVars)) + + for key := range gotVars { + require.Equal(t, gotVars[key], tc.wantVars[key]) + } + } +} + +func TestCheckVariablePattern(t *testing.T) { + type test struct { + vc VariableConfig + name string + pattern string + wantErrMsg string + } + + tests := []test{ + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, name: "NAME", pattern: "n[a-z]me", + wantErrMsg: "variable \"NAME\" was not found in the current variable map", + }, + { + vc: VariableConfig{ + setVariableMap: SetVariableMap{"NAME": &SetVariable{Value: "name"}}, + }, name: "NAME", pattern: "n[^a]me", + wantErrMsg: "provided value for variable \"NAME\" does not match pattern \"n[^a]me\"", + }, + { + vc: VariableConfig{ + setVariableMap: SetVariableMap{"NAME": &SetVariable{Value: "name"}}, + }, name: "NAME", pattern: "n[a-z]me", wantErrMsg: "", + }, + { + vc: VariableConfig{ + setVariableMap: SetVariableMap{"NAME": &SetVariable{Value: "name"}}, + }, name: "NAME", pattern: "n[a-z-bad-pattern", wantErrMsg: "error parsing regexp: missing closing ]: `[a-z-bad-pattern`", + }, + } + + for _, tc := range tests { + got := tc.vc.CheckVariablePattern(tc.name, tc.pattern) + if tc.wantErrMsg != "" { + require.EqualError(t, got, tc.wantErrMsg) + } else { + require.NoError(t, got) + } + } +}