Improve semver parsing robustness for versions without 'v' prefix

Replace chartify.FindSemVerInfo with enhanced findSemVerInfo function that handles
versions with or without 'v' prefix. This addresses the "unable to find semver info
in 5.7.1" error by ensuring consistent version parsing regardless of input format.

- Add findSemVerInfo function with flexible regex pattern
- Update parseHelmVersion to use enhanced version parsing
- Add comprehensive test cases for various version formats including issue #2124 scenarios
- Remove unused chartify import from helmexec package

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-08-14 13:38:52 +00:00
parent 6c86962827
commit 9c1db04e27
3 changed files with 105 additions and 10 deletions

View File

@ -7,12 +7,12 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/helmfile/chartify"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
@ -64,21 +64,44 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
return zap.New(core).Sugar() 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) { func parseHelmVersion(versionStr string) (*semver.Version, error) {
if len(versionStr) == 0 { if len(versionStr) == 0 {
return nil, fmt.Errorf("empty helm version") return nil, fmt.Errorf("empty helm version")
} }
// Check if version string starts with "v", if not add it v, err := findSemVerInfo(versionStr)
processedVersion := strings.TrimSpace(versionStr)
if !strings.HasPrefix(processedVersion, "v") {
processedVersion = "v" + processedVersion
}
v, err := chartify.FindSemVerInfo(processedVersion)
if err != nil { 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) ver, err := semver.NewVersion(v)

View File

@ -1158,6 +1158,18 @@ func TestParseHelmVersion(t *testing.T) {
want: semver.MustParse("v3.2.4"), want: semver.MustParse("v3.2.4"),
wantErr: false, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -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)
}
})
}
}