mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-26 13:17:32 +00:00
.github
atomicfile
cmd
control
derp
disco
internal
ipn
log
logpolicy
logtail
metrics
net
paths
portlist
safesocket
scripts
smallzstd
syncs
tailcfg
tempfork
tsconst
tstest
tstime
tsweb
types
util
version
distro
.gitignore
GENERATE.go
all.do
clean.do
cmdname.go
cmdname_ios.go
cmp.go
cmp_test.go
mkversion_test.go
print.go
prop.go
prop_ios.go
prop_notios.go
ver.go.do
ver.go.in
version-info.sh.do
version.go
version.h.do
version.sh
version.xcconfig.do
xversion.go
wgengine
.gitattributes
.gitignore
AUTHORS
CODE_OF_CONDUCT.md
Dockerfile
LICENSE
Makefile
PATENTS
README.md
SECURITY.md
VERSION.txt
api.md
build_dist.sh
go.mod
go.sum
shell.nix
141 lines
3.5 KiB
Go
141 lines
3.5 KiB
Go
![]() |
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package version
|
||
|
|
||
|
import (
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// AtLeast returns whether version is at least the specified minimum
|
||
|
// version.
|
||
|
//
|
||
|
// Version comparison in Tailscale is a little complex, because we
|
||
|
// switched "styles" a few times, and additionally have a completely
|
||
|
// separate track of version numbers for OSS-only builds.
|
||
|
//
|
||
|
// AtLeast acts conservatively, returning true only if it's certain
|
||
|
// that version is at least minimum. As a result, it can produce false
|
||
|
// negatives, for example when an OSS build supports a given feature,
|
||
|
// but AtLeast is called with an official release number as the
|
||
|
// minimum
|
||
|
//
|
||
|
// version and minimum can both be either an official Tailscale
|
||
|
// version numbers (major.minor.patch-extracommits-extrastring), or an
|
||
|
// OSS build datestamp (date.YYYYMMDD). For Tailscale version numbers,
|
||
|
// AtLeast also accepts a prefix of a full version, in which case all
|
||
|
// missing fields are assumed to be zero.
|
||
|
func AtLeast(version string, minimum string) bool {
|
||
|
v, ok := parse(version)
|
||
|
if !ok {
|
||
|
return false
|
||
|
}
|
||
|
m, ok := parse(minimum)
|
||
|
if !ok {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case v.Datestamp != 0 && m.Datestamp == 0:
|
||
|
// OSS version vs. Tailscale version
|
||
|
return false
|
||
|
case v.Datestamp == 0 && m.Datestamp != 0:
|
||
|
// Tailscale version vs. OSS version
|
||
|
return false
|
||
|
case v.Datestamp != 0:
|
||
|
// OSS version vs. OSS version
|
||
|
return v.Datestamp >= m.Datestamp
|
||
|
case v.Major == m.Major && v.Minor == m.Minor && v.Patch == m.Patch && v.ExtraCommits == m.ExtraCommits:
|
||
|
// Exactly equal Tailscale versions
|
||
|
return true
|
||
|
case v.Major != m.Major:
|
||
|
return v.Major > m.Major
|
||
|
case v.Minor != m.Minor:
|
||
|
return v.Minor > m.Minor
|
||
|
case v.Patch != m.Patch:
|
||
|
return v.Patch > m.Patch
|
||
|
default:
|
||
|
return v.ExtraCommits > m.ExtraCommits
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type parsed struct {
|
||
|
Major, Minor, Patch, ExtraCommits int // for Tailscale version e.g. e.g. "0.99.1-20"
|
||
|
Datestamp int // for OSS version e.g. "date.20200612"
|
||
|
}
|
||
|
|
||
|
func parse(version string) (parsed, bool) {
|
||
|
if strings.HasPrefix(version, "date.") {
|
||
|
stamp, err := strconv.Atoi(version[5:])
|
||
|
if err != nil {
|
||
|
return parsed{}, false
|
||
|
}
|
||
|
return parsed{Datestamp: stamp}, true
|
||
|
}
|
||
|
|
||
|
var ret parsed
|
||
|
|
||
|
major, rest, err := splitNumericPrefix(version)
|
||
|
if err != nil {
|
||
|
return parsed{}, false
|
||
|
}
|
||
|
ret.Major = major
|
||
|
if len(rest) == 0 {
|
||
|
return ret, true
|
||
|
}
|
||
|
|
||
|
ret.Minor, rest, err = splitNumericPrefix(rest[1:])
|
||
|
if err != nil {
|
||
|
return parsed{}, false
|
||
|
}
|
||
|
if len(rest) == 0 {
|
||
|
return ret, true
|
||
|
}
|
||
|
|
||
|
// Optional patch version, if the next separator is a dot.
|
||
|
if rest[0] == '.' {
|
||
|
ret.Patch, rest, err = splitNumericPrefix(rest[1:])
|
||
|
if err != nil {
|
||
|
return parsed{}, false
|
||
|
}
|
||
|
if len(rest) == 0 {
|
||
|
return ret, true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Optional extraCommits, if the next bit can be completely
|
||
|
// consumed as an integer.
|
||
|
if rest[0] != '-' {
|
||
|
return parsed{}, false
|
||
|
}
|
||
|
|
||
|
var trailer string
|
||
|
ret.ExtraCommits, trailer, err = splitNumericPrefix(rest[1:])
|
||
|
if err != nil || (len(trailer) > 0 && trailer[0] != '-') {
|
||
|
// rest was probably the string trailer, ignore it.
|
||
|
ret.ExtraCommits = 0
|
||
|
}
|
||
|
return ret, true
|
||
|
}
|
||
|
|
||
|
func splitNumericPrefix(s string) (int, string, error) {
|
||
|
for i, r := range s {
|
||
|
if r >= '0' && r <= '9' {
|
||
|
continue
|
||
|
}
|
||
|
ret, err := strconv.Atoi(s[:i])
|
||
|
if err != nil {
|
||
|
return 0, "", err
|
||
|
}
|
||
|
return ret, s[i:], nil
|
||
|
}
|
||
|
|
||
|
ret, err := strconv.Atoi(s)
|
||
|
if err != nil {
|
||
|
return 0, "", err
|
||
|
}
|
||
|
return ret, "", nil
|
||
|
}
|