mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			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.Index(ap, "x") != -1 {
 | |
| 				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
 | |
| }
 |