use Masterminds/semver/v3 for validation and update bump logic

Signed-off-by: Jan Braje <12571346+janluak@users.noreply.github.com>
pull/32402/head
Jan Braje 3 months ago
parent e9ae6b68e3
commit 3a65d94d4b
No known key found for this signature in database
GPG Key ID: AA98A2FCFE70D0C9

@ -17,13 +17,12 @@ limitations under the License.
package action
import (
"errors"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/Masterminds/semver/v3"
"sigs.k8s.io/yaml"
chart "helm.sh/helm/v4/pkg/chart/v2"
@ -65,284 +64,53 @@ func (b *Bump) Run(bumpType string, chartpath string) (string, error) {
b.bump = bumpType
currentVersion := strings.TrimSpace(string(cv))
if !isValidVersion(currentVersion) {
parsedVersion, err := semver.NewVersion(currentVersion)
if err != nil {
return "", fmt.Errorf("invalid original version: %s", currentVersion)
}
var newVersion string
var newVersion semver.Version
version, err := semver.StrictNewVersion(b.bump)
if err != nil {
switch b.bump {
case "major":
newVersion, err = bumpMajor(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump major version: %w", err)
}
newVersion = parsedVersion.IncMajor()
case "minor":
newVersion, err = bumpMinor(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump minor version: %w", err)
}
newVersion = parsedVersion.IncMinor()
case "patch":
newVersion, err = bumpPatch(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump patch version: %w", err)
}
newVersion = parsedVersion.IncPatch()
case "stable":
newVersion, err = bumpStable(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump stable version: %w", err)
}
case "alpha":
newVersion, err = bumpAlpha(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump alpha version: %w", err)
}
case "beta":
newVersion, err = bumpBeta(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump beta version: %w", err)
}
case "rc":
newVersion, err = bumpRC(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump rc version: %w", err)
}
case "post":
newVersion, err = bumpPost(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump post version: %w", err)
}
case "dev":
newVersion, err = bumpDev(currentVersion)
if err != nil {
return "", fmt.Errorf("failed to bump dev version: %w", err)
}
newVersion, _ = parsedVersion.SetPrerelease("")
default:
if !isValidVersion(b.bump) {
return "", fmt.Errorf("invalid bump type or version: %s", b.bump)
}
newVersion = b.bump
}
// Update the chart metadata with the new version
b.chart.Metadata.Version = newVersion
// Save the updated chart to disk (this will update Chart.yaml)
if chartpath != "" {
err = util.SaveChartfile(filepath.Join(chartpath, "Chart.yaml"), b.chart.Metadata)
if err != nil {
return "", fmt.Errorf("failed to save updated chart: %w", err)
}
}
return newVersion, nil
}
// bumpMajor increases the major version number (e.g., 1.2.3 -> 2.0.0)
func bumpMajor(version string) (string, error) {
parts := strings.Split(version, ".")
if len(parts) < 3 {
return "", errors.New("invalid version format for major bump")
}
major, err := strconv.Atoi(parts[0])
if err != nil {
return "", errors.New("invalid major version number")
}
preRelease := parsedVersion.Prerelease()
newMajor := major + 1
return fmt.Sprintf("%d.0.0", newMajor), nil
}
var preReleaseVersion int
// bumpMinor increases the minor version number (e.g., 1.2.3 -> 1.3.0)
func bumpMinor(version string) (string, error) {
parts := strings.Split(version, ".")
if len(parts) < 3 {
return "", errors.New("invalid version format for minor bump")
}
parts := strings.Split(preRelease, ".")
major, err := strconv.Atoi(parts[0])
if err != nil {
return "", errors.New("invalid major version number")
}
minor, err := strconv.Atoi(parts[1])
if err != nil {
return "", errors.New("invalid minor version number")
}
newMinor := minor + 1
return fmt.Sprintf("%d.%d.0", major, newMinor), nil
}
// bumpPatch increases the patch version number (e.g., 1.2.3 -> 1.2.4)
func bumpPatch(version string) (string, error) {
parts := strings.Split(version, ".")
if len(parts) < 3 {
return "", errors.New("invalid version format for patch bump")
}
major, err := strconv.Atoi(parts[0])
if err != nil {
return "", errors.New("invalid major version number")
}
minor, err := strconv.Atoi(parts[1])
if err != nil {
return "", errors.New("invalid minor version number")
}
patch, err := strconv.Atoi(parts[2])
if err != nil {
return "", errors.New("invalid patch version number")
}
newPatch := patch + 1
return fmt.Sprintf("%d.%d.%d", major, minor, newPatch), nil
}
// bumpStable removes any pre-release suffix (e.g., 1.2.3-alpha -> 1.2.3)
func bumpStable(version string) (string, error) {
// Remove any pre-release suffixes (like -alpha, -beta, etc.)
re := regexp.MustCompile(`^(\d+\.\d+\.\d+)(-.+)?$`)
matches := re.FindStringSubmatch(version)
if len(matches) < 2 {
return "", errors.New("invalid version format for stable bump")
}
return matches[1], nil
}
// bumpAlpha increases the pre-release version (e.g., 1.2.3-alpha -> 1.2.3-alpha.1)
func bumpAlpha(version string) (string, error) {
// Check if version has an alpha suffix - simple regex that works with all cases
re := regexp.MustCompile(`^(\d+\.\d+\.\d+)(-alpha(?:\.(\d+))?)?$`)
matches := re.FindStringSubmatch(version)
if len(matches) < 2 {
return "", errors.New("invalid version format for alpha bump")
}
baseVersion := matches[1]
var preRelease string
if len(matches) >= 4 && matches[3] != "" {
// Increment the pre-release number
num, err := strconv.Atoi(matches[3])
if err != nil {
return "", errors.New("invalid alpha pre-release number")
}
preRelease = fmt.Sprintf(".%d", num+1)
if len(parts) == 1 {
preReleaseVersion = 1
} else {
// Start with .1 if no pre-release number exists
preRelease = ".1"
preReleaseVersion, _ = strconv.Atoi(parts[1])
preReleaseVersion++
}
return baseVersion + "-alpha" + preRelease, nil
}
// bumpBeta increases the pre-release version (e.g., 1.2.3-beta -> 1.2.3-beta.1)
func bumpBeta(version string) (string, error) {
// Check if version has a beta suffix - simple regex that works with all cases
re := regexp.MustCompile(`^(\d+\.\d+\.\d+)(-beta(?:\.(\d+))?)?$`)
matches := re.FindStringSubmatch(version)
if len(matches) < 2 {
return "", errors.New("invalid version format for beta bump")
newPreReleaseString := fmt.Sprintf("%s.%d", bumpType, preReleaseVersion)
newVersion, _ = parsedVersion.SetPrerelease(newPreReleaseString)
}
baseVersion := matches[1]
var preRelease string
if len(matches) >= 4 && matches[3] != "" {
// Increment the pre-release number
num, err := strconv.Atoi(matches[3])
if err != nil {
return "", errors.New("invalid beta pre-release number")
}
preRelease = fmt.Sprintf(".%d", num+1)
} else {
// Start with .1 if no pre-release number exists
preRelease = ".1"
newVersion = *version
}
return baseVersion + "-beta" + preRelease, nil
}
// bumpRC increases the pre-release version (e.g., 1.2.3-rc -> 1.2.3-rc.1)
func bumpRC(version string) (string, error) {
// Check if version has a rc suffix - simple regex that works with all cases
re := regexp.MustCompile(`^(\d+\.\d+\.\d+)(-rc(?:\.(\d+))?)?$`)
matches := re.FindStringSubmatch(version)
if len(matches) < 2 {
return "", errors.New("invalid version format for rc bump")
}
b.chart.Metadata.Version = newVersion.String()
baseVersion := matches[1]
var preRelease string
if len(matches) >= 4 && matches[3] != "" {
// Increment the pre-release number
num, err := strconv.Atoi(matches[3])
if err != nil {
return "", errors.New("invalid rc pre-release number")
}
preRelease = fmt.Sprintf(".%d", num+1)
} else {
// Start with .1 if no pre-release number exists
preRelease = ".1"
}
return baseVersion + "-rc" + preRelease, nil
}
// bumpPost increases the post-release version (e.g., 1.2.3-post -> 1.2.3-post.1)
func bumpPost(version string) (string, error) {
// Check if version has a post-suffix
re := regexp.MustCompile(`^(\d+\.\d+\.\d+)(-post(\.(\d+))?)?$`)
matches := re.FindStringSubmatch(version)
if len(matches) < 2 {
return "", errors.New("invalid version format for post bump")
}
baseVersion := matches[1]
var preRelease string
if len(matches) >= 4 && matches[4] != "" {
// Increment the pre-release number
num, err := strconv.Atoi(matches[4])
if err != nil {
return "", errors.New("invalid post pre-release number")
}
preRelease = fmt.Sprintf(".%d", num+1)
} else {
// Start with .1 if no pre-release number exists
preRelease = ".1"
}
return baseVersion + "-post" + preRelease, nil
}
// bumpDev increases the dev version (e.g., 1.2.3-dev -> 1.2.3-dev.1)
func bumpDev(version string) (string, error) {
// Check if version has a dev suffix
re := regexp.MustCompile(`^(\d+\.\d+\.\d+)(-dev(\.(\d+))?)?$`)
matches := re.FindStringSubmatch(version)
if len(matches) < 2 {
return "", errors.New("invalid version format for dev bump")
}
baseVersion := matches[1]
var preRelease string
if len(matches) >= 4 && matches[4] != "" {
// Increment the pre-release number
num, err := strconv.Atoi(matches[4])
// Save the updated chart to disk (this will update Chart.yaml)
if chartpath != "" {
err = util.SaveChartfile(filepath.Join(chartpath, "Chart.yaml"), b.chart.Metadata)
if err != nil {
return "", errors.New("invalid dev pre-release number")
}
preRelease = fmt.Sprintf(".%d", num+1)
} else {
// Start with .1 if no pre-release number exists
preRelease = ".1"
return "", fmt.Errorf("failed to save updated chart: %w", err)
}
return baseVersion + "-dev" + preRelease, nil
}
// isValidVersion checks if a string is a valid semantic version format
func isValidVersion(version string) bool {
re := regexp.MustCompile(`^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+(\.\d+)?)?$`)
return re.MatchString(version)
return b.chart.Metadata.Version, nil
}

@ -370,47 +370,3 @@ func TestBump_Run_ExplicitVersion(t *testing.T) {
require.Equal(t, "1.5.3-post.1", result)
})
}
func TestBump_Run_InvalidVersion(t *testing.T) {
t.Parallel()
cfg := actionConfigFixture(t)
client := NewBump(cfg)
client.chart = &chart.Chart{
Metadata: &chart.Metadata{Name: "test"},
}
t.Run("Invalid bump type", func(t *testing.T) {
client.chart.Metadata.Version = "1.2.3"
result, err := client.Run("invalid", "")
require.Error(t, err)
require.Empty(t, result)
})
}
func TestBump_IsValidVersion(t *testing.T) {
t.Parallel()
testCases := []struct {
version string
expected bool
}{
{"1.2.3", true},
{"1.2.3-alpha", true},
{"1.2.3-beta.1", true},
{"1.2.3-rc.2", true},
{"1.2.3-post.1", true},
{"1.2.3-dev.1", true},
{"1.2", false},
{"invalid", false},
{"1.2.3.4", false},
{"1.a.3", false},
}
for _, tc := range testCases {
t.Run("Version "+tc.version, func(t *testing.T) {
result := isValidVersion(tc.version)
require.Equal(t, tc.expected, result)
})
}
}

Loading…
Cancel
Save