Skip to content

Commit

Permalink
Append --force-update for specific helm versions. (#1494)
Browse files Browse the repository at this point in the history
* 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: helm/helm#8777.

* Add test cases.
  • Loading branch information
wi1dcard committed Oct 12, 2020
1 parent b284b7b commit 5d8eba9
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 134 deletions.
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -4,7 +4,7 @@ go 1.14

require (
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/aryann/difflib v0.0.0-20170710044230-e206f873d14a
github.com/go-test/deep v1.0.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -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/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/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/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=
Expand Down Expand Up @@ -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.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
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.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/app_test.go
Expand Up @@ -2462,7 +2462,7 @@ func (helm *mockHelmExec) GetVersion() 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
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/app/mocks_test.go
Expand Up @@ -97,7 +97,7 @@ func (helm *noCallHelmExec) GetVersion() 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()
return false
}
16 changes: 9 additions & 7 deletions pkg/exectest/helm.go
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"sync"

"github.com/Masterminds/semver/v3"
"github.com/roboll/helmfile/pkg/helmexec"
)

Expand All @@ -30,7 +31,7 @@ type Helm struct {
Diffed []Release
FailOnUnexpectedDiff bool
FailOnUnexpectedList bool
Version *helmexec.Version
Version *semver.Version

UpdateDepsCallbacks map[string]func(string) error

Expand Down Expand Up @@ -163,19 +164,20 @@ func (helm *Helm) IsHelm3() bool {
}

func (helm *Helm) GetVersion() helmexec.Version {
if helm.Version != nil {
return *helm.Version
return helmexec.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 {
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()) {
Expand Down
72 changes: 33 additions & 39 deletions pkg/helmexec/exec.go
Expand Up @@ -6,11 +6,11 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"

"github.com/Masterminds/semver/v3"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
Expand All @@ -22,7 +22,7 @@ type decryptedSecret struct {

type execer struct {
helmBinary string
version Version
version semver.Version
runner Runner
logger *zap.SugaredLogger
kubeContext string
Expand All @@ -48,51 +48,33 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
return zap.New(core).Sugar()
}

// versionRegex matches versions like v1.1.1 and v1.1
var versionRegex = regexp.MustCompile("v(?P<major>\\d+)\\.(?P<minor>\\d+)(?:\\.(?P<patch>\\d+))?")

func parseHelmVersion(versionStr string) (Version, error) {
func parseHelmVersion(versionStr string) (semver.Version, error) {
if len(versionStr) == 0 {
return Version{}, nil
return semver.Version{}, nil
}

matches := versionRegex.FindStringSubmatch(versionStr)
if len(matches) == 0 {
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]
}
versionStr = strings.TrimLeft(versionStr, "Client: ")
versionStr = strings.TrimRight(versionStr, "\n")

// We ignore errors because regex matches only integers
// If any of the parts does not exist - default "0" will be used
major, _ := strconv.Atoi(result["major"])
minor, _ := strconv.Atoi(result["minor"])
patch, _ := strconv.Atoi(result["patch"])
ver, err := semver.NewVersion(versionStr)
if err != nil {
return semver.Version{}, fmt.Errorf("error parsing helm verion '%s'", versionStr)
}

// Support explicit helm3 opt-in via environment variable
if os.Getenv("HELMFILE_HELM3") != "" && major < 3 {
return Version{
Major: 3,
Minor: 0,
Patch: 0,
}, nil
if os.Getenv("HELMFILE_HELM3") != "" && ver.Major() < 3 {
return *semver.MustParse("v3.0.0"), nil
}

return Version{
Major: major,
Minor: minor,
Patch: patch,
}, nil
return *ver, nil
}

func getHelmVersion(helmBinary string, runner Runner) (Version, error) {
func getHelmVersion(helmBinary string, runner Runner) (semver.Version, error) {

// Autodetect from `helm verison`
bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, 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))
Expand Down Expand Up @@ -130,9 +112,16 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
return fmt.Errorf("empty field name")
}
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 != "" {
args = append(args, "--cert-file", certfile, "--key-file", keyfile)
}
Expand Down Expand Up @@ -394,13 +383,18 @@ func (helm *execer) write(out []byte) {
}

func (helm *execer) IsHelm3() bool {
return helm.version.Major == 3
return helm.version.Major() == 3
}

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 {
return helm.version.Major >= major && helm.version.Minor >= minor && helm.version.Patch >= patch
func (helm *execer) IsVersionAtLeast(versionStr string) bool {
ver := semver.MustParse(versionStr)
return helm.version.Equal(ver) || helm.version.GreaterThan(ver)
}
75 changes: 24 additions & 51 deletions pkg/helmexec/exec_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"reflect"
"testing"

"github.com/Masterminds/semver/v3"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -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) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
Expand Down Expand Up @@ -557,63 +578,15 @@ func Test_GetVersion(t *testing.T) {
func Test_IsVersionAtLeast(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
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")
}

if helm.IsVersionAtLeast(2, 19, 0) {
if helm.IsVersionAtLeast("2.19.0") {
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")
}

}

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)
}
})
}
}
2 changes: 1 addition & 1 deletion pkg/helmexec/helmexec.go
Expand Up @@ -28,7 +28,7 @@ type Interface interface {
DecryptSecret(context HelmContext, name string, flags ...string) (string, error)
IsHelm3() bool
GetVersion() Version
IsVersionAtLeast(major int, minor int, patch int) bool
IsVersionAtLeast(versionStr string) bool
}

type DependencyUpdater interface {
Expand Down
13 changes: 7 additions & 6 deletions pkg/state/chart_dependency.go
Expand Up @@ -2,18 +2,19 @@ package state

import (
"fmt"
"github.com/Masterminds/semver"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"

"github.com/Masterminds/semver/v3"
goversion "github.com/hashicorp/go-version"
"github.com/r3labs/diff"
"github.com/roboll/helmfile/pkg/app/version"
"github.com/roboll/helmfile/pkg/helmexec"
"go.uber.org/zap"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
)

type ChartMeta struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/state/state.go
Expand Up @@ -2069,7 +2069,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp

if release.CreateNamespace != nil && *release.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")
} else if release.CreateNamespace != nil || st.HelmDefaults.CreateNamespace != nil {
// createNamespace was set explicitly, but not running supported version of helm - error
Expand Down

0 comments on commit 5d8eba9

Please sign in to comment.