mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
When manually setting the API version to use, automatic API version negotiation should no longer be performed. Instead of keeping track of these options individually, we can mark negotiation to have happend if either the version was set manually, or if API version negotiation took place. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
370 lines
11 KiB
Go
370 lines
11 KiB
Go
package client
|
|
|
|
import (
|
|
"net/http"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
)
|
|
|
|
func TestOptionWithHostFromEnv(t *testing.T) {
|
|
c, err := New(WithHostFromEnv())
|
|
assert.NilError(t, err)
|
|
assert.Check(t, c.client != nil)
|
|
assert.Check(t, is.Equal(c.basePath, ""))
|
|
if runtime.GOOS == "windows" {
|
|
assert.Check(t, is.Equal(c.host, "npipe:////./pipe/docker_engine"))
|
|
assert.Check(t, is.Equal(c.proto, "npipe"))
|
|
assert.Check(t, is.Equal(c.addr, "//./pipe/docker_engine"))
|
|
} else {
|
|
assert.Check(t, is.Equal(c.host, "unix:///var/run/docker.sock"))
|
|
assert.Check(t, is.Equal(c.proto, "unix"))
|
|
assert.Check(t, is.Equal(c.addr, "/var/run/docker.sock"))
|
|
}
|
|
|
|
t.Setenv("DOCKER_HOST", "tcp://foo.example.com:2376/test/")
|
|
|
|
c, err = New(WithHostFromEnv())
|
|
assert.NilError(t, err)
|
|
assert.Check(t, c.client != nil)
|
|
assert.Check(t, is.Equal(c.basePath, "/test/"))
|
|
assert.Check(t, is.Equal(c.host, "tcp://foo.example.com:2376/test/"))
|
|
assert.Check(t, is.Equal(c.proto, "tcp"))
|
|
assert.Check(t, is.Equal(c.addr, "foo.example.com:2376"))
|
|
}
|
|
|
|
func TestOptionWithTimeout(t *testing.T) {
|
|
timeout := 10 * time.Second
|
|
c, err := New(WithTimeout(timeout))
|
|
assert.NilError(t, err)
|
|
assert.Check(t, c.client != nil)
|
|
assert.Check(t, is.Equal(c.client.Timeout, timeout))
|
|
}
|
|
|
|
func TestOptionWithAPIVersion(t *testing.T) {
|
|
tests := []struct {
|
|
doc string
|
|
version string
|
|
expected string
|
|
expError string
|
|
}{
|
|
{
|
|
doc: "empty version",
|
|
version: "",
|
|
expected: MaxAPIVersion,
|
|
},
|
|
{
|
|
doc: "custom lower version with whitespace, no v-prefix",
|
|
version: " 1.50 ",
|
|
expected: "1.50",
|
|
},
|
|
{
|
|
// We currently allow downgrading the client to an unsupported lower version for testing.
|
|
doc: "downgrade unsupported version, no v-prefix",
|
|
version: "1.0",
|
|
expected: "1.0",
|
|
},
|
|
{
|
|
doc: "custom lower version, no v-prefix",
|
|
version: "1.50",
|
|
expected: "1.50",
|
|
},
|
|
{
|
|
// We currently allow upgrading the client to an unsupported higher version for testing.
|
|
doc: "upgrade version, no v-prefix",
|
|
version: "9.99",
|
|
expected: "9.99",
|
|
},
|
|
{
|
|
doc: "empty version, with v-prefix",
|
|
version: "v",
|
|
expected: MaxAPIVersion,
|
|
},
|
|
{
|
|
doc: "whitespace, with v-prefix",
|
|
version: " v1.0 ",
|
|
expected: "1.0",
|
|
},
|
|
{
|
|
doc: "downgrade unsupported version, with v-prefix",
|
|
version: "v1.0",
|
|
expected: "1.0",
|
|
},
|
|
{
|
|
doc: "custom lower version with whitespace and v-prefix",
|
|
version: " v1.50 ",
|
|
expected: "1.50",
|
|
},
|
|
{
|
|
doc: "custom lower version, with v-prefix",
|
|
version: "v1.50",
|
|
expected: "1.50",
|
|
},
|
|
{
|
|
doc: "upgrade version, with v-prefix",
|
|
version: "v9.99",
|
|
expected: "9.99",
|
|
},
|
|
{
|
|
doc: "malformed version",
|
|
version: "something-weird",
|
|
expError: "invalid API version (something-weird): must be formatted <major>.<minor>",
|
|
},
|
|
{
|
|
doc: "no minor",
|
|
version: "1",
|
|
expError: "invalid API version (1): must be formatted <major>.<minor>",
|
|
},
|
|
{
|
|
doc: "too many digits",
|
|
version: "1.2.3",
|
|
expError: "invalid API version (1.2.3): invalid minor version: must be formatted <major>.<minor>",
|
|
},
|
|
{
|
|
doc: "embedded whitespace",
|
|
version: "v 1.0",
|
|
expError: "invalid API version (v 1.0): invalid major version: must be formatted <major>.<minor>",
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
client, err := New(WithAPIVersion(tc.version))
|
|
if tc.expError != "" {
|
|
assert.Check(t, is.ErrorContains(err, tc.expError))
|
|
assert.Check(t, client == nil)
|
|
} else {
|
|
assert.NilError(t, err)
|
|
assert.Check(t, client != nil)
|
|
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
|
|
isNoOp := strings.TrimPrefix(strings.TrimSpace(tc.version), "v") == ""
|
|
assert.Check(t, is.Equal(client.negotiated.Load(), !isNoOp))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOptionWithAPIVersionFromEnv(t *testing.T) {
|
|
tests := []struct {
|
|
doc string
|
|
version string
|
|
expected string
|
|
expError string
|
|
}{
|
|
{
|
|
doc: "empty version",
|
|
version: "",
|
|
expected: MaxAPIVersion,
|
|
},
|
|
{
|
|
doc: "custom lower version with whitespace, no v-prefix",
|
|
version: " 1.50 ",
|
|
expected: "1.50",
|
|
},
|
|
{
|
|
// We currently allow downgrading the client to an unsupported lower version for testing.
|
|
doc: "downgrade unsupported version, no v-prefix",
|
|
version: "1.0",
|
|
expected: "1.0",
|
|
},
|
|
{
|
|
doc: "custom lower version, no v-prefix",
|
|
version: "1.50",
|
|
expected: "1.50",
|
|
},
|
|
{
|
|
// We currently allow upgrading the client to an unsupported higher version for testing.
|
|
doc: "upgrade version, no v-prefix",
|
|
version: "9.99",
|
|
expected: "9.99",
|
|
},
|
|
{
|
|
doc: "empty version, with v-prefix",
|
|
version: "v",
|
|
expected: MaxAPIVersion,
|
|
},
|
|
{
|
|
doc: "whitespace, with v-prefix",
|
|
version: " v1.0 ",
|
|
expected: "1.0",
|
|
},
|
|
{
|
|
doc: "downgrade unsupported version, with v-prefix",
|
|
version: "v1.0",
|
|
expected: "1.0",
|
|
},
|
|
{
|
|
doc: "custom lower version with whitespace and v-prefix",
|
|
version: " v1.50 ",
|
|
expected: "1.50",
|
|
},
|
|
{
|
|
doc: "custom lower version, with v-prefix",
|
|
version: "v1.50",
|
|
expected: "1.50",
|
|
},
|
|
{
|
|
doc: "upgrade version, with v-prefix",
|
|
version: "v9.99",
|
|
expected: "9.99",
|
|
},
|
|
{
|
|
doc: "malformed version",
|
|
version: "something-weird",
|
|
expError: "invalid API version (something-weird): must be formatted <major>.<minor>",
|
|
},
|
|
{
|
|
doc: "no minor",
|
|
version: "1",
|
|
expError: "invalid API version (1): must be formatted <major>.<minor>",
|
|
},
|
|
{
|
|
doc: "too many digits",
|
|
version: "1.2.3",
|
|
expError: "invalid API version (1.2.3): invalid minor version: must be formatted <major>.<minor>",
|
|
},
|
|
{
|
|
doc: "embedded whitespace",
|
|
version: "v 1.0",
|
|
expError: "invalid API version (v 1.0): invalid major version: must be formatted <major>.<minor>",
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
t.Setenv(EnvOverrideAPIVersion, tc.version)
|
|
client, err := New(WithAPIVersionFromEnv())
|
|
if tc.expError != "" {
|
|
assert.Check(t, is.ErrorContains(err, tc.expError))
|
|
assert.Check(t, client == nil)
|
|
} else {
|
|
assert.NilError(t, err)
|
|
assert.Check(t, client != nil)
|
|
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
|
|
isNoOp := strings.TrimPrefix(strings.TrimSpace(tc.version), "v") == ""
|
|
assert.Check(t, is.Equal(client.negotiated.Load(), !isNoOp))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestOptionOverridePriority validates that overriding the API version through
|
|
// [WithAPIVersionFromEnv] takes precedence over other manual options, regardless
|
|
// the order in which they're passed.
|
|
func TestOptionOverridePriority(t *testing.T) {
|
|
t.Run("no env-var set", func(t *testing.T) {
|
|
client, err := New(WithAPIVersionFromEnv(), WithAPIVersion("1.50"))
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(client.ClientVersion(), "1.50"))
|
|
assert.Check(t, is.Equal(client.negotiated.Load(), true))
|
|
})
|
|
|
|
const expected = "1.51"
|
|
t.Setenv(EnvOverrideAPIVersion, expected)
|
|
|
|
t.Run("WithAPIVersionFromEnv first", func(t *testing.T) {
|
|
client, err := New(WithAPIVersionFromEnv(), WithAPIVersion("1.50"))
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(client.ClientVersion(), expected))
|
|
assert.Check(t, is.Equal(client.negotiated.Load(), true))
|
|
})
|
|
|
|
t.Run("WithAPIVersionFromEnv last", func(t *testing.T) {
|
|
client, err := New(WithAPIVersion("1.50"), WithAPIVersionFromEnv())
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(client.ClientVersion(), expected))
|
|
assert.Check(t, is.Equal(client.negotiated.Load(), true))
|
|
})
|
|
|
|
t.Run("FromEnv first", func(t *testing.T) {
|
|
client, err := New(FromEnv, WithAPIVersion("1.50"))
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(client.ClientVersion(), expected))
|
|
assert.Check(t, is.Equal(client.negotiated.Load(), true))
|
|
})
|
|
|
|
t.Run("FromEnv last", func(t *testing.T) {
|
|
client, err := New(WithAPIVersion("1.50"), FromEnv)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(client.ClientVersion(), expected))
|
|
assert.Check(t, is.Equal(client.negotiated.Load(), true))
|
|
})
|
|
}
|
|
|
|
func TestWithUserAgent(t *testing.T) {
|
|
const userAgent = "Magic-Client/v1.2.3"
|
|
t.Run("user-agent", func(t *testing.T) {
|
|
c, err := New(
|
|
WithUserAgent(userAgent),
|
|
WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
|
|
return &http.Response{StatusCode: http.StatusOK}, nil
|
|
}),
|
|
)
|
|
assert.NilError(t, err)
|
|
_, err = c.Ping(t.Context(), PingOptions{})
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, c.Close())
|
|
})
|
|
t.Run("user-agent and custom headers", func(t *testing.T) {
|
|
c, err := New(
|
|
WithUserAgent(userAgent),
|
|
WithHTTPHeaders(map[string]string{"User-Agent": "should-be-ignored/1.0.0", "Other-Header": "hello-world"}),
|
|
WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
|
|
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
|
|
return &http.Response{StatusCode: http.StatusOK}, nil
|
|
}),
|
|
)
|
|
assert.NilError(t, err)
|
|
_, err = c.Ping(t.Context(), PingOptions{})
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, c.Close())
|
|
})
|
|
t.Run("custom headers", func(t *testing.T) {
|
|
c, err := New(
|
|
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
|
|
WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), "from-custom-headers/1.0.0"))
|
|
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
|
|
return &http.Response{StatusCode: http.StatusOK}, nil
|
|
}),
|
|
)
|
|
assert.NilError(t, err)
|
|
_, err = c.Ping(t.Context(), PingOptions{})
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, c.Close())
|
|
})
|
|
t.Run("no user-agent set", func(t *testing.T) {
|
|
c, err := New(
|
|
WithHTTPHeaders(map[string]string{"Other-Header": "hello-world"}),
|
|
WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
|
|
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
|
|
return &http.Response{StatusCode: http.StatusOK}, nil
|
|
}),
|
|
)
|
|
assert.NilError(t, err)
|
|
_, err = c.Ping(t.Context(), PingOptions{})
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, c.Close())
|
|
})
|
|
t.Run("reset custom user-agent", func(t *testing.T) {
|
|
c, err := New(
|
|
WithUserAgent(""),
|
|
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
|
|
WithMockClient(func(req *http.Request) (*http.Response, error) {
|
|
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
|
|
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
|
|
return &http.Response{StatusCode: http.StatusOK}, nil
|
|
}),
|
|
)
|
|
assert.NilError(t, err)
|
|
_, err = c.Ping(t.Context(), PingOptions{})
|
|
assert.NilError(t, err)
|
|
assert.NilError(t, c.Close())
|
|
})
|
|
}
|