diff --git a/pkg/helmexec/exec.go b/pkg/helmexec/exec.go index e2df4423..ce18a0df 100644 --- a/pkg/helmexec/exec.go +++ b/pkg/helmexec/exec.go @@ -7,12 +7,12 @@ import ( "net/url" "os" "path/filepath" + "regexp" "strconv" "strings" "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" @@ -64,21 +64,44 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger { return zap.New(core).Sugar() } +// findSemVerInfo extracts semantic version information from a version string. +// Unlike chartify.FindSemVerInfo, this function handles versions with or without "v" prefix +// and ensures the returned version always has the "v" prefix for consistency. +func findSemVerInfo(version string) (string, error) { + // Trim whitespace and clean the version string + version = strings.TrimSpace(version) + + // Regex pattern that matches semantic versions with optional "v" prefix + // This is based on chartify's semVerRegex but made more flexible + semVerRegex := `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + // Try to find a version match + re := regexp.MustCompile(semVerRegex) + matches := re.FindStringSubmatch(version) + + if len(matches) == 0 { + return "", fmt.Errorf("unable to find semver info in %s", version) + } + + // Reconstruct the version with "v" prefix for consistency + versionPart := matches[0] + if !strings.HasPrefix(versionPart, "v") { + versionPart = "v" + versionPart + } + + return versionPart, nil +} + func parseHelmVersion(versionStr string) (*semver.Version, error) { if len(versionStr) == 0 { return nil, fmt.Errorf("empty helm version") } - // Check if version string starts with "v", if not add it - processedVersion := strings.TrimSpace(versionStr) - if !strings.HasPrefix(processedVersion, "v") { - processedVersion = "v" + processedVersion - } - - v, err := chartify.FindSemVerInfo(processedVersion) - + v, err := findSemVerInfo(versionStr) if err != nil { - return nil, fmt.Errorf("error find helm srmver version '%s': %w", versionStr, err) + return nil, fmt.Errorf("error find helm semver version '%s': %w", versionStr, err) } ver, err := semver.NewVersion(v) diff --git a/pkg/helmexec/exec_test.go b/pkg/helmexec/exec_test.go index bbf88808..803c303b 100644 --- a/pkg/helmexec/exec_test.go +++ b/pkg/helmexec/exec_test.go @@ -1158,6 +1158,18 @@ func TestParseHelmVersion(t *testing.T) { want: semver.MustParse("v3.2.4"), wantErr: false, }, + { + name: "kustomize version format - issue #2124", + version: "5.7.1", + want: semver.MustParse("v5.7.1"), + wantErr: false, + }, + { + name: "kustomize structured output format", + version: "{v5.7.1 2025-07-23T12:45:29Z }", + want: semver.MustParse("v5.7.1"), + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/helmexec/semver_test.go b/pkg/helmexec/semver_test.go new file mode 100644 index 00000000..93e5c5f7 --- /dev/null +++ b/pkg/helmexec/semver_test.go @@ -0,0 +1,60 @@ +package helmexec + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Test the specific scenario mentioned in issue #2124 +func TestFindSemVerInfo_Issue2124(t *testing.T) { + testCases := []struct { + name string + input string + expected string + wantErr bool + }{ + { + name: "issue #2124 - kustomize version 5.7.1 without v prefix", + input: "5.7.1", + expected: "v5.7.1", + wantErr: false, + }, + { + name: "kustomize version with v prefix", + input: "v5.7.1", + expected: "v5.7.1", + wantErr: false, + }, + { + name: "kustomize structured output", + input: "{v5.7.1 2025-07-23T12:45:29Z }", + expected: "v5.7.1", + wantErr: false, + }, + { + name: "helm version format", + input: "v3.18.4+gd80839c", + expected: "v3.18.4+gd80839c", + wantErr: false, + }, + { + name: "invalid version", + input: "not-a-version", + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := findSemVerInfo(tc.input) + + if tc.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expected, result) + } + }) + } +} \ No newline at end of file