mirror of
https://github.com/containers/podman.git
synced 2025-06-06 15:00:40 +08:00
Add the ability to attach remotely to a container
Also, you can now podman-remote run -it. There are some bugs that need to be ironed out but I would prefer to merge this so we can make both progress on start and exec as well as the bugs. * when doing podman-remote run -it foo /bin/bash, you have to press enter to get the prompt to display. with the localized podman, we had to teach it connect to the console first and then start the container so we did not miss anything. * when executing "exit" in the console, we get a hard lockup likely because nobody knows what to do. * custom detach keys are not supported * podman-remote run -it alpine ls does not currently work. only dropping to a shell works. Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
21
API.md
21
API.md
@ -3,6 +3,10 @@ Podman Service Interface and API description. The master version of this docume
|
||||
in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in the upstream libpod repository.
|
||||
## Index
|
||||
|
||||
[func Attach(name: string) ](#Attach)
|
||||
|
||||
[func AttachControl(name: string) ](#AttachControl)
|
||||
|
||||
[func BuildImage(build: BuildInfo) MoreResponse](#BuildImage)
|
||||
|
||||
[func BuildImageHierarchyMap(name: string) string](#BuildImageHierarchyMap)
|
||||
@ -135,6 +139,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
|
||||
|
||||
[func SendFile(type: string, length: int) string](#SendFile)
|
||||
|
||||
[func Spec(name: string) string](#Spec)
|
||||
|
||||
[func StartContainer(name: string) string](#StartContainer)
|
||||
|
||||
[func StartPod(name: string) string](#StartPod)
|
||||
@ -252,6 +258,16 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
|
||||
[error WantsMoreRequired](#WantsMoreRequired)
|
||||
|
||||
## Methods
|
||||
### <a name="Attach"></a>func Attach
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
method Attach(name: [string](https://godoc.org/builtin#string)) </div>
|
||||
|
||||
### <a name="AttachControl"></a>func AttachControl
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
method AttachControl(name: [string](https://godoc.org/builtin#string)) </div>
|
||||
|
||||
### <a name="BuildImage"></a>func BuildImage
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
@ -945,6 +961,11 @@ search results per registry.
|
||||
|
||||
method SendFile(type: [string](https://godoc.org/builtin#string), length: [int](https://godoc.org/builtin#int)) [string](https://godoc.org/builtin#string)</div>
|
||||
Sendfile allows a remote client to send a file to the host
|
||||
### <a name="Spec"></a>func Spec
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
method Spec(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div>
|
||||
Spec returns the oci spec for a container. This call is for development of Podman only and generally should not be used.
|
||||
### <a name="StartContainer"></a>func StartContainer
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
|
@ -1,11 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||
"github.com/containers/libpod/cmd/podman/libpodruntime"
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/pkg/adapter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -39,49 +35,21 @@ func init() {
|
||||
flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process")
|
||||
flags.BoolVarP(&attachCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
||||
markFlagHiddenForRemoteClient("latest", flags)
|
||||
// TODO allow for passing of a new deatch keys
|
||||
markFlagHiddenForRemoteClient("detach-keys", flags)
|
||||
}
|
||||
|
||||
func attachCmd(c *cliconfig.AttachValues) error {
|
||||
args := c.InputArgs
|
||||
var ctr *libpod.Container
|
||||
|
||||
if len(c.InputArgs) > 1 || (len(c.InputArgs) == 0 && !c.Latest) {
|
||||
return errors.Errorf("attach requires the name or id of one running container or the latest flag")
|
||||
}
|
||||
|
||||
runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
|
||||
if remoteclient && len(c.InputArgs) != 1 {
|
||||
return errors.Errorf("attach requires the name or id of one running container")
|
||||
}
|
||||
runtime, err := adapter.GetRuntime(&c.PodmanCommand)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating libpod runtime")
|
||||
return errors.Wrapf(err, "error creating runtime")
|
||||
}
|
||||
defer runtime.Shutdown(false)
|
||||
|
||||
if c.Latest {
|
||||
ctr, err = runtime.GetLatestContainer()
|
||||
} else {
|
||||
ctr, err = runtime.LookupContainer(args[0])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to exec into %s", args[0])
|
||||
}
|
||||
|
||||
conState, err := ctr.State()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to determine state of %s", args[0])
|
||||
}
|
||||
if conState != libpod.ContainerStateRunning {
|
||||
return errors.Errorf("you can only attach to running containers")
|
||||
}
|
||||
|
||||
inputStream := os.Stdin
|
||||
if c.NoStdin {
|
||||
inputStream = nil
|
||||
}
|
||||
|
||||
// If the container is in a pod, also set to recursively start dependencies
|
||||
if err := adapter.StartAttachCtr(getContext(), ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach {
|
||||
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
|
||||
}
|
||||
|
||||
return nil
|
||||
return runtime.Attach(getContext(), c)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ const remoteclient = false
|
||||
// Commands that the local client implements
|
||||
func getMainCommands() []*cobra.Command {
|
||||
rootCommands := []*cobra.Command{
|
||||
_attachCommand,
|
||||
_commitCommand,
|
||||
_execCommand,
|
||||
_generateCommand,
|
||||
@ -47,7 +46,6 @@ func getImageSubCommands() []*cobra.Command {
|
||||
func getContainerSubCommands() []*cobra.Command {
|
||||
|
||||
return []*cobra.Command{
|
||||
_attachCommand,
|
||||
_checkpointCommand,
|
||||
_cleanupCommand,
|
||||
_commitCommand,
|
||||
|
@ -50,6 +50,7 @@ var (
|
||||
|
||||
// Commands that are universally implemented.
|
||||
containerCommands = []*cobra.Command{
|
||||
_attachCommand,
|
||||
_containerExistsCommand,
|
||||
_contInspectSubCommand,
|
||||
_diffCommand,
|
||||
|
@ -38,6 +38,7 @@ var (
|
||||
// Commands that the remote and local client have
|
||||
// implemented.
|
||||
var mainCommands = []*cobra.Command{
|
||||
_attachCommand,
|
||||
_buildCommand,
|
||||
_diffCommand,
|
||||
_createCommand,
|
||||
|
@ -658,8 +658,9 @@ method PauseContainer(name: string) -> (container: string)
|
||||
# See also [PauseContainer](#PauseContainer).
|
||||
method UnpauseContainer(name: string) -> (container: string)
|
||||
|
||||
# This method has not be implemented yet.
|
||||
# method AttachToContainer() -> (notimplemented: NotImplemented)
|
||||
method Attach(name: string) -> ()
|
||||
|
||||
method AttachControl(name: string) -> ()
|
||||
|
||||
# GetAttachSockets takes the name or ID of an existing container. It returns file paths for two sockets needed
|
||||
# to properly communicate with a container. The first is the actual I/O socket that the container uses. The
|
||||
@ -1154,6 +1155,9 @@ method PodStateData(name: string) -> (config: string)
|
||||
# This call is for the development of Podman only and should not be used.
|
||||
method CreateFromCC(in: []string) -> (id: string)
|
||||
|
||||
# Spec returns the oci spec for a container. This call is for development of Podman only and generally should not be used.
|
||||
method Spec(name: string) -> (config: string)
|
||||
|
||||
# Sendfile allows a remote client to send a file to the host
|
||||
method SendFile(type: string, length: int) -> (file_handle: string)
|
||||
|
||||
|
@ -407,3 +407,39 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share
|
||||
logrus.Debugf("Setting maximum workers to %d", maxWorkers)
|
||||
return shared.GetPsContainerOutput(r.Runtime, opts, c.Filter, maxWorkers)
|
||||
}
|
||||
|
||||
// Attach ...
|
||||
func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error {
|
||||
var (
|
||||
ctr *libpod.Container
|
||||
err error
|
||||
)
|
||||
|
||||
if c.Latest {
|
||||
ctr, err = r.Runtime.GetLatestContainer()
|
||||
} else {
|
||||
ctr, err = r.Runtime.LookupContainer(c.InputArgs[0])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to exec into %s", c.InputArgs[0])
|
||||
}
|
||||
|
||||
conState, err := ctr.State()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to determine state of %s", ctr.ID())
|
||||
}
|
||||
if conState != libpod.ContainerStateRunning {
|
||||
return errors.Errorf("you can only attach to running containers")
|
||||
}
|
||||
|
||||
inputStream := os.Stdin
|
||||
if c.NoStdin {
|
||||
inputStream = nil
|
||||
}
|
||||
// If the container is in a pod, also set to recursively start dependencies
|
||||
if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach {
|
||||
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -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,19 +337,33 @@ 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)
|
||||
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) {
|
||||
return 0, libpod.ErrNotImplemented
|
||||
@ -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
|
||||
}
|
||||
|
75
pkg/varlinkapi/attach.go
Normal file
75
pkg/varlinkapi/attach.go
Normal file
@ -0,0 +1,75 @@
|
||||
// +build varlink
|
||||
|
||||
package varlinkapi
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/containers/libpod/cmd/podman/varlink"
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// Close is method to close the writer
|
||||
|
||||
// Attach ...
|
||||
func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string) error {
|
||||
var finalErr error
|
||||
resize := make(chan remotecommand.TerminalSize)
|
||||
errChan := make(chan error)
|
||||
|
||||
if !call.WantsUpgrade() {
|
||||
return call.ReplyErrorOccurred("client must use upgraded connection to attach")
|
||||
}
|
||||
ctr, err := i.Runtime.LookupContainer(name)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
|
||||
// These are the varlink sockets
|
||||
reader := call.Call.Reader
|
||||
writer := call.Call.Writer
|
||||
|
||||
// This pipe is used to pass stdin from the client to the input stream
|
||||
// once the msg has been "decoded"
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
stdoutWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdout)
|
||||
// TODO if runc ever starts passing stderr, we can too
|
||||
//stderrWriter := NewVirtWriteCloser(writer, ToStderr)
|
||||
|
||||
streams := libpod.AttachStreams{
|
||||
OutputStream: stdoutWriter,
|
||||
InputStream: pr,
|
||||
// Runc eats the error stream
|
||||
ErrorStream: stdoutWriter,
|
||||
AttachInput: true,
|
||||
AttachOutput: true,
|
||||
// Runc eats the error stream
|
||||
AttachError: true,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := virtwriter.Reader(reader, nil, nil, pw, resize); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// TODO allow for customizable detach keys
|
||||
if err := ctr.Attach(&streams, "", resize); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
// Blocking on an error
|
||||
case finalErr = <-errChan:
|
||||
// Need to close up shop
|
||||
_ = finalErr
|
||||
}
|
||||
quitWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.Quit)
|
||||
_, err = quitWriter.Write([]byte("HANG-UP"))
|
||||
return call.Writer.Flush()
|
||||
}
|
@ -634,6 +634,22 @@ func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prev
|
||||
return call.ReplyGetContainerStatsWithHistory(cStats)
|
||||
}
|
||||
|
||||
// Spec ...
|
||||
func (i *LibpodAPI) Spec(call iopodman.VarlinkCall, name string) error {
|
||||
ctr, err := i.Runtime.LookupContainer(name)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
|
||||
spec := ctr.Spec()
|
||||
b, err := json.Marshal(spec)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
|
||||
return call.ReplySpec(string(b))
|
||||
}
|
||||
|
||||
// GetContainersLogs is the varlink endpoint to obtain one or more container logs
|
||||
func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error {
|
||||
var wg sync.WaitGroup
|
||||
|
155
pkg/varlinkapi/virtwriter/virtwriter.go
Normal file
155
pkg/varlinkapi/virtwriter/virtwriter.go
Normal file
@ -0,0 +1,155 @@
|
||||
package virtwriter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// SocketDest is the "key" to where IO should go on the varlink
|
||||
// multiplexed socket
|
||||
type SocketDest int
|
||||
|
||||
const (
|
||||
// ToStdout indicates traffic should go stdout
|
||||
ToStdout SocketDest = iota
|
||||
// ToStdin indicates traffic came from stdin
|
||||
ToStdin SocketDest = iota
|
||||
// ToStderr indicates traffuc should go to stderr
|
||||
ToStderr SocketDest = iota
|
||||
// TerminalResize indicates a terminal resize event has occurred
|
||||
// and data should be passed to resizer
|
||||
TerminalResize SocketDest = iota
|
||||
// Quit and detach
|
||||
Quit SocketDest = iota
|
||||
)
|
||||
|
||||
// IntToSocketDest returns a socketdest based on integer input
|
||||
func IntToSocketDest(i int) SocketDest {
|
||||
switch i {
|
||||
case ToStdout.Int():
|
||||
return ToStdout
|
||||
case ToStderr.Int():
|
||||
return ToStderr
|
||||
case ToStdin.Int():
|
||||
return ToStdin
|
||||
case TerminalResize.Int():
|
||||
return TerminalResize
|
||||
case Quit.Int():
|
||||
return Quit
|
||||
default:
|
||||
return ToStderr
|
||||
}
|
||||
}
|
||||
|
||||
// Int returns the integer representation of the socket dest
|
||||
func (sd SocketDest) Int() int {
|
||||
return int(sd)
|
||||
}
|
||||
|
||||
// VirtWriteCloser are writers for attach which include the dest
|
||||
// of the data
|
||||
type VirtWriteCloser struct {
|
||||
writer *bufio.Writer
|
||||
dest SocketDest
|
||||
}
|
||||
|
||||
// NewVirtWriteCloser is a constructor
|
||||
func NewVirtWriteCloser(w *bufio.Writer, dest SocketDest) VirtWriteCloser {
|
||||
return VirtWriteCloser{w, dest}
|
||||
}
|
||||
|
||||
// Close is a required method for a writecloser
|
||||
func (v VirtWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write prepends a header to the input message. The header is
|
||||
// 8bytes. Position one contains the destination. Positions
|
||||
// 5,6,7,8 are a big-endian encoded uint32 for len of the message.
|
||||
func (v VirtWriteCloser) Write(input []byte) (int, error) {
|
||||
header := []byte{byte(v.dest), 0, 0, 0}
|
||||
// Go makes us define the byte for big endian
|
||||
mlen := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(mlen, uint32(len(input)))
|
||||
// append the message len to the header
|
||||
msg := append(header, mlen...)
|
||||
// append the message to the header
|
||||
msg = append(msg, input...)
|
||||
_, err := v.writer.Write(msg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = v.writer.Flush()
|
||||
return len(input), err
|
||||
}
|
||||
|
||||
// Reader decodes the content that comes over the wire and directs it to the proper destination.
|
||||
func Reader(r *bufio.Reader, output, errput *os.File, input *io.PipeWriter, resize chan remotecommand.TerminalSize) error {
|
||||
var saveb []byte
|
||||
var eom int
|
||||
for {
|
||||
readb := make([]byte, 32*1024)
|
||||
n, err := r.Read(readb)
|
||||
// TODO, later may be worth checking in len of the read is 0
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b := append(saveb, readb[0:n]...)
|
||||
// no sense in reading less than the header len
|
||||
for len(b) > 7 {
|
||||
eom = int(binary.BigEndian.Uint32(b[4:8])) + 8
|
||||
// The message and header are togther
|
||||
if len(b) >= eom {
|
||||
out := append([]byte{}, b[8:eom]...)
|
||||
|
||||
switch IntToSocketDest(int(b[0])) {
|
||||
case ToStdout:
|
||||
n, err := output.Write(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n < len(out) {
|
||||
return errors.New("short write error occurred on stdout")
|
||||
}
|
||||
case ToStderr:
|
||||
n, err := errput.Write(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n < len(out) {
|
||||
return errors.New("short write error occurred on stderr")
|
||||
}
|
||||
case ToStdin:
|
||||
n, err := input.Write(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n < len(out) {
|
||||
return errors.New("short write error occurred on stdin")
|
||||
}
|
||||
case TerminalResize:
|
||||
// Resize events come over in bytes, need to be reserialized
|
||||
resizeEvent := remotecommand.TerminalSize{}
|
||||
if err := json.Unmarshal(out, &resizeEvent); err != nil {
|
||||
return err
|
||||
}
|
||||
resize <- resizeEvent
|
||||
case Quit:
|
||||
return nil
|
||||
}
|
||||
b = b[eom:]
|
||||
} else {
|
||||
// We do not have the header and full message, need to slurp again
|
||||
saveb = b
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user