Use gopkg.in/yaml.v2 for Helmfile v0.x (#609)

This should fix #435 for Helmfile v0.x releases since the next v0.150.0.
We introduce a new envvar to opt-in to the new YAML library, so that you can give it a shot before upgrading your Helmfile to v1. The same envvar can be used to opt-out of the new YAML library after you upgrade to Helmfile v1, giving you a more flexible migration story.

Signed-off-by: Yusuke Kuoka <ykuoka@gmail.com>
This commit is contained in:
Yusuke Kuoka 2023-01-04 18:17:24 +09:00 committed by GitHub
parent 490bb5d147
commit 4688cf0132
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 357 additions and 246 deletions

View File

@ -61,6 +61,7 @@ jobs:
plugin-secrets-version: 3.15.0 plugin-secrets-version: 3.15.0
plugin-diff-version: 3.5.0 plugin-diff-version: 3.5.0
extra-helmfile-flags: extra-helmfile-flags:
v1mode:
- helm-version: v3.9.4 - helm-version: v3.9.4
kustomize-version: v4.5.7 kustomize-version: v4.5.7
# We assume that the helm-secrets plugin is supposed to # We assume that the helm-secrets plugin is supposed to
@ -71,16 +72,26 @@ jobs:
plugin-secrets-version: 4.0.0 plugin-secrets-version: 4.0.0
plugin-diff-version: 3.6.0 plugin-diff-version: 3.6.0
extra-helmfile-flags: extra-helmfile-flags:
v1mode:
- helm-version: v3.10.3 - helm-version: v3.10.3
kustomize-version: v4.4.1 kustomize-version: v4.4.1
plugin-secrets-version: 3.15.0 plugin-secrets-version: 3.15.0
plugin-diff-version: 3.5.0 plugin-diff-version: 3.5.0
extra-helmfile-flags: extra-helmfile-flags:
v1mode:
- helm-version: v3.10.3 - helm-version: v3.10.3
kustomize-version: v4.5.7 kustomize-version: v4.5.7
plugin-secrets-version: 4.0.0 plugin-secrets-version: 4.0.0
plugin-diff-version: 3.6.0 plugin-diff-version: 3.6.0
extra-helmfile-flags: extra-helmfile-flags:
v1mode:
# Helmfile v1
- helm-version: v3.10.3
kustomize-version: v4.5.7
plugin-secrets-version: 4.0.0
plugin-diff-version: 3.6.0
extra-helmfile-flags:
v1mode: "true"
# In case you need to test some optional helmfile features, # In case you need to test some optional helmfile features,
# enable it via extra-helmfile-flags below. # enable it via extra-helmfile-flags below.
- helm-version: v3.10.3 - helm-version: v3.10.3
@ -88,6 +99,7 @@ jobs:
plugin-secrets-version: 4.0.0 plugin-secrets-version: 4.0.0
plugin-diff-version: 3.6.0 plugin-diff-version: 3.6.0
extra-helmfile-flags: "--enable-live-output" extra-helmfile-flags: "--enable-live-output"
v1mode:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Cache libraries - name: Cache libraries
@ -131,6 +143,7 @@ jobs:
HELMFILE_HELM3: 1 HELMFILE_HELM3: 1
TERM: xterm TERM: xterm
EXTRA_HELMFILE_FLAGS: ${{ matrix.extra-helmfile-flags }} EXTRA_HELMFILE_FLAGS: ${{ matrix.extra-helmfile-flags }}
HELMFILE_V1MODE: ${{ matrix.v1mode }}
run: make integration run: make integration
e2e_tests: e2e_tests:
needs: tests needs: tests

View File

@ -15,8 +15,8 @@ import (
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem" ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/runtime"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
"github.com/helmfile/helmfile/pkg/yaml"
) )
func TestTemplate(t *testing.T) { func TestTemplate(t *testing.T) {
@ -309,20 +309,21 @@ releases:
} }
func TestTemplate_StrictParsing(t *testing.T) { func TestTemplate_StrictParsing(t *testing.T) {
v := yaml.GoccyGoYaml
yaml.GoccyGoYaml = true
t.Cleanup(func() {
yaml.GoccyGoYaml = v
})
type testcase struct { type testcase struct {
ns string goccyGoYaml bool
error string ns string
error string
} }
check := func(t *testing.T, tc testcase) { check := func(t *testing.T, tc testcase) {
t.Helper() t.Helper()
v := runtime.GoccyGoYaml
runtime.GoccyGoYaml = tc.goccyGoYaml
t.Cleanup(func() {
runtime.GoccyGoYaml = v
})
var helm = &exectest.Helm{ var helm = &exectest.Helm{
FailOnUnexpectedList: true, FailOnUnexpectedList: true,
FailOnUnexpectedDiff: true, FailOnUnexpectedDiff: true,
@ -381,8 +382,9 @@ releases:
}) })
} }
t.Run("fail due to known field", func(t *testing.T) { t.Run("fail due to unknown field with goccy/go-yaml", func(t *testing.T) {
check(t, testcase{ check(t, testcase{
goccyGoYaml: true,
error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1: [4:3] unknown field "foobar" error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1: [4:3] unknown field "foobar"
2 | releases: 2 | releases:
3 | - name: app1 3 | - name: app1
@ -391,6 +393,14 @@ releases:
5 | chart: incubator/raw`, 5 | chart: incubator/raw`,
}) })
}) })
t.Run("fail due to unknown field with gopkg.in/yaml.v2", func(t *testing.T) {
check(t, testcase{
goccyGoYaml: false,
error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1: yaml: unmarshal errors:
line 4: field foobar not found in type state.ReleaseSpec`,
})
})
} }
func TestTemplate_CyclicInheritance(t *testing.T) { func TestTemplate_CyclicInheritance(t *testing.T) {

View File

@ -9,7 +9,7 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp" "regexp"
"runtime" goruntime "runtime"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@ -25,6 +25,7 @@ import (
ffs "github.com/helmfile/helmfile/pkg/filesystem" ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/runtime"
"github.com/helmfile/helmfile/pkg/state" "github.com/helmfile/helmfile/pkg/state"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
"github.com/helmfile/helmfile/pkg/testutil" "github.com/helmfile/helmfile/pkg/testutil"
@ -4338,7 +4339,15 @@ myrelease4 testNamespace true true id:myrelease1 mychart1
assert.Equal(t, expected, out) assert.Equal(t, expected, out)
} }
func TestSetValuesTemplate(t *testing.T) { func testSetValuesTemplate(t *testing.T, goccyGoYaml bool) {
t.Helper()
v := runtime.GoccyGoYaml
runtime.GoccyGoYaml = goccyGoYaml
t.Cleanup(func() {
runtime.GoccyGoYaml = v
})
files := map[string]string{ files := map[string]string{
"/path/to/helmfile.yaml": ` "/path/to/helmfile.yaml": `
releases: releases:
@ -4357,7 +4366,7 @@ releases:
`, `,
} }
expectedValues := []interface{}{ expectedValues := []interface{}{
map[interface{}]interface{}{"val1": "zipkin"}, map[string]interface{}{"val1": "zipkin"},
map[string]interface{}{"val2": "val2"}} map[string]interface{}{"val2": "val2"}}
expectedSetValues := []state.SetValue{ expectedSetValues := []state.SetValue{
{Name: "name-zipkin", Value: "val-zipkin"}, {Name: "name-zipkin", Value: "val-zipkin"},
@ -4402,7 +4411,17 @@ releases:
} }
} }
func TestSetValuesTemplate(t *testing.T) {
t.Run("with goccy/go-yaml", func(t *testing.T) {
testSetValuesTemplate(t, true)
})
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
testSetValuesTemplate(t, false)
})
}
func location() string { func location() string {
_, fn, line, _ := runtime.Caller(1) _, fn, line, _ := goruntime.Caller(1)
return fmt.Sprintf("%s:%d", filepath.Base(fn), line) return fmt.Sprintf("%s:%d", filepath.Base(fn), line)
} }

View File

@ -10,4 +10,6 @@ const (
Helm3 = "HELMFILE_HELM3" Helm3 = "HELMFILE_HELM3"
UpgradeNoticeDisabled = "HELMFILE_UPGRADE_NOTICE_DISABLED" UpgradeNoticeDisabled = "HELMFILE_UPGRADE_NOTICE_DISABLED"
V1Mode = "HELMFILE_V1MODE" V1Mode = "HELMFILE_V1MODE"
GoccyGoYaml = "HELMFILE_GOCCY_GOYAML"
CacheHome = "HELMFILE_CACHE_HOME"
) )

View File

@ -27,6 +27,10 @@ func init() {
} }
func CacheDir() string { func CacheDir() string {
if h := os.Getenv(envvar.CacheHome); h != "" {
return h
}
dir, err := os.UserCacheDir() dir, err := os.UserCacheDir()
if err != nil { if err != nil {
// fall back to relative path with hidden directory // fall back to relative path with hidden directory

View File

@ -13,13 +13,23 @@ import (
var ( var (
V1Mode bool V1Mode bool
// GoccyGoYaml is set to true in order to let Helmfile use
// goccy/go-yaml instead of gopkg.in/yaml.v2.
// It's false by default in Helmfile v0.x and true by default for Helmfile v1.x.
GoccyGoYaml bool
// We set this via ldflags at build-time so that we can use the // We set this via ldflags at build-time so that we can use the
// value specified at the build time as the runtime default. // value specified at the build time as the runtime default.
v1Mode string v1Mode string
) )
func Info() string { func Info() string {
return fmt.Sprintf("V1 mode = %v", V1Mode) yamlLib := "gopkg.in/yaml.v2"
if GoccyGoYaml {
yamlLib = "goccy/go-yaml"
}
return fmt.Sprintf("V1 mode = %v\nYAML library = %v", V1Mode, yamlLib)
} }
func init() { func init() {
@ -34,4 +44,14 @@ func init() {
default: default:
V1Mode, _ = strconv.ParseBool(v1Mode) V1Mode, _ = strconv.ParseBool(v1Mode)
} }
// You can switch the YAML library at runtime via an envvar:
switch os.Getenv(envvar.GoccyGoYaml) {
case "true":
GoccyGoYaml = true
case "false":
GoccyGoYaml = false
default:
GoccyGoYaml = V1Mode
}
} }

View File

@ -3,6 +3,7 @@ package state
import ( import (
"fmt" "fmt"
"github.com/helmfile/helmfile/pkg/maputil"
"github.com/helmfile/helmfile/pkg/tmpl" "github.com/helmfile/helmfile/pkg/tmpl"
"github.com/helmfile/helmfile/pkg/yaml" "github.com/helmfile/helmfile/pkg/yaml"
) )
@ -107,13 +108,18 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, string(serialized), err) return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, string(serialized), err)
} }
var deserialized map[interface{}]interface{} var deserialized map[string]interface{}
if err := yaml.Unmarshal(s.Bytes(), &deserialized); err != nil { if err := yaml.Unmarshal(s.Bytes(), &deserialized); err != nil {
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, ts, err) return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, ts, err)
} }
result.ValuesTemplate[i] = deserialized m, err := maputil.CastKeysToStrings(deserialized)
if err != nil {
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, ts, err)
}
result.ValuesTemplate[i] = m
} }
} }
@ -130,6 +136,12 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%s\": %v", r.Name, i, ts, err) return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%s\": %v", r.Name, i, ts, err)
} }
result.Values[i] = s.String() result.Values[i] = s.String()
case map[interface{}]interface{}:
m, err := maputil.CastKeysToStrings(ts)
if err != nil {
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%s\": %v", r.Name, i, ts, err)
}
result.Values[i] = m
} }
} }

View File

@ -121,7 +121,7 @@ func TestHelmState_executeTemplates(t *testing.T) {
Verify: nil, Verify: nil,
Name: "app", Name: "app",
Namespace: "dev", Namespace: "dev",
Values: []interface{}{map[interface{}]interface{}{"key": "app-val0"}}, Values: []interface{}{map[string]interface{}{"key": "app-val0"}},
}, },
}, },
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/helmfile/helmfile/pkg/envvar" "github.com/helmfile/helmfile/pkg/envvar"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/maputil"
"github.com/helmfile/helmfile/pkg/yaml" "github.com/helmfile/helmfile/pkg/yaml"
) )
@ -286,11 +287,17 @@ func ToYaml(v interface{}) (string, error) {
} }
func FromYaml(str string) (Values, error) { func FromYaml(str string) (Values, error) {
m := Values{} m := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(str), &m); err != nil { if err := yaml.Unmarshal([]byte(str), &m); err != nil {
return nil, fmt.Errorf("%s, offending yaml: %s", err, str) return nil, fmt.Errorf("%s, offending yaml: %s", err, str)
} }
m, err := maputil.CastKeysToStrings(m)
if err != nil {
return nil, fmt.Errorf("%s, offending yaml: %s", err, str)
}
return m, nil return m, nil
} }

View File

@ -5,12 +5,13 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"path/filepath" "path/filepath"
"runtime" goruntime "runtime"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/runtime"
) )
func TestCreateFuncMap(t *testing.T) { func TestCreateFuncMap(t *testing.T) {
@ -126,7 +127,7 @@ func TestReadDir(t *testing.T) {
"sampleDirectory/file3.yaml", "sampleDirectory/file3.yaml",
} }
var expectedArray []string var expectedArray []string
if runtime.GOOS == "windows" { if goruntime.GOOS == "windows" {
expectedArray = expectedArrayWindows expectedArray = expectedArrayWindows
} else { } else {
expectedArray = expectedArrayUnix expectedArray = expectedArrayUnix
@ -177,16 +178,29 @@ func TestReadFile_PassAbsPath(t *testing.T) {
require.Equal(t, actual, expected) require.Equal(t, actual, expected)
} }
func TestToYaml_UnsupportedNestedMapKey(t *testing.T) { func TestToYaml_NestedMapInterfaceKey(t *testing.T) {
expected := "foo:\n bar: BAR\n" v := runtime.GoccyGoYaml
t.Cleanup(func() {
runtime.GoccyGoYaml = v
})
// nolint: unconvert // nolint: unconvert
vals := Values(map[string]interface{}{ vals := Values(map[string]interface{}{
"foo": map[interface{}]interface{}{ "foo": map[interface{}]interface{}{
"bar": "BAR", "bar": "BAR",
}, },
}) })
runtime.GoccyGoYaml = true
actual, err := ToYaml(vals) actual, err := ToYaml(vals)
require.Equal(t, expected, actual) 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) require.NoError(t, err, "expected nil, but got: %v, when type: map[interface {}]interface {}", err)
} }
@ -205,21 +219,51 @@ func TestToYaml(t *testing.T) {
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
} }
func TestFromYaml(t *testing.T) { 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: raw := `foo:
bar: BAR bar: BAR
` `
// nolint: unconvert
expected := Values(map[string]interface{}{
"foo": map[string]interface{}{
"bar": "BAR",
},
})
actual, err := FromYaml(raw) actual, err := FromYaml(raw)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, actual) 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]interface{}{
"foo": map[string]interface{}{
"bar": "BAR",
},
}),
)
})
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
testFromYaml(
t,
false,
// nolint: unconvert
Values(map[string]interface{}{
"foo": map[string]interface{}{
"bar": "BAR",
},
}),
)
})
}
func TestFromYamlToJson(t *testing.T) { func TestFromYamlToJson(t *testing.T) {
input := `foo: input := `foo:
bar: BAR bar: BAR

View File

@ -6,12 +6,8 @@ import (
"github.com/goccy/go-yaml" "github.com/goccy/go-yaml"
v2 "gopkg.in/yaml.v2" v2 "gopkg.in/yaml.v2"
)
var ( "github.com/helmfile/helmfile/pkg/runtime"
// We'll derive the default from the build once
// is merged
GoccyGoYaml bool = true
) )
type Encoder interface { type Encoder interface {
@ -21,7 +17,7 @@ type Encoder interface {
// NewEncoder creates and returns a function that is used to encode a Go object to a YAML document // NewEncoder creates and returns a function that is used to encode a Go object to a YAML document
func NewEncoder(w io.Writer) Encoder { func NewEncoder(w io.Writer) Encoder {
if GoccyGoYaml { if runtime.GoccyGoYaml {
return yaml.NewEncoder(w) return yaml.NewEncoder(w)
} }
@ -29,7 +25,7 @@ func NewEncoder(w io.Writer) Encoder {
} }
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
if GoccyGoYaml { if runtime.GoccyGoYaml {
return yaml.Unmarshal(data, v) return yaml.Unmarshal(data, v)
} }
@ -41,7 +37,7 @@ func Unmarshal(data []byte, v interface{}) error {
// When strict is true, this function ensures that every field found in the YAML document // When strict is true, this function ensures that every field found in the YAML document
// to have the corresponding field in the decoded Go struct. // to have the corresponding field in the decoded Go struct.
func NewDecoder(data []byte, strict bool) func(interface{}) error { func NewDecoder(data []byte, strict bool) func(interface{}) error {
if GoccyGoYaml { if runtime.GoccyGoYaml {
var opts []yaml.DecodeOption var opts []yaml.DecodeOption
if strict { if strict {
opts = append(opts, yaml.DisallowUnknownField()) opts = append(opts, yaml.DisallowUnknownField())
@ -66,11 +62,10 @@ func NewDecoder(data []byte, strict bool) func(interface{}) error {
} }
func Marshal(v interface{}) ([]byte, error) { func Marshal(v interface{}) ([]byte, error) {
if GoccyGoYaml { if runtime.GoccyGoYaml {
var b bytes.Buffer var b bytes.Buffer
yamlEncoder := yaml.NewEncoder( yamlEncoder := yaml.NewEncoder(
&b, &b,
yaml.IndentSequence(true),
yaml.Indent(2), yaml.Indent(2),
) )
err := yamlEncoder.Encode(v) err := yamlEncoder.Encode(v)

View File

@ -4,9 +4,19 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/helmfile/helmfile/pkg/runtime"
) )
func TestYamlMarshal(t *testing.T) { func testYamlMarshal(t *testing.T, goccyGoYaml bool) {
t.Helper()
v := runtime.GoccyGoYaml
runtime.GoccyGoYaml = goccyGoYaml
t.Cleanup(func() {
runtime.GoccyGoYaml = v
})
tests := []struct { tests := []struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Info []struct { Info []struct {
@ -22,7 +32,7 @@ func TestYamlMarshal(t *testing.T) {
Age int `yaml:"age"` Age int `yaml:"age"`
Address string `yaml:"address"` Address string `yaml:"address"`
}{{Age: 20, Address: "New York"}}, }{{Age: 20, Address: "New York"}},
expected: "name: John\ninfo:\n - age: 20\n address: New York\n", expected: "name: John\ninfo:\n- age: 20\n address: New York\n",
}, },
} }
@ -32,3 +42,13 @@ func TestYamlMarshal(t *testing.T) {
require.Equal(t, tt.expected, string(actual)) require.Equal(t, tt.expected, string(actual))
} }
} }
func TestYamlMarshal(t *testing.T) {
t.Run("with goccy/go-yaml", func(t *testing.T) {
testYamlMarshal(t, true)
})
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
testYamlMarshal(t, false)
})
}

View File

@ -8,7 +8,8 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" goruntime "runtime"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -57,6 +58,18 @@ func (f fakeInit) Force() bool {
} }
func TestHelmfileTemplateWithBuildCommand(t *testing.T) { func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
t.Run("with goccy/go-yaml", func(t *testing.T) {
testHelmfileTemplateWithBuildCommand(t, true)
})
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
testHelmfileTemplateWithBuildCommand(t, false)
})
}
func testHelmfileTemplateWithBuildCommand(t *testing.T, goccyGoYaml bool) {
t.Setenv(envvar.GoccyGoYaml, strconv.FormatBool(goccyGoYaml))
localChartPortSets := make(map[int]struct{}) localChartPortSets := make(map[int]struct{})
logger := helmexec.NewLogger(os.Stderr, "info") logger := helmexec.NewLogger(os.Stderr, "info")
@ -69,10 +82,10 @@ func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
err := helmfileInit.CheckHelmPlugins() err := helmfileInit.CheckHelmPlugins()
require.NoError(t, err) require.NoError(t, err)
_, filename, _, _ := runtime.Caller(0) _, filename, _, _ := goruntime.Caller(0)
projectRoot := filepath.Join(filepath.Dir(filename), "..", "..", "..", "..") projectRoot := filepath.Join(filepath.Dir(filename), "..", "..", "..", "..")
helmfileBin := filepath.Join(projectRoot, "helmfile") helmfileBin := filepath.Join(projectRoot, "helmfile")
if runtime.GOOS == "windows" { if goruntime.GOOS == "windows" {
helmfileBin = helmfileBin + ".exe" helmfileBin = helmfileBin + ".exe"
} }
testdataDir := "testdata/snapshot" testdataDir := "testdata/snapshot"
@ -200,6 +213,15 @@ func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
} }
} }
tmpDir := t.TempDir()
// HELM_CACHE_HOME contains downloaded chart archives
helmCacheHome := filepath.Join(tmpDir, "helm_cache")
// HELMFILE_CACHE_HOME contains remote charts and manifests downloaded by Helmfile using the go-getter integration
helmfileCacheHome := filepath.Join(tmpDir, "helmfile_cache")
// HELM_CONFIG_HOME contains the registry auth file (registry.json) and the index of all the repos added via helm-repo-add (repositories.yaml).
helmConfigHome := filepath.Join(tmpDir, "helm_config")
t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s", helmCacheHome, helmfileCacheHome, helmConfigHome)
inputFile := filepath.Join(testdataDir, name, "input.yaml") inputFile := filepath.Join(testdataDir, name, "input.yaml")
outputFile := filepath.Join(testdataDir, name, "output.yaml") outputFile := filepath.Join(testdataDir, name, "output.yaml")
@ -214,6 +236,9 @@ func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
cmd.Env, cmd.Env,
envvar.TempDir+"=/tmp/helmfile", envvar.TempDir+"=/tmp/helmfile",
envvar.DisableRunnerUniqueID+"=1", envvar.DisableRunnerUniqueID+"=1",
"HELM_CACHE_HOME="+helmCacheHome,
"HELM_CONFIG_HOME="+helmConfigHome,
"HELMFILE_CACHE_HOME="+helmfileCacheHome,
) )
got, err := cmd.CombinedOutput() got, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@ -231,6 +256,9 @@ func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`) gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`)
// Replace helm version with $HelmVersion // Replace helm version with $HelmVersion
gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`) gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`)
// Replace all occurrences of HELMFILE_CACHE_HOME with /home/runner/.cache/helmfile
// for stable test result
gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "/home/runner/.cache/helmfile")
// OCI based helm charts are pulled and exported under temporary directory. // OCI based helm charts are pulled and exported under temporary directory.
// We are not sure the exact name of the temporary directory generated by helmfile, // We are not sure the exact name of the temporary directory generated by helmfile,

