| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- /*
- 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"
- "errors"
- "fmt"
- "regexp"
- "strconv"
- "strings"
- apimachineryversion "k8s.io/apimachinery/pkg/version"
- )
- // Version is an opaque 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
- }
- // HighestSupportedVersion returns the highest supported version
- // This function assumes that the highest supported version must be v1.x.
- func HighestSupportedVersion(versions []string) (*Version, error) {
- if len(versions) == 0 {
- return nil, errors.New("empty array for supported versions")
- }
- var (
- highestSupportedVersion *Version
- theErr error
- )
- for i := len(versions) - 1; i >= 0; i-- {
- currentHighestVer, err := ParseGeneric(versions[i])
- if err != nil {
- theErr = err
- continue
- }
- if currentHighestVer.Major() > 1 {
- continue
- }
- if highestSupportedVersion == nil || highestSupportedVersion.LessThan(currentHighestVer) {
- highestSupportedVersion = currentHighestVer
- }
- }
- if highestSupportedVersion == nil {
- return nil, fmt.Errorf(
- "could not find a highest supported version from versions (%v) reported: %+v",
- versions, theErr)
- }
- if highestSupportedVersion.Major() != 1 {
- return nil, fmt.Errorf("highest supported version reported is %v, must be v1.x", highestSupportedVersion)
- }
- return highestSupportedVersion, 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
- }
- // Parse tries to do ParseSemantic first to keep more information.
- // If ParseSemantic fails, it would just do ParseGeneric.
- func Parse(str string) (*Version, error) {
- v, err := parse(str, true)
- if err != nil {
- return parse(str, false)
- }
- return v, err
- }
- // MustParse is like Parse except that it panics on error
- func MustParse(str string) *Version {
- v, err := Parse(str)
- if err != nil {
- panic(err)
- }
- return v
- }
- // ParseMajorMinor parses a "generic" version string and returns a version with the major and minor version.
- func ParseMajorMinor(str string) (*Version, error) {
- v, err := ParseGeneric(str)
- if err != nil {
- return nil, err
- }
- return MajorMinor(v.Major(), v.Minor()), nil
- }
- // MustParseMajorMinor is like ParseMajorMinor except that it panics on error
- func MustParseMajorMinor(str string) *Version {
- v, err := ParseMajorMinor(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
- }
- // MajorMinor returns a version with the provided major and minor version.
- func MajorMinor(major, minor uint) *Version {
- return &Version{components: []uint{major, minor}}
- }
- // Major returns the major release number
- func (v *Version) Major() uint {
- return v.components[0]
- }
- // Minor returns the minor release number
- func (v *Version) Minor() uint {
- return v.components[1]
- }
- // Patch returns the patch release number if v is a Semantic Version, or 0
- func (v *Version) Patch() uint {
- if len(v.components) < 3 {
- return 0
- }
- return v.components[2]
- }
- // BuildMetadata returns the build metadata, if v is a Semantic Version, or ""
- func (v *Version) BuildMetadata() string {
- return v.buildMetadata
- }
- // PreRelease returns the prerelease metadata, if v is a Semantic Version, or ""
- func (v *Version) PreRelease() string {
- return v.preRelease
- }
- // Components returns the version number components
- func (v *Version) Components() []uint {
- return v.components
- }
- // WithMajor returns copy of the version object with requested major number
- func (v *Version) WithMajor(major uint) *Version {
- result := *v
- result.components = []uint{major, v.Minor(), v.Patch()}
- return &result
- }
- // WithMinor returns copy of the version object with requested minor number
- func (v *Version) WithMinor(minor uint) *Version {
- result := *v
- result.components = []uint{v.Major(), minor, v.Patch()}
- return &result
- }
- // SubtractMinor returns the version with offset from the original minor, with the same major and no patch.
- // If -offset >= current minor, the minor would be 0.
- func (v *Version) OffsetMinor(offset int) *Version {
- var minor uint
- if offset >= 0 {
- minor = v.Minor() + uint(offset)
- } else {
- diff := uint(-offset)
- if diff < v.Minor() {
- minor = v.Minor() - diff
- }
- }
- return MajorMinor(v.Major(), minor)
- }
- // SubtractMinor returns the version diff minor versions back, with the same major and no patch.
- // If diff >= current minor, the minor would be 0.
- func (v *Version) SubtractMinor(diff uint) *Version {
- return v.OffsetMinor(-int(diff))
- }
- // AddMinor returns the version diff minor versions forward, with the same major and no patch.
- func (v *Version) AddMinor(diff uint) *Version {
- return v.OffsetMinor(int(diff))
- }
- // WithPatch returns copy of the version object with requested patch number
- func (v *Version) WithPatch(patch uint) *Version {
- result := *v
- result.components = []uint{v.Major(), v.Minor(), patch}
- return &result
- }
- // WithPreRelease returns copy of the version object with requested prerelease
- func (v *Version) WithPreRelease(preRelease string) *Version {
- if len(preRelease) == 0 {
- return v
- }
- result := *v
- result.components = []uint{v.Major(), v.Minor(), v.Patch()}
- result.preRelease = preRelease
- return &result
- }
- // WithBuildMetadata returns copy of the version object with requested buildMetadata
- func (v *Version) WithBuildMetadata(buildMetadata string) *Version {
- result := *v
- result.components = []uint{v.Major(), v.Minor(), v.Patch()}
- result.buildMetadata = buildMetadata
- return &result
- }
- // 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 {
- if v == nil {
- return "<nil>"
- }
- 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 {
- vLen := len(v.components)
- oLen := len(other.components)
- for i := 0; i < vLen && i < oLen; i++ {
- switch {
- case other.components[i] < v.components[i]:
- return 1
- case other.components[i] > v.components[i]:
- return -1
- }
- }
- // If components are common but one has more items and they are not zeros, it is bigger
- switch {
- case oLen < vLen && !onlyZeros(v.components[oLen:]):
- return 1
- case oLen > vLen && !onlyZeros(other.components[vLen:]):
- 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 := 0; i < len(vPR) && i < len(oPR); i++ {
- 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
- }
- }
- switch {
- case len(oPR) < len(vPR):
- return 1
- case len(oPR) > len(vPR):
- return -1
- }
- return 0
- }
- // returns false if array contain any non-zero element
- func onlyZeros(array []uint) bool {
- for _, num := range array {
- if num != 0 {
- return false
- }
- }
- return true
- }
- // EqualTo tests if a version is equal to a given version.
- func (v *Version) EqualTo(other *Version) bool {
- if v == nil {
- return other == nil
- }
- if other == nil {
- return false
- }
- return v.compareInternal(other) == 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
- }
- // GreaterThan tests if a version is greater than a given version.
- func (v *Version) GreaterThan(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
- }
- // WithInfo returns copy of the version object.
- // Deprecated: The Info field has been removed from the Version struct. This method no longer modifies the Version object.
- func (v *Version) WithInfo(info apimachineryversion.Info) *Version {
- result := *v
- return &result
- }
- // Info returns the version information of a component.
- // Deprecated: Use Info() from effective version instead.
- func (v *Version) Info() *apimachineryversion.Info {
- if v == nil {
- return nil
- }
- // in case info is empty, or the major and minor in info is different from the actual major and minor
- return &apimachineryversion.Info{
- Major: Itoa(v.Major()),
- Minor: Itoa(v.Minor()),
- GitVersion: v.String(),
- }
- }
- func Itoa(i uint) string {
- if i == 0 {
- return ""
- }
- return strconv.Itoa(int(i))
- }
|