Bumping k8s dependencies to 1.13

This commit is contained in:
Cheng Xing
2018-11-16 14:08:25 -08:00
parent 305407125c
commit b4c0b68ec7
8002 changed files with 884099 additions and 276228 deletions

View File

@@ -136,12 +136,12 @@ func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []strin
negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols)
if len(negotiatedProtocol) == 0 {
w.WriteHeader(http.StatusForbidden)
for i := range serverProtocols {
w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i])
}
fmt.Fprintf(w, "unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols)
return "", fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server supports %v", clientProtocols, serverProtocols)
err := fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols)
http.Error(w, err.Error(), http.StatusForbidden)
return "", err
}
w.Header().Add(HeaderProtocolVersion, negotiatedProtocol)

View File

@@ -67,6 +67,9 @@ type SpdyRoundTripper struct {
// followRedirects indicates if the round tripper should examine responses for redirects and
// follow them.
followRedirects bool
// requireSameHostRedirects restricts redirect following to only follow redirects to the same host
// as the original request.
requireSameHostRedirects bool
}
var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{}
@@ -75,14 +78,18 @@ var _ utilnet.Dialer = &SpdyRoundTripper{}
// NewRoundTripper creates a new SpdyRoundTripper that will use
// the specified tlsConfig.
func NewRoundTripper(tlsConfig *tls.Config, followRedirects bool) httpstream.UpgradeRoundTripper {
return NewSpdyRoundTripper(tlsConfig, followRedirects)
func NewRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) httpstream.UpgradeRoundTripper {
return NewSpdyRoundTripper(tlsConfig, followRedirects, requireSameHostRedirects)
}
// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use
// the specified tlsConfig. This function is mostly meant for unit tests.
func NewSpdyRoundTripper(tlsConfig *tls.Config, followRedirects bool) *SpdyRoundTripper {
return &SpdyRoundTripper{tlsConfig: tlsConfig, followRedirects: followRedirects}
func NewSpdyRoundTripper(tlsConfig *tls.Config, followRedirects, requireSameHostRedirects bool) *SpdyRoundTripper {
return &SpdyRoundTripper{
tlsConfig: tlsConfig,
followRedirects: followRedirects,
requireSameHostRedirects: requireSameHostRedirects,
}
}
// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during
@@ -257,7 +264,7 @@ func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
)
if s.followRedirects {
conn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, req.URL, header, req.Body, s)
conn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, req.URL, header, req.Body, s, s.requireSameHostRedirects)
} else {
clone := utilnet.CloneRequest(req)
clone.Header = header

View File

@@ -282,7 +282,7 @@ func TestRoundTripAndNewConnection(t *testing.T) {
t.Fatalf("%s: Error creating request: %s", k, err)
}
spdyTransport := NewSpdyRoundTripper(testCase.clientTLS, redirect)
spdyTransport := NewSpdyRoundTripper(testCase.clientTLS, redirect, redirect)
var proxierCalled bool
var proxyCalledWithHost string
@@ -391,8 +391,8 @@ func TestRoundTripRedirects(t *testing.T) {
}{
{0, true},
{1, true},
{10, true},
{11, false},
{9, true},
{10, false},
}
for _, test := range tests {
t.Run(fmt.Sprintf("with %d redirects", test.redirects), func(t *testing.T) {
@@ -425,7 +425,7 @@ func TestRoundTripRedirects(t *testing.T) {
t.Fatalf("Error creating request: %s", err)
}
spdyTransport := NewSpdyRoundTripper(nil, true)
spdyTransport := NewSpdyRoundTripper(nil, true, true)
client := &http.Client{Transport: spdyTransport}
resp, err := client.Do(req)

View File

@@ -74,15 +74,15 @@ func (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Reque
connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))
upgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade))
if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "unable to upgrade: missing upgrade headers in request: %#v", req.Header)
errorMsg := fmt.Sprintf("unable to upgrade: missing upgrade headers in request: %#v", req.Header)
http.Error(w, errorMsg, http.StatusBadRequest)
return nil
}
hijacker, ok := w.(http.Hijacker)
if !ok {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "unable to upgrade: unable to hijack response")
errorMsg := fmt.Sprintf("unable to upgrade: unable to hijack response")
http.Error(w, errorMsg, http.StatusInternalServerError)
return nil
}

