mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-03-09 08:59:30 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
477 lines
11 KiB
Go
477 lines
11 KiB
Go
package semver
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
numbers string = "0123456789"
|
|
alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
|
|
alphanum = alphas + numbers
|
|
)
|
|
|
|
// SpecVersion is the latest fully supported spec version of semver
|
|
var SpecVersion = Version{
|
|
Major: 2,
|
|
Minor: 0,
|
|
Patch: 0,
|
|
}
|
|
|
|
// Version represents a semver compatible version
|
|
type Version struct {
|
|
Major uint64
|
|
Minor uint64
|
|
Patch uint64
|
|
Pre []PRVersion
|
|
Build []string //No Precedence
|
|
}
|
|
|
|
// Version to string
|
|
func (v Version) String() string {
|
|
b := make([]byte, 0, 5)
|
|
b = strconv.AppendUint(b, v.Major, 10)
|
|
b = append(b, '.')
|
|
b = strconv.AppendUint(b, v.Minor, 10)
|
|
b = append(b, '.')
|
|
b = strconv.AppendUint(b, v.Patch, 10)
|
|
|
|
if len(v.Pre) > 0 {
|
|
b = append(b, '-')
|
|
b = append(b, v.Pre[0].String()...)
|
|
|
|
for _, pre := range v.Pre[1:] {
|
|
b = append(b, '.')
|
|
b = append(b, pre.String()...)
|
|
}
|
|
}
|
|
|
|
if len(v.Build) > 0 {
|
|
b = append(b, '+')
|
|
b = append(b, v.Build[0]...)
|
|
|
|
for _, build := range v.Build[1:] {
|
|
b = append(b, '.')
|
|
b = append(b, build...)
|
|
}
|
|
}
|
|
|
|
return string(b)
|
|
}
|
|
|
|
// FinalizeVersion discards prerelease and build number and only returns
|
|
// major, minor and patch number.
|
|
func (v Version) FinalizeVersion() string {
|
|
b := make([]byte, 0, 5)
|
|
b = strconv.AppendUint(b, v.Major, 10)
|
|
b = append(b, '.')
|
|
b = strconv.AppendUint(b, v.Minor, 10)
|
|
b = append(b, '.')
|
|
b = strconv.AppendUint(b, v.Patch, 10)
|
|
return string(b)
|
|
}
|
|
|
|
// Equals checks if v is equal to o.
|
|
func (v Version) Equals(o Version) bool {
|
|
return (v.Compare(o) == 0)
|
|
}
|
|
|
|
// EQ checks if v is equal to o.
|
|
func (v Version) EQ(o Version) bool {
|
|
return (v.Compare(o) == 0)
|
|
}
|
|
|
|
// NE checks if v is not equal to o.
|
|
func (v Version) NE(o Version) bool {
|
|
return (v.Compare(o) != 0)
|
|
}
|
|
|
|
// GT checks if v is greater than o.
|
|
func (v Version) GT(o Version) bool {
|
|
return (v.Compare(o) == 1)
|
|
}
|
|
|
|
// GTE checks if v is greater than or equal to o.
|
|
func (v Version) GTE(o Version) bool {
|
|
return (v.Compare(o) >= 0)
|
|
}
|
|
|
|
// GE checks if v is greater than or equal to o.
|
|
func (v Version) GE(o Version) bool {
|
|
return (v.Compare(o) >= 0)
|
|
}
|
|
|
|
// LT checks if v is less than o.
|
|
func (v Version) LT(o Version) bool {
|
|
return (v.Compare(o) == -1)
|
|
}
|
|
|
|
// LTE checks if v is less than or equal to o.
|
|
func (v Version) LTE(o Version) bool {
|
|
return (v.Compare(o) <= 0)
|
|
}
|
|
|
|
// LE checks if v is less than or equal to o.
|
|
func (v Version) LE(o Version) bool {
|
|
return (v.Compare(o) <= 0)
|
|
}
|
|
|
|
// Compare compares Versions v to o:
|
|
// -1 == v is less than o
|
|
// 0 == v is equal to o
|
|
// 1 == v is greater than o
|
|
func (v Version) Compare(o Version) int {
|
|
if v.Major != o.Major {
|
|
if v.Major > o.Major {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
if v.Minor != o.Minor {
|
|
if v.Minor > o.Minor {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
if v.Patch != o.Patch {
|
|
if v.Patch > o.Patch {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Quick comparison if a version has no prerelease versions
|
|
if len(v.Pre) == 0 && len(o.Pre) == 0 {
|
|
return 0
|
|
} else if len(v.Pre) == 0 && len(o.Pre) > 0 {
|
|
return 1
|
|
} else if len(v.Pre) > 0 && len(o.Pre) == 0 {
|
|
return -1
|
|
}
|
|
|
|
i := 0
|
|
for ; i < len(v.Pre) && i < len(o.Pre); i++ {
|
|
if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
|
|
continue
|
|
} else if comp == 1 {
|
|
return 1
|
|
} else {
|
|
return -1
|
|
}
|
|
}
|
|
|
|
// If all pr versions are the equal but one has further prversion, this one greater
|
|
if i == len(v.Pre) && i == len(o.Pre) {
|
|
return 0
|
|
} else if i == len(v.Pre) && i < len(o.Pre) {
|
|
return -1
|
|
} else {
|
|
return 1
|
|
}
|
|
|
|
}
|
|
|
|
// IncrementPatch increments the patch version
|
|
func (v *Version) IncrementPatch() error {
|
|
v.Patch++
|
|
return nil
|
|
}
|
|
|
|
// IncrementMinor increments the minor version
|
|
func (v *Version) IncrementMinor() error {
|
|
v.Minor++
|
|
v.Patch = 0
|
|
return nil
|
|
}
|
|
|
|
// IncrementMajor increments the major version
|
|
func (v *Version) IncrementMajor() error {
|
|
v.Major++
|
|
v.Minor = 0
|
|
v.Patch = 0
|
|
return nil
|
|
}
|
|
|
|
// Validate validates v and returns error in case
|
|
func (v Version) Validate() error {
|
|
// Major, Minor, Patch already validated using uint64
|
|
|
|
for _, pre := range v.Pre {
|
|
if !pre.IsNum { //Numeric prerelease versions already uint64
|
|
if len(pre.VersionStr) == 0 {
|
|
return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
|
|
}
|
|
if !containsOnly(pre.VersionStr, alphanum) {
|
|
return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, build := range v.Build {
|
|
if len(build) == 0 {
|
|
return fmt.Errorf("Build meta data can not be empty %q", build)
|
|
}
|
|
if !containsOnly(build, alphanum) {
|
|
return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
|
|
func New(s string) (*Version, error) {
|
|
v, err := Parse(s)
|
|
vp := &v
|
|
return vp, err
|
|
}
|
|
|
|
// Make is an alias for Parse, parses version string and returns a validated Version or error
|
|
func Make(s string) (Version, error) {
|
|
return Parse(s)
|
|
}
|
|
|
|
// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
|
|
// specs to be parsed by this library. It does so by normalizing versions before passing them to
|
|
// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions
|
|
// with only major and minor components specified, and removes leading 0s.
|
|
func ParseTolerant(s string) (Version, error) {
|
|
s = strings.TrimSpace(s)
|
|
s = strings.TrimPrefix(s, "v")
|
|
|
|
// Split into major.minor.(patch+pr+meta)
|
|
parts := strings.SplitN(s, ".", 3)
|
|
// Remove leading zeros.
|
|
for i, p := range parts {
|
|
if len(p) > 1 {
|
|
p = strings.TrimLeft(p, "0")
|
|
if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") {
|
|
p = "0" + p
|
|
}
|
|
parts[i] = p
|
|
}
|
|
}
|
|
// Fill up shortened versions.
|
|
if len(parts) < 3 {
|
|
if strings.ContainsAny(parts[len(parts)-1], "+-") {
|
|
return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
|
|
}
|
|
for len(parts) < 3 {
|
|
parts = append(parts, "0")
|
|
}
|
|
}
|
|
s = strings.Join(parts, ".")
|
|
|
|
return Parse(s)
|
|
}
|
|
|
|
// Parse parses version string and returns a validated Version or error
|
|
func Parse(s string) (Version, error) {
|
|
if len(s) == 0 {
|
|
return Version{}, errors.New("Version string empty")
|
|
}
|
|
|
|
// Split into major.minor.(patch+pr+meta)
|
|
parts := strings.SplitN(s, ".", 3)
|
|
if len(parts) != 3 {
|
|
return Version{}, errors.New("No Major.Minor.Patch elements found")
|
|
}
|
|
|
|
// Major
|
|
if !containsOnly(parts[0], numbers) {
|
|
return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
|
|
}
|
|
if hasLeadingZeroes(parts[0]) {
|
|
return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
|
|
}
|
|
major, err := strconv.ParseUint(parts[0], 10, 64)
|
|
if err != nil {
|
|
return Version{}, err
|
|
}
|
|
|
|
// Minor
|
|
if !containsOnly(parts[1], numbers) {
|
|
return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
|
|
}
|
|
if hasLeadingZeroes(parts[1]) {
|
|
return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
|
|
}
|
|
minor, err := strconv.ParseUint(parts[1], 10, 64)
|
|
if err != nil {
|
|
return Version{}, err
|
|
}
|
|
|
|
v := Version{}
|
|
v.Major = major
|
|
v.Minor = minor
|
|
|
|
var build, prerelease []string
|
|
patchStr := parts[2]
|
|
|
|
if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
|
|
build = strings.Split(patchStr[buildIndex+1:], ".")
|
|
patchStr = patchStr[:buildIndex]
|
|
}
|
|
|
|
if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
|
|
prerelease = strings.Split(patchStr[preIndex+1:], ".")
|
|
patchStr = patchStr[:preIndex]
|
|
}
|
|
|
|
if !containsOnly(patchStr, numbers) {
|
|
return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
|
|
}
|
|
if hasLeadingZeroes(patchStr) {
|
|
return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
|
|
}
|
|
patch, err := strconv.ParseUint(patchStr, 10, 64)
|
|
if err != nil {
|
|
return Version{}, err
|
|
}
|
|
|
|
v.Patch = patch
|
|
|
|
// Prerelease
|
|
for _, prstr := range prerelease {
|
|
parsedPR, err := NewPRVersion(prstr)
|
|
if err != nil {
|
|
return Version{}, err
|
|
}
|
|
v.Pre = append(v.Pre, parsedPR)
|
|
}
|
|
|
|
// Build meta data
|
|
for _, str := range build {
|
|
if len(str) == 0 {
|
|
return Version{}, errors.New("Build meta data is empty")
|
|
}
|
|
if !containsOnly(str, alphanum) {
|
|
return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
|
|
}
|
|
v.Build = append(v.Build, str)
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// MustParse is like Parse but panics if the version cannot be parsed.
|
|
func MustParse(s string) Version {
|
|
v, err := Parse(s)
|
|
if err != nil {
|
|
panic(`semver: Parse(` + s + `): ` + err.Error())
|
|
}
|
|
return v
|
|
}
|
|
|
|
// PRVersion represents a PreRelease Version
|
|
type PRVersion struct {
|
|
VersionStr string
|
|
VersionNum uint64
|
|
IsNum bool
|
|
}
|
|
|
|
// NewPRVersion creates a new valid prerelease version
|
|
func NewPRVersion(s string) (PRVersion, error) {
|
|
if len(s) == 0 {
|
|
return PRVersion{}, errors.New("Prerelease is empty")
|
|
}
|
|
v := PRVersion{}
|
|
if containsOnly(s, numbers) {
|
|
if hasLeadingZeroes(s) {
|
|
return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
|
|
}
|
|
num, err := strconv.ParseUint(s, 10, 64)
|
|
|
|
// Might never be hit, but just in case
|
|
if err != nil {
|
|
return PRVersion{}, err
|
|
}
|
|
v.VersionNum = num
|
|
v.IsNum = true
|
|
} else if containsOnly(s, alphanum) {
|
|
v.VersionStr = s
|
|
v.IsNum = false
|
|
} else {
|
|
return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// IsNumeric checks if prerelease-version is numeric
|
|
func (v PRVersion) IsNumeric() bool {
|
|
return v.IsNum
|
|
}
|
|
|
|
// Compare compares two PreRelease Versions v and o:
|
|
// -1 == v is less than o
|
|
// 0 == v is equal to o
|
|
// 1 == v is greater than o
|
|
func (v PRVersion) Compare(o PRVersion) int {
|
|
if v.IsNum && !o.IsNum {
|
|
return -1
|
|
} else if !v.IsNum && o.IsNum {
|
|
return 1
|
|
} else if v.IsNum && o.IsNum {
|
|
if v.VersionNum == o.VersionNum {
|
|
return 0
|
|
} else if v.VersionNum > o.VersionNum {
|
|
return 1
|
|
} else {
|
|
return -1
|
|
}
|
|
} else { // both are Alphas
|
|
if v.VersionStr == o.VersionStr {
|
|
return 0
|
|
} else if v.VersionStr > o.VersionStr {
|
|
return 1
|
|
} else {
|
|
return -1
|
|
}
|
|
}
|
|
}
|
|
|
|
// PreRelease version to string
|
|
func (v PRVersion) String() string {
|
|
if v.IsNum {
|
|
return strconv.FormatUint(v.VersionNum, 10)
|
|
}
|
|
return v.VersionStr
|
|
}
|
|
|
|
func containsOnly(s string, set string) bool {
|
|
return strings.IndexFunc(s, func(r rune) bool {
|
|
return !strings.ContainsRune(set, r)
|
|
}) == -1
|
|
}
|
|
|
|
func hasLeadingZeroes(s string) bool {
|
|
return len(s) > 1 && s[0] == '0'
|
|
}
|
|
|
|
// NewBuildVersion creates a new valid build version
|
|
func NewBuildVersion(s string) (string, error) {
|
|
if len(s) == 0 {
|
|
return "", errors.New("Buildversion is empty")
|
|
}
|
|
if !containsOnly(s, alphanum) {
|
|
return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// FinalizeVersion returns the major, minor and patch number only and discards
|
|
// prerelease and build number.
|
|
func FinalizeVersion(s string) (string, error) {
|
|
v, err := Parse(s)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
v.Pre = nil
|
|
v.Build = nil
|
|
|
|
finalVer := v.String()
|
|
return finalVer, nil
|
|
}
|