github.1git.de/docker/cli@v26.1.3+incompatible/e2e/global/cli_test.go (about) 1 package global 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "strings" 11 "syscall" 12 "testing" 13 "time" 14 15 "github.com/docker/cli/e2e/internal/fixtures" 16 "github.com/docker/cli/e2e/testutils" 17 "github.com/docker/cli/internal/test" 18 "github.com/docker/cli/internal/test/environment" 19 "github.com/docker/docker/api/types/versions" 20 "gotest.tools/v3/assert" 21 "gotest.tools/v3/icmd" 22 "gotest.tools/v3/poll" 23 "gotest.tools/v3/skip" 24 ) 25 26 func TestTLSVerify(t *testing.T) { 27 // Remote daemons use TLS and this test is not applicable when TLS is required. 28 skip.If(t, environment.RemoteDaemon()) 29 30 icmd.RunCmd(icmd.Command("docker", "ps")).Assert(t, icmd.Success) 31 32 // Regardless of whether we specify true or false we need to 33 // test to make sure tls is turned on if --tlsverify is specified at all 34 result := icmd.RunCmd(icmd.Command("docker", "--tlsverify=false", "ps")) 35 result.Assert(t, icmd.Expected{ExitCode: 1, Err: "unable to resolve docker endpoint:"}) 36 37 result = icmd.RunCmd(icmd.Command("docker", "--tlsverify=true", "ps")) 38 result.Assert(t, icmd.Expected{ExitCode: 1, Err: "ca.pem"}) 39 } 40 41 // TestTCPSchemeUsesHTTPProxyEnv verifies that the cli uses HTTP_PROXY if 42 // DOCKER_HOST is set to use the 'tcp://' scheme. 43 // 44 // Prior to go1.16, https:// schemes would use HTTPS_PROXY, and any other 45 // scheme would use HTTP_PROXY. However, golang/net@7b1cca2 (per a request in 46 // golang/go#40909) changed this behavior to only use HTTP_PROXY for http:// 47 // schemes, no longer using a proxy for any other scheme. 48 // 49 // Docker uses the tcp:// scheme as a default for API connections, to indicate 50 // that the API is not "purely" HTTP. Various parts in the code also *require* 51 // this scheme to be used. While we could change the default and allow http(s) 52 // schemes to be used, doing so will take time, taking into account that there 53 // are many installs in existence that have tcp:// configured as DOCKER_HOST. 54 // 55 // Note that due to Golang's use of sync.Once for proxy-detection, this test 56 // cannot be done as a unit-test, hence it being an e2e test. 57 func TestTCPSchemeUsesHTTPProxyEnv(t *testing.T) { 58 const responseJSON = `{"Version": "99.99.9", "ApiVersion": "1.41", "MinAPIVersion": "1.12"}` 59 var received string 60 proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 61 received = r.Host 62 w.Header().Set("Content-Type", "application/json") 63 _, _ = w.Write([]byte(responseJSON)) 64 })) 65 defer proxyServer.Close() 66 67 // Configure the CLI to use our proxyServer. DOCKER_HOST can point to any 68 // address (as it won't be connected to), but must use tcp:// for this test, 69 // to verify it's using HTTP_PROXY. 70 result := icmd.RunCmd( 71 icmd.Command("docker", "version", "--format", "{{ .Server.Version }}"), 72 icmd.WithEnv("HTTP_PROXY="+proxyServer.URL, "DOCKER_HOST=tcp://docker.acme.example.com:2376"), 73 ) 74 // Verify the command ran successfully, and that it connected to the proxyServer 75 result.Assert(t, icmd.Success) 76 assert.Equal(t, strings.TrimSpace(result.Stdout()), "99.99.9") 77 assert.Equal(t, received, "docker.acme.example.com:2376") 78 } 79 80 // Test that the prompt command exits with 0 81 // when the user sends SIGINT/SIGTERM to the process 82 func TestPromptExitCode(t *testing.T) { 83 t.Parallel() 84 85 ctx, cancel := context.WithCancel(context.Background()) 86 t.Cleanup(cancel) 87 88 dir := fixtures.SetupConfigFile(t) 89 t.Cleanup(dir.Remove) 90 91 defaultCmdOpts := []icmd.CmdOp{ 92 fixtures.WithConfig(dir.Path()), 93 fixtures.WithNotary, 94 } 95 96 testCases := []struct { 97 name string 98 run func(t *testing.T) icmd.Cmd 99 }{ 100 { 101 name: "volume prune", 102 run: func(t *testing.T) icmd.Cmd { 103 t.Helper() 104 return icmd.Command("docker", "volume", "prune") 105 }, 106 }, 107 { 108 name: "network prune", 109 run: func(t *testing.T) icmd.Cmd { 110 t.Helper() 111 return icmd.Command("docker", "network", "prune") 112 }, 113 }, 114 { 115 name: "container prune", 116 run: func(t *testing.T) icmd.Cmd { 117 t.Helper() 118 return icmd.Command("docker", "container", "prune") 119 }, 120 }, 121 { 122 name: "image prune", 123 run: func(t *testing.T) icmd.Cmd { 124 t.Helper() 125 return icmd.Command("docker", "image", "prune") 126 }, 127 }, 128 { 129 name: "system prune", 130 run: func(t *testing.T) icmd.Cmd { 131 t.Helper() 132 return icmd.Command("docker", "system", "prune") 133 }, 134 }, 135 { 136 name: "revoke trust", 137 run: func(t *testing.T) icmd.Cmd { 138 t.Helper() 139 return icmd.Command("docker", "trust", "revoke", "example/trust-demo") 140 }, 141 }, 142 { 143 name: "plugin install", 144 run: func(t *testing.T) icmd.Cmd { 145 t.Helper() 146 skip.If(t, versions.LessThan(environment.DaemonAPIVersion(t), "1.44")) 147 148 pluginDir := testutils.SetupPlugin(t, ctx) 149 t.Cleanup(pluginDir.Remove) 150 151 plugin := "registry:5000/plugin-content-trust-install:latest" 152 153 icmd.RunCommand("docker", "plugin", "create", plugin, pluginDir.Path()).Assert(t, icmd.Success) 154 icmd.RunCmd(icmd.Command("docker", "plugin", "push", plugin), defaultCmdOpts...).Assert(t, icmd.Success) 155 icmd.RunCmd(icmd.Command("docker", "plugin", "rm", "-f", plugin), defaultCmdOpts...).Assert(t, icmd.Success) 156 return icmd.Command("docker", "plugin", "install", plugin) 157 }, 158 }, 159 { 160 name: "plugin upgrade", 161 run: func(t *testing.T) icmd.Cmd { 162 t.Helper() 163 skip.If(t, versions.LessThan(environment.DaemonAPIVersion(t), "1.44")) 164 165 pluginLatestDir := testutils.SetupPlugin(t, ctx) 166 t.Cleanup(pluginLatestDir.Remove) 167 pluginNextDir := testutils.SetupPlugin(t, ctx) 168 t.Cleanup(pluginNextDir.Remove) 169 170 plugin := "registry:5000/plugin-content-trust-upgrade" 171 172 icmd.RunCommand("docker", "plugin", "create", plugin+":latest", pluginLatestDir.Path()).Assert(t, icmd.Success) 173 icmd.RunCommand("docker", "plugin", "create", plugin+":next", pluginNextDir.Path()).Assert(t, icmd.Success) 174 icmd.RunCmd(icmd.Command("docker", "plugin", "push", plugin+":latest"), defaultCmdOpts...).Assert(t, icmd.Success) 175 icmd.RunCmd(icmd.Command("docker", "plugin", "push", plugin+":next"), defaultCmdOpts...).Assert(t, icmd.Success) 176 icmd.RunCmd(icmd.Command("docker", "plugin", "rm", "-f", plugin+":latest"), defaultCmdOpts...).Assert(t, icmd.Success) 177 icmd.RunCmd(icmd.Command("docker", "plugin", "rm", "-f", plugin+":next"), defaultCmdOpts...).Assert(t, icmd.Success) 178 icmd.RunCmd(icmd.Command("docker", "plugin", "install", "--disable", "--grant-all-permissions", plugin+":latest"), defaultCmdOpts...).Assert(t, icmd.Success) 179 return icmd.Command("docker", "plugin", "upgrade", plugin+":latest", plugin+":next") 180 }, 181 }, 182 } 183 184 for _, tc := range testCases { 185 tc := tc 186 t.Run("case="+tc.name, func(t *testing.T) { 187 t.Parallel() 188 189 buf := new(bytes.Buffer) 190 bufioWriter := bufio.NewWriter(buf) 191 192 writeDone := make(chan struct{}) 193 w := test.NewWriterWithHook(bufioWriter, func(p []byte) { 194 writeDone <- struct{}{} 195 }) 196 197 drainChCtx, drainChCtxCancel := context.WithCancel(ctx) 198 t.Cleanup(drainChCtxCancel) 199 200 drainChannel(drainChCtx, writeDone) 201 202 r, _ := io.Pipe() 203 defer r.Close() 204 result := icmd.StartCmd(tc.run(t), 205 append(defaultCmdOpts, 206 icmd.WithStdout(w), 207 icmd.WithStderr(w), 208 icmd.WithStdin(r))...) 209 210 poll.WaitOn(t, func(t poll.LogT) poll.Result { 211 select { 212 case <-ctx.Done(): 213 return poll.Error(ctx.Err()) 214 default: 215 216 if err := bufioWriter.Flush(); err != nil { 217 return poll.Continue(err.Error()) 218 } 219 if strings.Contains(buf.String(), "[y/N]") { 220 return poll.Success() 221 } 222 223 return poll.Continue("command did not prompt for confirmation, instead prompted:\n%s\n", buf.String()) 224 } 225 }, poll.WithDelay(100*time.Millisecond), poll.WithTimeout(1*time.Second)) 226 227 drainChCtxCancel() 228 229 assert.NilError(t, result.Cmd.Process.Signal(syscall.SIGINT)) 230 231 proc, err := result.Cmd.Process.Wait() 232 assert.NilError(t, err) 233 assert.Equal(t, proc.ExitCode(), 0, "expected exit code to be 0, got %d", proc.ExitCode()) 234 235 processCtx, processCtxCancel := context.WithTimeout(ctx, time.Second) 236 t.Cleanup(processCtxCancel) 237 238 select { 239 case <-processCtx.Done(): 240 t.Fatal("timed out waiting for new line after process exit") 241 case <-writeDone: 242 buf.Reset() 243 assert.NilError(t, bufioWriter.Flush()) 244 assert.Equal(t, buf.String(), "\n", "expected a new line after the process exits from SIGINT") 245 } 246 }) 247 } 248 } 249 250 func drainChannel(ctx context.Context, ch <-chan struct{}) { 251 go func() { 252 for { 253 select { 254 case <-ctx.Done(): 255 return 256 case <-ch: 257 } 258 } 259 }() 260 }