mirror of https://github.com/docker/cli.git
274 lines
6.7 KiB
Go
274 lines
6.7 KiB
Go
package container
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/config/configfile"
|
|
"github.com/docker/cli/internal/test"
|
|
"github.com/docker/cli/opts"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/pkg/errors"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
"gotest.tools/v3/fs"
|
|
)
|
|
|
|
func withDefaultOpts(options ExecOptions) ExecOptions {
|
|
options.Env = opts.NewListOpts(opts.ValidateEnv)
|
|
options.EnvFile = opts.NewListOpts(nil)
|
|
if len(options.Command) == 0 {
|
|
options.Command = []string{"command"}
|
|
}
|
|
return options
|
|
}
|
|
|
|
func TestParseExec(t *testing.T) {
|
|
content := `ONE=1
|
|
TWO=2
|
|
`
|
|
|
|
tmpFile := fs.NewFile(t, t.Name(), fs.WithContent(content))
|
|
defer tmpFile.Remove()
|
|
|
|
testcases := []struct {
|
|
options ExecOptions
|
|
configFile configfile.ConfigFile
|
|
expected container.ExecOptions
|
|
}{
|
|
{
|
|
expected: container.ExecOptions{
|
|
Cmd: []string{"command"},
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
},
|
|
options: withDefaultOpts(ExecOptions{}),
|
|
},
|
|
{
|
|
expected: container.ExecOptions{
|
|
Cmd: []string{"command1", "command2"},
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
},
|
|
options: withDefaultOpts(ExecOptions{
|
|
Command: []string{"command1", "command2"},
|
|
}),
|
|
},
|
|
{
|
|
options: withDefaultOpts(ExecOptions{
|
|
Interactive: true,
|
|
TTY: true,
|
|
User: "uid",
|
|
}),
|
|
expected: container.ExecOptions{
|
|
User: "uid",
|
|
AttachStdin: true,
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Tty: true,
|
|
Cmd: []string{"command"},
|
|
},
|
|
},
|
|
{
|
|
options: withDefaultOpts(ExecOptions{Detach: true}),
|
|
expected: container.ExecOptions{
|
|
Detach: true,
|
|
Cmd: []string{"command"},
|
|
},
|
|
},
|
|
{
|
|
options: withDefaultOpts(ExecOptions{
|
|
TTY: true,
|
|
Interactive: true,
|
|
Detach: true,
|
|
}),
|
|
expected: container.ExecOptions{
|
|
Detach: true,
|
|
Tty: true,
|
|
Cmd: []string{"command"},
|
|
},
|
|
},
|
|
{
|
|
options: withDefaultOpts(ExecOptions{Detach: true}),
|
|
configFile: configfile.ConfigFile{DetachKeys: "de"},
|
|
expected: container.ExecOptions{
|
|
Cmd: []string{"command"},
|
|
DetachKeys: "de",
|
|
Detach: true,
|
|
},
|
|
},
|
|
{
|
|
options: withDefaultOpts(ExecOptions{
|
|
Detach: true,
|
|
DetachKeys: "ab",
|
|
}),
|
|
configFile: configfile.ConfigFile{DetachKeys: "de"},
|
|
expected: container.ExecOptions{
|
|
Cmd: []string{"command"},
|
|
DetachKeys: "ab",
|
|
Detach: true,
|
|
},
|
|
},
|
|
{
|
|
expected: container.ExecOptions{
|
|
Cmd: []string{"command"},
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Env: []string{"ONE=1", "TWO=2"},
|
|
},
|
|
options: func() ExecOptions {
|
|
o := withDefaultOpts(ExecOptions{})
|
|
o.EnvFile.Set(tmpFile.Path())
|
|
return o
|
|
}(),
|
|
},
|
|
{
|
|
expected: container.ExecOptions{
|
|
Cmd: []string{"command"},
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Env: []string{"ONE=1", "TWO=2", "ONE=override"},
|
|
},
|
|
options: func() ExecOptions {
|
|
o := withDefaultOpts(ExecOptions{})
|
|
o.EnvFile.Set(tmpFile.Path())
|
|
o.Env.Set("ONE=override")
|
|
return o
|
|
}(),
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
execConfig, err := parseExec(testcase.options, &testcase.configFile)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.DeepEqual(testcase.expected, *execConfig))
|
|
}
|
|
}
|
|
|
|
func TestParseExecNoSuchFile(t *testing.T) {
|
|
execOpts := withDefaultOpts(ExecOptions{})
|
|
execOpts.EnvFile.Set("no-such-env-file")
|
|
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
|
|
assert.ErrorContains(t, err, "no-such-env-file")
|
|
assert.Check(t, os.IsNotExist(err))
|
|
assert.Check(t, execConfig == nil)
|
|
}
|
|
|
|
func TestRunExec(t *testing.T) {
|
|
testcases := []struct {
|
|
doc string
|
|
options ExecOptions
|
|
client *fakeClient
|
|
expectedError string
|
|
expectedOut string
|
|
expectedErr string
|
|
}{
|
|
{
|
|
doc: "successful detach",
|
|
options: withDefaultOpts(ExecOptions{
|
|
Detach: true,
|
|
}),
|
|
client: &fakeClient{execCreateFunc: execCreateWithID},
|
|
},
|
|
{
|
|
doc: "inspect error",
|
|
options: NewExecOptions(),
|
|
client: &fakeClient{
|
|
inspectFunc: func(string) (types.ContainerJSON, error) {
|
|
return types.ContainerJSON{}, errors.New("failed inspect")
|
|
},
|
|
},
|
|
expectedError: "failed inspect",
|
|
},
|
|
{
|
|
doc: "missing exec ID",
|
|
options: NewExecOptions(),
|
|
expectedError: "exec ID empty",
|
|
client: &fakeClient{},
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.doc, func(t *testing.T) {
|
|
fakeCLI := test.NewFakeCli(testcase.client)
|
|
|
|
err := RunExec(context.TODO(), fakeCLI, "thecontainer", testcase.options)
|
|
if testcase.expectedError != "" {
|
|
assert.ErrorContains(t, err, testcase.expectedError)
|
|
} else if !assert.Check(t, err) {
|
|
return
|
|
}
|
|
assert.Check(t, is.Equal(testcase.expectedOut, fakeCLI.OutBuffer().String()))
|
|
assert.Check(t, is.Equal(testcase.expectedErr, fakeCLI.ErrBuffer().String()))
|
|
})
|
|
}
|
|
}
|
|
|
|
func execCreateWithID(_ string, _ container.ExecOptions) (types.IDResponse, error) {
|
|
return types.IDResponse{ID: "execid"}, nil
|
|
}
|
|
|
|
func TestGetExecExitStatus(t *testing.T) {
|
|
execID := "the exec id"
|
|
expectedErr := errors.New("unexpected error")
|
|
|
|
testcases := []struct {
|
|
inspectError error
|
|
exitCode int
|
|
expectedError error
|
|
}{
|
|
{
|
|
inspectError: nil,
|
|
exitCode: 0,
|
|
},
|
|
{
|
|
inspectError: expectedErr,
|
|
expectedError: expectedErr,
|
|
},
|
|
{
|
|
exitCode: 15,
|
|
expectedError: cli.StatusError{StatusCode: 15},
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
client := &fakeClient{
|
|
execInspectFunc: func(id string) (container.ExecInspect, error) {
|
|
assert.Check(t, is.Equal(execID, id))
|
|
return container.ExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError
|
|
},
|
|
}
|
|
err := getExecExitStatus(context.Background(), client, execID)
|
|
assert.Check(t, is.Equal(testcase.expectedError, err))
|
|
}
|
|
}
|
|
|
|
func TestNewExecCommandErrors(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
args []string
|
|
expectedError string
|
|
containerInspectFunc func(img string) (types.ContainerJSON, error)
|
|
}{
|
|
{
|
|
name: "client-error",
|
|
args: []string{"5cb5bb5e4a3b", "-t", "-i", "bash"},
|
|
expectedError: "something went wrong",
|
|
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
|
|
return types.ContainerJSON{}, errors.Errorf("something went wrong")
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
fakeCLI := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})
|
|
cmd := NewExecCommand(fakeCLI)
|
|
cmd.SetOut(io.Discard)
|
|
cmd.SetArgs(tc.args)
|
|
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
|
}
|
|
}
|