github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/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 }