Skip to content

Commit

Permalink
feat: add version library
Browse files Browse the repository at this point in the history
The Harvester Version library augments the widelyused SemVer package,
adding Harvester-specific logic, especially for Harvester upgrades. It
is meant to be consumed by the Harvester controller and command-line
helper tool.

Signed-off-by: Zespre Chang <[email protected]>
  • Loading branch information
starbops committed Jun 20, 2024
1 parent ed59246 commit eb4251a
Show file tree
Hide file tree
Showing 7 changed files with 641 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
)

require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
14 changes: 14 additions & 0 deletions version/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package version

import "errors"

var (
ErrInvalidVersion = errors.New("invalid version string")
ErrIncomparableVersion = errors.New("incomparable: dev version")

ErrInvalidHarvesterVersion = errors.New("invalid harvester version")
ErrMinUpgradeRequirement = errors.New("current version does not meet minimum upgrade requirement")
ErrDowngrade = errors.New("downgrading is prohibited")
ErrDevUpgrade = errors.New("upgrading from dev versions to non-dev versions is prohibited")
ErrPrereleaseCrossVersionUpgrade = errors.New("cross-version upgrades from/to any prerelease version are prohibited")
)
96 changes: 96 additions & 0 deletions version/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package version

type HarvesterUpgradeVersion struct {
currentVersion *HarvesterVersion
upgradeVersion *HarvesterVersion
minUpgradableVersion *HarvesterVersion
}

func NewHarvesterUpgradeVersion(cv, uv, mv *HarvesterVersion) *HarvesterUpgradeVersion {
return &HarvesterUpgradeVersion{
currentVersion: cv,
upgradeVersion: uv,
minUpgradableVersion: mv,
}
}

func (u *HarvesterUpgradeVersion) IsUpgrade() error {
if u.currentVersion == nil || u.upgradeVersion == nil {
return ErrInvalidHarvesterVersion
}

isDowngrade, err := u.currentVersion.IsNewer(u.upgradeVersion)
if err != nil {
return err
}

if isDowngrade {
return ErrDowngrade
}

return nil
}

// IsUpgradable checks that whether the current version satisfies the minimum upgrade requirement.
func (u *HarvesterUpgradeVersion) IsUpgradable() error {
// If minUpgradableVersion is nil means there's no restriction.
if u.minUpgradableVersion == nil {
return nil
}

isOlderThanMinUpgradableVersion, err := u.currentVersion.IsOlder(u.minUpgradableVersion)
if err != nil {
return err
}

if isOlderThanMinUpgradableVersion {
return ErrMinUpgradeRequirement
}

return nil
}