View File

@@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by protoc-gen-gogo.
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/intstr/generated.proto
// DO NOT EDIT!
/*
Package intstr is a generated protocol buffer package.
@@ -81,24 +80,6 @@ func (m *IntOrString) MarshalTo(dAtA []byte) (int, error) {
return i, nil
}
func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)

View File

@@ -29,7 +29,7 @@ option go_package = "intstr";
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
// TODO: Rename to Int32OrString
//
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +k8s:openapi-gen=true

View File

@@ -31,12 +31,12 @@ func TestGetNameFromCallsite(t *testing.T) {
{
name: "ignore-package",
ignoredPackages: []string{"k8s.io/apimachinery/pkg/util/naming"},
expected: "testing/testing.go:777",
expected: "testing/testing.go:827",
},
{
name: "ignore-file",
ignoredPackages: []string{"k8s.io/apimachinery/pkg/util/naming/from_stack_test.go"},
expected: "testing/testing.go:777",
expected: "testing/testing.go:827",
},
{
name: "ignore-multiple",

View File

@@ -321,9 +321,10 @@ type Dialer interface {
// ConnectWithRedirects uses dialer to send req, following up to 10 redirects (relative to
// originalLocation). It returns the opened net.Conn and the raw response bytes.
func ConnectWithRedirects(originalMethod string, originalLocation *url.URL, header http.Header, originalBody io.Reader, dialer Dialer) (net.Conn, []byte, error) {
// If requireSameHostRedirects is true, only redirects to the same host are permitted.
func ConnectWithRedirects(originalMethod string, originalLocation *url.URL, header http.Header, originalBody io.Reader, dialer Dialer, requireSameHostRedirects bool) (net.Conn, []byte, error) {
const (
maxRedirects = 10
maxRedirects = 9 // Fail on the 10th redirect
maxResponseSize = 16384 // play it safe to allow the potential for lots of / large headers
)
@@ -387,10 +388,6 @@ redirectLoop:
resp.Body.Close() // not used
// Reset the connection.
intermediateConn.Close()
intermediateConn = nil
// Prepare to follow the redirect.
redirectStr := resp.Header.Get("Location")
if redirectStr == "" {
@@ -404,6 +401,15 @@ redirectLoop:
if err != nil {
return nil, nil, fmt.Errorf("malformed Location header: %v", err)
}
// Only follow redirects to the same host. Otherwise, propagate the redirect response back.
if requireSameHostRedirects && location.Hostname() != originalLocation.Hostname() {
break redirectLoop
}
// Reset the connection.
intermediateConn.Close()
intermediateConn = nil
}
connToReturn := intermediateConn

View File

@@ -19,14 +19,23 @@ limitations under the License.
package net
import (
"bufio"
"bytes"
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/util/wait"
)
func TestGetClientIP(t *testing.T) {
@@ -280,3 +289,153 @@ func TestJoinPreservingTrailingSlash(t *testing.T) {
})
}
}
func TestConnectWithRedirects(t *testing.T) {
tests := []struct {
desc string
redirects []string
method string // initial request method, empty == GET
expectError bool
expectedRedirects int
newPort bool // special case different port test
}{{
desc: "relative redirects allowed",
redirects: []string{"/ok"},
expectedRedirects: 1,
}, {
desc: "redirects to the same host are allowed",
redirects: []string{"http://HOST/ok"}, // HOST replaced with server address in test
expectedRedirects: 1,
}, {
desc: "POST redirects to GET",
method: http.MethodPost,
redirects: []string{"/ok"},
expectedRedirects: 1,
}, {
desc: "PUT redirects to GET",
method: http.MethodPut,
redirects: []string{"/ok"},
expectedRedirects: 1,
}, {
desc: "DELETE redirects to GET",
method: http.MethodDelete,
redirects: []string{"/ok"},
expectedRedirects: 1,
}, {
desc: "9 redirects are allowed",
redirects: []string{"/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9"},
expectedRedirects: 9,
}, {
desc: "10 redirects are forbidden",
redirects: []string{"/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/10"},
expectError: true,
}, {
desc: "redirect to different host are prevented",
redirects: []string{"http://example.com/foo"},
expectedRedirects: 0,
}, {
desc: "multiple redirect to different host forbidden",
redirects: []string{"/1", "/2", "/3", "http://example.com/foo"},
expectedRedirects: 3,
}, {
desc: "redirect to different port is allowed",
redirects: []string{"http://HOST/foo"},
expectedRedirects: 1,
newPort: true,
}}
const resultString = "Test output"
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
redirectCount := 0
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Verify redirect request.
if redirectCount > 0 {
expectedURL, err := url.Parse(test.redirects[redirectCount-1])
require.NoError(t, err, "test URL error")
assert.Equal(t, req.URL.Path, expectedURL.Path, "unknown redirect path")
assert.Equal(t, http.MethodGet, req.Method, "redirects must always be GET")
}
if redirectCount < len(test.redirects) {
http.Redirect(w, req, test.redirects[redirectCount], http.StatusFound)
redirectCount++
} else if redirectCount == len(test.redirects) {
w.Write([]byte(resultString))
} else {
t.Errorf("unexpected number of redirects %d to %s", redirectCount, req.URL.String())
}
}))
defer s.Close()
u, err := url.Parse(s.URL)
require.NoError(t, err, "Error parsing server URL")
host := u.Host
// Special case new-port test with a secondary server.
if test.newPort {
s2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte(resultString))
}))
defer s2.Close()
u2, err := url.Parse(s2.URL)
require.NoError(t, err, "Error parsing secondary server URL")
// Sanity check: secondary server uses same hostname, different port.
require.Equal(t, u.Hostname(), u2.Hostname(), "sanity check: same hostname")
require.NotEqual(t, u.Port(), u2.Port(), "sanity check: different port")
// Redirect to the secondary server.
host = u2.Host
}
// Update redirect URLs with actual host.
for i := range test.redirects {
test.redirects[i] = strings.Replace(test.redirects[i], "HOST", host, 1)
}
method := test.method
if method == "" {
method = http.MethodGet
}
netdialer := &net.Dialer{
Timeout: wait.ForeverTestTimeout,
KeepAlive: wait.ForeverTestTimeout,
}
dialer := DialerFunc(func(req *http.Request) (net.Conn, error) {
conn, err := netdialer.Dial("tcp", req.URL.Host)
if err != nil {
return conn, err
}
if err = req.Write(conn); err != nil {
require.NoError(t, conn.Close())
return nil, fmt.Errorf("error sending request: %v", err)
}
return conn, err
})
conn, rawResponse, err := ConnectWithRedirects(method, u, http.Header{} /*body*/, nil, dialer, true)
if test.expectError {
require.Error(t, err, "expected request error")
return
}
require.NoError(t, err, "unexpected request error")
assert.NoError(t, conn.Close(), "error closing connection")
resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(rawResponse)), nil)
require.NoError(t, err, "unexpected request error")
result, err := ioutil.ReadAll(resp.Body)
require.NoError(t, resp.Body.Close())
if test.expectedRedirects < len(test.redirects) {
// Expect the last redirect to be returned.
assert.Equal(t, http.StatusFound, resp.StatusCode, "Final response is not a redirect")
assert.Equal(t, test.redirects[len(test.redirects)-1], resp.Header.Get("Location"))
assert.NotEqual(t, resultString, string(result), "wrong content")
} else {
assert.Equal(t, resultString, string(result), "stream content does not match")
}
})
}
}

