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>
417 lines
9.9 KiB
Go
417 lines
9.9 KiB
Go
package semver
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
type wildcardType int
|
|
|
|
const (
|
|
noneWildcard wildcardType = iota
|
|
majorWildcard wildcardType = 1
|
|
minorWildcard wildcardType = 2
|
|
patchWildcard wildcardType = 3
|
|
)
|
|
|
|
func wildcardTypefromInt(i int) wildcardType {
|
|
switch i {
|
|
case 1:
|
|
return majorWildcard
|
|
case 2:
|
|
return minorWildcard
|
|
case 3:
|
|
return patchWildcard
|
|
default:
|
|
return noneWildcard
|
|
}
|
|
}
|
|
|
|
type comparator func(Version, Version) bool
|
|
|
|
var (
|
|
compEQ comparator = func(v1 Version, v2 Version) bool {
|
|
return v1.Compare(v2) == 0
|
|
}
|
|
compNE = func(v1 Version, v2 Version) bool {
|
|
return v1.Compare(v2) != 0
|
|
}
|
|
compGT = func(v1 Version, v2 Version) bool {
|
|
return v1.Compare(v2) == 1
|
|
}
|
|
compGE = func(v1 Version, v2 Version) bool {
|
|
return v1.Compare(v2) >= 0
|
|
}
|
|
compLT = func(v1 Version, v2 Version) bool {
|
|
return v1.Compare(v2) == -1
|
|
}
|
|
compLE = func(v1 Version, v2 Version) bool {
|
|
return v1.Compare(v2) <= 0
|
|
}
|
|
)
|
|
|
|
type versionRange struct {
|
|
v Version
|
|
c comparator
|
|
}
|
|
|
|
// rangeFunc creates a Range from the given versionRange.
|
|
func (vr *versionRange) rangeFunc() Range {
|
|
return Range(func(v Version) bool {
|
|
return vr.c(v, vr.v)
|
|
})
|
|
}
|
|
|
|
// Range represents a range of versions.
|
|
// A Range can be used to check if a Version satisfies it:
|
|
//
|
|
// range, err := semver.ParseRange(">1.0.0 <2.0.0")
|
|
// range(semver.MustParse("1.1.1") // returns true
|
|
type Range func(Version) bool
|
|
|
|
// OR combines the existing Range with another Range using logical OR.
|
|
func (rf Range) OR(f Range) Range {
|
|
return Range(func(v Version) bool {
|
|
return rf(v) || f(v)
|
|
})
|
|
}
|
|
|
|
// AND combines the existing Range with another Range using logical AND.
|
|
func (rf Range) AND(f Range) Range {
|
|
return Range(func(v Version) bool {
|
|
return rf(v) && f(v)
|
|
})
|
|
}
|
|
|
|
// ParseRange parses a range and returns a Range.
|
|
// If the range could not be parsed an error is returned.
|
|
//
|
|
// Valid ranges are:
|
|
// - "<1.0.0"
|
|
// - "<=1.0.0"
|
|
// - ">1.0.0"
|
|
// - ">=1.0.0"
|
|
// - "1.0.0", "=1.0.0", "==1.0.0"
|
|
// - "!1.0.0", "!=1.0.0"
|
|
//
|
|
// A Range can consist of multiple ranges separated by space:
|
|
// Ranges can be linked by logical AND:
|
|
// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
|
|
// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
|
|
//
|
|
// Ranges can also be linked by logical OR:
|
|
// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
|
|
//
|
|
// AND has a higher precedence than OR. It's not possible to use brackets.
|
|
//
|
|
// Ranges can be combined by both AND and OR
|
|
//
|
|
// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
|
|
func ParseRange(s string) (Range, error) {
|
|
parts := splitAndTrim(s)
|
|
orParts, err := splitORParts(parts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
expandedParts, err := expandWildcardVersion(orParts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var orFn Range
|
|
for _, p := range expandedParts {
|
|
var andFn Range
|
|
for _, ap := range p {
|
|
opStr, vStr, err := splitComparatorVersion(ap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vr, err := buildVersionRange(opStr, vStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
|
|
}
|
|
rf := vr.rangeFunc()
|
|
|
|
// Set function
|
|
if andFn == nil {
|
|
andFn = rf
|
|
} else { // Combine with existing function
|
|
andFn = andFn.AND(rf)
|
|
}
|
|
}
|
|
if orFn == nil {
|
|
orFn = andFn
|
|
} else {
|
|
orFn = orFn.OR(andFn)
|
|
}
|
|
|
|
}
|
|
return orFn, nil
|
|
}
|
|
|
|
// splitORParts splits the already cleaned parts by '||'.
|
|
// Checks for invalid positions of the operator and returns an
|
|
// error if found.
|
|
func splitORParts(parts []string) ([][]string, error) {
|
|
var ORparts [][]string
|
|
last := 0
|
|
for i, p := range parts {
|
|
if p == "||" {
|
|
if i == 0 {
|
|
return nil, fmt.Errorf("First element in range is '||'")
|
|
}
|
|
ORparts = append(ORparts, parts[last:i])
|
|
last = i + 1
|
|
}
|
|
}
|
|
if last == len(parts) {
|
|
return nil, fmt.Errorf("Last element in range is '||'")
|
|
}
|
|
ORparts = append(ORparts, parts[last:])
|
|
return ORparts, nil
|
|
}
|
|
|
|
// buildVersionRange takes a slice of 2: operator and version
|
|
// and builds a versionRange, otherwise an error.
|
|
func buildVersionRange(opStr, vStr string) (*versionRange, error) {
|
|
c := parseComparator(opStr)
|
|
if c == nil {
|
|
return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
|
|
}
|
|
v, err := Parse(vStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
|
|
}
|
|
|
|
return &versionRange{
|
|
v: v,
|
|
c: c,
|
|
}, nil
|
|
|
|
}
|
|
|
|
// inArray checks if a byte is contained in an array of bytes
|
|
func inArray(s byte, list []byte) bool {
|
|
for _, el := range list {
|
|
if el == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// splitAndTrim splits a range string by spaces and cleans whitespaces
|
|
func splitAndTrim(s string) (result []string) {
|
|
last := 0
|
|
var lastChar byte
|
|
excludeFromSplit := []byte{'>', '<', '='}
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
|
|
if last < i-1 {
|
|
result = append(result, s[last:i])
|
|
}
|
|
last = i + 1
|
|
} else if s[i] != ' ' {
|
|
lastChar = s[i]
|
|
}
|
|
}
|
|
if last < len(s)-1 {
|
|
result = append(result, s[last:])
|
|
}
|
|
|
|
for i, v := range result {
|
|
result[i] = strings.Replace(v, " ", "", -1)
|
|
}
|
|
|
|
// parts := strings.Split(s, " ")
|
|
// for _, x := range parts {
|
|
// if s := strings.TrimSpace(x); len(s) != 0 {
|
|
// result = append(result, s)
|
|
// }
|
|
// }
|
|
return
|
|
}
|
|
|
|
// splitComparatorVersion splits the comparator from the version.
|
|
// Input must be free of leading or trailing spaces.
|
|
func splitComparatorVersion(s string) (string, string, error) {
|
|
i := strings.IndexFunc(s, unicode.IsDigit)
|
|
if i == -1 {
|
|
return "", "", fmt.Errorf("Could not get version from string: %q", s)
|
|
}
|
|
return strings.TrimSpace(s[0:i]), s[i:], nil
|
|
}
|
|
|
|
// getWildcardType will return the type of wildcard that the
|
|
// passed version contains
|
|
func getWildcardType(vStr string) wildcardType {
|
|
parts := strings.Split(vStr, ".")
|
|
nparts := len(parts)
|
|
wildcard := parts[nparts-1]
|
|
|
|
possibleWildcardType := wildcardTypefromInt(nparts)
|
|
if wildcard == "x" {
|
|
return possibleWildcardType
|
|
}
|
|
|
|
return noneWildcard
|
|
}
|
|
|
|
// createVersionFromWildcard will convert a wildcard version
|
|
// into a regular version, replacing 'x's with '0's, handling
|
|
// special cases like '1.x.x' and '1.x'
|
|
func createVersionFromWildcard(vStr string) string {
|
|
// handle 1.x.x
|
|
vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
|
|
vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
|
|
parts := strings.Split(vStr2, ".")
|
|
|
|
// handle 1.x
|
|
if len(parts) == 2 {
|
|
return vStr2 + ".0"
|
|
}
|
|
|
|
return vStr2
|
|
}
|
|
|
|
// incrementMajorVersion will increment the major version
|
|
// of the passed version
|
|
func incrementMajorVersion(vStr string) (string, error) {
|
|
parts := strings.Split(vStr, ".")
|
|
i, err := strconv.Atoi(parts[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
parts[0] = strconv.Itoa(i + 1)
|
|
|
|
return strings.Join(parts, "."), nil
|
|
}
|
|
|
|
// incrementMajorVersion will increment the minor version
|
|
// of the passed version
|
|
func incrementMinorVersion(vStr string) (string, error) {
|
|
parts := strings.Split(vStr, ".")
|
|
i, err := strconv.Atoi(parts[1])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
parts[1] = strconv.Itoa(i + 1)
|
|
|
|
return strings.Join(parts, "."), nil
|
|
}
|
|
|
|
// expandWildcardVersion will expand wildcards inside versions
|
|
// following these rules:
|
|
//
|
|
// * when dealing with patch wildcards:
|
|
// >= 1.2.x will become >= 1.2.0
|
|
// <= 1.2.x will become < 1.3.0
|
|
// > 1.2.x will become >= 1.3.0
|
|
// < 1.2.x will become < 1.2.0
|
|
// != 1.2.x will become < 1.2.0 >= 1.3.0
|
|
//
|
|
// * when dealing with minor wildcards:
|
|
// >= 1.x will become >= 1.0.0
|
|
// <= 1.x will become < 2.0.0
|
|
// > 1.x will become >= 2.0.0
|
|
// < 1.0 will become < 1.0.0
|
|
// != 1.x will become < 1.0.0 >= 2.0.0
|
|
//
|
|
// * when dealing with wildcards without
|
|
// version operator:
|
|
// 1.2.x will become >= 1.2.0 < 1.3.0
|
|
// 1.x will become >= 1.0.0 < 2.0.0
|
|
func expandWildcardVersion(parts [][]string) ([][]string, error) {
|
|
var expandedParts [][]string
|
|
for _, p := range parts {
|
|
var newParts []string
|
|
for _, ap := range p {
|
|
if strings.Contains(ap, "x") {
|
|
opStr, vStr, err := splitComparatorVersion(ap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
versionWildcardType := getWildcardType(vStr)
|
|
flatVersion := createVersionFromWildcard(vStr)
|
|
|
|
var resultOperator string
|
|
var shouldIncrementVersion bool
|
|
switch opStr {
|
|
case ">":
|
|
resultOperator = ">="
|
|
shouldIncrementVersion = true
|
|
case ">=":
|
|
resultOperator = ">="
|
|
case "<":
|
|
resultOperator = "<"
|
|
case "<=":
|
|
resultOperator = "<"
|
|
shouldIncrementVersion = true
|
|
case "", "=", "==":
|
|
newParts = append(newParts, ">="+flatVersion)
|
|
resultOperator = "<"
|
|
shouldIncrementVersion = true
|
|
case "!=", "!":
|
|
newParts = append(newParts, "<"+flatVersion)
|
|
resultOperator = ">="
|
|
shouldIncrementVersion = true
|
|
}
|
|
|
|
var resultVersion string
|
|
if shouldIncrementVersion {
|
|
switch versionWildcardType {
|
|
case patchWildcard:
|
|
resultVersion, _ = incrementMinorVersion(flatVersion)
|
|
case minorWildcard:
|
|
resultVersion, _ = incrementMajorVersion(flatVersion)
|
|
}
|
|
} else {
|
|
resultVersion = flatVersion
|
|
}
|
|
|
|
ap = resultOperator + resultVersion
|
|
}
|
|
newParts = append(newParts, ap)
|
|
}
|
|
expandedParts = append(expandedParts, newParts)
|
|
}
|
|
|
|
return expandedParts, nil
|
|
}
|
|
|
|
func parseComparator(s string) comparator {
|
|
switch s {
|
|
case "==":
|
|
fallthrough
|
|
case "":
|
|
fallthrough
|
|
case "=":
|
|
return compEQ
|
|
case ">":
|
|
return compGT
|
|
case ">=":
|
|
return compGE
|
|
case "<":
|
|
return compLT
|
|
case "<=":
|
|
return compLE
|
|
case "!":
|
|
fallthrough
|
|
case "!=":
|
|
return compNE
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MustParseRange is like ParseRange but panics if the range cannot be parsed.
|
|
func MustParseRange(s string) Range {
|
|
r, err := ParseRange(s)
|
|
if err != nil {
|
|
panic(`semver: ParseRange(` + s + `): ` + err.Error())
|
|
}
|
|
return r
|
|
}
|