-
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
Sep 17, 2018
1 parent
318e7fd
commit 0e98bef
Showing
5 changed files
with
282 additions
and
0 deletions.
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,109 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/spf13/cobra" | ||
"io" | ||
"os" | ||
|
||
"github.com/alibaba/pouch/pkg/ioutils" | ||
"github.com/alibaba/pouch/pkg/term" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// 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" | ||
|
||
// 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") | ||
} | ||
|
||
// runAttach is used to attach a container. | ||
func (ac *AttachCommand) runAttach(args []string) error { | ||
name := args[0] | ||
|
||
ctx := context.Background() | ||
apiClient := ac.cli.Client() | ||
|
||
in, out, err := setRawMode(!ac.noStdin, false) | ||
if err != nil { | ||
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") | ||
} | ||
}() | ||
|
||
conn, br, err := apiClient.ContainerAttach(ctx, name, !ac.noStdin) | ||
if err != nil { | ||
return fmt.Errorf("failed to attach container: %v", err) | ||
} | ||
defer conn.Close() | ||
|
||
var inReader io.Reader = os.Stdin | ||
escapeKeys := term.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 { | ||
logrus.Warnf("invalid detach escape keys, using default: %s", err) | ||
} else { | ||
escapeKeys = customEscapeKeys | ||
} | ||
} | ||
inReader = ioutils.NewReadCloserWrapper(term.NewEscapeProxy(os.Stdin, escapeKeys), os.Stdin.Close) | ||
|
||
wait := make(chan struct{}) | ||
go func() { | ||
io.Copy(os.Stdout, br) | ||
wait <- struct{}{} | ||
}() | ||
go func() { | ||
_, err := io.Copy(conn, inReader) | ||
if _, ok := err.(term.EscapeError); ok { | ||
wait <- struct{}{} | ||
} | ||
}() | ||
|
||
<-wait | ||
return nil | ||
} | ||
|
||
// example shows examples in attach command, and is used in auto-generated cli docs. | ||
func (ac *AttachCommand) example() string { | ||
return `$ pouch ps | ||
Name ID Status Image Runtime | ||
foo 71b9c1 Running docker.io/library/busybox:latest runc | ||
$ pouch attach foo` | ||
} |
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,66 @@ | ||
package term | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// ASCII list the possible supported ASCII key sequence | ||
var ASCII = []string{ | ||
"ctrl-@", | ||
"ctrl-a", | ||
"ctrl-b", | ||
"ctrl-c", | ||
"ctrl-d", | ||
"ctrl-e", | ||
"ctrl-f", | ||
"ctrl-g", | ||
"ctrl-h", | ||
"ctrl-i", | ||
"ctrl-j", | ||
"ctrl-k", | ||
"ctrl-l", | ||
"ctrl-m", | ||
"ctrl-n", | ||
"ctrl-o", | ||
"ctrl-p", | ||
"ctrl-q", | ||
"ctrl-r", | ||
"ctrl-s", | ||
"ctrl-t", | ||
"ctrl-u", | ||
"ctrl-v", | ||
"ctrl-w", | ||
"ctrl-x", | ||
"ctrl-y", | ||
"ctrl-z", | ||
"ctrl-[", | ||
"ctrl-\\", | ||
"ctrl-]", | ||
"ctrl-^", | ||
"ctrl-_", | ||
} | ||
|
||
// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code. | ||
func ToBytes(keys string) ([]byte, error) { | ||
codes := []byte{} | ||
next: | ||
for _, key := range strings.Split(keys, ",") { | ||
if len(key) != 1 { | ||
for code, ctrl := range ASCII { | ||
if ctrl == key { | ||
codes = append(codes, byte(code)) | ||
continue next | ||
} | ||
} | ||
if key == "DEL" { | ||
codes = append(codes, 127) | ||
} else { | ||
return nil, fmt.Errorf("Unknown character: '%s'", key) | ||
} | ||
} else { | ||
codes = append(codes, key[0]) | ||
} | ||
} | ||
return codes, nil | ||
} |
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,81 @@ | ||
package term | ||
|
||
import ( | ||
"io" | ||
) | ||
|
||
// DefaultEscapeKeys represents the default escape key sequence: ctrl-p, ctrl-q | ||
var DefaultEscapeKeys = []byte{16, 17} | ||
|
||
// EscapeError is special error which returned by a TTY proxy reader's Read() | ||
// method in case its detach escape sequence is read. | ||
type EscapeError struct{} | ||
|
||
func (EscapeError) Error() string { | ||
return "read escape sequence" | ||
} | ||
|
||
// escapeProxy is used only for attaches with a TTY. It is used to proxy | ||
// stdin keypresses from the underlying reader and look for the passed in | ||
// escape key sequence to signal a detach. | ||
type escapeProxy struct { | ||
escapeKeys []byte | ||
escapeKeyPos int | ||
r io.Reader | ||
} | ||
|
||
// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader | ||
// and detects when the specified escape keys are read, in which case the Read | ||
// method will return an error of type EscapeError. | ||
func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader { | ||
return &escapeProxy{ | ||
escapeKeys: escapeKeys, | ||
r: r, | ||
} | ||
} | ||
|
||
func (r *escapeProxy) Read(buf []byte) (int, error) { | ||
nr, err := r.r.Read(buf) | ||
|
||
if len(r.escapeKeys) == 0 { | ||
return nr, err | ||
} | ||
|
||
preserve := func() { | ||
// this preserves the original key presses in the passed in buffer | ||
nr += r.escapeKeyPos | ||
preserve := make([]byte, 0, r.escapeKeyPos+len(buf)) | ||
preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...) | ||
preserve = append(preserve, buf...) | ||
r.escapeKeyPos = 0 | ||
copy(buf[0:nr], preserve) | ||
} | ||
|
||
if nr != 1 || err != nil { | ||
if r.escapeKeyPos > 0 { | ||
preserve() | ||
} | ||
return nr, err | ||
} | ||
|
||
if buf[0] != r.escapeKeys[r.escapeKeyPos] { | ||
if r.escapeKeyPos > 0 { | ||
preserve() | ||
} | ||
return nr, nil | ||
} | ||
|
||
if r.escapeKeyPos == len(r.escapeKeys)-1 { | ||
return 0, EscapeError{} | ||
} | ||
|
||
// Looks like we've got an escape key, but we need to match again on the next | ||
// read. | ||
// Store the current escape key we found so we can look for the next one on | ||
// the next read. | ||
// Since this is an escape key, make sure we don't let the caller read it | ||
// If later on we find that this is not the escape sequence, we'll add the | ||
// keys back | ||
r.escapeKeyPos++ | ||
return nr - r.escapeKeyPos, nil | ||
} |