Append --force-update for specific helm versions. (#1494)

* Parse and process helm version using github.com/Masterminds/semver/v3.

* Add --force-update only when Helm version >= 3.3.2, < 3.3.4.

See: https://github.com/helm/helm/pull/8777.

* Add test cases.
This commit is contained in:
Wi1dcard 2020-10-12 08:20:55 +08:00 committed by GitHub
parent b284b7be83
commit 5d8eba9b29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 134 deletions

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.14
require ( require (
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go v35.0.0+incompatible // indirect
github.com/Masterminds/semver v1.4.2 github.com/Masterminds/semver/v3 v3.1.0
github.com/Masterminds/sprig/v3 v3.1.0 github.com/Masterminds/sprig/v3 v3.1.0
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
github.com/go-test/deep v1.0.3 github.com/go-test/deep v1.0.3

2
go.sum
View File

@ -181,6 +181,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
@ -1108,6 +1109,7 @@ google.golang.org/api v0.21.0 h1:zS+Q/CJJnVlXpXQVIz+lH0ZT2lBuT2ac7XD8Y/3w6hY=
google.golang.org/api v0.26.0 h1:VJZ8h6E8ip82FRpQl848c5vAadxlTXrUh8RzQzSRm08= google.golang.org/api v0.26.0 h1:VJZ8h6E8ip82FRpQl848c5vAadxlTXrUh8RzQzSRm08=
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
google.golang.org/api v0.31.0 h1:1w5Sz/puhxFo9lTtip2n47k7toB/U2nCqOKNHd3Yrbo= google.golang.org/api v0.31.0 h1:1w5Sz/puhxFo9lTtip2n47k7toB/U2nCqOKNHd3Yrbo=
google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

View File

@ -2462,7 +2462,7 @@ func (helm *mockHelmExec) GetVersion() helmexec.Version {
return helmexec.Version{} return helmexec.Version{}
} }
func (helm *mockHelmExec) IsVersionAtLeast(major int, minor int, patch int) bool { func (helm *mockHelmExec) IsVersionAtLeast(versionStr string) bool {
return false return false
} }

View File

@ -97,7 +97,7 @@ func (helm *noCallHelmExec) GetVersion() helmexec.Version {
return helmexec.Version{} return helmexec.Version{}
} }
func (helm *noCallHelmExec) IsVersionAtLeast(major int, minor int, patch int) bool { func (helm *noCallHelmExec) IsVersionAtLeast(versionStr string) bool {
helm.doPanic() helm.doPanic()
return false return false
} }

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/Masterminds/semver/v3"
"github.com/roboll/helmfile/pkg/helmexec" "github.com/roboll/helmfile/pkg/helmexec"
) )
@ -30,7 +31,7 @@ type Helm struct {
Diffed []Release Diffed []Release
FailOnUnexpectedDiff bool FailOnUnexpectedDiff bool
FailOnUnexpectedList bool FailOnUnexpectedList bool
Version *helmexec.Version Version *semver.Version
UpdateDepsCallbacks map[string]func(string) error UpdateDepsCallbacks map[string]func(string) error
@ -163,19 +164,20 @@ func (helm *Helm) IsHelm3() bool {
} }
func (helm *Helm) GetVersion() helmexec.Version { func (helm *Helm) GetVersion() helmexec.Version {
if helm.Version != nil { return helmexec.Version{
return *helm.Version Major: int(helm.Version.Major()),
Minor: int(helm.Version.Minor()),
Patch: int(helm.Version.Patch()),
} }
return helmexec.Version{}
} }
func (helm *Helm) IsVersionAtLeast(major int, minor int, patch int) bool { func (helm *Helm) IsVersionAtLeast(versionStr string) bool {
if helm.Version == nil { if helm.Version == nil {
return false return false
} }
return helm.Version.Major >= major && minor >= helm.Version.Minor && patch >= helm.Version.Patch ver := semver.MustParse(versionStr)
return helm.Version.Equal(ver) || helm.Version.GreaterThan(ver)
} }
func (helm *Helm) sync(m *sync.Mutex, f func()) { func (helm *Helm) sync(m *sync.Mutex, f func()) {

View File

@ -6,11 +6,11 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"github.com/Masterminds/semver/v3"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
@ -22,7 +22,7 @@ type decryptedSecret struct {
type execer struct { type execer struct {
helmBinary string helmBinary string
version Version version semver.Version
runner Runner runner Runner
logger *zap.SugaredLogger logger *zap.SugaredLogger
kubeContext string kubeContext string
@ -48,51 +48,33 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
return zap.New(core).Sugar() return zap.New(core).Sugar()
} }
// versionRegex matches versions like v1.1.1 and v1.1 func parseHelmVersion(versionStr string) (semver.Version, error) {
var versionRegex = regexp.MustCompile("v(?P<major>\\d+)\\.(?P<minor>\\d+)(?:\\.(?P<patch>\\d+))?")
func parseHelmVersion(versionStr string) (Version, error) {
if len(versionStr) == 0 { if len(versionStr) == 0 {
return Version{}, nil return semver.Version{}, nil
} }
matches := versionRegex.FindStringSubmatch(versionStr) versionStr = strings.TrimLeft(versionStr, "Client: ")
if len(matches) == 0 { versionStr = strings.TrimRight(versionStr, "\n")
return Version{}, fmt.Errorf("error parsing helm verion '%s'", versionStr)
}
result := make(map[string]string)
for i, name := range versionRegex.SubexpNames() {
result[name] = matches[i]
}
// We ignore errors because regex matches only integers ver, err := semver.NewVersion(versionStr)
// If any of the parts does not exist - default "0" will be used if err != nil {
major, _ := strconv.Atoi(result["major"]) return semver.Version{}, fmt.Errorf("error parsing helm verion '%s'", versionStr)
minor, _ := strconv.Atoi(result["minor"]) }
patch, _ := strconv.Atoi(result["patch"])
// Support explicit helm3 opt-in via environment variable // Support explicit helm3 opt-in via environment variable
if os.Getenv("HELMFILE_HELM3") != "" && major < 3 { if os.Getenv("HELMFILE_HELM3") != "" && ver.Major() < 3 {
return Version{ return *semver.MustParse("v3.0.0"), nil
Major: 3,
Minor: 0,
Patch: 0,
}, nil
} }
return Version{ return *ver, nil
Major: major,
Minor: minor,
Patch: patch,
}, nil
} }
func getHelmVersion(helmBinary string, runner Runner) (Version, error) { func getHelmVersion(helmBinary string, runner Runner) (semver.Version, error) {
// Autodetect from `helm verison` // Autodetect from `helm verison`
bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil) bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
if err != nil { if err != nil {
return Version{}, fmt.Errorf("error determining helm version: %w", err) return semver.Version{}, fmt.Errorf("error determining helm version: %w", err)
} }
return parseHelmVersion(string(bytes)) return parseHelmVersion(string(bytes))
@ -130,9 +112,16 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
return fmt.Errorf("empty field name") return fmt.Errorf("empty field name")
} }
args = append(args, "repo", "add", name, repository) args = append(args, "repo", "add", name, repository)
if helm.IsHelm3() && helm.IsVersionAtLeast(3, 3, 2) {
args = append(args, "--force-update") // See https://github.com/helm/helm/pull/8777
if cons, err := semver.NewConstraint(">= 3.3.2, < 3.3.4"); err == nil {
if cons.Check(&helm.version) {
args = append(args, "--force-update")
}
} else {
panic(err)
} }
if certfile != "" && keyfile != "" { if certfile != "" && keyfile != "" {
args = append(args, "--cert-file", certfile, "--key-file", keyfile) args = append(args, "--cert-file", certfile, "--key-file", keyfile)
} }
@ -394,13 +383,18 @@ func (helm *execer) write(out []byte) {
} }
func (helm *execer) IsHelm3() bool { func (helm *execer) IsHelm3() bool {
return helm.version.Major == 3 return helm.version.Major() == 3
} }
func (helm *execer) GetVersion() Version { func (helm *execer) GetVersion() Version {
return helm.version return Version{
Major: int(helm.version.Major()),
Minor: int(helm.version.Minor()),
Patch: int(helm.version.Patch()),
}
} }
func (helm *execer) IsVersionAtLeast(major int, minor int, patch int) bool { func (helm *execer) IsVersionAtLeast(versionStr string) bool {
return helm.version.Major >= major && helm.version.Minor >= minor && helm.version.Patch >= patch ver := semver.MustParse(versionStr)
return helm.version.Equal(ver) || helm.version.GreaterThan(ver)
} }

