diff --git a/TODO.md b/TODO.md index de61fa4ec..9246e3faa 100644 --- a/TODO.md +++ b/TODO.md @@ -24,7 +24,7 @@ - [x] `ps` - [x] `restore` - [x] `rm` - - [ ] `run` (WIP: decentral1se) + - [x] `run` - [ ] `rollback` - [ ] `secret` - [ ] `generate` diff --git a/cli/app/run.go b/cli/app/run.go index 508206e0e..8380e8a35 100644 --- a/cli/app/run.go +++ b/cli/app/run.go @@ -7,7 +7,9 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/client" + "coopcloud.tech/abra/client/container" "coopcloud.tech/abra/config" + "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/sirupsen/logrus" @@ -77,13 +79,14 @@ var appRunCommand = &cli.Command{ cmd := c.Args().Slice()[2:] execCreateOpts := types.ExecConfig{ - //AttachStderr: true, - AttachStdin: true, - //AttachStdout: true, - Cmd: cmd, - Detach: false, - Tty: true, + AttachStderr: true, + AttachStdin: true, + AttachStdout: true, + Cmd: cmd, + Detach: false, + Tty: true, } + if user != "" { execCreateOpts.User = user } @@ -91,40 +94,19 @@ var appRunCommand = &cli.Command{ execCreateOpts.Tty = false } - // container := containers[0] - // idResp, err := cl.ContainerExecCreate(ctx, container.ID, execCreateOpts) - // if err != nil { - // logrus.Fatal(err) - // } + // FIXME: an absolutely monumental hack to instantiate another command-line + // client withing our command-line client so that we pass something down + // the tubes that satisfies the necessary interface requirements. We should + // refactor our vendored container code to not require all this cruft. For + // now, It Works. + dcli, err := command.NewDockerCli() + if err != nil { + logrus.Fatal(err) + } - // execAttachOpts := types.ExecStartCheck{Detach: false, Tty: true} - // hResp, err := cl.ContainerExecAttach(ctx, idResp.ID, execAttachOpts) - // if err != nil { - // logrus.Fatal(err) - // } - // defer hResp.Close() - - // var outBuf, errBuf bytes.Buffer - // outputDone := make(chan error) - // go func() { - // _, err = stdcopy.StdCopy(&outBuf, &errBuf, hResp.Reader) - // outputDone <- err - // }() - - // select { - // case err := <-outputDone: - // if err != nil { - // logrus.Fatal(err) - // } - // break - // case <-ctx.Done(): - // break - // } - - // iresp, err := cl.ContainerExecInspect(ctx, idResp.ID) - // if err != nil { - // logrus.Fatal(err) - // } + if err := container.RunExec(dcli, cl, containers[0].ID, &execCreateOpts); err != nil { + logrus.Fatal(err) + } return nil }, diff --git a/client/container/exec.go b/client/container/exec.go index 22db6f8c6..2443daabc 100644 --- a/client/container/exec.go +++ b/client/container/exec.go @@ -8,41 +8,19 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/cliconfig/configfile" apiclient "github.com/docker/docker/client" "github.com/sirupsen/logrus" ) -type execOptions struct { - detachKeys string - interactive bool - tty bool - detach bool - user string - privileged bool - env opts.ListOpts - workdir string - container string - command []string - envFile opts.ListOpts -} - -func RunExec(dockerCli command.Cli, options execOptions) error { - execConfig, err := parseExec(options, dockerCli.ConfigFile()) - if err != nil { - return err - } - +func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string, execConfig *types.ExecConfig) error { ctx := context.Background() - client := dockerCli.Client() // We need to check the tty _before_ we do the ContainerExecCreate, because // otherwise if we error out we will leak execIDs on the server (and // there's no easy way to clean those up). But also in order to make "not // exist" errors take precedence we do a dummy inspect first. - if _, err := client.ContainerInspect(ctx, options.container); err != nil { + if _, err := client.ContainerInspect(ctx, containerID); err != nil { return err } if !execConfig.Detach { @@ -51,7 +29,7 @@ func RunExec(dockerCli command.Cli, options execOptions) error { } } - response, err := client.ContainerExecCreate(ctx, options.container, *execConfig) + response, err := client.ContainerExecCreate(ctx, containerID, *execConfig) if err != nil { return err } @@ -68,10 +46,11 @@ func RunExec(dockerCli command.Cli, options execOptions) error { } return client.ContainerExecStart(ctx, execID, execStartCheck) } - return interactiveExec(ctx, dockerCli, execConfig, execID) + return interactiveExec(ctx, dockerCli, client, execConfig, execID) } -func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *types.ExecConfig, execID string) error { +func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclient.Client, + execConfig *types.ExecConfig, execID string) error { // Interactive exec requested. var ( out, stderr io.Writer @@ -92,7 +71,6 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ } } - client := dockerCli.Client() execStartCheck := types.ExecStartCheck{ Tty: execConfig.Tty, } @@ -122,7 +100,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ }() if execConfig.Tty && dockerCli.In().IsTerminal() { - if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil { + if err := MonitorTtySize(ctx, client, dockerCli, execID, true); err != nil { fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err) } } @@ -150,38 +128,3 @@ func getExecExitStatus(ctx context.Context, client apiclient.ContainerAPIClient, } return nil } - -// parseExec parses the specified args for the specified command and generates -// an ExecConfig from it. -func parseExec(execOpts execOptions, configFile *configfile.ConfigFile) (*types.ExecConfig, error) { - execConfig := &types.ExecConfig{ - User: execOpts.user, - Privileged: execOpts.privileged, - Tty: execOpts.tty, - Cmd: execOpts.command, - Detach: execOpts.detach, - WorkingDir: execOpts.workdir, - } - - // collect all the environment variables for the container - var err error - if execConfig.Env, err = opts.ReadKVEnvStrings(execOpts.envFile.GetAll(), execOpts.env.GetAll()); err != nil { - return nil, err - } - - // If -d is not set, attach to everything by default - if !execOpts.detach { - execConfig.AttachStdout = true - execConfig.AttachStderr = true - if execOpts.interactive { - execConfig.AttachStdin = true - } - } - - if execOpts.detachKeys != "" { - execConfig.DetachKeys = execOpts.detachKeys - } else { - execConfig.DetachKeys = configFile.DetachKeys - } - return execConfig, nil -} diff --git a/client/container/tty.go b/client/container/tty.go index cc64f999d..3843f3b2d 100644 --- a/client/container/tty.go +++ b/client/container/tty.go @@ -11,6 +11,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" "github.com/docker/docker/client" + apiclient "github.com/docker/docker/client" "github.com/moby/sys/signal" "github.com/sirupsen/logrus" ) @@ -40,23 +41,23 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin } // resizeTty is to resize the tty with cli out's tty size -func resizeTty(ctx context.Context, cli command.Cli, id string, isExec bool) error { +func resizeTty(ctx context.Context, client *apiclient.Client, cli command.Cli, id string, isExec bool) error { height, width := cli.Out().GetTtySize() - return resizeTtyTo(ctx, cli.Client(), id, height, width, isExec) + return resizeTtyTo(ctx, client, id, height, width, isExec) } // initTtySize is to init the tty's size to the same as the window, if there is an error, it will retry 5 times. -func initTtySize(ctx context.Context, cli command.Cli, id string, isExec bool, resizeTtyFunc func(ctx context.Context, cli command.Cli, id string, isExec bool) error) { +func initTtySize(ctx context.Context, client *apiclient.Client, cli command.Cli, id string, isExec bool, resizeTtyFunc func(ctx context.Context, client *apiclient.Client, cli command.Cli, id string, isExec bool) error) { rttyFunc := resizeTtyFunc if rttyFunc == nil { rttyFunc = resizeTty } - if err := rttyFunc(ctx, cli, id, isExec); err != nil { + if err := rttyFunc(ctx, client, cli, id, isExec); err != nil { go func() { var err error for retry := 0; retry < 5; retry++ { time.Sleep(10 * time.Millisecond) - if err = rttyFunc(ctx, cli, id, isExec); err == nil { + if err = rttyFunc(ctx, client, cli, id, isExec); err == nil { break } } @@ -68,8 +69,8 @@ func initTtySize(ctx context.Context, cli command.Cli, id string, isExec bool, r } // MonitorTtySize updates the container tty size when the terminal tty changes size -func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool) error { - initTtySize(ctx, cli, id, isExec, resizeTty) +func MonitorTtySize(ctx context.Context, client *apiclient.Client, cli command.Cli, id string, isExec bool) error { + initTtySize(ctx, client, cli, id, isExec, resizeTty) if runtime.GOOS == "windows" { go func() { prevH, prevW := cli.Out().GetTtySize() @@ -78,7 +79,7 @@ func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool h, w := cli.Out().GetTtySize() if prevW != w || prevH != h { - resizeTty(ctx, cli, id, isExec) + resizeTty(ctx, client, cli, id, isExec) } prevH = h prevW = w @@ -89,7 +90,7 @@ func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool gosignal.Notify(sigchan, signal.SIGWINCH) go func() { for range sigchan { - resizeTty(ctx, cli, id, isExec) + resizeTty(ctx, client, cli, id, isExec) } }() } diff --git a/go.mod b/go.mod index 4653f5b2c..10421f8e8 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/hetznercloud/hcloud-go v1.28.0 github.com/moby/sys/mount v0.2.0 // indirect + github.com/moby/sys/signal v0.5.0 github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 github.com/morikuni/aec v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.5 diff --git a/go.sum b/go.sum index 505c3bd1c..29a9aee8d 100644 --- a/go.sum +++ b/go.sum @@ -538,6 +538,8 @@ github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7s github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/signal v0.5.0 h1:MzpEFrMxugDynb1gkTIThU1O3wEmrAkOY+G9dHcHnCc= +github.com/moby/sys/signal v0.5.0/go.mod h1:JwObcMnOrUy2VTP5swPKWwywH0Mbgk8Y5qua9iwtIRM= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= @@ -929,8 +931,9 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=