2023-01-31 05:45:31 -05:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
Package compatibility offers helper functions for comparing and filtering versions.
|
|
|
|
*/
|
|
|
|
package compatibility
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"golang.org/x/mod/semver"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrMajorMismatch signals that the major version of two compared versions don't match.
|
|
|
|
ErrMajorMismatch = errors.New("missmatching major version")
|
|
|
|
// ErrMinorDrift signals that the minor version of two compared versions are further apart than one.
|
2023-02-15 04:18:49 -05:00
|
|
|
ErrMinorDrift = errors.New("version difference larger than one minor version")
|
2023-01-31 05:45:31 -05:00
|
|
|
// ErrSemVer signals that a given version does not adhere to the Semver syntax.
|
2023-01-31 06:12:19 -05:00
|
|
|
ErrSemVer = errors.New("invalid semantic version")
|
2023-02-15 04:18:49 -05:00
|
|
|
// ErrOutdatedCLI signals that the configured version is newer than the CLI. This is not allowed.
|
|
|
|
ErrOutdatedCLI = errors.New("target version newer than cli version")
|
2023-01-31 05:45:31 -05:00
|
|
|
)
|
|
|
|
|
2023-02-28 04:23:09 -05:00
|
|
|
// InvalidUpgradeError present an invalid upgrade. It wraps the source and destination version for improved debuggability.
|
|
|
|
type InvalidUpgradeError struct {
|
|
|
|
from string
|
|
|
|
to string
|
|
|
|
innerErr error
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInvalidUpgradeError returns a new InvalidUpgradeError.
|
|
|
|
func NewInvalidUpgradeError(from string, to string, innerErr error) *InvalidUpgradeError {
|
|
|
|
return &InvalidUpgradeError{from: from, to: to, innerErr: innerErr}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unwrap returns the inner error, which is nil in this case.
|
2023-03-03 03:38:23 -05:00
|
|
|
func (e InvalidUpgradeError) Unwrap() error {
|
2023-02-28 04:23:09 -05:00
|
|
|
return e.innerErr
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error returns the String representation of this error.
|
2023-03-03 03:38:23 -05:00
|
|
|
func (e InvalidUpgradeError) Error() string {
|
2023-02-28 04:23:09 -05:00
|
|
|
return fmt.Sprintf("upgrading from %s to %s is not a valid upgrade: %s", e.from, e.to, e.innerErr)
|
|
|
|
}
|
|
|
|
|
2023-01-31 06:12:19 -05:00
|
|
|
// EnsurePrefixV returns the input string prefixed with the letter "v", if the string doesn't already start with that letter.
|
|
|
|
func EnsurePrefixV(str string) string {
|
2023-01-31 05:45:31 -05:00
|
|
|
if strings.HasPrefix(str, "v") {
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
return "v" + str
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsValidUpgrade checks that a and b adhere to a version drift of 1 and b is greater than a.
|
|
|
|
func IsValidUpgrade(a, b string) error {
|
2023-01-31 06:12:19 -05:00
|
|
|
a = EnsurePrefixV(a)
|
|
|
|
b = EnsurePrefixV(b)
|
2023-01-31 05:45:31 -05:00
|
|
|
|
|
|
|
if !semver.IsValid(a) || !semver.IsValid(b) {
|
2023-02-28 04:23:09 -05:00
|
|
|
return NewInvalidUpgradeError(a, b, ErrSemVer)
|
2023-01-31 05:45:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if semver.Compare(a, b) >= 0 {
|
2023-02-28 04:23:09 -05:00
|
|
|
return NewInvalidUpgradeError(a, b, errors.New("current version newer than or equal to new version"))
|
2023-01-31 05:45:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
aMajor, aMinor, err := parseCanonicalSemver(a)
|
|
|
|
if err != nil {
|
2023-02-28 04:23:09 -05:00
|
|
|
return NewInvalidUpgradeError(a, b, err)
|
2023-01-31 05:45:31 -05:00
|
|
|
}
|
|
|
|
bMajor, bMinor, err := parseCanonicalSemver(b)
|
|
|
|
if err != nil {
|
2023-02-28 04:23:09 -05:00
|
|
|
return NewInvalidUpgradeError(a, b, err)
|
2023-01-31 05:45:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if aMajor != bMajor {
|
2023-02-28 04:23:09 -05:00
|
|
|
return NewInvalidUpgradeError(a, b, ErrMajorMismatch)
|
2023-01-31 05:45:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if bMinor-aMinor > 1 {
|
2023-02-28 04:23:09 -05:00
|
|
|
return NewInvalidUpgradeError(a, b, ErrMinorDrift)
|
2023-01-31 05:45:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// BinaryWith tests that this binarie's version is greater or equal than some target version, but not further away than one minor version.
|
2023-03-01 05:55:12 -05:00
|
|
|
func BinaryWith(binaryVersion, target string) error {
|
|
|
|
binaryVersion = EnsurePrefixV(binaryVersion)
|
2023-01-31 06:12:19 -05:00
|
|
|
target = EnsurePrefixV(target)
|
2023-01-31 05:45:31 -05:00
|
|
|
if !semver.IsValid(binaryVersion) || !semver.IsValid(target) {
|
|
|
|
return ErrSemVer
|
|
|
|
}
|
|
|
|
cliMajor, cliMinor, err := parseCanonicalSemver(binaryVersion)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
targetMajor, targetMinor, err := parseCanonicalSemver(target)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Major versions always have to match.
|
|
|
|
if cliMajor != targetMajor {
|
|
|
|
return ErrMajorMismatch
|
|
|
|
}
|
2023-02-15 04:22:54 -05:00
|
|
|
// For images we allow newer patch versions, therefore this only checks the minor version.
|
|
|
|
if semver.Compare(semver.MajorMinor(binaryVersion), semver.MajorMinor(target)) == -1 {
|
2023-02-15 04:18:49 -05:00
|
|
|
return ErrOutdatedCLI
|
2023-01-31 05:45:31 -05:00
|
|
|
}
|
|
|
|
// Abort if minor version drift between CLI and versionA value is greater than 1.
|
|
|
|
if cliMinor-targetMinor > 1 {
|
|
|
|
return ErrMinorDrift
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FilterNewerVersion filters the list of versions to only include versions newer than currentVersion.
|
|
|
|
func FilterNewerVersion(currentVersion string, newVersions []string) []string {
|
2023-01-31 06:12:19 -05:00
|
|
|
currentVersion = EnsurePrefixV(currentVersion)
|
2023-01-31 05:45:31 -05:00
|
|
|
var result []string
|
|
|
|
|
|
|
|
for _, image := range newVersions {
|
2023-01-31 06:12:19 -05:00
|
|
|
image = EnsurePrefixV(image)
|
2023-01-31 05:45:31 -05:00
|
|
|
// check if image is newer than current version
|
|
|
|
if semver.Compare(image, currentVersion) <= 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
result = append(result, image)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// NextMinorVersion returns the next minor version for a given canonical semver.
|
|
|
|
// The returned format is vMAJOR.MINOR.
|
|
|
|
func NextMinorVersion(version string) (string, error) {
|
2023-01-31 06:12:19 -05:00
|
|
|
major, minor, err := parseCanonicalSemver(EnsurePrefixV(version))
|
2023-01-31 05:45:31 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("v%d.%d", major, minor+1), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseCanonicalSemver(version string) (major int, minor int, err error) {
|
2023-01-31 06:12:19 -05:00
|
|
|
version = semver.MajorMinor(version) // ensure version is in canonical form (vX.Y.Z)
|
|
|
|
if version == "" {
|
|
|
|
return 0, 0, fmt.Errorf("invalid semver: '%s'", version)
|
|
|
|
}
|
|
|
|
_, err = fmt.Sscanf(version, "v%d.%d", &major, &minor)
|
2023-01-31 05:45:31 -05:00
|
|
|
if err != nil {
|
|
|
|
return 0, 0, fmt.Errorf("parsing version: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return major, minor, nil
|
|
|
|
}
|