View File

@ -9,6 +9,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/Masterminds/semver/v3"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -71,6 +72,26 @@ func Test_SetHelmBinary(t *testing.T) {
} }
} }
func Test_AddRepo_Helm_3_3_2(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := &execer{
helmBinary: "helm",
version: *semver.MustParse("3.3.2"),
logger: logger,
kubeContext: "dev",
runner: &mockRunner{},
}
helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "")
expected := `Adding repo myRepo https://repo.example.com/
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --force-update --cert-file cert.pem --key-file key.pem
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --force-update --cert-file cert.pem --key-file key.pem:
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
}
func Test_AddRepo(t *testing.T) { func Test_AddRepo(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
@ -557,63 +578,15 @@ func Test_GetVersion(t *testing.T) {
func Test_IsVersionAtLeast(t *testing.T) { func Test_IsVersionAtLeast(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner) helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
if !helm.IsVersionAtLeast(2, 1, 0) { if !helm.IsVersionAtLeast("2.1.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1") t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
} }
if helm.IsVersionAtLeast(2, 19, 0) { if helm.IsVersionAtLeast("2.19.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 2.19") t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 2.19")
} }
if helm.IsVersionAtLeast(3, 2, 0) { if helm.IsVersionAtLeast("3.2.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 3.2") t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 3.2")
} }
}
func Test_parseHelmVersion(t *testing.T) {
tests := []struct {
ver string
want Version
wantErr bool
}{
{
ver: "v1.2.3",
want: Version{
Major: 1,
Minor: 2,
Patch: 3,
},
wantErr: false,
},
{
ver: "v1.2",
want: Version{
Major: 1,
Minor: 2,
Patch: 0,
},
wantErr: false,
},
{
ver: "v1",
wantErr: true,
},
{
ver: "1.1.1",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.ver, func(t *testing.T) {
got, err := parseHelmVersion(tt.ver)
if (err != nil) != tt.wantErr {
t.Errorf("parseHelmVersion() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseHelmVersion() got = %v, want %v", got, tt.want)
}
})
}
} }

View File

@ -28,7 +28,7 @@ type Interface interface {
DecryptSecret(context HelmContext, name string, flags ...string) (string, error) DecryptSecret(context HelmContext, name string, flags ...string) (string, error)
IsHelm3() bool IsHelm3() bool
GetVersion() Version GetVersion() Version
IsVersionAtLeast(major int, minor int, patch int) bool IsVersionAtLeast(versionStr string) bool
} }
type DependencyUpdater interface { type DependencyUpdater interface {

View File

@ -2,18 +2,19 @@ package state
import ( import (
"fmt" "fmt"
"github.com/Masterminds/semver" "io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/Masterminds/semver/v3"
goversion "github.com/hashicorp/go-version" goversion "github.com/hashicorp/go-version"
"github.com/r3labs/diff" "github.com/r3labs/diff"
"github.com/roboll/helmfile/pkg/app/version" "github.com/roboll/helmfile/pkg/app/version"
"github.com/roboll/helmfile/pkg/helmexec" "github.com/roboll/helmfile/pkg/helmexec"
"go.uber.org/zap" "go.uber.org/zap"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
) )
type ChartMeta struct { type ChartMeta struct {

View File

@ -2069,7 +2069,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
if release.CreateNamespace != nil && *release.CreateNamespace || if release.CreateNamespace != nil && *release.CreateNamespace ||
release.CreateNamespace == nil && (st.HelmDefaults.CreateNamespace == nil || *st.HelmDefaults.CreateNamespace) { release.CreateNamespace == nil && (st.HelmDefaults.CreateNamespace == nil || *st.HelmDefaults.CreateNamespace) {
if helm.IsVersionAtLeast(3, 2, 0) { if helm.IsVersionAtLeast("3.2.0") {
flags = append(flags, "--create-namespace") flags = append(flags, "--create-namespace")
} else if release.CreateNamespace != nil || st.HelmDefaults.CreateNamespace != nil { } else if release.CreateNamespace != nil || st.HelmDefaults.CreateNamespace != nil {
// createNamespace was set explicitly, but not running supported version of helm - error // createNamespace was set explicitly, but not running supported version of helm - error

View File

@ -8,6 +8,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/Masterminds/semver/v3"
"github.com/roboll/helmfile/pkg/exectest" "github.com/roboll/helmfile/pkg/exectest"
"github.com/roboll/helmfile/pkg/helmexec" "github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/testhelper" "github.com/roboll/helmfile/pkg/testhelper"
@ -165,7 +166,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
version *helmexec.Version version *semver.Version
defaults HelmSpec defaults HelmSpec
release *ReleaseSpec release *ReleaseSpec
want []string want []string
@ -582,11 +583,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
defaults: HelmSpec{ defaults: HelmSpec{
Verify: false, Verify: false,
}, },
version: &helmexec.Version{ version: semver.MustParse("3.2.0"),
Major: 3,
Minor: 2,
Patch: 0,
},
release: &ReleaseSpec{ release: &ReleaseSpec{
Chart: "test/chart", Chart: "test/chart",
Version: "0.1", Version: "0.1",
@ -606,11 +603,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Verify: false, Verify: false,
CreateNamespace: &disable, CreateNamespace: &disable,
}, },
version: &helmexec.Version{ version: semver.MustParse("3.2.0"),
Major: 3,
Minor: 2,
Patch: 0,
},
release: &ReleaseSpec{ release: &ReleaseSpec{
Chart: "test/chart", Chart: "test/chart",
Version: "0.1", Version: "0.1",
@ -629,11 +622,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Verify: false, Verify: false,
CreateNamespace: &disable, CreateNamespace: &disable,
}, },
version: &helmexec.Version{ version: semver.MustParse("3.2.0"),
Major: 3,
Minor: 2,
Patch: 0,
},
release: &ReleaseSpec{ release: &ReleaseSpec{
Chart: "test/chart", Chart: "test/chart",
Version: "0.1", Version: "0.1",
@ -654,11 +643,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Verify: false, Verify: false,
CreateNamespace: &enable, CreateNamespace: &enable,
}, },
version: &helmexec.Version{ version: semver.MustParse("3.2.0"),
Major: 3,
Minor: 2,
Patch: 0,
},
release: &ReleaseSpec{ release: &ReleaseSpec{
Chart: "test/chart", Chart: "test/chart",
Version: "0.1", Version: "0.1",
@ -678,11 +663,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Verify: false, Verify: false,
CreateNamespace: &enable, CreateNamespace: &enable,
}, },
version: &helmexec.Version{ version: semver.MustParse("2.16.0"),
Major: 2,
Minor: 16,
Patch: 0,
},
release: &ReleaseSpec{ release: &ReleaseSpec{
Chart: "test/chart", Chart: "test/chart",
Version: "0.1", Version: "0.1",