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 }