github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/command/cli_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/x509"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"os"
    11  	"runtime"
    12  	"testing"
    13  
    14  	cliconfig "github.com/docker/cli/cli/config"
    15  	"github.com/docker/cli/cli/config/configfile"
    16  	"github.com/docker/cli/cli/flags"
    17  	"github.com/docker/docker/api"
    18  	"github.com/docker/docker/api/types"
    19  	"github.com/docker/docker/client"
    20  	"github.com/pkg/errors"
    21  	"gotest.tools/v3/assert"
    22  	is "gotest.tools/v3/assert/cmp"
    23  	"gotest.tools/v3/env"
    24  	"gotest.tools/v3/fs"
    25  )
    26  
    27  func TestNewAPIClientFromFlags(t *testing.T) {
    28  	host := "unix://path"
    29  	if runtime.GOOS == "windows" {
    30  		host = "npipe://./"
    31  	}
    32  	opts := &flags.CommonOptions{Hosts: []string{host}}
    33  	configFile := &configfile.ConfigFile{
    34  		HTTPHeaders: map[string]string{
    35  			"My-Header": "Custom-Value",
    36  		},
    37  	}
    38  	apiclient, err := NewAPIClientFromFlags(opts, configFile)
    39  	assert.NilError(t, err)
    40  	assert.Check(t, is.Equal(host, apiclient.DaemonHost()))
    41  
    42  	expectedHeaders := map[string]string{
    43  		"My-Header":  "Custom-Value",
    44  		"User-Agent": UserAgent(),
    45  	}
    46  	assert.Check(t, is.DeepEqual(expectedHeaders, apiclient.(*client.Client).CustomHTTPHeaders()))
    47  	assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
    48  	assert.DeepEqual(t, configFile.HTTPHeaders, map[string]string{"My-Header": "Custom-Value"})
    49  }
    50  
    51  func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
    52  	host := ":2375"
    53  	opts := &flags.CommonOptions{Hosts: []string{host}}
    54  	configFile := &configfile.ConfigFile{
    55  		HTTPHeaders: map[string]string{
    56  			"My-Header": "Custom-Value",
    57  		},
    58  	}
    59  	apiclient, err := NewAPIClientFromFlags(opts, configFile)
    60  	assert.NilError(t, err)
    61  	assert.Check(t, is.Equal("tcp://localhost"+host, apiclient.DaemonHost()))
    62  
    63  	expectedHeaders := map[string]string{
    64  		"My-Header":  "Custom-Value",
    65  		"User-Agent": UserAgent(),
    66  	}
    67  	assert.Check(t, is.DeepEqual(expectedHeaders, apiclient.(*client.Client).CustomHTTPHeaders()))
    68  	assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
    69  }
    70  
    71  func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
    72  	customVersion := "v3.3.3"
    73  	defer env.Patch(t, "DOCKER_API_VERSION", customVersion)()
    74  	defer env.Patch(t, "DOCKER_HOST", ":2375")()
    75  
    76  	opts := &flags.CommonOptions{}
    77  	configFile := &configfile.ConfigFile{}
    78  	apiclient, err := NewAPIClientFromFlags(opts, configFile)
    79  	assert.NilError(t, err)
    80  	assert.Check(t, is.Equal(customVersion, apiclient.ClientVersion()))
    81  }
    82  
    83  func TestNewAPIClientFromFlagsWithHttpProxyEnv(t *testing.T) {
    84  	defer env.Patch(t, "HTTP_PROXY", "http://proxy.acme.com:1234")()
    85  	defer env.Patch(t, "DOCKER_HOST", "tcp://docker.acme.com:2376")()
    86  
    87  	opts := &flags.CommonOptions{}
    88  	configFile := &configfile.ConfigFile{}
    89  	apiclient, err := NewAPIClientFromFlags(opts, configFile)
    90  	assert.NilError(t, err)
    91  	transport, ok := apiclient.HTTPClient().Transport.(*http.Transport)
    92  	assert.Assert(t, ok)
    93  	assert.Assert(t, transport.Proxy != nil)
    94  	request, err := http.NewRequest(http.MethodGet, "tcp://docker.acme.com:2376", nil)
    95  	assert.NilError(t, err)
    96  	url, err := transport.Proxy(request)
    97  	assert.NilError(t, err)
    98  	assert.Check(t, is.Equal("http://proxy.acme.com:1234", url.String()))
    99  }
   100  
   101  type fakeClient struct {
   102  	client.Client
   103  	pingFunc   func() (types.Ping, error)
   104  	version    string
   105  	negotiated bool
   106  }
   107  
   108  func (c *fakeClient) Ping(_ context.Context) (types.Ping, error) {
   109  	return c.pingFunc()
   110  }
   111  
   112  func (c *fakeClient) ClientVersion() string {
   113  	return c.version
   114  }
   115  
   116  func (c *fakeClient) NegotiateAPIVersionPing(types.Ping) {
   117  	c.negotiated = true
   118  }
   119  
   120  func TestInitializeFromClient(t *testing.T) {
   121  	defaultVersion := "v1.55"
   122  
   123  	var testcases = []struct {
   124  		doc            string
   125  		pingFunc       func() (types.Ping, error)
   126  		expectedServer ServerInfo
   127  		negotiated     bool
   128  	}{
   129  		{
   130  			doc: "successful ping",
   131  			pingFunc: func() (types.Ping, error) {
   132  				return types.Ping{Experimental: true, OSType: "linux", APIVersion: "v1.30"}, nil
   133  			},
   134  			expectedServer: ServerInfo{HasExperimental: true, OSType: "linux"},
   135  			negotiated:     true,
   136  		},
   137  		{
   138  			doc: "failed ping, no API version",
   139  			pingFunc: func() (types.Ping, error) {
   140  				return types.Ping{}, errors.New("failed")
   141  			},
   142  			expectedServer: ServerInfo{HasExperimental: true},
   143  		},
   144  		{
   145  			doc: "failed ping, with API version",
   146  			pingFunc: func() (types.Ping, error) {
   147  				return types.Ping{APIVersion: "v1.33"}, errors.New("failed")
   148  			},
   149  			expectedServer: ServerInfo{HasExperimental: true},
   150  			negotiated:     true,
   151  		},
   152  	}
   153  
   154  	for _, testcase := range testcases {
   155  		testcase := testcase
   156  		t.Run(testcase.doc, func(t *testing.T) {
   157  			apiclient := &fakeClient{
   158  				pingFunc: testcase.pingFunc,
   159  				version:  defaultVersion,
   160  			}
   161  
   162  			cli := &DockerCli{client: apiclient}
   163  			cli.initializeFromClient()
   164  			assert.Check(t, is.DeepEqual(testcase.expectedServer, cli.serverInfo))
   165  			assert.Check(t, is.Equal(testcase.negotiated, apiclient.negotiated))
   166  		})
   167  	}
   168  }
   169  
   170  // The CLI no longer disables/hides experimental CLI features, however, we need
   171  // to verify that existing configuration files do not break
   172  func TestExperimentalCLI(t *testing.T) {
   173  	defaultVersion := "v1.55"
   174  
   175  	var testcases = []struct {
   176  		doc        string
   177  		configfile string
   178  	}{
   179  		{
   180  			doc:        "default",
   181  			configfile: `{}`,
   182  		},
   183  		{
   184  			doc: "experimental",
   185  			configfile: `{
   186  	"experimental": "enabled"
   187  }`,
   188  		},
   189  	}
   190  
   191  	for _, testcase := range testcases {
   192  		testcase := testcase
   193  		t.Run(testcase.doc, func(t *testing.T) {
   194  			dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
   195  			defer dir.Remove()
   196  			apiclient := &fakeClient{
   197  				version: defaultVersion,
   198  				pingFunc: func() (types.Ping, error) {
   199  					return types.Ping{Experimental: true, OSType: "linux", APIVersion: defaultVersion}, nil
   200  				},
   201  			}
   202  
   203  			cli := &DockerCli{client: apiclient, err: os.Stderr}
   204  			cliconfig.SetDir(dir.Path())
   205  			err := cli.Initialize(flags.NewClientOptions())
   206  			assert.NilError(t, err)
   207  			// For backward-compatibility, HasExperimental will always be "true"
   208  			assert.Check(t, is.Equal(true, cli.ClientInfo().HasExperimental))
   209  		})
   210  	}
   211  }
   212  
   213  func TestGetClientWithPassword(t *testing.T) {
   214  	expected := "password"
   215  
   216  	var testcases = []struct {
   217  		doc             string
   218  		password        string
   219  		retrieverErr    error
   220  		retrieverGiveup bool
   221  		newClientErr    error
   222  		expectedErr     string
   223  	}{
   224  		{
   225  			doc:      "successful connect",
   226  			password: expected,
   227  		},
   228  		{
   229  			doc:             "password retriever exhausted",
   230  			retrieverGiveup: true,
   231  			retrieverErr:    errors.New("failed"),
   232  			expectedErr:     "private key is encrypted, but could not get passphrase",
   233  		},
   234  		{
   235  			doc:          "password retriever error",
   236  			retrieverErr: errors.New("failed"),
   237  			expectedErr:  "failed",
   238  		},
   239  		{
   240  			doc:          "newClient error",
   241  			newClientErr: errors.New("failed to connect"),
   242  			expectedErr:  "failed to connect",
   243  		},
   244  	}
   245  
   246  	for _, testcase := range testcases {
   247  		testcase := testcase
   248  		t.Run(testcase.doc, func(t *testing.T) {
   249  			passRetriever := func(_, _ string, _ bool, attempts int) (passphrase string, giveup bool, err error) {
   250  				// Always return an invalid pass first to test iteration
   251  				switch attempts {
   252  				case 0:
   253  					return "something else", false, nil
   254  				default:
   255  					return testcase.password, testcase.retrieverGiveup, testcase.retrieverErr
   256  				}
   257  			}
   258  
   259  			newClient := func(currentPassword string) (client.APIClient, error) {
   260  				if testcase.newClientErr != nil {
   261  					return nil, testcase.newClientErr
   262  				}
   263  				if currentPassword == expected {
   264  					return &client.Client{}, nil
   265  				}
   266  				return &client.Client{}, x509.IncorrectPasswordError
   267  			}
   268  
   269  			_, err := getClientWithPassword(passRetriever, newClient)
   270  			if testcase.expectedErr != "" {
   271  				assert.ErrorContains(t, err, testcase.expectedErr)
   272  				return
   273  			}
   274  
   275  			assert.NilError(t, err)
   276  		})
   277  	}
   278  }
   279  
   280  func TestNewDockerCliAndOperators(t *testing.T) {
   281  	// Test default operations and also overriding default ones
   282  	cli, err := NewDockerCli(
   283  		WithContentTrust(true),
   284  	)
   285  	assert.NilError(t, err)
   286  	// Check streams are initialized
   287  	assert.Check(t, cli.In() != nil)
   288  	assert.Check(t, cli.Out() != nil)
   289  	assert.Check(t, cli.Err() != nil)
   290  	assert.Equal(t, cli.ContentTrustEnabled(), true)
   291  
   292  	// Apply can modify a dockerCli after construction
   293  	inbuf := bytes.NewBuffer([]byte("input"))
   294  	outbuf := bytes.NewBuffer(nil)
   295  	errbuf := bytes.NewBuffer(nil)
   296  	err = cli.Apply(
   297  		WithInputStream(ioutil.NopCloser(inbuf)),
   298  		WithOutputStream(outbuf),
   299  		WithErrorStream(errbuf),
   300  	)
   301  	assert.NilError(t, err)
   302  	// Check input stream
   303  	inputStream, err := ioutil.ReadAll(cli.In())
   304  	assert.NilError(t, err)
   305  	assert.Equal(t, string(inputStream), "input")
   306  	// Check output stream
   307  	fmt.Fprintf(cli.Out(), "output")
   308  	outputStream, err := ioutil.ReadAll(outbuf)
   309  	assert.NilError(t, err)
   310  	assert.Equal(t, string(outputStream), "output")
   311  	// Check error stream
   312  	fmt.Fprintf(cli.Err(), "error")
   313  	errStream, err := ioutil.ReadAll(errbuf)
   314  	assert.NilError(t, err)
   315  	assert.Equal(t, string(errStream), "error")
   316  }
   317  
   318  func TestInitializeShouldAlwaysCreateTheContextStore(t *testing.T) {
   319  	cli, err := NewDockerCli()
   320  	assert.NilError(t, err)
   321  	assert.NilError(t, cli.Initialize(flags.NewClientOptions(), WithInitializeClient(func(cli *DockerCli) (client.APIClient, error) {
   322  		return client.NewClientWithOpts()
   323  	})))
   324  	assert.Check(t, cli.ContextStore() != nil)
   325  }