View File

@ -6,23 +6,23 @@ helmBinary: helm
environments: environments:
default: {} default: {}
repositories: repositories:
- name: aservo - name: aservo
url: https://aservo.github.io/charts url: https://aservo.github.io/charts
releases: releases:
- chart: aservo/util - chart: aservo/util
version: 0.0.1 version: 0.0.1
name: default-shared-resources name: default-shared-resources
namespace: default namespace: default
labels: labels:
service: shared-resources service: shared-resources
- chart: aservo/util - chart: aservo/util
version: 0.0.1 version: 0.0.1
needs: needs:
- default/default-shared-resources - default/default-shared-resources
name: default-release-resources name: default-release-resources
namespace: default namespace: default
labels: labels:
service: release-resources service: release-resources
templates: templates:
defaults: defaults:
name: default-{{ .Release.Labels.service }} name: default-{{ .Release.Labels.service }}

View File

@ -1,8 +1,4 @@
Building dependency release=foo, chart=$WD/temp1/foo Building dependency release=foo, chart=$WD/temp1/foo
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "myrepo" chart repository
...Successfully got an update from the "istio" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 1 charts Saving 1 charts
Downloading raw from repo oci://localhost:5000/myrepo Downloading raw from repo oci://localhost:5000/myrepo
Deleting outdated charts Deleting outdated charts

