-
Notifications
You must be signed in to change notification settings - Fork 949
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: zhangyue <zy675793960@yeah.net>
- Loading branch information
zhangyue
committed
Oct 20, 2018
1 parent
b2542f1
commit 41aa003
Showing
14 changed files
with
894 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"github.com/alibaba/pouch/apis/types" | ||
"github.com/alibaba/pouch/client" | ||
|
||
"github.com/docker/docker/pkg/ioutils" | ||
"github.com/docker/docker/pkg/term" | ||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
// AttachDescription is used to describe attach command in detail and auto generate command doc. | ||
var AttachDescription = "Attach local standard input, output, and error streams to a running container" | ||
|
||
var defaultEscapeKeys = []byte{16, 17} | ||
|
||
// AttachCommand is used to implement 'attach' command. | ||
type AttachCommand struct { | ||
baseCommand | ||
|
||
// flags for attach command | ||
noStdin bool | ||
detachKeys string | ||
} | ||
|
||
// Init initialize "attach" command. | ||
func (ac *AttachCommand) Init(c *Cli) { | ||
ac.cli = c | ||
ac.cmd = &cobra.Command{ | ||
Use: "attach [OPTIONS] CONTAINER", | ||
Short: "Attach local standard input, output, and error streams to a running container", | ||
Long: AttachDescription, | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return ac.runAttach(args) | ||
}, | ||
Example: ac.example(), | ||
} | ||
ac.addFlags() | ||
} | ||
|
||
// addFlags adds flags for specific command. | ||
func (ac *AttachCommand) addFlags() { | ||
flagSet := ac.cmd.Flags() | ||
flagSet.BoolVar(&ac.noStdin, "no-stdin", false, "Do not attach STDIN") | ||
flagSet.StringVar(&ac.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") | ||
// TODO: sig-proxy will be supported in the future. | ||
//flagSet.BoolVar(&ac.sigProxy, "sig-proxy", true, "Proxy all received signals to the process") | ||
} | ||
|
||
func inspectAndCheckState(ctx context.Context, cli client.CommonAPIClient, name string) (*types.ContainerJSON, error) { | ||
c, err := cli.ContainerGet(ctx, name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if !c.State.Running { | ||
return nil, errors.New("You cannot attach to a stopped container, start it first") | ||
} | ||
if c.State.Paused { | ||
return nil, errors.New("You cannot attach to a paused container, unpause it first") | ||
} | ||
if c.State.Restarting { | ||
return nil, errors.New("You cannot attach to a restarting container, wait until it is running") | ||
} | ||
|
||
return c, nil | ||
} | ||
|
||
// runAttach is used to attach a container. | ||
func (ac *AttachCommand) runAttach(args []string) error { | ||
name := args[0] | ||
|
||
ctx := context.Background() | ||
apiClient := ac.cli.Client() | ||
|
||
c, err := inspectAndCheckState(ctx, apiClient, name) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var inReader io.Reader = os.Stdin | ||
if !ac.noStdin && c.Config.Tty { | ||
if term.IsTerminal(os.Stdin.Fd()) { | ||
in, out, err := setRawMode(!ac.noStdin, false) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to set raw mode %s", err) | ||
return fmt.Errorf("failed to set raw mode") | ||
} | ||
defer func() { | ||
if err := restoreMode(in, out); err != nil { | ||
fmt.Fprintf(os.Stderr, "failed to restore term mode %s", err) | ||
} | ||
}() | ||
} | ||
|
||
escapeKeys := defaultEscapeKeys | ||
// Wrap the input to detect detach escape sequence. | ||
// Use default escape keys if an invalid sequence is given. | ||
if ac.detachKeys != "" { | ||
customEscapeKeys, err := term.ToBytes(ac.detachKeys) | ||
if err != nil { | ||
return fmt.Errorf("Invalid detach keys (%s) provided", ac.detachKeys) | ||
} | ||
escapeKeys = customEscapeKeys | ||
} | ||
inReader = ioutils.NewReadCloserWrapper(term.NewEscapeProxy(os.Stdin, escapeKeys), os.Stdin.Close) | ||
} | ||
|
||
conn, br, err := apiClient.ContainerAttach(ctx, name, !ac.noStdin) | ||
if err != nil { | ||
return fmt.Errorf("failed to attach container: %v", err) | ||
} | ||
defer conn.Close() | ||
|
||
// double check in case container in wrong state | ||
_, err = inspectAndCheckState(ctx, apiClient, name) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
outputDone := make(chan error, 1) | ||
go func() { | ||
var err error | ||
_, err = io.Copy(os.Stdout, br) | ||
if err != nil { | ||
logrus.Debugf("Error receive stdout: %s", err) | ||
} | ||
outputDone <- err | ||
}() | ||
|
||
inputDone := make(chan struct{}) | ||
detached := make(chan error, 1) | ||
go func() { | ||
if !ac.noStdin { | ||
_, err := io.Copy(conn, inReader) | ||
if _, ok := err.(term.EscapeError); ok { | ||
detached <- err | ||
} | ||
if err != nil { | ||
logrus.Debugf("Error send stdin: %s", err) | ||
} | ||
} | ||
close(inputDone) | ||
|
||
}() | ||
|
||
select { | ||
case err := <-outputDone: | ||
if err != nil { | ||
logrus.Debugf("receive stdout error: %s", err) | ||
return err | ||
} | ||
case <-inputDone: | ||
select { | ||
// Wait for output to complete streaming. | ||
case err := <-outputDone: | ||
logrus.Debugf("receive stdout error: %s", err) | ||
return err | ||
case <-ctx.Done(): | ||
} | ||
case err := <-detached: | ||
// Got a detach key sequence. | ||
return err | ||
case <-ctx.Done(): | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// example shows examples in attach command, and is used in auto-generated cli docs. | ||
func (ac *AttachCommand) example() string { | ||
return `$ pouch run -d --name foo busybox sh -c 'while true; do sleep 1; echo hello; done' | ||
Name ID Status Image Runtime | ||
foo 71b9c1 Running docker.io/library/busybox:latest runc | ||
$ pouch attach foo | ||
hello | ||
hello | ||
hello` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package ioutils | ||
|
||
import "io" | ||
|
||
// ReadCloserWrapper wraps an io.Reader, and implements an io.ReadCloser | ||
// It calls the given callback function when closed. It should be constructed | ||
// with NewReadCloserWrapper | ||
type ReadCloserWrapper struct { | ||
io.Reader | ||
closer func() error | ||
} | ||
|
||
// NewReadCloserWrapper returns a new io.ReadCloser. | ||
func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { | ||
return &ReadCloserWrapper{ | ||
Reader: r, | ||
closer: closer, | ||
} | ||
|
||
} | ||
|
||
// Close calls back the passed closer function | ||
func (r *ReadCloserWrapper) Close() error { | ||
return r.closer() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/alibaba/pouch/test/command" | ||
"github.com/alibaba/pouch/test/environment" | ||
|
||
"github.com/go-check/check" | ||
"github.com/gotestyourself/gotestyourself/icmd" | ||
) | ||
|
||
// PouchCreateSuite is the test suite for attach CLI. | ||
type PouchAttachSuite struct{} | ||
|
||
func init() { | ||
check.Suite(&PouchAttachSuite{}) | ||
} | ||
|
||
// SetUpSuite does common setup in the beginning of each test suite. | ||
func (suite *PouchAttachSuite) SetUpSuite(c *check.C) { | ||
SkipIfFalse(c, environment.IsLinux) | ||
|
||
environment.PruneAllContainers(apiClient) | ||
|
||
PullImage(c, busyboxImage) | ||
} | ||
|
||
// TearDownTest does cleanup work in the end of each test. | ||
func (suite *PouchAttachSuite) TearDownTest(c *check.C) { | ||
} | ||
|
||
// TestPouchAttachRunningContainer is to verify the correctness of attach a running container. | ||
func (suite *PouchAttachSuite) TestPouchAttachRunningContainer(c *check.C) { | ||
name := "TestPouchAttachRunningContainer" | ||
|
||
res := command.PouchRun("run", "-d", "--name", name, busyboxImage, "/bin/sh", "-c", "while true; do echo hello; done") | ||
|
||
defer DelContainerForceMultyTime(c, name) | ||
res.Assert(c, icmd.Success) | ||
|
||
cmd := exec.Command(environment.PouchBinary, "attach", name) | ||
|
||
out, err := cmd.StdoutPipe() | ||
if err != nil { | ||
c.Fatal(err) | ||
} | ||
defer out.Close() | ||
|
||
if err := cmd.Start(); err != nil { | ||
c.Fatal(err) | ||
} | ||
|
||
buf := make([]byte, 1024) | ||
|
||
if _, err := out.Read(buf); err != nil && err != io.EOF { | ||
c.Fatal(err) | ||
} | ||
|
||
if !strings.Contains(string(buf), "hello") { | ||
c.Fatalf("unexpected output %s expected hello\n", string(buf)) | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.