Files
Olares/cli/pkg/upgrade/version.go

256 lines
8.1 KiB
Go

package upgrade
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/beclab/Olares/cli/pkg/utils"
"github.com/beclab/Olares/cli/version"
)
type VersionSpec struct {
Version string `json:"version"`
Major uint64 `json:"major"`
Minor uint64 `json:"minor"`
Patch uint64 `json:"patch"`
ReleaseType string `json:"releaseType"`
ReleaseNum int `json:"releaseNum"`
PreRelease bool `json:"prerelease"`
AddedBreakingChange bool `json:"addedBreakingChange"`
NeedRestart bool `json:"needRestart"`
MinimumUpgradableVersions MinimumVersionConstraints `json:"minimumUpgradableVersions"`
}
type VersionConstraints map[string]*semver.Version
type MinimumVersionConstraints VersionConstraints
func (c MinimumVersionConstraints) SatisfiedBy(base *semver.Version) (bool, error) {
if base == nil {
return false, nil
}
var minVersion *semver.Version
prerelease := base.Prerelease()
if prerelease == "" {
minVersion = c[releaseTypeStable]
} else {
prereleaseType, _, err := parsePrereleaseVersion(prerelease)
if err != nil {
return false, fmt.Errorf("invalid version '%s': %v", base, err)
}
minVersion = c[prereleaseType]
}
return minVersion != nil && minVersion.LessThanEqual(base), nil
}
type releaseLine string
var (
mainLine = releaseLine("main")
dailyLine = releaseLine("daily")
// the versions when current upgrade framework is introduced
minUpgradableStableVersion = semver.MustParse("1.12.0")
minUpgradableDailyVersion = semver.MustParse("1.12.0-0")
releaseTypeStable = "stable"
releaseTypeDaily = "daily"
releaseTypeRC = "rc"
releaseTypeBeta = "beta"
releaseTypeAlpha = "alpha"
prereleaseSep = "."
dailyUpgraders []breakingUpgrader
mainUpgraders []breakingUpgrader
)
func getReleaseLineOfVersion(v *semver.Version) releaseLine {
preRelease := v.Prerelease()
mainLinePrereleasePrefixes := []string{releaseTypeRC, releaseTypeBeta, releaseTypeAlpha}
if preRelease == "" {
return mainLine
}
for _, prefix := range mainLinePrereleasePrefixes {
if strings.HasPrefix(preRelease, prefix) {
return mainLine
}
}
return dailyLine
}
func Check(base *semver.Version, target *semver.Version) error {
if base == nil {
return fmt.Errorf("base version is nil")
}
cliVersion, err := utils.ParseOlaresVersionString(version.VERSION)
if err != nil {
return fmt.Errorf("invalid olares-cli version :\"%s\"", version.VERSION)
}
if target != nil {
if !target.GreaterThan(base) {
return fmt.Errorf("base version: %s, target version: %s, no need to upgrade", base, target)
}
if !target.Equal(cliVersion) {
return fmt.Errorf("target version: %s is not the same with cli version: %s", target, cliVersion)
}
}
currentVersionSpec, err := CurrentVersionSpec()
if err != nil {
return fmt.Errorf("failed to get current version's upgrade spec: %v", err)
}
satisfied, err := currentVersionSpec.MinimumUpgradableVersions.SatisfiedBy(base)
if err != nil {
return err
}
if !satisfied {
return fmt.Errorf("cannot upgrade to version '%s' from '%s': version constraints not satified: %v", target, base, currentVersionSpec.MinimumUpgradableVersions)
}
return nil
}
func getUpgraderByVersion(target *semver.Version) upgrader {
for _, upgraders := range [][]breakingUpgrader{
dailyUpgraders,
mainUpgraders,
} {
for _, u := range upgraders {
if u.Version().Equal(target) {
return u
}
}
}
return upgraderBase{}
}
func parsePrereleaseVersion(prereleaseVersion string) (string, int, error) {
if !strings.Contains(prereleaseVersion, prereleaseSep) {
n, err := strconv.Atoi(prereleaseVersion)
if err != nil {
return "", 0, fmt.Errorf("invalid prereleaseVersion: %s", prereleaseVersion)
}
return releaseTypeDaily, n, nil
}
parts := strings.Split(prereleaseVersion, prereleaseSep)
if len(parts) != 2 {
return "", 0, fmt.Errorf("invalid prereleaseVersion: %s", prereleaseVersion)
}
tStr, nStr := parts[0], parts[1]
if tStr != releaseTypeRC && tStr != releaseTypeBeta && tStr != releaseTypeAlpha {
return "", 0, fmt.Errorf("invalid prereleaseVersion: %s", prereleaseVersion)
}
n, err := strconv.Atoi(nStr)
if err != nil {
return "", 0, fmt.Errorf("invalid prereleaseVersion: %s", prereleaseVersion)
}
return tStr, n, nil
}
func formatPrereleaseVersion(releaseType string, releaseNum int) string {
if releaseType == releaseTypeDaily {
return strconv.Itoa(releaseNum)
}
return fmt.Sprintf("%s%s%s", releaseType, prereleaseSep, strconv.Itoa(releaseNum))
}
func CurrentVersionSpec() (spec *VersionSpec, err error) {
v, err := semver.NewVersion(version.VERSION)
if err != nil {
return nil, fmt.Errorf("current version '%s' is invalid: %v", version.VERSION, err)
}
spec = &VersionSpec{}
spec.Version, spec.Major, spec.Minor, spec.Patch = v.Original(), v.Major(), v.Minor(), v.Patch()
if v.Prerelease() != "" {
spec.PreRelease = true
spec.ReleaseType, spec.ReleaseNum, err = parsePrereleaseVersion(v.Prerelease())
if err != nil {
return nil, err
}
} else {
spec.ReleaseType = releaseTypeStable
}
u := getUpgraderByVersion(v)
spec.AddedBreakingChange = u.AddedBreakingChange()
spec.NeedRestart = u.NeedRestart()
if spec.ReleaseType == releaseTypeDaily {
lastBreakingVersion := getLastBreakingVersion(dailyUpgraders, v)
if lastBreakingVersion == nil {
lastBreakingVersion = minUpgradableDailyVersion
}
spec.MinimumUpgradableVersions = MinimumVersionConstraints{releaseTypeDaily: lastBreakingVersion}
} else {
lastBreakingVersion := getLastBreakingVersion(mainUpgraders, v)
if lastBreakingVersion == nil {
lastBreakingVersion = minUpgradableStableVersion
}
// all mainline release types support upgrade from stable release
spec.MinimumUpgradableVersions = MinimumVersionConstraints{
releaseTypeStable: semver.New(lastBreakingVersion.Major(), lastBreakingVersion.Minor(), lastBreakingVersion.Patch(), "", ""),
}
switch spec.ReleaseType {
// both stable and rc release types support upgrade from stable/rc release
case releaseTypeStable, releaseTypeRC:
spec.MinimumUpgradableVersions[releaseTypeRC] = semver.New(lastBreakingVersion.Major(), lastBreakingVersion.Minor(), lastBreakingVersion.Patch(), formatPrereleaseVersion(releaseTypeRC, 0), "")
case releaseTypeAlpha:
// alpha release type supports upgrade from the last alpha release
// if it exists
// and no breaking change is added to the current version
if !spec.AddedBreakingChange && spec.ReleaseNum > 0 {
spec.MinimumUpgradableVersions[releaseTypeAlpha] = semver.New(spec.Major, spec.Minor, spec.Patch, formatPrereleaseVersion(releaseTypeAlpha, spec.ReleaseNum-1), "")
}
}
}
return spec, nil
}
func getLastBreakingVersion(upgraders []breakingUpgrader, current *semver.Version) *semver.Version {
sort.Slice(upgraders, func(i, j int) bool {
return upgraders[i].Version().LessThan(upgraders[j].Version())
})
for i := len(upgraders) - 1; i >= 0; i-- {
if !upgraders[i].AddedBreakingChange() {
continue
}
if upgraders[i].Version().GreaterThanEqual(current) {
continue
}
return upgraders[i].Version()
}
return nil
}
func samePatchLevelVersion(a, b *semver.Version) bool {
return a.Major() == b.Major() && a.Minor() == b.Minor() && a.Patch() == b.Patch()
}
func sameMinorLevelVersion(a, b *semver.Version) bool {
return a.Major() == b.Major() && a.Minor() == b.Minor()
}
func sameMajorLevelVersion(a, b *semver.Version) bool {
return a.Major() == b.Major()
}
func registerDailyUpgrader(upgrader breakingUpgrader) {
dailyUpgraders = append(dailyUpgraders, upgrader)
sort.Slice(dailyUpgraders, func(i, j int) bool {
return dailyUpgraders[i].Version().LessThan(dailyUpgraders[j].Version())
})
}
func registerMainUpgrader(upgrader breakingUpgrader) {
mainUpgraders = append(mainUpgraders, upgrader)
sort.Slice(mainUpgraders, func(i, j int) bool {
return mainUpgraders[i].Version().LessThan(mainUpgraders[j].Version())
})
}