470 lines
13 KiB
Go
470 lines
13 KiB
Go
package tmpl
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"path/filepath"
|
|
goruntime "runtime"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/helmfile/helmfile/pkg/filesystem"
|
|
"github.com/helmfile/helmfile/pkg/runtime"
|
|
)
|
|
|
|
func TestCreateFuncMap(t *testing.T) {
|
|
currentVal := disableInsecureFeatures
|
|
|
|
{
|
|
disableInsecureFeatures = false
|
|
ctx := &Context{basePath: "."}
|
|
funcMaps := ctx.createFuncMap()
|
|
args := make([]any, 0)
|
|
outputExec, _ := funcMaps["exec"].(func(command string, args []any, inputs ...string) (string, error))("ls", args)
|
|
require.Contains(t, outputExec, "context.go")
|
|
}
|
|
|
|
disableInsecureFeatures = currentVal
|
|
}
|
|
|
|
func TestCreateFuncMap_DisabledInsecureFeatures(t *testing.T) {
|
|
currentVal := disableInsecureFeatures
|
|
|
|
{
|
|
disableInsecureFeatures = true
|
|
ctx := &Context{basePath: "."}
|
|
funcMaps := ctx.createFuncMap()
|
|
args := make([]any, 0)
|
|
_, err1 := funcMaps["exec"].(func(command string, args []any, inputs ...string) (string, error))("ls", args)
|
|
require.ErrorIs(t, err1, DisableInsecureFeaturesErr)
|
|
_, err2 := funcMaps["readFile"].(func(filename string) (string, error))("context_funcs_test.go")
|
|
require.ErrorIs(t, err2, DisableInsecureFeaturesErr)
|
|
}
|
|
|
|
disableInsecureFeatures = currentVal
|
|
}
|
|
|
|
func TestCreateFuncMap_SkipInsecureTemplateFunctions(t *testing.T) {
|
|
currentVal := skipInsecureTemplateFunctions
|
|
|
|
{
|
|
skipInsecureTemplateFunctions = true
|
|
ctx := &Context{basePath: "."}
|
|
funcMaps := ctx.createFuncMap()
|
|
args := make([]any, 0)
|
|
actual1, err1 := funcMaps["exec"].(func(command string, args []any, inputs ...string) (string, error))("ls", args)
|
|
require.Equal(t, "", actual1)
|
|
require.ErrorIs(t, err1, nil)
|
|
actual2, err2 := funcMaps["readFile"].(func(filename string) (string, error))("context_funcs_test.go")
|
|
require.Equal(t, "", actual2)
|
|
require.ErrorIs(t, err2, nil)
|
|
}
|
|
|
|
skipInsecureTemplateFunctions = currentVal
|
|
}
|
|
|
|
func newFSExpecting(expectedFilename string, expected string) *filesystem.FileSystem {
|
|
return filesystem.FromFileSystem(filesystem.FileSystem{
|
|
ReadFile: func(filename string) ([]byte, error) {
|
|
if filename != expectedFilename {
|
|
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename)
|
|
}
|
|
return []byte(expected), nil
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestReadFile(t *testing.T) {
|
|
expected := `foo:
|
|
bar: BAR
|
|
`
|
|
expectedFilename := "values.yaml"
|
|
ctx := &Context{basePath: ".", fs: newFSExpecting(expectedFilename, expected)}
|
|
actual, err := ctx.ReadFile(expectedFilename)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, actual)
|
|
}
|
|
|
|
type entry struct {
|
|
name string
|
|
fType fs.FileMode
|
|
isDir bool
|
|
}
|
|
|
|
func (e *entry) Name() string {
|
|
return e.name
|
|
}
|
|
|
|
func (e *entry) IsDir() bool {
|
|
return e.isDir
|
|
}
|
|
|
|
func (e *entry) Type() fs.FileMode {
|
|
return e.fType
|
|
}
|
|
|
|
func (e *entry) Info() (fs.FileInfo, error) {
|
|
return nil, fmt.Errorf("You should not call this method")
|
|
}
|
|
|
|
func TestReadDir(t *testing.T) {
|
|
result := []fs.DirEntry{
|
|
&entry{name: "file1.yaml"},
|
|
&entry{name: "file2.yaml"},
|
|
&entry{name: "file3.yaml"},
|
|
&entry{name: "folder1", isDir: true},
|
|
}
|
|
expectedArrayWindows := []string{
|
|
"sampleDirectory\\file1.yaml",
|
|
"sampleDirectory\\file2.yaml",
|
|
"sampleDirectory\\file3.yaml",
|
|
}
|
|
expectedArrayUnix := []string{
|
|
"sampleDirectory/file1.yaml",
|
|
"sampleDirectory/file2.yaml",
|
|
"sampleDirectory/file3.yaml",
|
|
}
|
|
var expectedArray []string
|
|
if goruntime.GOOS == "windows" {
|
|
expectedArray = expectedArrayWindows
|
|
} else {
|
|
expectedArray = expectedArrayUnix
|
|
}
|
|
|
|
expectedDirname := "sampleDirectory"
|
|
ctx := &Context{basePath: ".", fs: filesystem.FromFileSystem(filesystem.FileSystem{ReadDir: func(dirname string) ([]fs.DirEntry, error) {
|
|
if dirname != expectedDirname {
|
|
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedDirname, dirname)
|
|
}
|
|
return result, nil
|
|
}})}
|
|
|
|
actual, err := ctx.ReadDir(expectedDirname)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, expectedArray, actual)
|
|
}
|
|
|
|
func TestReadDirEntries(t *testing.T) {
|
|
result := []fs.DirEntry{
|
|
&entry{name: "file1.yaml"},
|
|
&entry{name: "file2.yaml"},
|
|
&entry{name: "file3.yaml"},
|
|
&entry{name: "folder1", isDir: true},
|
|
}
|
|
|
|
expectedDirname := "sampleDirectory"
|
|
ctx := &Context{basePath: ".", fs: filesystem.FromFileSystem(filesystem.FileSystem{ReadDir: func(dirname string) ([]fs.DirEntry, error) {
|
|
if dirname != expectedDirname {
|
|
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedDirname, dirname)
|
|
}
|
|
return result, nil
|
|
}})}
|
|
|
|
actual, err := ctx.ReadDirEntries(expectedDirname)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, result, actual)
|
|
}
|
|
|
|
func TestReadFile_PassAbsPath(t *testing.T) {
|
|
expected := `foo:
|
|
bar: BAR
|
|
`
|
|
expectedFilename, _ := filepath.Abs("values.yaml")
|
|
ctx := &Context{basePath: ".", fs: newFSExpecting(expectedFilename, expected)}
|
|
actual, err := ctx.ReadFile(expectedFilename)
|
|
require.NoError(t, err)
|
|
require.Equal(t, actual, expected)
|
|
}
|
|
|
|
func TestToYaml_NestedMapInterfaceKey(t *testing.T) {
|
|
v := runtime.GoccyGoYaml
|
|
t.Cleanup(func() {
|
|
runtime.GoccyGoYaml = v
|
|
})
|
|
|
|
// nolint: unconvert
|
|
vals := Values(map[string]any{
|
|
"foo": map[any]any{
|
|
"bar": "BAR",
|
|
},
|
|
})
|
|
|
|
runtime.GoccyGoYaml = true
|
|
|
|
actual, err := ToYaml(vals)
|
|
require.Equal(t, "foo:\n bar: BAR\n", actual)
|
|
require.NoError(t, err, "expected nil, but got: %v, when type: map[interface {}]interface {}", err)
|
|
|
|
runtime.GoccyGoYaml = false
|
|
|
|
actual, err = ToYaml(vals)
|
|
require.Equal(t, "foo:\n bar: BAR\n", actual)
|
|
require.NoError(t, err, "expected nil, but got: %v, when type: map[interface {}]interface {}", err)
|
|
}
|
|
|
|
func TestToYaml(t *testing.T) {
|
|
expected := `foo:
|
|
bar: BAR
|
|
`
|
|
// nolint: unconvert
|
|
vals := Values(map[string]any{
|
|
"foo": map[string]any{
|
|
"bar": "BAR",
|
|
},
|
|
})
|
|
actual, err := ToYaml(vals)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, actual)
|
|
}
|
|
|
|
func testFromYaml(t *testing.T, goccyGoYaml bool, expected Values) {
|
|
t.Helper()
|
|
|
|
v := runtime.GoccyGoYaml
|
|
runtime.GoccyGoYaml = goccyGoYaml
|
|
t.Cleanup(func() {
|
|
runtime.GoccyGoYaml = v
|
|
})
|
|
|
|
raw := `foo:
|
|
bar: BAR
|
|
`
|
|
actual, err := FromYaml(raw)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, actual)
|
|
}
|
|
|
|
func TestFromYaml(t *testing.T) {
|
|
t.Run("with goccy/go-yaml", func(t *testing.T) {
|
|
testFromYaml(
|
|
t,
|
|
true,
|
|
// nolint: unconvert
|
|
Values(map[string]any{
|
|
"foo": map[string]any{
|
|
"bar": "BAR",
|
|
},
|
|
}),
|
|
)
|
|
})
|
|
|
|
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
|
|
testFromYaml(
|
|
t,
|
|
false,
|
|
// nolint: unconvert
|
|
Values(map[string]any{
|
|
"foo": map[string]any{
|
|
"bar": "BAR",
|
|
},
|
|
}),
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestFromYamlToJson(t *testing.T) {
|
|
input := `foo:
|
|
bar: BAR
|
|
`
|
|
want := `{"foo":{"bar":"BAR"}}`
|
|
|
|
m, err := FromYaml(input)
|
|
require.NoError(t, err)
|
|
|
|
got, err := json.Marshal(m)
|
|
require.NoError(t, err)
|
|
require.Equal(t, string(got), want)
|
|
}
|
|
|
|
func TestSetValueAtPath_OneComponent(t *testing.T) {
|
|
input := map[string]any{
|
|
"foo": "",
|
|
}
|
|
expected := map[string]any{
|
|
"foo": "FOO",
|
|
}
|
|
actual, err := SetValueAtPath("foo", "FOO", input)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, actual)
|
|
}
|
|
|
|
func TestSetValueAtPath_TwoComponents(t *testing.T) {
|
|
input := map[string]any{
|
|
"foo": map[any]any{
|
|
"bar": "",
|
|
},
|
|
}
|
|
expected := map[string]any{
|
|
"foo": map[any]any{
|
|
"bar": "FOO_BAR",
|
|
},
|
|
}
|
|
actual, err := SetValueAtPath("foo.bar", "FOO_BAR", input)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, actual)
|
|
}
|
|
|
|
func TestTpl(t *testing.T) {
|
|
ctx := &Context{basePath: "."}
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
hasErr bool
|
|
data map[string]any
|
|
}{
|
|
{
|
|
name: "simple",
|
|
input: `foo: {{ .foo }}`,
|
|
expected: `foo: Foo`,
|
|
data: map[string]any{
|
|
"foo": "Foo",
|
|
},
|
|
},
|
|
{
|
|
name: "multiline_input",
|
|
input: `{{ .name }}
|
|
end`,
|
|
expected: "multiline\nend",
|
|
data: map[string]any{
|
|
"name": "multiline",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
actual, err := ctx.Tpl(tt.input, tt.data)
|
|
if tt.hasErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
require.Equal(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRequired(t *testing.T) {
|
|
type args struct {
|
|
warn string
|
|
val any
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want any
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "required val is nil",
|
|
args: args{warn: "This value is required", val: nil},
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "required val is empty string",
|
|
args: args{warn: "This value is required", val: ""},
|
|
want: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "required val is existed",
|
|
args: args{warn: "This value is required", val: "foo"},
|
|
want: "foo",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
testCase := tt
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
got, err := Required(testCase.args.warn, testCase.args.val)
|
|
if testCase.wantErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
require.Equal(t, testCase.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRequiredEnv tests that RequiredEnv returns an error if the environment variable is not set.
|
|
func TestRequiredEnv(t *testing.T) {
|
|
// test that the environment variable is not set
|
|
envKey := "HelmFile"
|
|
envVal, err := RequiredEnv(envKey)
|
|
|
|
require.NotNilf(t, err, "Expected error to be returned when environment variable %s is not set", envKey)
|
|
require.Emptyf(t, envVal, "Expected empty string to be returned when environment variable %s is not set", envKey)
|
|
|
|
// test that the environment variable is set to an empty string
|
|
t.Setenv(envKey, "")
|
|
envVal, err = RequiredEnv(envKey)
|
|
|
|
require.NotNilf(t, err, "Expected error to be returned when environment variable %s is set to an empty string", envKey)
|
|
require.Emptyf(t, envVal, "Expected empty string to be returned when environment variable %s is set to an empty string", envKey)
|
|
|
|
// test that the environment variable is set to a non-empty string
|
|
expected := "helmfile"
|
|
t.Setenv(envKey, expected)
|
|
|
|
envVal, err = RequiredEnv(envKey)
|
|
require.Nilf(t, err, "Expected no error to be returned when environment variable %s is set to a non-empty string", envKey)
|
|
require.Equalf(t, expected, envVal, "Expected %s to be returned when environment variable %s is set to a non-empty string", expected, envKey)
|
|
}
|
|
|
|
// TestExec tests that Exec returns the expected output.
|
|
func TestExec(t *testing.T) {
|
|
ctx := &Context{basePath: "."}
|
|
|
|
// test that the command is executed
|
|
expected := "foo\n"
|
|
output, err := ctx.Exec("echo", []any{"foo"}, "")
|
|
require.Nilf(t, err, "Expected no error to be returned when executing command")
|
|
require.Equalf(t, expected, output, "Expected %s to be returned when executing command", expected)
|
|
|
|
// test that the command is executed with no-zero exit code
|
|
_, err = ctx.Exec("bash", []any{"-c", "exit 1"}, "")
|
|
require.Error(t, err, "Expected error to be returned when executing command with non-zero exit code")
|
|
}
|
|
|
|
// TestEnvExec tests that EnvExec returns the expected output.
|
|
// TODO: in the next major version, this test should be removed.
|
|
func TestEnvExec(t *testing.T) {
|
|
ctx := &Context{basePath: "."}
|
|
|
|
expected := "foo"
|
|
|
|
testKey := "testkey"
|
|
|
|
// test that the command is executed with environment variables
|
|
output, err := ctx.EnvExec(map[string]any{testKey: "foo"}, "bash", []any{"-c", fmt.Sprintf("echo -n $%s", testKey)}, "")
|
|
|
|
require.Nilf(t, err, "Expected no error to be returned when executing command with environment variables")
|
|
|
|
require.Equalf(t, expected, output, "Expected %s to be returned when executing command with environment variables", expected)
|
|
|
|
// test that the command is executed with invalid environment variables
|
|
output, err = ctx.EnvExec(map[string]any{testKey: 123}, "bash", []any{"-c", fmt.Sprintf("echo -n $%s", testKey)}, "")
|
|
|
|
require.Errorf(t, err, "Expected error to be returned when executing command with invalid environment variables")
|
|
require.Emptyf(t, output, "Expected empty string to be returned when executing command with invalid environment variables")
|
|
|
|
// test that the command is executed with no environment variables
|
|
output, err = ctx.EnvExec(nil, "bash", []any{"-c", fmt.Sprintf("echo -n $%s", testKey)}, "")
|
|
require.Nilf(t, err, "Expected no error to be returned when executing command with no environment variables")
|
|
|
|
require.Emptyf(t, output, "Expected empty string to be returned when executing command with no environment variables")
|
|
|
|
// test that the command is executed with os environment variables
|
|
t.Setenv(testKey, "foo")
|
|
output, err = ctx.EnvExec(nil, "bash", []any{"-c", fmt.Sprintf("echo -n $%s", testKey)}, "")
|
|
|
|
require.Nilf(t, err, "Expected no error to be returned when executing command with environment variables")
|
|
|
|
require.Equalf(t, expected, output, "Expected %s to be returned when executing command with environment variables", expected)
|
|
}
|