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  }