The raw mode is actually only needed when you attach to a container.
Having it enabled all the time can be a pain, e.g: if docker crashes
your terminal will end up in a broken state.
Since we are currently missing a real API for the docker daemon to
negotiate this kind of options, this changeset actually enable the raw
mode on the login (because it outputs a password), run and attach
commands.
This "optional raw mode" is implemented by passing a more complicated
interface than io.Writer as the stdout argument of each command. This
interface (DockerConn) exposes a method which allows the command to set
the terminal in raw mode or not.
Finally, the code added by this changeset will be deprecated by a real
API for the docker daemon.
... | ... |
@@ -62,7 +62,7 @@ func (srv *Server) Help() string { |
62 | 62 |
} |
63 | 63 |
|
64 | 64 |
// 'docker login': login / register a user to registry service. |
65 |
-func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
65 |
+func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
66 | 66 |
// Read a line on raw terminal with support for simple backspace |
67 | 67 |
// sequences and echo. |
68 | 68 |
// |
... | ... |
@@ -71,7 +71,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin |
71 | 71 |
// - we have to read a password (without echoing it); |
72 | 72 |
// - the rcli "protocol" only supports cannonical and raw modes and you |
73 | 73 |
// can't tune it once the command as been started. |
74 |
- var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { |
|
74 |
+ var readStringOnRawTerminal = func(stdin io.Reader, stdout rcli.DockerConn, echo bool) string { |
|
75 | 75 |
char := make([]byte, 1) |
76 | 76 |
buffer := make([]byte, 64) |
77 | 77 |
var i = 0 |
... | ... |
@@ -106,13 +106,15 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin |
106 | 106 |
} |
107 | 107 |
return string(buffer[:i]) |
108 | 108 |
} |
109 |
- var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string { |
|
109 |
+ var readAndEchoString = func(stdin io.Reader, stdout rcli.DockerConn) string { |
|
110 | 110 |
return readStringOnRawTerminal(stdin, stdout, true) |
111 | 111 |
} |
112 |
- var readString = func(stdin io.Reader, stdout io.Writer) string { |
|
112 |
+ var readString = func(stdin io.Reader, stdout rcli.DockerConn) string { |
|
113 | 113 |
return readStringOnRawTerminal(stdin, stdout, false) |
114 | 114 |
} |
115 | 115 |
|
116 |
+ stdout.SetOptionRawTerminal() |
|
117 |
+ |
|
116 | 118 |
cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server") |
117 | 119 |
if err := cmd.Parse(args); err != nil { |
118 | 120 |
return nil |
... | ... |
@@ -158,7 +160,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin |
158 | 158 |
} |
159 | 159 |
|
160 | 160 |
// 'docker wait': block until a container stops |
161 |
-func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
161 |
+func (srv *Server) CmdWait(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
162 | 162 |
cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.") |
163 | 163 |
if err := cmd.Parse(args); err != nil { |
164 | 164 |
return nil |
... | ... |
@@ -178,14 +180,14 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string |
178 | 178 |
} |
179 | 179 |
|
180 | 180 |
// 'docker version': show version information |
181 |
-func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
181 |
+func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
182 | 182 |
fmt.Fprintf(stdout, "Version:%s\n", VERSION) |
183 | 183 |
fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT) |
184 | 184 |
return nil |
185 | 185 |
} |
186 | 186 |
|
187 | 187 |
// 'docker info': display system-wide information. |
188 |
-func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
188 |
+func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
189 | 189 |
images, _ := srv.runtime.graph.All() |
190 | 190 |
var imgcount int |
191 | 191 |
if images == nil { |
... | ... |
@@ -214,7 +216,7 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string |
214 | 214 |
return nil |
215 | 215 |
} |
216 | 216 |
|
217 |
-func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
217 |
+func (srv *Server) CmdStop(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
218 | 218 |
cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container") |
219 | 219 |
if err := cmd.Parse(args); err != nil { |
220 | 220 |
return nil |
... | ... |
@@ -236,7 +238,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string |
236 | 236 |
return nil |
237 | 237 |
} |
238 | 238 |
|
239 |
-func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
239 |
+func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
240 | 240 |
cmd := rcli.Subcmd(stdout, "restart", "[OPTIONS] NAME", "Restart a running container") |
241 | 241 |
if err := cmd.Parse(args); err != nil { |
242 | 242 |
return nil |
... | ... |
@@ -258,7 +260,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str |
258 | 258 |
return nil |
259 | 259 |
} |
260 | 260 |
|
261 |
-func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
261 |
+func (srv *Server) CmdStart(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
262 | 262 |
cmd := rcli.Subcmd(stdout, "start", "[OPTIONS] NAME", "Start a stopped container") |
263 | 263 |
if err := cmd.Parse(args); err != nil { |
264 | 264 |
return nil |
... | ... |
@@ -280,7 +282,7 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin |
280 | 280 |
return nil |
281 | 281 |
} |
282 | 282 |
|
283 |
-func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
283 |
+func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
284 | 284 |
cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container") |
285 | 285 |
if err := cmd.Parse(args); err != nil { |
286 | 286 |
return nil |
... | ... |
@@ -315,7 +317,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str |
315 | 315 |
return nil |
316 | 316 |
} |
317 | 317 |
|
318 |
-func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
318 |
+func (srv *Server) CmdPort(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
319 | 319 |
cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") |
320 | 320 |
if err := cmd.Parse(args); err != nil { |
321 | 321 |
return nil |
... | ... |
@@ -339,7 +341,7 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string |
339 | 339 |
} |
340 | 340 |
|
341 | 341 |
// 'docker rmi NAME' removes all images with the name NAME |
342 |
-func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) { |
|
342 |
+func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) (err error) { |
|
343 | 343 |
cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image") |
344 | 344 |
if err := cmd.Parse(args); err != nil { |
345 | 345 |
return nil |
... | ... |
@@ -356,7 +358,7 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) |
356 | 356 |
return nil |
357 | 357 |
} |
358 | 358 |
|
359 |
-func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
359 |
+func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
360 | 360 |
cmd := rcli.Subcmd(stdout, "history", "[OPTIONS] IMAGE", "Show the history of an image") |
361 | 361 |
if err := cmd.Parse(args); err != nil { |
362 | 362 |
return nil |
... | ... |
@@ -382,7 +384,7 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str |
382 | 382 |
}) |
383 | 383 |
} |
384 | 384 |
|
385 |
-func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
385 |
+func (srv *Server) CmdRm(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
386 | 386 |
cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container") |
387 | 387 |
if err := cmd.Parse(args); err != nil { |
388 | 388 |
return nil |
... | ... |
@@ -400,7 +402,7 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) |
400 | 400 |
} |
401 | 401 |
|
402 | 402 |
// 'docker kill NAME' kills a running container |
403 |
-func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
403 |
+func (srv *Server) CmdKill(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
404 | 404 |
cmd := rcli.Subcmd(stdout, "kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container") |
405 | 405 |
if err := cmd.Parse(args); err != nil { |
406 | 406 |
return nil |
... | ... |
@@ -417,7 +419,7 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string |
417 | 417 |
return nil |
418 | 418 |
} |
419 | 419 |
|
420 |
-func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
420 |
+func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
421 | 421 |
cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") |
422 | 422 |
var archive io.Reader |
423 | 423 |
var resp *http.Response |
... | ... |
@@ -464,7 +466,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri |
464 | 464 |
return nil |
465 | 465 |
} |
466 | 466 |
|
467 |
-func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
467 |
+func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
468 | 468 |
cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry") |
469 | 469 |
if err := cmd.Parse(args); err != nil { |
470 | 470 |
return nil |
... | ... |
@@ -523,7 +525,7 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string |
523 | 523 |
return nil |
524 | 524 |
} |
525 | 525 |
|
526 |
-func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
526 |
+func (srv *Server) CmdPull(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
527 | 527 |
cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry") |
528 | 528 |
if err := cmd.Parse(args); err != nil { |
529 | 529 |
return nil |
... | ... |
@@ -548,7 +550,7 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string |
548 | 548 |
return nil |
549 | 549 |
} |
550 | 550 |
|
551 |
-func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
551 |
+func (srv *Server) CmdImages(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
552 | 552 |
cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images") |
553 | 553 |
//limit := cmd.Int("l", 0, "Only show the N most recent versions of each image") |
554 | 554 |
quiet := cmd.Bool("q", false, "only show numeric IDs") |
... | ... |
@@ -638,7 +640,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri |
638 | 638 |
return nil |
639 | 639 |
} |
640 | 640 |
|
641 |
-func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
641 |
+func (srv *Server) CmdPs(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
642 | 642 |
cmd := rcli.Subcmd(stdout, |
643 | 643 |
"ps", "[OPTIONS]", "List containers") |
644 | 644 |
quiet := cmd.Bool("q", false, "Only display numeric IDs") |
... | ... |
@@ -685,7 +687,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) |
685 | 685 |
return nil |
686 | 686 |
} |
687 | 687 |
|
688 |
-func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
688 |
+func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
689 | 689 |
cmd := rcli.Subcmd(stdout, |
690 | 690 |
"commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", |
691 | 691 |
"Create a new image from a container's changes") |
... | ... |
@@ -706,7 +708,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri |
706 | 706 |
return nil |
707 | 707 |
} |
708 | 708 |
|
709 |
-func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
709 |
+func (srv *Server) CmdExport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
710 | 710 |
cmd := rcli.Subcmd(stdout, |
711 | 711 |
"export", "CONTAINER", |
712 | 712 |
"Export the contents of a filesystem as a tar archive") |
... | ... |
@@ -728,7 +730,7 @@ func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...stri |
728 | 728 |
return fmt.Errorf("No such container: %s", name) |
729 | 729 |
} |
730 | 730 |
|
731 |
-func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
731 |
+func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
732 | 732 |
cmd := rcli.Subcmd(stdout, |
733 | 733 |
"diff", "CONTAINER [OPTIONS]", |
734 | 734 |
"Inspect changes on a container's filesystem") |
... | ... |
@@ -752,7 +754,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string |
752 | 752 |
return nil |
753 | 753 |
} |
754 | 754 |
|
755 |
-func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
755 |
+func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
756 | 756 |
cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container") |
757 | 757 |
if err := cmd.Parse(args); err != nil { |
758 | 758 |
return nil |
... | ... |
@@ -784,7 +786,8 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string |
784 | 784 |
return fmt.Errorf("No such container: %s", cmd.Arg(0)) |
785 | 785 |
} |
786 | 786 |
|
787 |
-func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
787 |
+func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
788 |
+ stdout.SetOptionRawTerminal() |
|
788 | 789 |
cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container") |
789 | 790 |
if err := cmd.Parse(args); err != nil { |
790 | 791 |
return nil |
... | ... |
@@ -857,7 +860,7 @@ func (opts AttachOpts) Get(val string) bool { |
857 | 857 |
return false |
858 | 858 |
} |
859 | 859 |
|
860 |
-func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
860 |
+func (srv *Server) CmdTag(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
861 | 861 |
cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository") |
862 | 862 |
force := cmd.Bool("f", false, "Force") |
863 | 863 |
if err := cmd.Parse(args); err != nil { |
... | ... |
@@ -870,7 +873,8 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) |
870 | 870 |
return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force) |
871 | 871 |
} |
872 | 872 |
|
873 |
-func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
873 |
+func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { |
|
874 |
+ stdout.SetOptionRawTerminal() |
|
874 | 875 |
config, err := ParseRun(args, stdout) |
875 | 876 |
if err != nil { |
876 | 877 |
return err |
... | ... |
@@ -2,6 +2,7 @@ package main |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"flag" |
5 |
+ "fmt" |
|
5 | 6 |
"github.com/dotcloud/docker" |
6 | 7 |
"github.com/dotcloud/docker/rcli" |
7 | 8 |
"github.com/dotcloud/docker/term" |
... | ... |
@@ -56,30 +57,82 @@ func daemon() error { |
56 | 56 |
return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service) |
57 | 57 |
} |
58 | 58 |
|
59 |
-func runCommand(args []string) error { |
|
60 |
- var oldState *term.State |
|
61 |
- var err error |
|
62 |
- if term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" { |
|
63 |
- oldState, err = term.MakeRaw(int(os.Stdin.Fd())) |
|
64 |
- if err != nil { |
|
65 |
- return err |
|
59 |
+func setRawTerminal() (*term.State, error) { |
|
60 |
+ oldState, err := term.MakeRaw(int(os.Stdin.Fd())) |
|
61 |
+ if err != nil { |
|
62 |
+ return nil, err |
|
63 |
+ } |
|
64 |
+ c := make(chan os.Signal, 1) |
|
65 |
+ signal.Notify(c, os.Interrupt) |
|
66 |
+ go func() { |
|
67 |
+ for _ = range c { |
|
68 |
+ term.Restore(int(os.Stdin.Fd()), oldState) |
|
69 |
+ log.Printf("\nSIGINT received\n") |
|
70 |
+ os.Exit(0) |
|
66 | 71 |
} |
67 |
- defer term.Restore(int(os.Stdin.Fd()), oldState) |
|
68 |
- c := make(chan os.Signal, 1) |
|
69 |
- signal.Notify(c, os.Interrupt) |
|
70 |
- go func() { |
|
71 |
- for _ = range c { |
|
72 |
- term.Restore(int(os.Stdin.Fd()), oldState) |
|
73 |
- log.Printf("\nSIGINT received\n") |
|
74 |
- os.Exit(0) |
|
75 |
- } |
|
76 |
- }() |
|
72 |
+ }() |
|
73 |
+ return oldState, err |
|
74 |
+} |
|
75 |
+ |
|
76 |
+func restoreTerminal(state *term.State) { |
|
77 |
+ term.Restore(int(os.Stdin.Fd()), state) |
|
78 |
+} |
|
79 |
+ |
|
80 |
+type DockerLocalConn struct { |
|
81 |
+ file *os.File |
|
82 |
+ savedState *term.State |
|
83 |
+} |
|
84 |
+ |
|
85 |
+func newDockerLocalConn(output *os.File) *DockerLocalConn { |
|
86 |
+ return &DockerLocalConn{file: output} |
|
87 |
+} |
|
88 |
+ |
|
89 |
+func (c *DockerLocalConn) Read(b []byte) (int, error) { return c.file.Read(b) } |
|
90 |
+ |
|
91 |
+func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.file.Write(b) } |
|
92 |
+ |
|
93 |
+func (c *DockerLocalConn) Close() error { |
|
94 |
+ if c.savedState != nil { |
|
95 |
+ restoreTerminal(c.savedState) |
|
96 |
+ c.savedState = nil |
|
97 |
+ } |
|
98 |
+ return c.file.Close() |
|
99 |
+} |
|
100 |
+ |
|
101 |
+func (c *DockerLocalConn) CloseWrite() error { return nil } |
|
102 |
+ |
|
103 |
+func (c *DockerLocalConn) CloseRead() error { return nil } |
|
104 |
+ |
|
105 |
+func (c *DockerLocalConn) GetOptions() *rcli.DockerConnOptions { return nil } |
|
106 |
+ |
|
107 |
+func (c *DockerLocalConn) SetOptionRawTerminal() { |
|
108 |
+ if state, err := setRawTerminal(); err != nil { |
|
109 |
+ fmt.Fprintf( |
|
110 |
+ os.Stderr, |
|
111 |
+ "Can't set the terminal in raw mode: %v", |
|
112 |
+ err.Error(), |
|
113 |
+ ) |
|
114 |
+ } else { |
|
115 |
+ c.savedState = state |
|
77 | 116 |
} |
117 |
+} |
|
118 |
+ |
|
119 |
+func runCommand(args []string) error { |
|
78 | 120 |
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose |
79 | 121 |
// CloseWrite(), which we need to cleanly signal that stdin is closed without |
80 | 122 |
// closing the connection. |
81 | 123 |
// See http://code.google.com/p/go/issues/detail?id=3345 |
82 | 124 |
if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil { |
125 |
+ options := conn.GetOptions() |
|
126 |
+ if options.RawTerminal && |
|
127 |
+ term.IsTerminal(int(os.Stdin.Fd())) && |
|
128 |
+ os.Getenv("NORAW") == "" { |
|
129 |
+ if oldState, err := setRawTerminal(); err != nil { |
|
130 |
+ return err |
|
131 |
+ } else { |
|
132 |
+ defer restoreTerminal(oldState) |
|
133 |
+ } |
|
134 |
+ } |
|
83 | 135 |
receiveStdout := docker.Go(func() error { |
84 | 136 |
_, err := io.Copy(os.Stdout, conn) |
85 | 137 |
return err |
... | ... |
@@ -104,12 +157,11 @@ func runCommand(args []string) error { |
104 | 104 |
if err != nil { |
105 | 105 |
return err |
106 | 106 |
} |
107 |
- if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil { |
|
107 |
+ dockerConn := newDockerLocalConn(os.Stdout) |
|
108 |
+ defer dockerConn.Close() |
|
109 |
+ if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil { |
|
108 | 110 |
return err |
109 | 111 |
} |
110 | 112 |
} |
111 |
- if oldState != nil { |
|
112 |
- term.Restore(int(os.Stdin.Fd()), oldState) |
|
113 |
- } |
|
114 | 113 |
return nil |
115 | 114 |
} |
... | ... |
@@ -2,6 +2,7 @@ package rcli |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"bufio" |
5 |
+ "bytes" |
|
5 | 6 |
"encoding/json" |
6 | 7 |
"fmt" |
7 | 8 |
"io" |
... | ... |
@@ -15,22 +16,104 @@ import ( |
15 | 15 |
var DEBUG_FLAG bool = false |
16 | 16 |
var CLIENT_SOCKET io.Writer = nil |
17 | 17 |
|
18 |
+type DockerTCPConn struct { |
|
19 |
+ conn *net.TCPConn |
|
20 |
+ options *DockerConnOptions |
|
21 |
+ optionsBuf *[]byte |
|
22 |
+ handshaked bool |
|
23 |
+ client bool |
|
24 |
+} |
|
25 |
+ |
|
26 |
+func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn { |
|
27 |
+ return &DockerTCPConn{ |
|
28 |
+ conn: conn, |
|
29 |
+ options: &DockerConnOptions{}, |
|
30 |
+ client: client, |
|
31 |
+ } |
|
32 |
+} |
|
33 |
+ |
|
34 |
+func (c *DockerTCPConn) SetOptionRawTerminal() { |
|
35 |
+ c.options.RawTerminal = true |
|
36 |
+} |
|
37 |
+ |
|
38 |
+func (c *DockerTCPConn) GetOptions() *DockerConnOptions { |
|
39 |
+ if c.client && !c.handshaked { |
|
40 |
+ // Attempt to parse options encoded as a JSON dict and store |
|
41 |
+ // the reminder of what we read from the socket in a buffer. |
|
42 |
+ // |
|
43 |
+ // bufio (and its ReadBytes method) would have been nice here, |
|
44 |
+ // but if json.Unmarshal() fails (which will happen if we speak |
|
45 |
+ // to a version of docker that doesn't send any option), then |
|
46 |
+ // we can't put the data back in it for the next Read(). |
|
47 |
+ c.handshaked = true |
|
48 |
+ buf := make([]byte, 4096) |
|
49 |
+ if n, _ := c.conn.Read(buf); n > 0 { |
|
50 |
+ buf = buf[:n] |
|
51 |
+ if nl := bytes.IndexByte(buf, '\n'); nl != -1 { |
|
52 |
+ if err := json.Unmarshal(buf[:nl], c.options); err == nil { |
|
53 |
+ buf = buf[nl+1:] |
|
54 |
+ } |
|
55 |
+ } |
|
56 |
+ c.optionsBuf = &buf |
|
57 |
+ } |
|
58 |
+ } |
|
59 |
+ |
|
60 |
+ return c.options |
|
61 |
+} |
|
62 |
+ |
|
63 |
+func (c *DockerTCPConn) Read(b []byte) (int, error) { |
|
64 |
+ if c.optionsBuf != nil { |
|
65 |
+ // Consume what we buffered in GetOptions() first: |
|
66 |
+ optionsBuf := *c.optionsBuf |
|
67 |
+ optionsBuflen := len(optionsBuf) |
|
68 |
+ copied := copy(b, optionsBuf) |
|
69 |
+ if copied < optionsBuflen { |
|
70 |
+ optionsBuf = optionsBuf[copied:] |
|
71 |
+ c.optionsBuf = &optionsBuf |
|
72 |
+ return copied, nil |
|
73 |
+ } |
|
74 |
+ c.optionsBuf = nil |
|
75 |
+ return copied, nil |
|
76 |
+ } |
|
77 |
+ return c.conn.Read(b) |
|
78 |
+} |
|
79 |
+ |
|
80 |
+func (c *DockerTCPConn) Write(b []byte) (int, error) { |
|
81 |
+ optionsLen := 0 |
|
82 |
+ if !c.client && !c.handshaked { |
|
83 |
+ c.handshaked = true |
|
84 |
+ options, _ := json.Marshal(c.options) |
|
85 |
+ options = append(options, '\n') |
|
86 |
+ if optionsLen, err := c.conn.Write(options); err != nil { |
|
87 |
+ return optionsLen, err |
|
88 |
+ } |
|
89 |
+ } |
|
90 |
+ n, err := c.conn.Write(b) |
|
91 |
+ return n + optionsLen, err |
|
92 |
+} |
|
93 |
+ |
|
94 |
+func (c *DockerTCPConn) Close() error { return c.conn.Close() } |
|
95 |
+ |
|
96 |
+func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() } |
|
97 |
+ |
|
98 |
+func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() } |
|
99 |
+ |
|
18 | 100 |
// Connect to a remote endpoint using protocol `proto` and address `addr`, |
19 | 101 |
// issue a single call, and return the result. |
20 | 102 |
// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols. |
21 |
-func Call(proto, addr string, args ...string) (*net.TCPConn, error) { |
|
103 |
+func Call(proto, addr string, args ...string) (DockerConn, error) { |
|
22 | 104 |
cmd, err := json.Marshal(args) |
23 | 105 |
if err != nil { |
24 | 106 |
return nil, err |
25 | 107 |
} |
26 |
- conn, err := net.Dial(proto, addr) |
|
108 |
+ conn, err := dialDocker(proto, addr) |
|
27 | 109 |
if err != nil { |
28 | 110 |
return nil, err |
29 | 111 |
} |
30 | 112 |
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil { |
31 | 113 |
return nil, err |
32 | 114 |
} |
33 |
- return conn.(*net.TCPConn), nil |
|
115 |
+ return conn, nil |
|
34 | 116 |
} |
35 | 117 |
|
36 | 118 |
// Listen on `addr`, using protocol `proto`, for incoming rcli calls, |
... | ... |
@@ -46,6 +129,10 @@ func ListenAndServe(proto, addr string, service Service) error { |
46 | 46 |
if conn, err := listener.Accept(); err != nil { |
47 | 47 |
return err |
48 | 48 |
} else { |
49 |
+ conn, err := newDockerServerConn(conn) |
|
50 |
+ if err != nil { |
|
51 |
+ return err |
|
52 |
+ } |
|
49 | 53 |
go func() { |
50 | 54 |
if DEBUG_FLAG { |
51 | 55 |
CLIENT_SOCKET = conn |
... | ... |
@@ -63,7 +150,7 @@ func ListenAndServe(proto, addr string, service Service) error { |
63 | 63 |
|
64 | 64 |
// Parse an rcli call on a new connection, and pass it to `service` if it |
65 | 65 |
// is valid. |
66 |
-func Serve(conn io.ReadWriter, service Service) error { |
|
66 |
+func Serve(conn DockerConn, service Service) error { |
|
67 | 67 |
r := bufio.NewReader(conn) |
68 | 68 |
var args []string |
69 | 69 |
if line, err := r.ReadString('\n'); err != nil { |
... | ... |
@@ -13,10 +13,49 @@ import ( |
13 | 13 |
"fmt" |
14 | 14 |
"io" |
15 | 15 |
"log" |
16 |
+ "net" |
|
16 | 17 |
"reflect" |
17 | 18 |
"strings" |
18 | 19 |
) |
19 | 20 |
|
21 |
+type DockerConnOptions struct { |
|
22 |
+ RawTerminal bool |
|
23 |
+} |
|
24 |
+ |
|
25 |
+type DockerConn interface { |
|
26 |
+ io.ReadWriteCloser |
|
27 |
+ CloseWrite() error |
|
28 |
+ CloseRead() error |
|
29 |
+ GetOptions() *DockerConnOptions |
|
30 |
+ SetOptionRawTerminal() |
|
31 |
+} |
|
32 |
+ |
|
33 |
+var UnknownDockerProto = errors.New("Only TCP is actually supported by Docker at the moment") |
|
34 |
+ |
|
35 |
+func dialDocker(proto string, addr string) (DockerConn, error) { |
|
36 |
+ conn, err := net.Dial(proto, addr) |
|
37 |
+ if err != nil { |
|
38 |
+ return nil, err |
|
39 |
+ } |
|
40 |
+ switch i := conn.(type) { |
|
41 |
+ case *net.TCPConn: |
|
42 |
+ return NewDockerTCPConn(i, true), nil |
|
43 |
+ } |
|
44 |
+ return nil, UnknownDockerProto |
|
45 |
+} |
|
46 |
+ |
|
47 |
+func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) { |
|
48 |
+ switch i := conn.(type) { |
|
49 |
+ case *net.TCPConn: |
|
50 |
+ return NewDockerTCPConn(i, client), nil |
|
51 |
+ } |
|
52 |
+ return nil, UnknownDockerProto |
|
53 |
+} |
|
54 |
+ |
|
55 |
+func newDockerServerConn(conn net.Conn) (DockerConn, error) { |
|
56 |
+ return newDockerFromConn(conn, false) |
|
57 |
+} |
|
58 |
+ |
|
20 | 59 |
type Service interface { |
21 | 60 |
Name() string |
22 | 61 |
Help() string |
... | ... |
@@ -26,11 +65,11 @@ type Cmd func(io.ReadCloser, io.Writer, ...string) error |
26 | 26 |
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error |
27 | 27 |
|
28 | 28 |
// FIXME: For reverse compatibility |
29 |
-func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
29 |
+func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error { |
|
30 | 30 |
return LocalCall(service, stdin, stdout, args...) |
31 | 31 |
} |
32 | 32 |
|
33 |
-func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
|
33 |
+func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error { |
|
34 | 34 |
if len(args) == 0 { |
35 | 35 |
args = []string{"help"} |
36 | 36 |
} |