2019-05-31 09:45:11 +00:00
/ *
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"
2023-12-20 12:23:59 +00:00
"errors"
2019-05-31 09:45:11 +00:00
"fmt"
"regexp"
"strconv"
"strings"
2024-08-19 08:01:33 +00:00
apimachineryversion "k8s.io/apimachinery/pkg/version"
2019-05-31 09:45:11 +00:00
)
2020-12-17 12:28:29 +00:00
// Version is an opaque representation of a version number
2019-05-31 09:45:11 +00:00
type Version struct {
components [ ] uint
semver bool
preRelease string
buildMetadata string
2024-08-19 08:01:33 +00:00
info apimachineryversion . Info
2019-05-31 09:45:11 +00:00
}
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
}
2023-12-20 12:23:59 +00:00
// 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
}
2019-05-31 09:45:11 +00:00
// 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
}
2024-08-19 08:01:33 +00:00
// 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
}
2019-05-31 09:45:11 +00:00
// 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
}
2023-08-17 05:15:28 +00:00
// MajorMinor returns a version with the provided major and minor version.
func MajorMinor ( major , minor uint ) * Version {
return & Version { components : [ ] uint { major , minor } }
}
2019-05-31 09:45:11 +00:00
// 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
}
2024-08-19 08:01:33 +00:00
// 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 ) )
}
2019-05-31 09:45:11 +00:00
// 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 {
2024-08-19 08:01:33 +00:00
if len ( preRelease ) == 0 {
return v
}
2019-05-31 09:45:11 +00:00
result := * v
result . components = [ ] uint { v . Major ( ) , v . Minor ( ) , v . Patch ( ) }
result . preRelease = preRelease
return & result
}
2020-01-14 10:38:55 +00:00
// 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
}
2019-05-31 09:45:11 +00:00
// 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 {
2020-12-17 12:28:29 +00:00
if v == nil {
return "<nil>"
}
2019-05-31 09:45:11 +00:00
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
}
2024-08-19 08:01:33 +00:00
// 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
}
2019-05-31 09:45:11 +00:00
// 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
}
2024-08-19 08:01:33 +00:00
// GreaterThan tests if a version is greater than a given version.
func ( v * Version ) GreaterThan ( other * Version ) bool {
return v . compareInternal ( other ) == 1
}
2019-05-31 09:45:11 +00:00
// 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
}
2024-08-19 08:01:33 +00:00
// WithInfo returns copy of the version object with requested info
func ( v * Version ) WithInfo ( info apimachineryversion . Info ) * Version {
result := * v
result . info = info
return & result
}
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
v . info . Major = itoa ( v . Major ( ) )
v . info . Minor = itoa ( v . Minor ( ) )
if v . info . GitVersion == "" {
v . info . GitVersion = v . String ( )
}
return & v . info
}
func itoa ( i uint ) string {
if i == 0 {
return ""
}
return strconv . Itoa ( int ( i ) )
}