View File

@@ -68,6 +68,8 @@ type UpgradeAwareHandler struct {
// InterceptRedirects determines whether the proxy should sniff backend responses for redirects,
// following them as necessary.
InterceptRedirects bool
// RequireSameHostRedirects only allows redirects to the same host. It is only used if InterceptRedirects=true.
RequireSameHostRedirects bool
// UseRequestLocation will use the incoming request URL when talking to the backend server.
UseRequestLocation bool
// FlushInterval controls how often the standard HTTP proxy will flush content from the upstream.
@@ -256,7 +258,7 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques
utilnet.AppendForwardedForHeader(clone)
if h.InterceptRedirects {
glog.V(6).Infof("Connecting to backend proxy (intercepting redirects) %s\n Headers: %v", &location, clone.Header)
backendConn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, &location, clone.Header, req.Body, utilnet.DialerFunc(h.DialForUpgrade))
backendConn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, &location, clone.Header, req.Body, utilnet.DialerFunc(h.DialForUpgrade), h.RequireSameHostRedirects)
} else {
glog.V(6).Infof("Connecting to backend proxy (direct dial) %s\n Headers: %v", &location, clone.Header)
clone.URL = &location

View File

@@ -27,7 +27,14 @@ var rng = struct {
sync.Mutex
rand *rand.Rand
}{
rand: rand.New(rand.NewSource(time.Now().UTC().UnixNano())),
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
// Int returns a non-negative pseudo-random int.
func Int() int {
rng.Lock()
defer rng.Unlock()
return rng.rand.Int()
}
// Intn generates an integer in range [0,max).

View File

@@ -74,7 +74,7 @@ func getSchema(f Fake, model string) (openapi.Schema, error) {
return m.LookupModel(model), nil
}
// GetSchemaOrDie returns returns the openapi schema.
// GetSchemaOrDie returns the openapi schema.
func GetSchemaOrDie(f Fake, model string) openapi.Schema {
s, err := getSchema(f, model)
if err != nil {

View File

@@ -297,7 +297,7 @@ func TestIsValidLabelValue(t *testing.T) {
"end-with-num-1",
"1234", // only num
strings.Repeat("a", 63), // to the limit
"", // empty value
"", // empty value
}
for i := range successCases {
if errs := IsValidLabelValue(successCases[i]); len(errs) != 0 {

18
vendor/k8s.io/apimachinery/pkg/util/version/doc.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
/*
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 provides utilities for version number comparisons
package version // import "k8s.io/apimachinery/pkg/util/version"

285
vendor/k8s.io/apimachinery/pkg/util/version/version.go generated vendored Normal file
View File

@@ -0,0 +1,285 @@
/*
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"
"fmt"
"regexp"
"strconv"
"strings"
)
// Version is an opqaue 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
}
// 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
}
// 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
}
// 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
}
// 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 {
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
}
// 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
}
// 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
}

View File

@@ -0,0 +1,348 @@
/*
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 (
"fmt"
"reflect"
"testing"
)
type testItem struct {
version string
unparsed string
equalsPrev bool
}
func testOne(v *Version, item, prev testItem) error {
str := v.String()
if item.unparsed == "" {
if str != item.version {
return fmt.Errorf("bad round-trip: %q -> %q", item.version, str)
}
} else {
if str != item.unparsed {
return fmt.Errorf("bad unparse: %q -> %q, expected %q", item.version, str, item.unparsed)
}
}
if prev.version != "" {
cmp, err := v.Compare(prev.version)
if err != nil {
return fmt.Errorf("unexpected parse error: %v", err)
}
rv, err := parse(prev.version, v.semver)
if err != nil {
return fmt.Errorf("unexpected parse error: %v", err)
}
rcmp, err := rv.Compare(item.version)
if err != nil {
return fmt.Errorf("unexpected parse error: %v", err)
}
switch {
case cmp == -1:
return fmt.Errorf("unexpected ordering %q < %q", item.version, prev.version)
case cmp == 0 && !item.equalsPrev:
return fmt.Errorf("unexpected comparison %q == %q", item.version, prev.version)
case cmp == 1 && item.equalsPrev:
return fmt.Errorf("unexpected comparison %q != %q", item.version, prev.version)
case cmp != -rcmp:
return fmt.Errorf("unexpected reverse comparison %q <=> %q %v %v %v %v", item.version, prev.version, cmp, rcmp, v.Components(), rv.Components())
}
}
return nil
}
func TestSemanticVersions(t *testing.T) {
tests := []testItem{
// This is every version string that appears in the 2.0 semver spec,
// sorted in strictly increasing order except as noted.
{version: "0.1.0"},
{version: "1.0.0-0.3.7"},
{version: "1.0.0-alpha"},
{version: "1.0.0-alpha+001", equalsPrev: true},
{version: "1.0.0-alpha.1"},
{version: "1.0.0-alpha.beta"},
{version: "1.0.0-beta"},
{version: "1.0.0-beta+exp.sha.5114f85", equalsPrev: true},
{version: "1.0.0-beta.2"},
{version: "1.0.0-beta.11"},
{version: "1.0.0-rc.1"},
{version: "1.0.0-x.7.z.92"},
{version: "1.0.0"},
{version: "1.0.0+20130313144700", equalsPrev: true},
{version: "1.8.0-alpha.3"},
{version: "1.8.0-alpha.3.673+73326ef01d2d7c"},
{version: "1.9.0"},
{version: "1.10.0"},
{version: "1.11.0"},
{version: "2.0.0"},
{version: "2.1.0"},
{version: "2.1.1"},
{version: "42.0.0"},
// We also allow whitespace and "v" prefix
{version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true},
{version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true},
{version: "43.0.0-1", unparsed: "43.0.0-1"},
{version: "43.0.0-1 ", unparsed: "43.0.0-1", equalsPrev: true},
{version: "v43.0.0-1", unparsed: "43.0.0-1", equalsPrev: true},
{version: " v43.0.0", unparsed: "43.0.0"},
{version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true},
}
var prev testItem
for _, item := range tests {
v, err := ParseSemantic(item.version)
if err != nil {
t.Errorf("unexpected parse error: %v", err)
continue
}
err = testOne(v, item, prev)
if err != nil {
t.Errorf("%v", err)
}
prev = item
}
}
func TestBadSemanticVersions(t *testing.T) {
tests := []string{
// "MUST take the form X.Y.Z"
"1",
"1.2",
"1.2.3.4",
".2.3",
"1..3",
"1.2.",
"",
"..",
// "where X, Y, and Z are non-negative integers"
"-1.2.3",
"1.-2.3",
"1.2.-3",
"1a.2.3",
"1.2a.3",
"1.2.3a",
"a1.2.3",
"a.b.c",
"1 .2.3",
"1. 2.3",
// "and MUST NOT contain leading zeroes."
"01.2.3",
"1.02.3",
"1.2.03",
// "[pre-release] identifiers MUST comprise only ASCII alphanumerics and hyphen"
"1.2.3-/",
// "[pre-release] identifiers MUST NOT be empty"
"1.2.3-",
"1.2.3-.",
"1.2.3-foo.",
"1.2.3-.foo",
// "Numeric [pre-release] identifiers MUST NOT include leading zeroes"
"1.2.3-01",
// "[build metadata] identifiers MUST comprise only ASCII alphanumerics and hyphen"
"1.2.3+/",
// "[build metadata] identifiers MUST NOT be empty"
"1.2.3+",
"1.2.3+.",
"1.2.3+foo.",
"1.2.3+.foo",
// whitespace/"v"-prefix checks
"v 1.2.3",
"vv1.2.3",
}
for i := range tests {
_, err := ParseSemantic(tests[i])
if err == nil {
t.Errorf("unexpected success parsing invalid semver %q", tests[i])
}
}
}
func TestGenericVersions(t *testing.T) {
tests := []testItem{
// This is all of the strings from TestSemanticVersions, plus some strings
// from TestBadSemanticVersions that should parse as generic versions,
// plus some additional strings.
{version: "0.1.0", unparsed: "0.1.0"},
{version: "1.0.0-0.3.7", unparsed: "1.0.0"},
{version: "1.0.0-alpha", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0-alpha+001", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0-alpha.1", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0-alpha.beta", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0.beta", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0-beta+exp.sha.5114f85", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0.beta.2", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0.beta.11", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0.rc.1", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0-x.7.z.92", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0", unparsed: "1.0.0", equalsPrev: true},
{version: "1.0.0+20130313144700", unparsed: "1.0.0", equalsPrev: true},
{version: "1.2", unparsed: "1.2"},
{version: "1.2a.3", unparsed: "1.2", equalsPrev: true},
{version: "1.2.3", unparsed: "1.2.3"},
{version: "1.2.3.0", unparsed: "1.2.3.0", equalsPrev: true},
{version: "1.2.3a", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.3-foo.", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.3-.foo", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.3-01", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.3+", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.3+foo.", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.3+.foo", unparsed: "1.2.3", equalsPrev: true},
{version: "1.02.3", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.03", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.003", unparsed: "1.2.3", equalsPrev: true},
{version: "1.2.3.4", unparsed: "1.2.3.4"},
{version: "1.2.3.4b3", unparsed: "1.2.3.4", equalsPrev: true},
{version: "1.2.3.4.5", unparsed: "1.2.3.4.5"},
{version: "1.9.0", unparsed: "1.9.0"},
{version: "1.9.0.0.0.0.0.0", unparsed: "1.9.0.0.0.0.0.0", equalsPrev: true},
{version: "1.10.0", unparsed: "1.10.0"},
{version: "1.11.0", unparsed: "1.11.0"},
{version: "1.11.0.0.5", unparsed: "1.11.0.0.5"},
{version: "2.0.0", unparsed: "2.0.0"},
{version: "2.1.0", unparsed: "2.1.0"},
{version: "2.1.1", unparsed: "2.1.1"},
{version: "42.0.0", unparsed: "42.0.0"},
{version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true},
{version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true},
{version: "42.0.0-1", unparsed: "42.0.0", equalsPrev: true},
{version: "42.0.0-1 ", unparsed: "42.0.0", equalsPrev: true},
{version: "v42.0.0-1", unparsed: "42.0.0", equalsPrev: true},
{version: " v43.0.0", unparsed: "43.0.0"},
{version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true},
}
var prev testItem
for _, item := range tests {
v, err := ParseGeneric(item.version)
if err != nil {
t.Errorf("unexpected parse error: %v", err)
continue
}
err = testOne(v, item, prev)
if err != nil {
t.Errorf("%v", err)
}
prev = item
}
}
func TestBadGenericVersions(t *testing.T) {
tests := []string{
"1",
"01.2.3",
"-1.2.3",
"1.-2.3",
".2.3",
"1..3",
"1a.2.3",
"a1.2.3",
"1 .2.3",
"1. 2.3",
"1.bob",
"bob",
"v 1.2.3",
"vv1.2.3",
"",
".",
}
for i := range tests {
_, err := ParseGeneric(tests[i])
if err == nil {
t.Errorf("unexpected success parsing invalid version %q", tests[i])
}
}
}
func TestComponents(t *testing.T) {
var tests = []struct {
version string
semver bool
expectedComponents []uint
expectedMajor uint
expectedMinor uint
expectedPatch uint
expectedPreRelease string
expectedBuildMetadata string
}{
{
version: "1.0.2",
semver: true,
expectedComponents: []uint{1, 0, 2},
expectedMajor: 1,
expectedMinor: 0,
expectedPatch: 2,
},
{
version: "1.0.2-alpha+001",
semver: true,
expectedComponents: []uint{1, 0, 2},
expectedMajor: 1,
expectedMinor: 0,
expectedPatch: 2,
expectedPreRelease: "alpha",
expectedBuildMetadata: "001",
},
{
version: "1.2",
semver: false,
expectedComponents: []uint{1, 2},
expectedMajor: 1,
expectedMinor: 2,
},
{
version: "1.0.2-beta+exp.sha.5114f85",
semver: true,
expectedComponents: []uint{1, 0, 2},
expectedMajor: 1,
expectedMinor: 0,
expectedPatch: 2,
expectedPreRelease: "beta",
expectedBuildMetadata: "exp.sha.5114f85",
},
}
for _, test := range tests {
version, _ := parse(test.version, test.semver)
if !reflect.DeepEqual(test.expectedComponents, version.Components()) {
t.Error("parse returned un'expected components")
}
if test.expectedMajor != version.Major() {
t.Errorf("parse returned version.Major %d, expected %d", test.expectedMajor, version.Major())
}
if test.expectedMinor != version.Minor() {
t.Errorf("parse returned version.Minor %d, expected %d", test.expectedMinor, version.Minor())
}
if test.expectedPatch != version.Patch() {
t.Errorf("parse returned version.Patch %d, expected %d", test.expectedPatch, version.Patch())
}
if test.expectedPreRelease != version.PreRelease() {
t.Errorf("parse returned version.PreRelease %s, expected %s", test.expectedPreRelease, version.PreRelease())
}
if test.expectedBuildMetadata != version.BuildMetadata() {
t.Errorf("parse returned version.BuildMetadata %s, expected %s", test.expectedBuildMetadata, version.BuildMetadata())
}
}
}

View File

@@ -197,8 +197,8 @@ stuff: 1
t.Fatal("expected error with yaml: violate, got no error")
}
fmt.Printf("err: %s\n", err.Error())
if !strings.Contains(err.Error(), "yaml: line 2:") {
t.Fatalf("expected %q to have 'yaml: line 2:' found a tab character", err.Error())
if !strings.Contains(err.Error(), "yaml: line 3:") {
t.Fatalf("expected %q to have 'yaml: line 3:' found a tab character", err.Error())
}
}