k8s.io/apimachinery@v0.29.2/pkg/util/version/version.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package version
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"regexp"
    24  	"strconv"
    25  	"strings"
    26  )
    27  
    28  // Version is an opaque representation of a version number
    29  type Version struct {
    30  	components    []uint
    31  	semver        bool
    32  	preRelease    string
    33  	buildMetadata string
    34  }
    35  
    36  var (
    37  	// versionMatchRE splits a version string into numeric and "extra" parts
    38  	versionMatchRE = regexp.MustCompile(`^\s*v?([0-9]+(?:\.[0-9]+)*)(.*)*$`)
    39  	// 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
    40  	extraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\s*$`)
    41  )
    42  
    43  func parse(str string, semver bool) (*Version, error) {
    44  	parts := versionMatchRE.FindStringSubmatch(str)
    45  	if parts == nil {
    46  		return nil, fmt.Errorf("could not parse %q as version", str)
    47  	}
    48  	numbers, extra := parts[1], parts[2]
    49  
    50  	components := strings.Split(numbers, ".")
    51  	if (semver && len(components) != 3) || (!semver && len(components) < 2) {
    52  		return nil, fmt.Errorf("illegal version string %q", str)
    53  	}
    54  
    55  	v := &Version{
    56  		components: make([]uint, len(components)),
    57  		semver:     semver,
    58  	}
    59  	for i, comp := range components {
    60  		if (i == 0 || semver) && strings.HasPrefix(comp, "0") && comp != "0" {
    61  			return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str)
    62  		}
    63  		num, err := strconv.ParseUint(comp, 10, 0)
    64  		if err != nil {
    65  			return nil, fmt.Errorf("illegal non-numeric version component %q in %q: %v", comp, str, err)
    66  		}
    67  		v.components[i] = uint(num)
    68  	}
    69  
    70  	if semver && extra != "" {
    71  		extraParts := extraMatchRE.FindStringSubmatch(extra)
    72  		if extraParts == nil {
    73  			return nil, fmt.Errorf("could not parse pre-release/metadata (%s) in version %q", extra, str)
    74  		}
    75  		v.preRelease, v.buildMetadata = extraParts[1], extraParts[2]
    76  
    77  		for _, comp := range strings.Split(v.preRelease, ".") {
    78  			if _, err := strconv.ParseUint(comp, 10, 0); err == nil {
    79  				if strings.HasPrefix(comp, "0") && comp != "0" {
    80  					return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str)
    81  				}
    82  			}
    83  		}
    84  	}
    85  
    86  	return v, nil
    87  }
    88  
    89  // HighestSupportedVersion returns the highest supported version
    90  // This function assumes that the highest supported version must be v1.x.
    91  func HighestSupportedVersion(versions []string) (*Version, error) {
    92  	if len(versions) == 0 {
    93  		return nil, errors.New("empty array for supported versions")
    94  	}
    95  
    96  	var (
    97  		highestSupportedVersion *Version
    98  		theErr                  error
    99  	)
   100  
   101  	for i := len(versions) - 1; i >= 0; i-- {
   102  		currentHighestVer, err := ParseGeneric(versions[i])
   103  		if err != nil {
   104  			theErr = err
   105  			continue
   106  		}
   107  
   108  		if currentHighestVer.Major() > 1 {
   109  			continue
   110  		}
   111  
   112  		if highestSupportedVersion == nil || highestSupportedVersion.LessThan(currentHighestVer) {
   113  			highestSupportedVersion = currentHighestVer
   114  		}
   115  	}
   116  
   117  	if highestSupportedVersion == nil {
   118  		return nil, fmt.Errorf(
   119  			"could not find a highest supported version from versions (%v) reported: %+v",
   120  			versions, theErr)
   121  	}
   122  
   123  	if highestSupportedVersion.Major() != 1 {
   124  		return nil, fmt.Errorf("highest supported version reported is %v, must be v1.x", highestSupportedVersion)
   125  	}
   126  
   127  	return highestSupportedVersion, nil
   128  }
   129  
   130  // ParseGeneric parses a "generic" version string. The version string must consist of two
   131  // or more dot-separated numeric fields (the first of which can't have leading zeroes),
   132  // followed by arbitrary uninterpreted data (which need not be separated from the final
   133  // numeric field by punctuation). For convenience, leading and trailing whitespace is
   134  // ignored, and the version can be preceded by the letter "v". See also ParseSemantic.
   135  func ParseGeneric(str string) (*Version, error) {
   136  	return parse(str, false)
   137  }
   138  
   139  // MustParseGeneric is like ParseGeneric except that it panics on error
   140  func MustParseGeneric(str string) *Version {
   141  	v, err := ParseGeneric(str)
   142  	if err != nil {
   143  		panic(err)
   144  	}
   145  	return v
   146  }
   147  
   148  // ParseSemantic parses a version string that exactly obeys the syntax and semantics of
   149  // the "Semantic Versioning" specification (http://semver.org/) (although it ignores
   150  // leading and trailing whitespace, and allows the version to be preceded by "v"). For
   151  // version strings that are not guaranteed to obey the Semantic Versioning syntax, use
   152  // ParseGeneric.
   153  func ParseSemantic(str string) (*Version, error) {
   154  	return parse(str, true)
   155  }
   156  
   157  // MustParseSemantic is like ParseSemantic except that it panics on error
   158  func MustParseSemantic(str string) *Version {
   159  	v, err := ParseSemantic(str)
   160  	if err != nil {
   161  		panic(err)
   162  	}
   163  	return v
   164  }
   165  
   166  // MajorMinor returns a version with the provided major and minor version.
   167  func MajorMinor(major, minor uint) *Version {
   168  	return &Version{components: []uint{major, minor}}
   169  }
   170  
   171  // Major returns the major release number
   172  func (v *Version) Major() uint {
   173  	return v.components[0]
   174  }
   175  
   176  // Minor returns the minor release number
   177  func (v *Version) Minor() uint {
   178  	return v.components[1]
   179  }
   180  
   181  // Patch returns the patch release number if v is a Semantic Version, or 0
   182  func (v *Version) Patch() uint {
   183  	if len(v.components) < 3 {
   184  		return 0
   185  	}
   186  	return v.components[2]
   187  }
   188  
   189  // BuildMetadata returns the build metadata, if v is a Semantic Version, or ""
   190  func (v *Version) BuildMetadata() string {
   191  	return v.buildMetadata
   192  }
   193  
   194  // PreRelease returns the prerelease metadata, if v is a Semantic Version, or ""
   195  func (v *Version) PreRelease() string {
   196  	return v.preRelease
   197  }
   198  
   199  // Components returns the version number components
   200  func (v *Version) Components() []uint {
   201  	return v.components
   202  }
   203  
   204  // WithMajor returns copy of the version object with requested major number
   205  func (v *Version) WithMajor(major uint) *Version {
   206  	result := *v
   207  	result.components = []uint{major, v.Minor(), v.Patch()}
   208  	return &result
   209  }
   210  
   211  // WithMinor returns copy of the version object with requested minor number
   212  func (v *Version) WithMinor(minor uint) *Version {
   213  	result := *v
   214  	result.components = []uint{v.Major(), minor, v.Patch()}
   215  	return &result
   216  }
   217  
   218  // WithPatch returns copy of the version object with requested patch number
   219  func (v *Version) WithPatch(patch uint) *Version {
   220  	result := *v
   221  	result.components = []uint{v.Major(), v.Minor(), patch}
   222  	return &result
   223  }
   224  
   225  // WithPreRelease returns copy of the version object with requested prerelease
   226  func (v *Version) WithPreRelease(preRelease string) *Version {
   227  	result := *v
   228  	result.components = []uint{v.Major(), v.Minor(), v.Patch()}
   229  	result.preRelease = preRelease
   230  	return &result
   231  }
   232  
   233  // WithBuildMetadata returns copy of the version object with requested buildMetadata
   234  func (v *Version) WithBuildMetadata(buildMetadata string) *Version {
   235  	result := *v
   236  	result.components = []uint{v.Major(), v.Minor(), v.Patch()}
   237  	result.buildMetadata = buildMetadata
   238  	return &result
   239  }
   240  
   241  // String converts a Version back to a string; note that for versions parsed with
   242  // ParseGeneric, this will not include the trailing uninterpreted portion of the version
   243  // number.
   244  func (v *Version) String() string {
   245  	if v == nil {
   246  		return "<nil>"
   247  	}
   248  	var buffer bytes.Buffer
   249  
   250  	for i, comp := range v.components {
   251  		if i > 0 {
   252  			buffer.WriteString(".")
   253  		}
   254  		buffer.WriteString(fmt.Sprintf("%d", comp))
   255  	}
   256  	if v.preRelease != "" {
   257  		buffer.WriteString("-")
   258  		buffer.WriteString(v.preRelease)
   259  	}
   260  	if v.buildMetadata != "" {
   261  		buffer.WriteString("+")
   262  		buffer.WriteString(v.buildMetadata)
   263  	}
   264  
   265  	return buffer.String()
   266  }
   267  
   268  // compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0
   269  // if they are equal
   270  func (v *Version) compareInternal(other *Version) int {
   271  
   272  	vLen := len(v.components)
   273  	oLen := len(other.components)
   274  	for i := 0; i < vLen && i < oLen; i++ {
   275  		switch {
   276  		case other.components[i] < v.components[i]:
   277  			return 1
   278  		case other.components[i] > v.components[i]:
   279  			return -1
   280  		}
   281  	}
   282  
   283  	// If components are common but one has more items and they are not zeros, it is bigger
   284  	switch {
   285  	case oLen < vLen && !onlyZeros(v.components[oLen:]):
   286  		return 1
   287  	case oLen > vLen && !onlyZeros(other.components[vLen:]):
   288  		return -1
   289  	}
   290  
   291  	if !v.semver || !other.semver {
   292  		return 0
   293  	}
   294  
   295  	switch {
   296  	case v.preRelease == "" && other.preRelease != "":
   297  		return 1
   298  	case v.preRelease != "" && other.preRelease == "":
   299  		return -1
   300  	case v.preRelease == other.preRelease: // includes case where both are ""
   301  		return 0
   302  	}
   303  
   304  	vPR := strings.Split(v.preRelease, ".")
   305  	oPR := strings.Split(other.preRelease, ".")
   306  	for i := 0; i < len(vPR) && i < len(oPR); i++ {
   307  		vNum, err := strconv.ParseUint(vPR[i], 10, 0)
   308  		if err == nil {
   309  			oNum, err := strconv.ParseUint(oPR[i], 10, 0)
   310  			if err == nil {
   311  				switch {
   312  				case oNum < vNum:
   313  					return 1
   314  				case oNum > vNum:
   315  					return -1
   316  				default:
   317  					continue
   318  				}
   319  			}
   320  		}
   321  		if oPR[i] < vPR[i] {
   322  			return 1
   323  		} else if oPR[i] > vPR[i] {
   324  			return -1
   325  		}
   326  	}
   327  
   328  	switch {
   329  	case len(oPR) < len(vPR):
   330  		return 1
   331  	case len(oPR) > len(vPR):
   332  		return -1
   333  	}
   334  
   335  	return 0
   336  }
   337  
   338  // returns false if array contain any non-zero element
   339  func onlyZeros(array []uint) bool {
   340  	for _, num := range array {
   341  		if num != 0 {
   342  			return false
   343  		}
   344  	}
   345  	return true
   346  }
   347  
   348  // AtLeast tests if a version is at least equal to a given minimum version. If both
   349  // Versions are Semantic Versions, this will use the Semantic Version comparison
   350  // algorithm. Otherwise, it will compare only the numeric components, with non-present
   351  // components being considered "0" (ie, "1.4" is equal to "1.4.0").
   352  func (v *Version) AtLeast(min *Version) bool {
   353  	return v.compareInternal(min) != -1
   354  }
   355  
   356  // LessThan tests if a version is less than a given version. (It is exactly the opposite
   357  // of AtLeast, for situations where asking "is v too old?" makes more sense than asking
   358  // "is v new enough?".)
   359  func (v *Version) LessThan(other *Version) bool {
   360  	return v.compareInternal(other) == -1
   361  }
   362  
   363  // Compare compares v against a version string (which will be parsed as either Semantic
   364  // or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if
   365  // it is greater than other, or 0 if they are equal.
   366  func (v *Version) Compare(other string) (int, error) {
   367  	ov, err := parse(other, v.semver)
   368  	if err != nil {
   369  		return 0, err
   370  	}
   371  	return v.compareInternal(ov), nil
   372  }