func (u *HarvesterUpgradeVersion) CheckUpgradeEligibility(strictMode bool) error {
// Upgrading to dev versions is always allowed
if u.upgradeVersion.isDev {
return nil
}

// Upgrading from dev versions is restricted if strict mode is enabled
if u.currentVersion.isDev {
if strictMode {
return ErrDevUpgrade
}
return nil
}

// Same-version upgrade is always allowed
isSameVersion, err := u.currentVersion.IsEqual(u.upgradeVersion)
if err != nil {
return err
}
if isSameVersion {
return nil
}

// Check if it's effectively a downgrade
if err := u.IsUpgrade(); err != nil {
return err
}

// Check the minimum upgradable version
if err := u.IsUpgradable(); err != nil {
return err
}

// Check if it's a prerelease cross-version upgrade
if u.currentVersion.isPrerelease {
currentStableVersion := u.currentVersion.GetStableVersion()
upgradeStableVersion := u.upgradeVersion.GetStableVersion()
if isSameStableVersion, _ := currentStableVersion.IsEqual(upgradeStableVersion); !isSameStableVersion {
return ErrPrereleaseCrossVersionUpgrade
}
}

return nil
}
147 changes: 147 additions & 0 deletions version/upgrade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package version

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestHarvesterUpgradeVersion_IsUpgrade(t *testing.T) {
var testCases = []struct {
name string
currentVersion string
upgradeVersion string
expectedErr error
}{
{"upgrade from a newer release version to an older one", "v1.3.0", "v1.2.1", ErrDowngrade},
{"upgrade from an older release version to a newer one", "v1.2.2", "v1.3.0", nil},

{"upgrade from a newer prerelease version to an older one", "v1.3.0-rc1", "v1.2.1-rc1", ErrDowngrade},
{"upgrade from a newer prerelease version to an older one", "v1.2.1-rc2", "v1.2.1-rc1", ErrDowngrade},
{"upgrade from an older prerelease version to a newer one", "v1.2.1-rc1", "v1.3.0-rc1", nil},
{"upgrade from an older prerelease version to a newer one", "v1.2.1-rc1", "v1.2.1-rc2", nil},

{"upgrade among two dev versions", "11223344", "aabbccdd", ErrIncomparableVersion},

{"upgrade from a newer prerelease version to an older release version", "v1.2.2-rc2", "v1.2.1", ErrDowngrade},
{"upgrade from an older prerelease version to a newer release version", "v1.2.1-rc2", "v1.2.2", nil},
{"upgrade from a newer release version to an older prerelease version", "v1.2.2", "v1.2.2-rc2", ErrDowngrade},
{"upgrade from a newer release version to an older prerelease version", "v1.2.2", "v1.2.1-rc2", ErrDowngrade},
{"upgrade from an older release version to a newer prerelease version", "v1.2.1", "v1.2.2-rc1", nil},

{"upgrade from a release version to a dev version", "v1.2.1", "aabbccdd", ErrIncomparableVersion},
{"upgrade from a dev version to a release version", "aabbccdd", "v1.2.1", ErrIncomparableVersion},
{"upgrade from a prerelease version to a dev version", "v1.2.2-rc1", "aabbccdd", ErrIncomparableVersion},
{"upgrade from a dev version to a prerelease version", "aabbccdd", "v1.2.2-rc1", ErrIncomparableVersion},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cv, _ := NewHarvesterVersion(tc.currentVersion)
uv, _ := NewHarvesterVersion(tc.upgradeVersion)

huv := NewHarvesterUpgradeVersion(cv, uv, nil)
actualErr := huv.IsUpgrade()
if tc.expectedErr != nil {
assert.Equal(t, tc.expectedErr, actualErr, tc.name)
} else {
assert.Nil(t, actualErr, tc.name)
}
})
}
}

func TestHarvesterUpgradeVersion_IsUpgradable(t *testing.T) {
var testCases = []struct {
name string
currentVersion string
minUpgradableVersion string
expectedErr error
}{
{"upgrade from a release version above the minimal requirement", "v1.2.1", "v1.2.0", nil},
{"upgrade from a release version lower than the minimal requirement", "v1.2.1", "v1.2.2", ErrMinUpgradeRequirement},
{"upgrade from the exact same release version of the minimal requirement", "v1.2.1", "v1.2.1", nil},

{"upgrade from a prerelease version lower than the minimal requirement", "v1.2.1-rc1", "v1.2.1", ErrMinUpgradeRequirement},
{"upgrade from a prerelease version above the minimal requirement (rc minUpgradableVersion)", "v1.2.1-rc2", "v1.2.1-rc1", nil},
{"upgrade from a prerelease version lower than the minimal requirement (rc minUpgradableVersion)", "v1.2.1-rc1", "v1.2.1-rc2", ErrMinUpgradeRequirement},
{"upgrade from a prerelease version lower than the minimal requirement (rc minUpgradableVersion)", "v1.2.0-rc1", "v1.2.1-rc2", ErrMinUpgradeRequirement},
{"upgrade from the exact same prerelease version of the minimal requirement (rc minUpgradableVersion)", "v1.2.1-rc1", "v1.2.1-rc1", nil},

{"upgrade from dev versions", "11223344", "v1.2.1", ErrIncomparableVersion},
{"upgrade from dev versions", "aabbccdd", "v1.2.1", ErrIncomparableVersion},

{"upgrade from a release version without any minimum requirement specified", "v1.2.1", "", nil},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cv, _ := NewHarvesterVersion(tc.currentVersion)
mv, _ := NewHarvesterVersion(tc.minUpgradableVersion)

huv := NewHarvesterUpgradeVersion(cv, nil, mv)
actualErr := huv.IsUpgradable()
if tc.expectedErr != nil {
assert.Equal(t, tc.expectedErr, actualErr, tc.name)
} else {
assert.Nil(t, actualErr, tc.name)
}
})
}
}

