github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/exec_test.go (about)

     1  package container
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/docker/cli/cli"
    10  	"github.com/docker/cli/cli/config/configfile"
    11  	"github.com/docker/cli/internal/test"
    12  	"github.com/docker/cli/opts"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/pkg/errors"
    15  	"gotest.tools/v3/assert"
    16  	is "gotest.tools/v3/assert/cmp"
    17  	"gotest.tools/v3/fs"
    18  )
    19  
    20  func withDefaultOpts(options ExecOptions) ExecOptions {
    21  	options.Env = opts.NewListOpts(opts.ValidateEnv)
    22  	options.EnvFile = opts.NewListOpts(nil)
    23  	if len(options.Command) == 0 {
    24  		options.Command = []string{"command"}
    25  	}
    26  	return options
    27  }
    28  
    29  func TestParseExec(t *testing.T) {
    30  	content := `ONE=1
    31  TWO=2
    32  	`
    33  
    34  	tmpFile := fs.NewFile(t, t.Name(), fs.WithContent(content))
    35  	defer tmpFile.Remove()
    36  
    37  	testcases := []struct {
    38  		options    ExecOptions
    39  		configFile configfile.ConfigFile
    40  		expected   types.ExecConfig
    41  	}{
    42  		{
    43  			expected: types.ExecConfig{
    44  				Cmd:          []string{"command"},
    45  				AttachStdout: true,
    46  				AttachStderr: true,
    47  			},
    48  			options: withDefaultOpts(ExecOptions{}),
    49  		},
    50  		{
    51  			expected: types.ExecConfig{
    52  				Cmd:          []string{"command1", "command2"},
    53  				AttachStdout: true,
    54  				AttachStderr: true,
    55  			},
    56  			options: withDefaultOpts(ExecOptions{
    57  				Command: []string{"command1", "command2"},
    58  			}),
    59  		},
    60  		{
    61  			options: withDefaultOpts(ExecOptions{
    62  				Interactive: true,
    63  				TTY:         true,
    64  				User:        "uid",
    65  			}),
    66  			expected: types.ExecConfig{
    67  				User:         "uid",
    68  				AttachStdin:  true,
    69  				AttachStdout: true,
    70  				AttachStderr: true,
    71  				Tty:          true,
    72  				Cmd:          []string{"command"},
    73  			},
    74  		},
    75  		{
    76  			options: withDefaultOpts(ExecOptions{Detach: true}),
    77  			expected: types.ExecConfig{
    78  				Detach: true,
    79  				Cmd:    []string{"command"},
    80  			},
    81  		},
    82  		{
    83  			options: withDefaultOpts(ExecOptions{
    84  				TTY:         true,
    85  				Interactive: true,
    86  				Detach:      true,
    87  			}),
    88  			expected: types.ExecConfig{
    89  				Detach: true,
    90  				Tty:    true,
    91  				Cmd:    []string{"command"},
    92  			},
    93  		},
    94  		{
    95  			options:    withDefaultOpts(ExecOptions{Detach: true}),
    96  			configFile: configfile.ConfigFile{DetachKeys: "de"},
    97  			expected: types.ExecConfig{
    98  				Cmd:        []string{"command"},
    99  				DetachKeys: "de",
   100  				Detach:     true,
   101  			},
   102  		},
   103  		{
   104  			options: withDefaultOpts(ExecOptions{
   105  				Detach:     true,
   106  				DetachKeys: "ab",
   107  			}),
   108  			configFile: configfile.ConfigFile{DetachKeys: "de"},
   109  			expected: types.ExecConfig{
   110  				Cmd:        []string{"command"},
   111  				DetachKeys: "ab",
   112  				Detach:     true,
   113  			},
   114  		},
   115  		{
   116  			expected: types.ExecConfig{
   117  				Cmd:          []string{"command"},
   118  				AttachStdout: true,
   119  				AttachStderr: true,
   120  				Env:          []string{"ONE=1", "TWO=2"},
   121  			},
   122  			options: func() ExecOptions {
   123  				o := withDefaultOpts(ExecOptions{})
   124  				o.EnvFile.Set(tmpFile.Path())
   125  				return o
   126  			}(),
   127  		},
   128  		{
   129  			expected: types.ExecConfig{
   130  				Cmd:          []string{"command"},
   131  				AttachStdout: true,
   132  				AttachStderr: true,
   133  				Env:          []string{"ONE=1", "TWO=2", "ONE=override"},
   134  			},
   135  			options: func() ExecOptions {
   136  				o := withDefaultOpts(ExecOptions{})
   137  				o.EnvFile.Set(tmpFile.Path())
   138  				o.Env.Set("ONE=override")
   139  				return o
   140  			}(),
   141  		},
   142  	}
   143  
   144  	for _, testcase := range testcases {
   145  		execConfig, err := parseExec(testcase.options, &testcase.configFile)
   146  		assert.NilError(t, err)
   147  		assert.Check(t, is.DeepEqual(testcase.expected, *execConfig))
   148  	}
   149  }
   150  
   151  func TestParseExecNoSuchFile(t *testing.T) {
   152  	execOpts := withDefaultOpts(ExecOptions{})
   153  	execOpts.EnvFile.Set("no-such-env-file")
   154  	execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
   155  	assert.ErrorContains(t, err, "no-such-env-file")
   156  	assert.Check(t, os.IsNotExist(err))
   157  	assert.Check(t, execConfig == nil)
   158  }
   159  
   160  func TestRunExec(t *testing.T) {
   161  	testcases := []struct {
   162  		doc           string
   163  		options       ExecOptions
   164  		client        fakeClient
   165  		expectedError string
   166  		expectedOut   string
   167  		expectedErr   string
   168  	}{
   169  		{
   170  			doc: "successful detach",
   171  			options: withDefaultOpts(ExecOptions{
   172  				Detach: true,
   173  			}),
   174  			client: fakeClient{execCreateFunc: execCreateWithID},
   175  		},
   176  		{
   177  			doc:     "inspect error",
   178  			options: NewExecOptions(),
   179  			client: fakeClient{
   180  				inspectFunc: func(string) (types.ContainerJSON, error) {
   181  					return types.ContainerJSON{}, errors.New("failed inspect")
   182  				},
   183  			},
   184  			expectedError: "failed inspect",
   185  		},
   186  		{
   187  			doc:           "missing exec ID",
   188  			options:       NewExecOptions(),
   189  			expectedError: "exec ID empty",
   190  		},
   191  	}
   192  
   193  	for _, testcase := range testcases {
   194  		t.Run(testcase.doc, func(t *testing.T) {
   195  			fakeCLI := test.NewFakeCli(&testcase.client)
   196  
   197  			err := RunExec(context.TODO(), fakeCLI, "thecontainer", testcase.options)
   198  			if testcase.expectedError != "" {
   199  				assert.ErrorContains(t, err, testcase.expectedError)
   200  			} else if !assert.Check(t, err) {
   201  				return
   202  			}
   203  			assert.Check(t, is.Equal(testcase.expectedOut, fakeCLI.OutBuffer().String()))
   204  			assert.Check(t, is.Equal(testcase.expectedErr, fakeCLI.ErrBuffer().String()))
   205  		})
   206  	}
   207  }
   208  
   209  func execCreateWithID(_ string, _ types.ExecConfig) (types.IDResponse, error) {
   210  	return types.IDResponse{ID: "execid"}, nil
   211  }
   212  
   213  func TestGetExecExitStatus(t *testing.T) {
   214  	execID := "the exec id"
   215  	expectedErr := errors.New("unexpected error")
   216  
   217  	testcases := []struct {
   218  		inspectError  error
   219  		exitCode      int
   220  		expectedError error
   221  	}{
   222  		{
   223  			inspectError: nil,
   224  			exitCode:     0,
   225  		},
   226  		{
   227  			inspectError:  expectedErr,
   228  			expectedError: expectedErr,
   229  		},
   230  		{
   231  			exitCode:      15,
   232  			expectedError: cli.StatusError{StatusCode: 15},
   233  		},
   234  	}
   235  
   236  	for _, testcase := range testcases {
   237  		client := &fakeClient{
   238  			execInspectFunc: func(id string) (types.ContainerExecInspect, error) {
   239  				assert.Check(t, is.Equal(execID, id))
   240  				return types.ContainerExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
   241  			},
   242  		}
   243  		err := getExecExitStatus(context.Background(), client, execID)
   244  		assert.Check(t, is.Equal(testcase.expectedError, err))
   245  	}
   246  }
   247  
   248  func TestNewExecCommandErrors(t *testing.T) {
   249  	testCases := []struct {
   250  		name                 string
   251  		args                 []string
   252  		expectedError        string
   253  		containerInspectFunc func(img string) (types.ContainerJSON, error)
   254  	}{
   255  		{
   256  			name:          "client-error",
   257  			args:          []string{"5cb5bb5e4a3b", "-t", "-i", "bash"},
   258  			expectedError: "something went wrong",
   259  			containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
   260  				return types.ContainerJSON{}, errors.Errorf("something went wrong")
   261  			},
   262  		},
   263  	}
   264  	for _, tc := range testCases {
   265  		fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
   266  		cmd := NewExecCommand(fakeCLI)
   267  		cmd.SetOut(io.Discard)
   268  		cmd.SetArgs(tc.args)
   269  		assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
   270  	}
   271  }