diff --git a/pkg/helmexec/exec.go b/pkg/helmexec/exec.go index 0792fb7f..77b3d96a 100644 --- a/pkg/helmexec/exec.go +++ b/pkg/helmexec/exec.go @@ -12,6 +12,7 @@ import ( "sync" "github.com/Masterminds/semver/v3" + "github.com/helmfile/chartify" "go.uber.org/zap" "go.uber.org/zap/zapcore" "helm.sh/helm/v3/pkg/chart" @@ -30,7 +31,7 @@ type decryptedSecret struct { type execer struct { helmBinary string enableLiveOutput bool - version semver.Version + version *semver.Version runner Runner logger *zap.SugaredLogger kubeContext string @@ -57,27 +58,30 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger { return zap.New(core).Sugar() } -func parseHelmVersion(versionStr string) (semver.Version, error) { +func parseHelmVersion(versionStr string) (*semver.Version, error) { if len(versionStr) == 0 { - return semver.Version{}, nil + return nil, fmt.Errorf("empty helm version") } - versionStr = strings.TrimLeft(versionStr, "Client: ") - versionStr = strings.TrimRight(versionStr, "\n") + v, err := chartify.FindSemVerInfo(versionStr) - ver, err := semver.NewVersion(versionStr) if err != nil { - return semver.Version{}, fmt.Errorf("error parsing helm version '%s'", versionStr) + return nil, fmt.Errorf("error find helm srmver version '%s': %w", versionStr, err) } - return *ver, nil + ver, err := semver.NewVersion(v) + if err != nil { + return nil, fmt.Errorf("error parsing helm version '%s'", versionStr) + } + + return ver, nil } -func GetHelmVersion(helmBinary string, runner Runner) (semver.Version, error) { +func GetHelmVersion(helmBinary string, runner Runner) (*semver.Version, error) { // Autodetect from `helm version` outBytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil, false) if err != nil { - return semver.Version{}, fmt.Errorf("error determining helm version: %w", err) + return nil, fmt.Errorf("error determining helm version: %w", err) } return parseHelmVersion(string(outBytes)) @@ -152,7 +156,7 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam // See https://github.com/helm/helm/pull/8777 if cons, err := semver.NewConstraint(">= 3.3.2"); err == nil { - if cons.Check(&helm.version) { + if cons.Check(helm.version) { args = append(args, "--force-update") } } else { @@ -450,7 +454,7 @@ func (helm *execer) ChartPull(chart string, path string, flags ...string) error var helmArgs []string helm.logger.Infof("Pulling %v", chart) helmVersionConstraint, _ := semver.NewConstraint(">= 3.7.0") - if helmVersionConstraint.Check(&helm.version) { + if helmVersionConstraint.Check(helm.version) { // in the 3.7.0 version, the chart pull has been replaced with helm pull // https://github.com/helm/helm/releases/tag/v3.7.0 ociChartURL, ociChartTag := resolveOciChart(chart) @@ -465,7 +469,7 @@ func (helm *execer) ChartPull(chart string, path string, flags ...string) error func (helm *execer) ChartExport(chart string, path string, flags ...string) error { helmVersionConstraint, _ := semver.NewConstraint(">= 3.7.0") - if helmVersionConstraint.Check(&helm.version) { + if helmVersionConstraint.Check(helm.version) { // in the 3.7.0 version, the chart export has been removed // https://github.com/helm/helm/releases/tag/v3.7.0 return nil diff --git a/pkg/helmexec/exec_test.go b/pkg/helmexec/exec_test.go index 393479d7..050038bd 100644 --- a/pkg/helmexec/exec_test.go +++ b/pkg/helmexec/exec_test.go @@ -8,6 +8,7 @@ import ( "path/filepath" "reflect" "regexp" + "strings" "testing" "github.com/Masterminds/semver/v3" @@ -27,6 +28,10 @@ func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]s } func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { + if len(mock.output) == 0 && strings.Join(args, " ") == "version --client --short" { + return []byte("v3.2.4+ge29ce2a"), nil + } + return mock.output, mock.err } @@ -94,7 +99,7 @@ func Test_AddRepo_Helm_3_3_2(t *testing.T) { logger := NewLogger(&buffer, "debug") helm := &execer{ helmBinary: "helm", - version: *semver.MustParse("3.3.2"), + version: semver.MustParse("3.3.2"), logger: logger, kubeContext: "dev", runner: &mockRunner{}, @@ -737,7 +742,7 @@ exec: helm --kube-context dev pull oci://repo/helm-charts --version 0.14.0 --des buffer.Reset() helm := &execer{ helmBinary: tt.helmBin, - version: *semver.MustParse(tt.helmVersion), + version: semver.MustParse(tt.helmVersion), logger: logger, kubeContext: "dev", runner: &mockRunner{}, @@ -786,7 +791,7 @@ exec: helm --kube-context dev chart export chart --destination path1 --untar --u buffer.Reset() helm := &execer{ helmBinary: tt.helmBin, - version: *semver.MustParse(tt.helmVersion), + version: semver.MustParse(tt.helmVersion), logger: logger, kubeContext: "dev", runner: &mockRunner{}, @@ -975,7 +980,7 @@ func Test_ShowChart(t *testing.T) { showChartRunner := mockRunner{output: []byte("name: my-chart\nversion: 3.2.0\n")} helm := &execer{ helmBinary: "helm", - version: *semver.MustParse("3.3.2"), + version: semver.MustParse("3.3.2"), logger: NewLogger(os.Stdout, "info"), kubeContext: "dev", runner: &showChartRunner, @@ -992,3 +997,55 @@ func Test_ShowChart(t *testing.T) { t.Errorf("helmexec.ShowChart() - expected chart version was %s, received: %s", "3.2.0", metadata.Version) } } + +func TestParseHelmVersion(t *testing.T) { + tests := []struct { + name string + version string + want *semver.Version + wantErr bool + }{ + { + name: "helm 2", + version: "Client: v2.16.1+ge13bc94\n", + want: semver.MustParse("v2.16.1+ge13bc94"), + wantErr: false, + }, + { + name: "helm 3", + version: "Client: v3.2.4+ge29ce2a\n", + want: semver.MustParse("v3.2.4+ge29ce2a"), + wantErr: false, + }, + { + name: "helm 3 with os arch and build info", + version: "Client v3.7.1+7.el8+g8f33223\n", + want: semver.MustParse("v3.7.1+7.el8"), + wantErr: false, + }, + { + name: "empty version", + version: "", + want: nil, + wantErr: true, + }, + { + name: "invalid version", + version: "oooooo", + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseHelmVersion(tt.version) + 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() = %v, want %v", got, tt.want) + } + }) + } +}