2017-08-18 12:49:23 -04:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
2019-01-28 08:52:58 -05:00
|
|
|
"bytes"
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2017-12-20 09:04:41 -05:00
|
|
|
"crypto/x509"
|
2019-01-28 08:52:58 -05:00
|
|
|
"fmt"
|
2022-02-25 08:35:28 -05:00
|
|
|
"io"
|
2017-08-18 12:49:23 -04:00
|
|
|
"os"
|
2018-02-27 10:54:36 -05:00
|
|
|
"runtime"
|
2017-08-18 12:49:23 -04:00
|
|
|
"testing"
|
|
|
|
|
2017-12-20 09:04:41 -05:00
|
|
|
cliconfig "github.com/docker/cli/cli/config"
|
2017-08-18 12:49:23 -04:00
|
|
|
"github.com/docker/cli/cli/config/configfile"
|
|
|
|
"github.com/docker/cli/cli/flags"
|
|
|
|
"github.com/docker/docker/api"
|
2017-09-20 16:13:03 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
2017-08-18 12:49:23 -04:00
|
|
|
"github.com/docker/docker/client"
|
2017-09-20 16:13:03 -04:00
|
|
|
"github.com/pkg/errors"
|
2020-02-22 12:12:14 -05:00
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
is "gotest.tools/v3/assert/cmp"
|
|
|
|
"gotest.tools/v3/env"
|
|
|
|
"gotest.tools/v3/fs"
|
2017-08-18 12:49:23 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestNewAPIClientFromFlags(t *testing.T) {
|
|
|
|
host := "unix://path"
|
2018-02-27 10:54:36 -05:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
host = "npipe://./"
|
|
|
|
}
|
2017-08-18 12:49:23 -04:00
|
|
|
opts := &flags.CommonOptions{Hosts: []string{host}}
|
|
|
|
configFile := &configfile.ConfigFile{
|
|
|
|
HTTPHeaders: map[string]string{
|
|
|
|
"My-Header": "Custom-Value",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
apiclient, err := NewAPIClientFromFlags(opts, configFile)
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(host, apiclient.DaemonHost()))
|
2017-08-18 12:49:23 -04:00
|
|
|
|
|
|
|
expectedHeaders := map[string]string{
|
|
|
|
"My-Header": "Custom-Value",
|
|
|
|
"User-Agent": UserAgent(),
|
|
|
|
}
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.DeepEqual(expectedHeaders, apiclient.(*client.Client).CustomHTTPHeaders()))
|
|
|
|
assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
|
2020-09-29 11:01:55 -04:00
|
|
|
assert.DeepEqual(t, configFile.HTTPHeaders, map[string]string{"My-Header": "Custom-Value"})
|
2017-08-18 12:49:23 -04:00
|
|
|
}
|
|
|
|
|
2018-10-16 03:34:54 -04:00
|
|
|
func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
|
|
|
|
host := ":2375"
|
|
|
|
opts := &flags.CommonOptions{Hosts: []string{host}}
|
|
|
|
configFile := &configfile.ConfigFile{
|
|
|
|
HTTPHeaders: map[string]string{
|
|
|
|
"My-Header": "Custom-Value",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
apiclient, err := NewAPIClientFromFlags(opts, configFile)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal("tcp://localhost"+host, apiclient.DaemonHost()))
|
|
|
|
|
|
|
|
expectedHeaders := map[string]string{
|
|
|
|
"My-Header": "Custom-Value",
|
|
|
|
"User-Agent": UserAgent(),
|
|
|
|
}
|
|
|
|
assert.Check(t, is.DeepEqual(expectedHeaders, apiclient.(*client.Client).CustomHTTPHeaders()))
|
|
|
|
assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
|
|
|
|
}
|
|
|
|
|
2017-08-18 12:49:23 -04:00
|
|
|
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
|
|
|
|
customVersion := "v3.3.3"
|
2018-04-23 08:13:52 -04:00
|
|
|
defer env.Patch(t, "DOCKER_API_VERSION", customVersion)()
|
2018-12-17 05:27:07 -05:00
|
|
|
defer env.Patch(t, "DOCKER_HOST", ":2375")()
|
2017-08-18 12:49:23 -04:00
|
|
|
|
|
|
|
opts := &flags.CommonOptions{}
|
|
|
|
configFile := &configfile.ConfigFile{}
|
|
|
|
apiclient, err := NewAPIClientFromFlags(opts, configFile)
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(customVersion, apiclient.ClientVersion()))
|
2017-08-18 12:49:23 -04:00
|
|
|
}
|
|
|
|
|
2017-09-20 16:13:03 -04:00
|
|
|
type fakeClient struct {
|
|
|
|
client.Client
|
|
|
|
pingFunc func() (types.Ping, error)
|
|
|
|
version string
|
|
|
|
negotiated bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeClient) Ping(_ context.Context) (types.Ping, error) {
|
|
|
|
return c.pingFunc()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeClient) ClientVersion() string {
|
|
|
|
return c.version
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *fakeClient) NegotiateAPIVersionPing(types.Ping) {
|
|
|
|
c.negotiated = true
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInitializeFromClient(t *testing.T) {
|
|
|
|
defaultVersion := "v1.55"
|
|
|
|
|
|
|
|
var testcases = []struct {
|
|
|
|
doc string
|
|
|
|
pingFunc func() (types.Ping, error)
|
|
|
|
expectedServer ServerInfo
|
|
|
|
negotiated bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
doc: "successful ping",
|
|
|
|
pingFunc: func() (types.Ping, error) {
|
|
|
|
return types.Ping{Experimental: true, OSType: "linux", APIVersion: "v1.30"}, nil
|
|
|
|
},
|
|
|
|
expectedServer: ServerInfo{HasExperimental: true, OSType: "linux"},
|
|
|
|
negotiated: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "failed ping, no API version",
|
|
|
|
pingFunc: func() (types.Ping, error) {
|
|
|
|
return types.Ping{}, errors.New("failed")
|
|
|
|
},
|
|
|
|
expectedServer: ServerInfo{HasExperimental: true},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "failed ping, with API version",
|
|
|
|
pingFunc: func() (types.Ping, error) {
|
|
|
|
return types.Ping{APIVersion: "v1.33"}, errors.New("failed")
|
|
|
|
},
|
|
|
|
expectedServer: ServerInfo{HasExperimental: true},
|
|
|
|
negotiated: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testcase := range testcases {
|
2019-10-29 09:37:37 -04:00
|
|
|
testcase := testcase
|
2017-09-20 16:13:03 -04:00
|
|
|
t.Run(testcase.doc, func(t *testing.T) {
|
|
|
|
apiclient := &fakeClient{
|
|
|
|
pingFunc: testcase.pingFunc,
|
|
|
|
version: defaultVersion,
|
|
|
|
}
|
|
|
|
|
|
|
|
cli := &DockerCli{client: apiclient}
|
|
|
|
cli.initializeFromClient()
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.DeepEqual(testcase.expectedServer, cli.serverInfo))
|
|
|
|
assert.Check(t, is.Equal(testcase.negotiated, apiclient.negotiated))
|
2017-09-20 16:13:03 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-02 08:19:34 -04:00
|
|
|
// The CLI no longer disables/hides experimental CLI features, however, we need
|
|
|
|
// to verify that existing configuration files do not break
|
2017-12-20 09:04:41 -05:00
|
|
|
func TestExperimentalCLI(t *testing.T) {
|
|
|
|
defaultVersion := "v1.55"
|
|
|
|
|
|
|
|
var testcases = []struct {
|
2020-10-02 08:19:34 -04:00
|
|
|
doc string
|
|
|
|
configfile string
|
2017-12-20 09:04:41 -05:00
|
|
|
}{
|
|
|
|
{
|
2020-10-02 08:19:34 -04:00
|
|
|
doc: "default",
|
|
|
|
configfile: `{}`,
|
2017-12-20 09:04:41 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "experimental",
|
|
|
|
configfile: `{
|
|
|
|
"experimental": "enabled"
|
|
|
|
}`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testcase := range testcases {
|
2019-10-29 09:37:37 -04:00
|
|
|
testcase := testcase
|
2017-12-20 09:04:41 -05:00
|
|
|
t.Run(testcase.doc, func(t *testing.T) {
|
|
|
|
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
|
|
|
|
defer dir.Remove()
|
|
|
|
apiclient := &fakeClient{
|
|
|
|
version: defaultVersion,
|
2019-01-31 12:50:58 -05:00
|
|
|
pingFunc: func() (types.Ping, error) {
|
|
|
|
return types.Ping{Experimental: true, OSType: "linux", APIVersion: defaultVersion}, nil
|
|
|
|
},
|
2017-12-20 09:04:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
cli := &DockerCli{client: apiclient, err: os.Stderr}
|
|
|
|
cliconfig.SetDir(dir.Path())
|
|
|
|
err := cli.Initialize(flags.NewClientOptions())
|
2018-03-06 14:44:13 -05:00
|
|
|
assert.NilError(t, err)
|
2020-10-02 08:19:34 -04:00
|
|
|
// For backward-compatibility, HasExperimental will always be "true"
|
|
|
|
assert.Check(t, is.Equal(true, cli.ClientInfo().HasExperimental))
|
2017-12-20 09:04:41 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-20 16:13:03 -04:00
|
|
|
func TestGetClientWithPassword(t *testing.T) {
|
|
|
|
expected := "password"
|
|
|
|
|
|
|
|
var testcases = []struct {
|
|
|
|
doc string
|
|
|
|
password string
|
|
|
|
retrieverErr error
|
|
|
|
retrieverGiveup bool
|
|
|
|
newClientErr error
|
|
|
|
expectedErr string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
doc: "successful connect",
|
|
|
|
password: expected,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "password retriever exhausted",
|
|
|
|
retrieverGiveup: true,
|
|
|
|
retrieverErr: errors.New("failed"),
|
|
|
|
expectedErr: "private key is encrypted, but could not get passphrase",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "password retriever error",
|
|
|
|
retrieverErr: errors.New("failed"),
|
|
|
|
expectedErr: "failed",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "newClient error",
|
|
|
|
newClientErr: errors.New("failed to connect"),
|
|
|
|
expectedErr: "failed to connect",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testcase := range testcases {
|
2019-10-29 09:37:37 -04:00
|
|
|
testcase := testcase
|
2017-09-20 16:13:03 -04:00
|
|
|
t.Run(testcase.doc, func(t *testing.T) {
|
|
|
|
passRetriever := func(_, _ string, _ bool, attempts int) (passphrase string, giveup bool, err error) {
|
|
|
|
// Always return an invalid pass first to test iteration
|
|
|
|
switch attempts {
|
|
|
|
case 0:
|
|
|
|
return "something else", false, nil
|
|
|
|
default:
|
|
|
|
return testcase.password, testcase.retrieverGiveup, testcase.retrieverErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newClient := func(currentPassword string) (client.APIClient, error) {
|
|
|
|
if testcase.newClientErr != nil {
|
|
|
|
return nil, testcase.newClientErr
|
|
|
|
}
|
|
|
|
if currentPassword == expected {
|
|
|
|
return &client.Client{}, nil
|
|
|
|
}
|
|
|
|
return &client.Client{}, x509.IncorrectPasswordError
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := getClientWithPassword(passRetriever, newClient)
|
|
|
|
if testcase.expectedErr != "" {
|
2018-03-06 14:03:47 -05:00
|
|
|
assert.ErrorContains(t, err, testcase.expectedErr)
|
2017-09-20 16:13:03 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-03-06 14:44:13 -05:00
|
|
|
assert.NilError(t, err)
|
2017-09-20 16:13:03 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-01-28 08:52:58 -05:00
|
|
|
|
|
|
|
func TestNewDockerCliAndOperators(t *testing.T) {
|
|
|
|
// Test default operations and also overriding default ones
|
|
|
|
cli, err := NewDockerCli(
|
|
|
|
WithContentTrust(true),
|
|
|
|
)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
// Check streams are initialized
|
|
|
|
assert.Check(t, cli.In() != nil)
|
|
|
|
assert.Check(t, cli.Out() != nil)
|
|
|
|
assert.Check(t, cli.Err() != nil)
|
|
|
|
assert.Equal(t, cli.ContentTrustEnabled(), true)
|
|
|
|
|
|
|
|
// Apply can modify a dockerCli after construction
|
|
|
|
inbuf := bytes.NewBuffer([]byte("input"))
|
|
|
|
outbuf := bytes.NewBuffer(nil)
|
|
|
|
errbuf := bytes.NewBuffer(nil)
|
2019-10-29 07:15:01 -04:00
|
|
|
err = cli.Apply(
|
2022-02-25 08:35:28 -05:00
|
|
|
WithInputStream(io.NopCloser(inbuf)),
|
2019-01-28 08:52:58 -05:00
|
|
|
WithOutputStream(outbuf),
|
|
|
|
WithErrorStream(errbuf),
|
|
|
|
)
|
2019-10-29 07:15:01 -04:00
|
|
|
assert.NilError(t, err)
|
2019-01-28 08:52:58 -05:00
|
|
|
// Check input stream
|
2022-02-25 08:35:28 -05:00
|
|
|
inputStream, err := io.ReadAll(cli.In())
|
2019-01-28 08:52:58 -05:00
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Equal(t, string(inputStream), "input")
|
|
|
|
// Check output stream
|
|
|
|
fmt.Fprintf(cli.Out(), "output")
|
2022-02-25 08:35:28 -05:00
|
|
|
outputStream, err := io.ReadAll(outbuf)
|
2019-01-28 08:52:58 -05:00
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Equal(t, string(outputStream), "output")
|
|
|
|
// Check error stream
|
|
|
|
fmt.Fprintf(cli.Err(), "error")
|
2022-02-25 08:35:28 -05:00
|
|
|
errStream, err := io.ReadAll(errbuf)
|
2019-01-28 08:52:58 -05:00
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Equal(t, string(errStream), "error")
|
|
|
|
}
|
2019-03-07 09:35:11 -05:00
|
|
|
|
|
|
|
func TestInitializeShouldAlwaysCreateTheContextStore(t *testing.T) {
|
|
|
|
cli, err := NewDockerCli()
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.NilError(t, cli.Initialize(flags.NewClientOptions(), WithInitializeClient(func(cli *DockerCli) (client.APIClient, error) {
|
|
|
|
return client.NewClientWithOpts()
|
|
|
|
})))
|
|
|
|
assert.Check(t, cli.ContextStore() != nil)
|
|
|
|
}
|