diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2019-04-10 08:51:26 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-10 08:51:26 -0700 |
commit | 1fb0a09591ae86b659dc87bd9e3f7fe29d8add8d (patch) | |
tree | 0f9df726674f9a28abbd08fc2aa9bfb71509ec1e /pkg/adapter/containers_remote.go | |
parent | 1701707dad4e4dba9d4331bd8917be28d138254d (diff) | |
parent | fbcda7772d9fb7667be3a26fbabea0a7b5ea9a58 (diff) | |
download | podman-1fb0a09591ae86b659dc87bd9e3f7fe29d8add8d.tar.gz podman-1fb0a09591ae86b659dc87bd9e3f7fe29d8add8d.tar.bz2 podman-1fb0a09591ae86b659dc87bd9e3f7fe29d8add8d.zip |
Merge pull request #2874 from baude/varlinkterm
Add the ability to attach remotely to a container
Diffstat (limited to 'pkg/adapter/containers_remote.go')
-rw-r--r-- | pkg/adapter/containers_remote.go | 152 |
1 files changed, 140 insertions, 12 deletions
diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 424c431df..1ae39749f 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -6,19 +6,25 @@ import ( "context" "encoding/json" "fmt" + "io" + "os" "strconv" "syscall" "time" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" + "github.com/containers/libpod/pkg/varlinkapi/virtwriter" + "github.com/docker/docker/pkg/term" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/varlink/go/varlink" + "golang.org/x/crypto/ssh/terminal" + "k8s.io/client-go/tools/remotecommand" ) // Inspect returns an inspect struct from varlink @@ -71,6 +77,19 @@ func (r *LocalRuntime) ContainerState(name string) (*libpod.ContainerState, erro } +// Spec obtains the container spec. +func (r *LocalRuntime) Spec(name string) (*specs.Spec, error) { + reply, err := iopodman.Spec().Call(r.Conn, name) + if err != nil { + return nil, err + } + data := specs.Spec{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + return nil, err + } + return &data, nil +} + // LookupContainer gets basic information about container over a varlink // connection and then translates it to a *Container func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { @@ -79,10 +98,6 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { return nil, err } config := r.Config(idOrName) - if err != nil { - return nil, err - } - return &Container{ remoteContainer{ r, @@ -322,18 +337,32 @@ func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateV // Run creates a container overvarlink and then starts it func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) { + // FIXME + // podman-remote run -it alpine ls DOES NOT WORK YET + // podman-remote run -it alpine /bin/sh does, i suspect there is some sort of + // timing issue between the socket availability and terminal setup and the command + // being run. + // TODO the exit codes for run need to be figured out for remote connections - if !c.Bool("detach") { - return 0, errors.New("the remote client only supports detached containers") - } results := shared.NewIntermediateLayer(&c.PodmanCommand) cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink()) if err != nil { return 0, err } - fmt.Println(cid) _, err = iopodman.StartContainer().Call(r.Conn, cid) - return 0, err + if err != nil { + return 0, err + } + errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid) + if err != nil { + return 0, err + } + if c.Bool("detach") { + fmt.Println(cid) + return 0, err + } + finalError := <-errChan + return 0, finalError } func ReadExitFile(runtimeTmp, ctrID string) (int, error) { @@ -411,3 +440,102 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share } return psContainers, nil } + +func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string) (chan error, error) { + var ( + oldTermState *term.State + ) + errChan := make(chan error) + spec, err := r.Spec(cid) + if err != nil { + return nil, err + } + resize := make(chan remotecommand.TerminalSize) + haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + + // Check if we are attached to a terminal. If we are, generate resize + // events, and set the terminal to raw mode + if haveTerminal && spec.Process.Terminal { + logrus.Debugf("Handling terminal attach") + + subCtx, cancel := context.WithCancel(ctx) + defer cancel() + + resizeTty(subCtx, resize) + oldTermState, err = term.SaveState(os.Stdin.Fd()) + if err != nil { + return nil, errors.Wrapf(err, "unable to save terminal state") + } + + logrus.SetFormatter(&RawTtyFormatter{}) + term.SetRawTerminal(os.Stdin.Fd()) + + } + + _, err = iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid) + if err != nil { + restoreTerminal(oldTermState) + return nil, err + } + + // These are the varlink sockets + reader := r.Conn.Reader + writer := r.Conn.Writer + + // These are the special writers that encode input from the client. + varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin) + varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize) + + go func() { + // Read from the wire and direct to stdout or stderr + err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil) + defer restoreTerminal(oldTermState) + errChan <- err + }() + + go func() { + for termResize := range resize { + b, err := json.Marshal(termResize) + if err != nil { + defer restoreTerminal(oldTermState) + errChan <- err + } + _, err = varlinkResizeWriter.Write(b) + if err != nil { + defer restoreTerminal(oldTermState) + errChan <- err + } + } + }() + + // Takes stdinput and sends it over the wire after being encoded + go func() { + if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil { + defer restoreTerminal(oldTermState) + errChan <- err + } + + }() + return errChan, nil + +} + +// Attach to a remote terminal +func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error { + ctr, err := r.LookupContainer(c.InputArgs[0]) + if err != nil { + return nil + } + if ctr.state.State != libpod.ContainerStateRunning { + return errors.New("you can only attach to running containers") + } + inputStream := os.Stdin + if c.NoStdin { + inputStream = nil + } + errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0]) + if err != nil { + return err + } + return <-errChan +} |