View File

@ -1,9 +1,5 @@
Building dependency release=foo, chart=../../charts/raw-0.1.0 Building dependency release=foo, chart=../../charts/raw-0.1.0
Building dependency release=baz, chart=$WD/temp1/baz Building dependency release=baz, chart=$WD/temp1/baz
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "myrepo" chart repository
...Successfully got an update from the "istio" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 1 charts Saving 1 charts
Downloading raw from repo oci://localhost:5001/myrepo Downloading raw from repo oci://localhost:5001/myrepo
Deleting outdated charts Deleting outdated charts

View File

@ -1,4 +1,3 @@
helmfileArgs: helmfileArgs:
- template - template
- --debug
- --concurrency=1 - --concurrency=1

View File

@ -1,103 +1,5 @@
processing file "input.yaml" in directory "testdata/snapshot/pr_560"
changing working directory to "/home/runner/work/helmfile/helmfile/test/e2e/template/helmfile/testdata/snapshot/pr_560"
first-pass rendering starting for "input.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "input.yaml.part.0":
0: releases:
1: - name: foo
2: chart: ../../charts/raw-0.1.0
3: values:
4: - templates:
5: - |
6: apiVersion: v1
7: kind: ConfigMap
8: metadata:
9: name: {{ .Release.Name }}-1
10: namespace: {{ .Release.Namespace }}
11: data:
12: foo: FOO
13: - git::https://github.com/helmfile/helmfile.git@test/e2e/template/helmfile/testdata/snapshot/pr_560/values.yaml?ref=main
14: secrets:
15: - git::https://github.com/helmfile/helmfile.git@test/e2e/template/helmfile/testdata/snapshot/pr_560/secrets.yaml?ref=main
16: missingFileHandler: Debug
17:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "input.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "input.yaml.part.0":
0: releases:
1: - name: foo
2: chart: ../../charts/raw-0.1.0
3: values:
4: - templates:
5: - |
6: apiVersion: v1
7: kind: ConfigMap
8: metadata:
9: name: {{ .Release.Name }}-1
10: namespace: {{ .Release.Namespace }}
11: data:
12: foo: FOO
13: - git::https://github.com/helmfile/helmfile.git@test/e2e/template/helmfile/testdata/snapshot/pr_560/values.yaml?ref=main
14: secrets:
15: - git::https://github.com/helmfile/helmfile.git@test/e2e/template/helmfile/testdata/snapshot/pr_560/secrets.yaml?ref=main
16: missingFileHandler: Debug
17:
merged environment: &{default map[] map[]}
helm> $HelmVersion
helm>
Building dependency release=foo, chart=../../charts/raw-0.1.0 Building dependency release=foo, chart=../../charts/raw-0.1.0
exec: helm dependency build ../../charts/raw-0.1.0
1 release(s) found in input.yaml
processing 1 groups of releases in this order:
GROUP RELEASES
1 foo
processing releases in group 1/1: foo
remote> getter: git
remote> scheme: https
remote> user:
remote> host: github.com
remote> dir: /helmfile/helmfile.git
remote> file: test/e2e/template/helmfile/testdata/snapshot/pr_560/values.yaml
remote> home: /home/runner/.cache/helmfile
remote> getter dest: values/https_github_com_helmfile_helmfile_git.ref=main
remote> cached dir: /home/runner/.cache/helmfile/values/https_github_com_helmfile_helmfile_git.ref=main
remote> downloading git::https://github.com/helmfile/helmfile.git?ref=main to values/https_github_com_helmfile_helmfile_git.ref=main
client: {Ctx:context.Background Src:git::https://github.com/helmfile/helmfile.git?ref=main Dst:/home/runner/.cache/helmfile/values/https_github_com_helmfile_helmfile_git.ref=main Pwd:/home/runner/.cache/helmfile Mode:3 Umask:---------- Detectors:[] Decompressors:map[] Getters:map[] Dir:false ProgressListener:<nil> Insecure:false DisableSymlinks:false Options:[]}
skipping missing values file matching "git::https://github.com/helmfile/helmfile.git@test/e2e/template/helmfile/testdata/snapshot/pr_560/values.yaml?ref=main"
remote> getter: git
remote> scheme: https
remote> user:
remote> host: github.com
remote> dir: /helmfile/helmfile.git
remote> file: test/e2e/template/helmfile/testdata/snapshot/pr_560/secrets.yaml
remote> home: /home/runner/.cache/helmfile
remote> getter dest: values/https_github_com_helmfile_helmfile_git.ref=main
remote> cached dir: /home/runner/.cache/helmfile/values/https_github_com_helmfile_helmfile_git.ref=main
skipping missing secrets file matching "git::https://github.com/helmfile/helmfile.git@test/e2e/template/helmfile/testdata/snapshot/pr_560/secrets.yaml?ref=main"
Templating release=foo, chart=../../charts/raw-0.1.0 Templating release=foo, chart=../../charts/raw-0.1.0
exec: helm template foo ../../charts/raw-0.1.0 --values /tmp/helmfile/foo-values-d459bc67c --debug
helm> install.go:192: [debug] Original chart version: ""
helm>
helm> install.go:209: [debug] CHART PATH: /home/runner/work/helmfile/helmfile/test/e2e/template/helmfile/testdata/charts/raw-0.1.0
helm>
helm>
helm> ---
helm> # Source: raw/templates/resources.yaml
helm> apiVersion: v1
helm> kind: ConfigMap
helm> metadata:
helm> name: foo-1
helm> namespace: default
helm> data:
helm> foo: FOO
helm>
--- ---
# Source: raw/templates/resources.yaml # Source: raw/templates/resources.yaml
apiVersion: v1 apiVersion: v1
@ -108,6 +10,3 @@ metadata:
data: data:
foo: FOO foo: FOO
Removed /tmp/helmfile/foo-values-d459bc67c
Removed /tmp/helmfile
changing working directory back to "/home/runner/work/helmfile/helmfile/test/e2e/template/helmfile"

View File

@ -3,3 +3,5 @@ localChartRepoServer:
port: 18084 port: 18084
helmfileArgs: helmfileArgs:
- template - template
# Prevent two releases foo and bar from racing and randomizing the log
- --concurrency=1

View File

@ -12,7 +12,7 @@ templates:
values: values:
- template1: template1 - template1: template1
valuesTemplate: valuesTemplate:
- template1Label: "{{` '{{ .Release.Labels.template1 }}' `}}" - template1Label: "{{`{{ .Release.Labels.template1 }}`}}"
labels: labels:
template1: template1 template1: template1
inherit: inherit:
@ -23,8 +23,8 @@ templates:
values: values:
- template2: template2 - template2: template2
valuesTemplate: valuesTemplate:
- inheritedBaseLabel: "{{` '{{ .Release.Labels.base }}' `}}" - inheritedBaseLabel: "{{`{{ .Release.Labels.base }}`}}"
template2Label: "{{` '{{ .Release.Labels.template2 }}' `}}" template2Label: "{{`{{ .Release.Labels.template2 }}`}}"
labels: labels:
template2: template2 template2: template2
inherit: inherit:
@ -46,7 +46,7 @@ releases:
name: {{`{{ .Release.Name }}`}}-1 name: {{`{{ .Release.Name }}`}}-1
namespace: {{`{{ .Release.Namespace }}`}} namespace: {{`{{ .Release.Namespace }}`}}
data: data:
{{` {{ .Values | toYaml | nindent 2 }} `}} {{` {{ (unset .Values "templates") | toYaml | nindent 2 }} `}}
- name: foo2 - name: foo2
chart: ../../charts/raw-0.1.0 chart: ../../charts/raw-0.1.0
inherit: inherit:
@ -60,4 +60,4 @@ releases:
name: {{`{{ .Release.Name }}`}}-1 name: {{`{{ .Release.Name }}`}}-1
namespace: {{`{{ .Release.Namespace }}`}} namespace: {{`{{ .Release.Namespace }}`}}
data: data:
{{` {{ .Values | toYaml | nindent 2 }} `}} {{` {{ (unset .Values "templates") | toYaml | nindent 2 }} `}}

View File

@ -16,9 +16,6 @@ data:
base: base base: base
template1: template1 template1: template1
template1Label: template1 template1Label: template1
templates:
- "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: {{ .Release.Name }}-1\n namespace:
{{ .Release.Namespace }}\ndata:\n {{ .Values | toYaml | nindent 2 }} \n"
Templating release=foo2, chart=../../charts/raw-0.1.0 Templating release=foo2, chart=../../charts/raw-0.1.0
--- ---
@ -33,13 +30,4 @@ data:
inheritedBaseLabel: base inheritedBaseLabel: base
template2: template2 template2: template2
template2Label: template2 template2Label: template2
templates:
- |-
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-1
namespace: {{ .Release.Namespace }}
data:
{{ .Values | toYaml | nindent 2 }}

View File

@ -2,6 +2,14 @@ if [[ helm_major_version -eq 3 ]]; then
chart_need_case_input_dir="${cases_dir}/chart-needs/input" chart_need_case_input_dir="${cases_dir}/chart-needs/input"
chart_need_case_output_dir="${cases_dir}/chart-needs/output" chart_need_case_output_dir="${cases_dir}/chart-needs/output"
config_file="helmfile.yaml"
if [[ ${HELMFILE_V1MODE} = true ]]; then
pushd "${chart_need_case_input_dir}"
mv "${config_file}" "${config_file}.gotmpl"
config_file="${config_file}.gotmpl"
popd
fi
chart_needs_tmp=$(mktemp -d) chart_needs_tmp=$(mktemp -d)
chart_needs_template_reverse=${chart_needs_tmp}/chart.needs.template.log chart_needs_template_reverse=${chart_needs_tmp}/chart.needs.template.log
chart_needs_lint_reverse=${chart_needs_tmp}/chart.needs.lint.log chart_needs_lint_reverse=${chart_needs_tmp}/chart.needs.lint.log
@ -20,46 +28,46 @@ if [[ helm_major_version -eq 3 ]]; then
for i in $(seq 10); do for i in $(seq 10); do
info "Comparing template/chart-needs #$i" info "Comparing template/chart-needs #$i"
${helmfile} -f ${chart_need_case_input_dir}/helmfile.yaml template --include-needs > ${chart_needs_template_reverse} || fail "\"helmfile template\" shouldn't fail" ${helmfile} -f ${chart_need_case_input_dir}/${config_file} template --include-needs > ${chart_needs_template_reverse} || fail "\"helmfile template\" shouldn't fail"
./dyff between -bs ${chart_need_case_output_dir}/template ${chart_needs_template_reverse} || fail "\"helmfile template\" should be consistent" ./dyff between -bs ${chart_need_case_output_dir}/template ${chart_needs_template_reverse} || fail "\"helmfile template\" should be consistent"
echo code=$? echo code=$?
done done
for i in $(seq 10); do for i in $(seq 10); do
info "Comparing lint/chart-needs #$i" info "Comparing lint/chart-needs #$i"
${helmfile} -f ${chart_need_case_input_dir}/helmfile.yaml lint --include-needs | grep -v Linting > ${chart_needs_lint_reverse} || fail "\"helmfile lint\" shouldn't fail" ${helmfile} -f ${chart_need_case_input_dir}/${config_file} lint --include-needs | grep -v Linting > ${chart_needs_lint_reverse} || fail "\"helmfile lint\" shouldn't fail"
diff -u ${lint_out_file} ${chart_needs_lint_reverse} || fail "\"helmfile lint\" should be consistent" diff -u ${lint_out_file} ${chart_needs_lint_reverse} || fail "\"helmfile lint\" should be consistent"
echo code=$? echo code=$?
done done
for i in $(seq 10); do for i in $(seq 10); do
info "Comparing diff/chart-needs #$i" info "Comparing diff/chart-needs #$i"
${helmfile} -f ${chart_need_case_input_dir}/helmfile.yaml diff --include-needs | grep -Ev "Comparing release=azuredisk-csi-storageclass, chart=/tmp/.*/azuredisk-csi-storageclass" > ${chart_needs_diff_reverse} || fail "\"helmfile diff\" shouldn't fail" ${helmfile} -f ${chart_need_case_input_dir}/${config_file} diff --include-needs | grep -Ev "Comparing release=azuredisk-csi-storageclass, chart=/tmp/.*/azuredisk-csi-storageclass" > ${chart_needs_diff_reverse} || fail "\"helmfile diff\" shouldn't fail"
diff -u ${diff_out_file} ${chart_needs_diff_reverse} || fail "\"helmfile diff\" should be consistent" diff -u ${diff_out_file} ${chart_needs_diff_reverse} || fail "\"helmfile diff\" should be consistent"
echo code=$? echo code=$?
done done
info "Applying ${chart_need_case_input_dir}/helmfile.yaml" info "Applying ${chart_need_case_input_dir}/${config_file}"
${helmfile} -f ${chart_need_case_input_dir}/helmfile.yaml apply --include-needs ${helmfile} -f ${chart_need_case_input_dir}/${config_file} apply --include-needs
code=$? code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}" [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}"
${kubectl} get storageclass managed-csi -o yaml | grep -q "provisioner: disk.csi.azure.com" || fail "storageclass managed-csi should be created when applying helmfile.yaml" ${kubectl} get storageclass managed-csi -o yaml | grep -q "provisioner: disk.csi.azure.com" || fail "storageclass managed-csi should be created when applying helmfile.yaml"
info "Destroying ${chart_need_case_input_dir}/helmfile.yaml" info "Destroying ${chart_need_case_input_dir}/${config_file}"
${helmfile} -f ${chart_need_case_input_dir}/helmfile.yaml destroy ${helmfile} -f ${chart_need_case_input_dir}/${config_file} destroy
code=$? code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile destroy: want 0, got ${code}" [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile destroy: want 0, got ${code}"
info "Syncing ${chart_need_case_input_dir}/helmfile.yaml" info "Syncing ${chart_need_case_input_dir}/${config_file}"
${helmfile} -f ${chart_need_case_input_dir}/helmfile.yaml sync --include-needs ${helmfile} -f ${chart_need_case_input_dir}/${config_file} sync --include-needs
code=$? code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}" [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}"
${kubectl} get storageclass managed-csi -o yaml | grep -q "provisioner: disk.csi.azure.com" || fail "storageclass managed-csi should be created when syncing helmfile.yaml" ${kubectl} get storageclass managed-csi -o yaml | grep -q "provisioner: disk.csi.azure.com" || fail "storageclass managed-csi should be created when syncing helmfile.yaml"
info "Destroying ${chart_need_case_input_dir}/helmfile.yaml" info "Destroying ${chart_need_case_input_dir}/${config_file}"
${helmfile} -f ${chart_need_case_input_dir}/helmfile.yaml destroy ${helmfile} -f ${chart_need_case_input_dir}/${config_file} destroy
code=$? code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile destroy: want 0, got ${code}" [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile destroy: want 0, got ${code}"