func TestHarvesterUpgradeVersion_CheckUpgradeEligibility(t *testing.T) {
var testCases = []struct {
name string
currentVersion string
upgradeVersion string
minUpgradableVersion string
strictMode bool
expectedErr error
}{
{"upgrade to same release version", "v1.2.1", "v1.2.1", "v1.1.2", true, nil},
{"upgrade from an old release version same as the minimal requirement of a new release version", "v1.1.2", "v1.2.1", "v1.1.2", true, nil},
{"upgrade from an old release version above the minimal requirement of a new release version", "v1.2.0", "v1.2.1", "v1.1.2", true, nil},
{"upgrade from an old release version below the minimal requirement of a new release version", "v1.1.1", "v1.2.1", "v1.1.2", true, ErrMinUpgradeRequirement},
{"upgrade from a new release version above the minimal requirement of an old release version", "v1.2.1", "v1.2.0", "v1.1.2", true, ErrDowngrade},
{"upgrade from an old release version same the minimal requirement of a new prerelease version", "v1.1.2", "v1.2.1-rc1", "v1.1.2", true, nil},
{"upgrade from an old release version above the minimal requirement of a new prerelease version", "v1.2.0", "v1.2.1-rc1", "v1.1.2", true, nil},
{"upgrade from an old release version below the minimal requirement of a new prerelease version", "v1.1.1", "v1.2.1-rc1", "v1.1.2", true, ErrMinUpgradeRequirement},
{"upgrade from a new release version above the minimal requirement of an old prerelease version", "v1.2.1", "v1.2.1-rc1", "v1.1.2", true, ErrDowngrade},
{"upgrade from a release version to a dev version", "v1.2.1", "v1.2-ab12cd34", "", true, nil},

{"upgrade to same prerelease version", "v1.2.2-rc1", "v1.2.2-rc1", "v1.2.1", true, nil},
{"upgrade from an old prerelease version below the minimal requirement of a new release version", "v1.1.2-rc1", "v1.2.1", "v1.1.2", true, ErrMinUpgradeRequirement},
{"upgrade from an old prerelease version above the minimal requirement of a new release version", "v1.2.0-rc1", "v1.2.1", "v1.1.2", true, ErrPrereleaseCrossVersionUpgrade},
{"upgrade from an old prerelease version below the minimal requirement of a new release version", "v1.1.1-rc1", "v1.2.1", "v1.1.2", true, ErrMinUpgradeRequirement},
{"upgrade from an old prerelease version above the minimal requirement of a new prerelease version", "v1.2.2-rc1", "v1.2.2-rc2", "v1.2.1", true, nil},
{"upgrade from an old prerelease version below the minimal requirement of a new prerelease version", "v1.2.1-rc1", "v1.2.2-rc2", "v1.2.1", true, ErrMinUpgradeRequirement},
{"upgrade from an old prerelease version above the minimal requirement of a new prerelease version", "v1.2.0-rc1", "v1.2.1-rc2", "v1.1.2", true, ErrPrereleaseCrossVersionUpgrade},
{"upgrade from a prerelease version to a dev version", "v1.2.2-rc1", "v1.2-ab12cd34", "", true, nil},

{"upgrade to same dev version", "v1.2-ab12cd34", "v1.2-ab12cd34", "", true, nil},
{"upgrade among two dev versions", "v1.2-ab12cd34", "v1.2-1234567", "", true, nil},
{"upgrade from a dev version to a release version (strict mode)", "v1.2-ab12cd34", "v1.2.1", "v1.1.2", true, ErrDevUpgrade},
{"upgrade from a dev version to a release version (loose mode)", "v1.2-ab12cd34", "v1.2.1", "v1.1.2", false, nil},
{"upgrade from a dev version to a prerelease version (strict mode)", "v1.2-ab12cd34", "v1.2.2-rc1", "v1.2.1", true, ErrDevUpgrade},
{"upgrade from a dev version to a prerelease version (loose mode)", "v1.2-ab12cd34", "v1.2.2-rc1", "v1.2.1", false, nil},

{"upgrade from an old release version to a new release version without any minimum requirement specified", "v1.2.1", "v1.3.0", "", true, nil},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cv, _ := NewHarvesterVersion(tc.currentVersion)
uv, _ := NewHarvesterVersion(tc.upgradeVersion)
mv, _ := NewHarvesterVersion(tc.minUpgradableVersion)

huv := NewHarvesterUpgradeVersion(cv, uv, mv)
actualErr := huv.CheckUpgradeEligibility(tc.strictMode)
if tc.expectedErr != nil {
assert.Equal(t, tc.expectedErr, actualErr, tc.name)
} else {
assert.Nil(t, actualErr, tc.name)
}
})
}
}
Loading

0 comments on commit eb4251a

Please sign in to comment.