-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
7 changed files
with
588 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package version | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrInComparableVersion = errors.New("incomparable: dev 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") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
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 { | ||
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 { | ||
isNotUpgradable, err := u.currentVersion.IsOlder(u.minUpgradableVersion) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if isNotUpgradable { | ||
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 | ||
} | ||
|
||
// General cases | ||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
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, err := NewHarvesterVersion(tc.currentVersion) | ||
assert.Nil(t, err, tc.name) | ||
|
||
uv, err := NewHarvesterVersion(tc.upgradeVersion) | ||
assert.Nil(t, err, tc.name) | ||
|
||
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}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
|
||
cv, err := NewHarvesterVersion(tc.currentVersion) | ||
assert.Nil(t, err, tc.name) | ||
|
||
mv, err := NewHarvesterVersion(tc.minUpgradableVersion) | ||
assert.Nil(t, err, tc.name) | ||
|
||
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}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
|
||
cv, err := NewHarvesterVersion(tc.currentVersion) | ||
assert.Nil(t, err, tc.name) | ||
|
||
uv, err := NewHarvesterVersion(tc.upgradeVersion) | ||
assert.Nil(t, err, tc.name) | ||
|
||
mv, err := NewHarvesterVersion(tc.minUpgradableVersion) | ||
assert.Nil(t, err, tc.name) | ||
|
||
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) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package version | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
semverv3 "github.com/Masterminds/semver/v3" | ||
) | ||
|
||
type HarvesterVersion struct { | ||
version *semverv3.Version | ||
rawVersion string | ||
isDev bool | ||
isPrerelease bool | ||
} | ||
|
||
func NewHarvesterVersion(versionStr string) (*HarvesterVersion, error) { | ||
var ( | ||
v *semverv3.Version | ||
isDev, isReleaseCandidate bool | ||
) | ||
|
||
trimmedVersionStr := strings.Trim(versionStr, "v") | ||
|
||
v, err := semverv3.StrictNewVersion(trimmedVersionStr) | ||
if err != nil { | ||
isDev = true | ||
} | ||
|
||
if !isDev && v.Prerelease() != "" { | ||
isReleaseCandidate = true | ||
} | ||
|
||
return &HarvesterVersion{ | ||
version: v, | ||
rawVersion: versionStr, | ||
isDev: isDev, | ||
isPrerelease: isReleaseCandidate, | ||
}, nil | ||
} | ||
|
||
// GetStableVersion returns the Version object without the suffix. For example, given "v1.2.2-rc1", it returns "v1.2.2" | ||
func (v *HarvesterVersion) GetStableVersion() *HarvesterVersion { | ||
sv := semverv3.New(v.version.Major(), v.version.Minor(), v.version.Patch(), "", "") | ||
return &HarvesterVersion{ | ||
version: sv, | ||
rawVersion: sv.Original(), | ||
isDev: false, | ||
isPrerelease: false, | ||
} | ||
} | ||
|
||
func (v *HarvesterVersion) IsNewer(version *HarvesterVersion) (bool, error) { | ||
if v.isDev || version.isDev { | ||
return false, ErrInComparableVersion | ||
} | ||
|
||
var constraint string | ||
|
||
if v.isPrerelease || version.isPrerelease { | ||
constraint = fmt.Sprintf("> %s-z", version.rawVersion) | ||
} else { | ||
constraint = fmt.Sprintf("> %s", version.rawVersion) | ||
} | ||
|
||
c, err := semverv3.NewConstraint(constraint) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return c.Check(v.version), nil | ||
} | ||
|
||
func (v *HarvesterVersion) IsEqual(version *HarvesterVersion) (bool, error) { | ||
if v.isDev || version.isDev { | ||
if v.rawVersion == version.rawVersion { | ||
return true, nil | ||
} | ||
return false, ErrInComparableVersion | ||
} | ||
|
||
constraint := version.rawVersion | ||
|
||
c, err := semverv3.NewConstraint(constraint) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return c.Check(v.version), nil | ||
} | ||
|
||
func (v *HarvesterVersion) IsOlder(version *HarvesterVersion) (bool, error) { | ||
if v.isDev || version.isDev { | ||
return false, ErrInComparableVersion | ||
} | ||
|
||
isNewer, err := v.IsNewer(version) | ||
if err != nil { | ||
return false, err | ||
} | ||
isEqual, err := v.IsEqual(version) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return !(isNewer || isEqual), nil | ||
} | ||
|
||
func (v *HarvesterVersion) String() string { | ||
if v.isDev { | ||
return v.rawVersion | ||
} | ||
|
||
return fmt.Sprintf("v%s", v.version.String()) | ||
} |
Oops, something went wrong.