mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			237 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2016 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package version
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"regexp"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// Version is an opqaue representation of a version number
 | 
						|
type Version struct {
 | 
						|
	components    []uint
 | 
						|
	semver        bool
 | 
						|
	preRelease    string
 | 
						|
	buildMetadata string
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	// versionMatchRE splits a version string into numeric and "extra" parts
 | 
						|
	versionMatchRE = regexp.MustCompile(`^\s*v?([0-9]+(?:\.[0-9]+)*)(.*)*$`)
 | 
						|
	// extraMatchRE splits the "extra" part of versionMatchRE into semver pre-release and build metadata; it does not validate the "no leading zeroes" constraint for pre-release
 | 
						|
	extraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\s*$`)
 | 
						|
)
 | 
						|
 | 
						|
func parse(str string, semver bool) (*Version, error) {
 | 
						|
	parts := versionMatchRE.FindStringSubmatch(str)
 | 
						|
	if parts == nil {
 | 
						|
		return nil, fmt.Errorf("could not parse %q as version", str)
 | 
						|
	}
 | 
						|
	numbers, extra := parts[1], parts[2]
 | 
						|
 | 
						|
	components := strings.Split(numbers, ".")
 | 
						|
	if (semver && len(components) != 3) || (!semver && len(components) < 2) {
 | 
						|
		return nil, fmt.Errorf("illegal version string %q", str)
 | 
						|
	}
 | 
						|
 | 
						|
	v := &Version{
 | 
						|
		components: make([]uint, len(components)),
 | 
						|
		semver:     semver,
 | 
						|
	}
 | 
						|
	for i, comp := range components {
 | 
						|
		if (i == 0 || semver) && strings.HasPrefix(comp, "0") && comp != "0" {
 | 
						|
			return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str)
 | 
						|
		}
 | 
						|
		num, err := strconv.ParseUint(comp, 10, 0)
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("illegal non-numeric version component %q in %q: %v", comp, str, err)
 | 
						|
		}
 | 
						|
		v.components[i] = uint(num)
 | 
						|
	}
 | 
						|
 | 
						|
	if semver && extra != "" {
 | 
						|
		extraParts := extraMatchRE.FindStringSubmatch(extra)
 | 
						|
		if extraParts == nil {
 | 
						|
			return nil, fmt.Errorf("could not parse pre-release/metadata (%s) in version %q", extra, str)
 | 
						|
		}
 | 
						|
		v.preRelease, v.buildMetadata = extraParts[1], extraParts[2]
 | 
						|
 | 
						|
		for _, comp := range strings.Split(v.preRelease, ".") {
 | 
						|
			if _, err := strconv.ParseUint(comp, 10, 0); err == nil {
 | 
						|
				if strings.HasPrefix(comp, "0") && comp != "0" {
 | 
						|
					return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return v, nil
 | 
						|
}
 | 
						|
 | 
						|
// ParseGeneric parses a "generic" version string. The version string must consist of two
 | 
						|
// or more dot-separated numeric fields (the first of which can't have leading zeroes),
 | 
						|
// followed by arbitrary uninterpreted data (which need not be separated from the final
 | 
						|
// numeric field by punctuation). For convenience, leading and trailing whitespace is
 | 
						|
// ignored, and the version can be preceded by the letter "v". See also ParseSemantic.
 | 
						|
func ParseGeneric(str string) (*Version, error) {
 | 
						|
	return parse(str, false)
 | 
						|
}
 | 
						|
 | 
						|
// MustParseGeneric is like ParseGeneric except that it panics on error
 | 
						|
func MustParseGeneric(str string) *Version {
 | 
						|
	v, err := ParseGeneric(str)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
// ParseSemantic parses a version string that exactly obeys the syntax and semantics of
 | 
						|
// the "Semantic Versioning" specification (http://semver.org/) (although it ignores
 | 
						|
// leading and trailing whitespace, and allows the version to be preceded by "v"). For
 | 
						|
// version strings that are not guaranteed to obey the Semantic Versioning syntax, use
 | 
						|
// ParseGeneric.
 | 
						|
func ParseSemantic(str string) (*Version, error) {
 | 
						|
	return parse(str, true)
 | 
						|
}
 | 
						|
 | 
						|
// MustParseSemantic is like ParseSemantic except that it panics on error
 | 
						|
func MustParseSemantic(str string) *Version {
 | 
						|
	v, err := ParseSemantic(str)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
// BuildMetadata returns the build metadata, if v is a Semantic Version, or ""
 | 
						|
func (v *Version) BuildMetadata() string {
 | 
						|
	return v.buildMetadata
 | 
						|
}
 | 
						|
 | 
						|
// String converts a Version back to a string; note that for versions parsed with
 | 
						|
// ParseGeneric, this will not include the trailing uninterpreted portion of the version
 | 
						|
// number.
 | 
						|
func (v *Version) String() string {
 | 
						|
	var buffer bytes.Buffer
 | 
						|
 | 
						|
	for i, comp := range v.components {
 | 
						|
		if i > 0 {
 | 
						|
			buffer.WriteString(".")
 | 
						|
		}
 | 
						|
		buffer.WriteString(fmt.Sprintf("%d", comp))
 | 
						|
	}
 | 
						|
	if v.preRelease != "" {
 | 
						|
		buffer.WriteString("-")
 | 
						|
		buffer.WriteString(v.preRelease)
 | 
						|
	}
 | 
						|
	if v.buildMetadata != "" {
 | 
						|
		buffer.WriteString("+")
 | 
						|
		buffer.WriteString(v.buildMetadata)
 | 
						|
	}
 | 
						|
 | 
						|
	return buffer.String()
 | 
						|
}
 | 
						|
 | 
						|
// compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0
 | 
						|
// if they are equal
 | 
						|
func (v *Version) compareInternal(other *Version) int {
 | 
						|
	for i := range v.components {
 | 
						|
		switch {
 | 
						|
		case i >= len(other.components):
 | 
						|
			if v.components[i] != 0 {
 | 
						|
				return 1
 | 
						|
			}
 | 
						|
		case other.components[i] < v.components[i]:
 | 
						|
			return 1
 | 
						|
		case other.components[i] > v.components[i]:
 | 
						|
			return -1
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !v.semver || !other.semver {
 | 
						|
		return 0
 | 
						|
	}
 | 
						|
 | 
						|
	switch {
 | 
						|
	case v.preRelease == "" && other.preRelease != "":
 | 
						|
		return 1
 | 
						|
	case v.preRelease != "" && other.preRelease == "":
 | 
						|
		return -1
 | 
						|
	case v.preRelease == other.preRelease: // includes case where both are ""
 | 
						|
		return 0
 | 
						|
	}
 | 
						|
 | 
						|
	vPR := strings.Split(v.preRelease, ".")
 | 
						|
	oPR := strings.Split(other.preRelease, ".")
 | 
						|
	for i := range vPR {
 | 
						|
		if i >= len(oPR) {
 | 
						|
			return 1
 | 
						|
		}
 | 
						|
		vNum, err := strconv.ParseUint(vPR[i], 10, 0)
 | 
						|
		if err == nil {
 | 
						|
			oNum, err := strconv.ParseUint(oPR[i], 10, 0)
 | 
						|
			if err == nil {
 | 
						|
				switch {
 | 
						|
				case oNum < vNum:
 | 
						|
					return 1
 | 
						|
				case oNum > vNum:
 | 
						|
					return -1
 | 
						|
				default:
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if oPR[i] < vPR[i] {
 | 
						|
			return 1
 | 
						|
		} else if oPR[i] > vPR[i] {
 | 
						|
			return -1
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 0
 | 
						|
}
 | 
						|
 | 
						|
// AtLeast tests if a version is at least equal to a given minimum version. If both
 | 
						|
// Versions are Semantic Versions, this will use the Semantic Version comparison
 | 
						|
// algorithm. Otherwise, it will compare only the numeric components, with non-present
 | 
						|
// components being considered "0" (ie, "1.4" is equal to "1.4.0").
 | 
						|
func (v *Version) AtLeast(min *Version) bool {
 | 
						|
	return v.compareInternal(min) != -1
 | 
						|
}
 | 
						|
 | 
						|
// LessThan tests if a version is less than a given version. (It is exactly the opposite
 | 
						|
// of AtLeast, for situations where asking "is v too old?" makes more sense than asking
 | 
						|
// "is v new enough?".)
 | 
						|
func (v *Version) LessThan(other *Version) bool {
 | 
						|
	return v.compareInternal(other) == -1
 | 
						|
}
 | 
						|
 | 
						|
// Compare compares v against a version string (which will be parsed as either Semantic
 | 
						|
// or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if
 | 
						|
// it is greater than other, or 0 if they are equal.
 | 
						|
func (v *Version) Compare(other string) (int, error) {
 | 
						|
	ov, err := parse(other, v.semver)
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
	return v.compareInternal(ov), nil
 | 
						|
}
 |