View File

@ -2,22 +2,30 @@ test_start "happypath - simple rollout of httpbin chart"
happypath_case_input_dir="${cases_dir}/happypath/input" happypath_case_input_dir="${cases_dir}/happypath/input"
happypath_case_output_dir="${cases_dir}/happypath/output" happypath_case_output_dir="${cases_dir}/happypath/output"
config_file="happypath.yaml"
info "Diffing ${happypath_case_input_dir}/happypath.yaml" if [[ ${HELMFILE_V1MODE} = true ]]; then
bash -c "${helmfile} -f ${happypath_case_input_dir}/happypath.yaml diff --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff" pushd "${happypath_case_input_dir}"
mv "${config_file}" "${config_file}.gotmpl"
config_file="${config_file}.gotmpl"
popd
fi
info "Diffing ${happypath_case_input_dir}/happypath.yaml without color" info "Diffing ${happypath_case_input_dir}/${config_file}"
bash -c "${helmfile} -f ${happypath_case_input_dir}/happypath.yaml --no-color diff --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff" bash -c "${helmfile} -f ${happypath_case_input_dir}/${config_file} diff --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff"
info "Diffing ${happypath_case_input_dir}/happypath.yaml with limited context" info "Diffing ${happypath_case_input_dir}/${config_file} without color"
bash -c "${helmfile} -f ${happypath_case_input_dir}/happypath.yaml diff --context 3 --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff" bash -c "${helmfile} -f ${happypath_case_input_dir}/${config_file} --no-color diff --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff"
info "Diffing ${happypath_case_input_dir}/happypath.yaml with altered output" info "Diffing ${happypath_case_input_dir}/${config_file} with limited context"
bash -c "${helmfile} -f ${happypath_case_input_dir}/happypath.yaml diff --output simple --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff" bash -c "${helmfile} -f ${happypath_case_input_dir}/${config_file} diff --context 3 --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff"
info "Templating ${happypath_case_input_dir}/happypath.yaml" info "Diffing ${happypath_case_input_dir}/${config_file} with altered output"
bash -c "${helmfile} -f ${happypath_case_input_dir}/${config_file} diff --output simple --detailed-exitcode; code="'$?'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile diff"
info "Templating ${happypath_case_input_dir}/${config_file}"
rm -rf ${dir}/tmp rm -rf ${dir}/tmp
${helmfile} -f ${happypath_case_input_dir}/happypath.yaml --debug template --output-dir tmp ${helmfile} -f ${happypath_case_input_dir}/${config_file} --debug template --output-dir tmp
code=$? code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile template: ${code}" [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile template: ${code}"
for output in $(ls -d ${dir}/tmp/*); do for output in $(ls -d ${dir}/tmp/*); do
@ -30,39 +38,39 @@ for output in $(ls -d ${dir}/tmp/*); do
done done
done done
info "Applying ${happypath_case_input_dir}/happypath.yaml" info "Applying ${happypath_case_input_dir}/${config_file}"
bash -c "${helmfile} -f ${happypath_case_input_dir}/happypath.yaml apply --detailed-exitcode; code="'$?'"; echo Code: "'$code'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile apply" bash -c "${helmfile} -f ${happypath_case_input_dir}/${config_file} apply --detailed-exitcode; code="'$?'"; echo Code: "'$code'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile apply"
info "Syncing ${happypath_case_input_dir}/happypath.yaml" info "Syncing ${happypath_case_input_dir}/${config_file}"
${helmfile} -f ${happypath_case_input_dir}/happypath.yaml sync ${helmfile} -f ${happypath_case_input_dir}/${config_file} sync
wait_deploy_ready httpbin-httpbin wait_deploy_ready httpbin-httpbin
retry 5 "curl --fail $(minikube service --url --namespace=${test_ns} httpbin-httpbin)/status/200" retry 5 "curl --fail $(minikube service --url --namespace=${test_ns} httpbin-httpbin)/status/200"
[ ${retry_result} -eq 0 ] || fail "httpbin failed to return 200 OK" [ ${retry_result} -eq 0 ] || fail "httpbin failed to return 200 OK"
info "Applying ${happypath_case_input_dir}/happypath.yaml" info "Applying ${happypath_case_input_dir}/${config_file}"
${helmfile} -f ${happypath_case_input_dir}/happypath.yaml apply --detailed-exitcode ${helmfile} -f ${happypath_case_input_dir}/${config_file} apply --detailed-exitcode
code=$? code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}" [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}"
info "Locking dependencies" info "Locking dependencies"
${helmfile} -f ${happypath_case_input_dir}/happypath.yaml deps ${helmfile} -f ${happypath_case_input_dir}/${config_file} deps
code=$? code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile deps: ${code}" [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile deps: ${code}"
info "Applying ${happypath_case_input_dir}/happypath.yaml with locked dependencies" info "Applying ${happypath_case_input_dir}/${config_file} with locked dependencies"
${helmfile} -f ${happypath_case_input_dir}/happypath.yaml apply ${helmfile} -f ${happypath_case_input_dir}/${config_file} apply
code=$? code=$?
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: ${code}" [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: ${code}"
${helm} list --namespace=${test_ns} || fail "unable to list releases" ${helm} list --namespace=${test_ns} || fail "unable to list releases"
info "Deleting release" info "Deleting release"
${helmfile} -f ${happypath_case_input_dir}/happypath.yaml delete ${helmfile} -f ${happypath_case_input_dir}/${config_file} destroy
${helm} status --namespace=${test_ns} httpbin &> /dev/null && fail "release should not exist anymore after a delete" ${helm} status --namespace=${test_ns} httpbin &> /dev/null && fail "release should not exist anymore after a delete"
info "Ensuring \"helmfile delete\" doesn't fail when no releases installed" info "Ensuring \"helmfile destroy\" doesn't fail when no releases installed"
${helmfile} -f ${happypath_case_input_dir}/happypath.yaml delete || fail "\"helmfile delete\" shouldn't fail when there are no installed releases" ${helmfile} -f ${happypath_case_input_dir}/${config_file} destroy || fail "\"helmfile delete\" shouldn't fail when there are no installed releases"
info "Ensuring \"helmfile template\" output does contain only YAML docs" info "Ensuring \"helmfile template\" output does contain only YAML docs"
(${helmfile} -f ${happypath_case_input_dir}/happypath.yaml template | kubectl apply -f -) || fail "\"helmfile template | kubectl apply -f -\" shouldn't fail" (${helmfile} -f ${happypath_case_input_dir}/${config_file} template | kubectl apply -f -) || fail "\"helmfile template | kubectl apply -f -\" shouldn't fail"
test_pass "happypath" test_pass "happypath"

View File

@ -2,6 +2,14 @@ if [[ helm_major_version -eq 3 ]]; then
postrender_diff_case_input_dir="${cases_dir}/postrender-diff/input" postrender_diff_case_input_dir="${cases_dir}/postrender-diff/input"
postrender_diff_case_output_dir="${cases_dir}/postrender-diff/output" postrender_diff_case_output_dir="${cases_dir}/postrender-diff/output"
config_file="helmfile.yaml"
if [[ ${HELMFILE_V1MODE} = true ]]; then
pushd "${postrender_diff_case_input_dir}"
mv "${config_file}" "${config_file}.gotmpl"
config_file="${config_file}.gotmpl"
popd
fi
postrender_diff_out_file=${postrender_diff_case_output_dir}/result postrender_diff_out_file=${postrender_diff_case_output_dir}/result
if [[ $EXTRA_HELMFILE_FLAGS == *--enable-live-output* ]]; then if [[ $EXTRA_HELMFILE_FLAGS == *--enable-live-output* ]]; then
postrender_diff_out_file=${postrender_diff_case_output_dir}/result-live postrender_diff_out_file=${postrender_diff_case_output_dir}/result-live
@ -14,7 +22,7 @@ if [[ helm_major_version -eq 3 ]]; then
info "Comparing postrender diff output ${postrender_diff_reverse} with ${postrender_diff_case_output_dir}/result.yaml" info "Comparing postrender diff output ${postrender_diff_reverse} with ${postrender_diff_case_output_dir}/result.yaml"
for i in $(seq 10); do for i in $(seq 10); do
info "Comparing build/postrender-diff #$i" info "Comparing build/postrender-diff #$i"
${helmfile} -f ${postrender_diff_case_input_dir}/helmfile.yaml diff --concurrency 1 &> ${postrender_diff_reverse} || fail "\"helmfile diff\" shouldn't fail" ${helmfile} -f ${postrender_diff_case_input_dir}/${config_file} diff --concurrency 1 &> ${postrender_diff_reverse} || fail "\"helmfile diff\" shouldn't fail"
diff -u ${postrender_diff_out_file} ${postrender_diff_reverse} || fail "\"helmfile diff\" should be consistent" diff -u ${postrender_diff_out_file} ${postrender_diff_reverse} || fail "\"helmfile diff\" should be consistent"
echo code=$? echo code=$?
done done

View File

@ -3,15 +3,37 @@ test_start "regression tests"
if [[ helm_major_version -eq 3 ]]; then if [[ helm_major_version -eq 3 ]]; then
regression_case_input_dir="${cases_dir}/regression/input" regression_case_input_dir="${cases_dir}/regression/input"
info "https://github.com/roboll/helmfile/issues/1857" info "https://github.com/roboll/helmfile/issues/1857"
(${helmfile} -f ${regression_case_input_dir}/issue.1857.yaml --state-values-set grafanaEnabled=true template | grep grafana 1>/dev/null) || fail "\"helmfile template\" shouldn't include grafana" config_file="issue.1857.yaml"
! (${helmfile} -f ${regression_case_input_dir}/issue.1857.yaml --state-values-set grafanaEnabled=false template | grep grafana) || fail "\"helmfile template\" shouldn't include grafana" if [[ ${HELMFILE_V1MODE} = true ]]; then
pushd "${regression_case_input_dir}"
mv "${config_file}" "${config_file}.gotmpl"
config_file="${config_file}.gotmpl"
popd
fi
(${helmfile} -f ${regression_case_input_dir}/${config_file} --state-values-set grafanaEnabled=true template | grep grafana 1>/dev/null) || fail "\"helmfile template\" shouldn't include grafana"
! (${helmfile} -f ${regression_case_input_dir}/${config_file} --state-values-set grafanaEnabled=false template | grep grafana) || fail "\"helmfile template\" shouldn't include grafana"
info "https://github.com/roboll/helmfile/issues/1867" info "https://github.com/roboll/helmfile/issues/1867"
(${helmfile} -f ${regression_case_input_dir}/issue.1867.yaml template 1>/dev/null) || fail "\"helmfile template\" shouldn't fail" config_file="issue.1867.yaml"
if [[ ${HELMFILE_V1MODE} = true ]]; then
pushd "${regression_case_input_dir}"
mv "${config_file}" "${config_file}.gotmpl"
config_file="${config_file}.gotmpl"
popd
fi
(${helmfile} -f ${regression_case_input_dir}/${config_file} template 1>/dev/null) || fail "\"helmfile template\" shouldn't fail"
info "https://github.com/roboll/helmfile/issues/2118" info "https://github.com/roboll/helmfile/issues/2118"
(${helmfile} -f ${regression_case_input_dir}/issue.2118.yaml template 1>/dev/null) || fail "\"helmfile template\" shouldn't fail" config_file="issue.2118.yaml"
if [[ ${HELMFILE_V1MODE} = true ]]; then
pushd "${regression_case_input_dir}"
mv "${config_file}" "${config_file}.gotmpl"
config_file="${config_file}.gotmpl"
popd
fi
(${helmfile} -f ${regression_case_input_dir}/${config_file} template 1>/dev/null) || fail "\"helmfile template\" shouldn't fail"
else else
info "There are no regression tests for helm 2 because all the target charts have dropped helm 2 support." info "There are no regression tests for helm 2 because all the target charts have dropped helm 2 support."
fi fi

View File

@ -5,6 +5,13 @@ if [[ helm_major_version -eq 3 ]]; then
secretssops_case_input_dir="${cases_dir}/secretssops/input" secretssops_case_input_dir="${cases_dir}/secretssops/input"
secretssops_case_output_dir="${cases_dir}/secretssops/output" secretssops_case_output_dir="${cases_dir}/secretssops/output"
config_file="secretssops.yaml"
if [[ ${HELMFILE_V1MODE} = true ]]; then
pushd "${secretssops_case_input_dir}"
mv "${config_file}" "${config_file}.gotmpl"
config_file="${config_file}.gotmpl"
popd
fi
mkdir -p ${secretssops_case_input_dir}/tmp mkdir -p ${secretssops_case_input_dir}/tmp
@ -19,7 +26,7 @@ if [[ helm_major_version -eq 3 ]]; then
info "Ensure helmfile fails when no helm-secrets is installed" info "Ensure helmfile fails when no helm-secrets is installed"
unset code unset code
${helmfile} -f ${secretssops_case_input_dir}/secretssops.yaml -e direct build || code="$?"; code="${code:-0}" ${helmfile} -f ${secretssops_case_input_dir}/${config_file} -e direct build || code="$?"; code="${code:-0}"
echo Code: "${code}" echo Code: "${code}"
[ "${code}" -ne 0 ] || fail "\"helmfile build\" should fail without secrets plugin" [ "${code}" -ne 0 ] || fail "\"helmfile build\" should fail without secrets plugin"
@ -31,7 +38,7 @@ if [[ helm_major_version -eq 3 ]]; then
${helm} plugin install https://github.com/jkroepke/helm-secrets --version v${HELM_SECRETS_VERSION} ${helm} plugin install https://github.com/jkroepke/helm-secrets --version v${HELM_SECRETS_VERSION}
info "Ensure helmfile succeed when helm-secrets is installed" info "Ensure helmfile succeed when helm-secrets is installed"
${helmfile} -f ${secretssops_case_input_dir}/secretssops.yaml -e direct build || fail "\"helmfile build\" shouldn't fail" ${helmfile} -f ${secretssops_case_input_dir}/${config_file} -e direct build || fail "\"helmfile build\" shouldn't fail"
test_pass "secretssops.2" test_pass "secretssops.2"
@ -46,7 +53,7 @@ if [[ helm_major_version -eq 3 ]]; then
info "Comparing build/direct output ${direct} with ${secretssops_case_output_dir}" info "Comparing build/direct output ${direct} with ${secretssops_case_output_dir}"
for i in $(seq 10); do for i in $(seq 10); do
info "Comparing build/direct #$i" info "Comparing build/direct #$i"
${helmfile} -f ${secretssops_case_input_dir}/secretssops.yaml -e direct template --skip-deps > ${direct} || fail "\"helmfile template\" shouldn't fail" ${helmfile} -f ${secretssops_case_input_dir}/${config_file} -e direct template --skip-deps > ${direct} || fail "\"helmfile template\" shouldn't fail"
./dyff between -bs ${secretssops_case_output_dir}/direct.build.yaml ${direct} || fail "\"helmfile template\" should be consistent" ./dyff between -bs ${secretssops_case_output_dir}/direct.build.yaml ${direct} || fail "\"helmfile template\" should be consistent"
echo code=$? echo code=$?
done done
@ -54,7 +61,7 @@ if [[ helm_major_version -eq 3 ]]; then
info "Comparing build/reverse output ${direct} with ${secretssops_case_output_dir}" info "Comparing build/reverse output ${direct} with ${secretssops_case_output_dir}"
for i in $(seq 10); do for i in $(seq 10); do
info "Comparing build/reverse #$i" info "Comparing build/reverse #$i"
${helmfile} -f ${secretssops_case_input_dir}/secretssops.yaml -e reverse template --skip-deps > ${reverse} || fail "\"helmfile template\" shouldn't fail" ${helmfile} -f ${secretssops_case_input_dir}/${config_file} -e reverse template --skip-deps > ${reverse} || fail "\"helmfile template\" shouldn't fail"
./dyff between -bs ${secretssops_case_output_dir}/reverse.build.yaml ${reverse} || fail "\"helmfile template\" should be consistent" ./dyff between -bs ${secretssops_case_output_dir}/reverse.build.yaml ${reverse} || fail "\"helmfile template\" should be consistent"
echo code=$? echo code=$?
done done

View File

@ -1,17 +1,21 @@
if [[ helm_major_version -eq 3 ]]; then if [[ helm_major_version -eq 3 ]]; then
yaml_overwrite_case_input_dir="${cases_dir}/yaml-overwrite/input" if [[ ${HELMFILE_V1MODE} = true ]]; then
yaml_overwrite_case_output_dir="${cases_dir}/yaml-overwrite/output" yaml_overwrite_case_input_dir="${cases_dir}/yaml-overwrite/input"
yaml_overwrite_case_output_dir="${cases_dir}/yaml-overwrite/output"
yaml_overwrite_tmp=$(mktemp -d) yaml_overwrite_tmp=$(mktemp -d)
yaml_overwrite_reverse=${yaml_overwrite_tmp}/yaml.override.build.yaml yaml_overwrite_reverse=${yaml_overwrite_tmp}/yaml.override.build.yaml
test_start "yaml overwrite feature" test_start "yaml overwrite feature"
info "Comparing yaml overwrite feature output ${yaml_overwrite_reverse} with ${yaml_overwrite_case_output_dir}/overwritten.yaml" info "Comparing yaml overwrite feature output ${yaml_overwrite_reverse} with ${yaml_overwrite_case_output_dir}/overwritten.yaml"
for i in $(seq 10); do for i in $(seq 10); do
info "Comparing build/yaml-overwrite #$i" info "Comparing build/yaml-overwrite #$i"
${helmfile} -f ${yaml_overwrite_case_input_dir}/issue.657.yaml template --skip-deps > ${yaml_overwrite_reverse} || fail "\"helmfile template\" shouldn't fail" ${helmfile} -f ${yaml_overwrite_case_input_dir}/issue.657.yaml.gotmpl template --skip-deps > ${yaml_overwrite_reverse} || fail "\"helmfile template\" shouldn't fail"
./dyff between -bs ${yaml_overwrite_case_output_dir}/overwritten.yaml ${yaml_overwrite_reverse} || fail "\"helmfile template\" should be consistent" ./dyff between -bs ${yaml_overwrite_case_output_dir}/overwritten.yaml ${yaml_overwrite_reverse} || fail "\"helmfile template\" should be consistent"
echo code=$? echo code=$?
done done
test_pass "yaml overwrite feature" test_pass "yaml overwrite feature"
fi else
test_pass "[skipped] yaml overwrite feature"
fi
fi