mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Only set the terminal in raw mode for commands which need it
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.
This commit is contained in:
parent
4e5001b46a
commit
7d0ab3858e
4 changed files with 238 additions and 56 deletions
60
commands.go
60
commands.go
|
@ -62,7 +62,7 @@ func (srv *Server) Help() string {
|
|||
}
|
||||
|
||||
// 'docker login': login / register a user to registry service.
|
||||
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
// Read a line on raw terminal with support for simple backspace
|
||||
// sequences and echo.
|
||||
//
|
||||
|
@ -71,7 +71,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
|||
// - we have to read a password (without echoing it);
|
||||
// - the rcli "protocol" only supports cannonical and raw modes and you
|
||||
// can't tune it once the command as been started.
|
||||
var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string {
|
||||
var readStringOnRawTerminal = func(stdin io.Reader, stdout rcli.DockerConn, echo bool) string {
|
||||
char := make([]byte, 1)
|
||||
buffer := make([]byte, 64)
|
||||
var i = 0
|
||||
|
@ -106,13 +106,15 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
|||
}
|
||||
return string(buffer[:i])
|
||||
}
|
||||
var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string {
|
||||
var readAndEchoString = func(stdin io.Reader, stdout rcli.DockerConn) string {
|
||||
return readStringOnRawTerminal(stdin, stdout, true)
|
||||
}
|
||||
var readString = func(stdin io.Reader, stdout io.Writer) string {
|
||||
var readString = func(stdin io.Reader, stdout rcli.DockerConn) string {
|
||||
return readStringOnRawTerminal(stdin, stdout, false)
|
||||
}
|
||||
|
||||
stdout.SetOptionRawTerminal()
|
||||
|
||||
cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -158,7 +160,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
|||
}
|
||||
|
||||
// 'docker wait': block until a container stops
|
||||
func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdWait(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -178,14 +180,14 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
}
|
||||
|
||||
// 'docker version': show version information
|
||||
func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
fmt.Fprintf(stdout, "Version:%s\n", VERSION)
|
||||
fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 'docker info': display system-wide information.
|
||||
func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
images, _ := srv.runtime.graph.All()
|
||||
var imgcount int
|
||||
if images == nil {
|
||||
|
@ -214,7 +216,7 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdStop(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -236,7 +238,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "restart", "[OPTIONS] NAME", "Restart a running container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -258,7 +260,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdStart(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "start", "[OPTIONS] NAME", "Start a stopped container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -280,7 +282,7 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -315,7 +317,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdPort(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -339,7 +341,7 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
}
|
||||
|
||||
// 'docker rmi NAME' removes all images with the name NAME
|
||||
func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) {
|
||||
func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) (err error) {
|
||||
cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -356,7 +358,7 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "history", "[OPTIONS] IMAGE", "Show the history of an image")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -382,7 +384,7 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str
|
|||
})
|
||||
}
|
||||
|
||||
func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdRm(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -400,7 +402,7 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
|||
}
|
||||
|
||||
// 'docker kill NAME' kills a running container
|
||||
func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdKill(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -417,7 +419,7 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
|
||||
var archive io.Reader
|
||||
var resp *http.Response
|
||||
|
@ -464,7 +466,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -523,7 +525,7 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdPull(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -548,7 +550,7 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdImages(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
|
||||
//limit := cmd.Int("l", 0, "Only show the N most recent versions of each image")
|
||||
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
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdPs(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout,
|
||||
"ps", "[OPTIONS]", "List containers")
|
||||
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)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout,
|
||||
"commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]",
|
||||
"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
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdExport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout,
|
||||
"export", "CONTAINER",
|
||||
"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
|
|||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout,
|
||||
"diff", "CONTAINER [OPTIONS]",
|
||||
"Inspect changes on a container's filesystem")
|
||||
|
@ -752,7 +754,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -784,7 +786,8 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
|
|||
return fmt.Errorf("No such container: %s", cmd.Arg(0))
|
||||
}
|
||||
|
||||
func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
stdout.SetOptionRawTerminal()
|
||||
cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -857,7 +860,7 @@ func (opts AttachOpts) Get(val string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdTag(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
|
||||
force := cmd.Bool("f", false, "Force")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
|
@ -870,7 +873,8 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
|||
return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force)
|
||||
}
|
||||
|
||||
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
stdout.SetOptionRawTerminal()
|
||||
config, err := ParseRun(args, stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"github.com/dotcloud/docker/term"
|
||||
|
@ -56,30 +57,82 @@ func daemon() error {
|
|||
return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service)
|
||||
}
|
||||
|
||||
func runCommand(args []string) error {
|
||||
var oldState *term.State
|
||||
var err error
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for _ = range c {
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
log.Printf("\nSIGINT received\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
func setRawTerminal() (*term.State, error) {
|
||||
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for _ = range c {
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
log.Printf("\nSIGINT received\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
return oldState, err
|
||||
}
|
||||
|
||||
func restoreTerminal(state *term.State) {
|
||||
term.Restore(int(os.Stdin.Fd()), state)
|
||||
}
|
||||
|
||||
type DockerLocalConn struct {
|
||||
file *os.File
|
||||
savedState *term.State
|
||||
}
|
||||
|
||||
func newDockerLocalConn(output *os.File) *DockerLocalConn {
|
||||
return &DockerLocalConn{file: output}
|
||||
}
|
||||
|
||||
func (c *DockerLocalConn) Read(b []byte) (int, error) { return c.file.Read(b) }
|
||||
|
||||
func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.file.Write(b) }
|
||||
|
||||
func (c *DockerLocalConn) Close() error {
|
||||
if c.savedState != nil {
|
||||
restoreTerminal(c.savedState)
|
||||
c.savedState = nil
|
||||
}
|
||||
return c.file.Close()
|
||||
}
|
||||
|
||||
func (c *DockerLocalConn) CloseWrite() error { return nil }
|
||||
|
||||
func (c *DockerLocalConn) CloseRead() error { return nil }
|
||||
|
||||
func (c *DockerLocalConn) GetOptions() *rcli.DockerConnOptions { return nil }
|
||||
|
||||
func (c *DockerLocalConn) SetOptionRawTerminal() {
|
||||
if state, err := setRawTerminal(); err != nil {
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
"Can't set the terminal in raw mode: %v",
|
||||
err.Error(),
|
||||
)
|
||||
} else {
|
||||
c.savedState = state
|
||||
}
|
||||
}
|
||||
|
||||
func runCommand(args []string) error {
|
||||
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
|
||||
// CloseWrite(), which we need to cleanly signal that stdin is closed without
|
||||
// closing the connection.
|
||||
// See http://code.google.com/p/go/issues/detail?id=3345
|
||||
if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
|
||||
options := conn.GetOptions()
|
||||
if options.RawTerminal &&
|
||||
term.IsTerminal(int(os.Stdin.Fd())) &&
|
||||
os.Getenv("NORAW") == "" {
|
||||
if oldState, err := setRawTerminal(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer restoreTerminal(oldState)
|
||||
}
|
||||
}
|
||||
receiveStdout := docker.Go(func() error {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
return err
|
||||
|
@ -104,12 +157,11 @@ func runCommand(args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil {
|
||||
dockerConn := newDockerLocalConn(os.Stdout)
|
||||
defer dockerConn.Close()
|
||||
if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if oldState != nil {
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
95
rcli/tcp.go
95
rcli/tcp.go
|
@ -2,6 +2,7 @@ package rcli
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -15,22 +16,104 @@ import (
|
|||
var DEBUG_FLAG bool = false
|
||||
var CLIENT_SOCKET io.Writer = nil
|
||||
|
||||
type DockerTCPConn struct {
|
||||
conn *net.TCPConn
|
||||
options *DockerConnOptions
|
||||
optionsBuf *[]byte
|
||||
handshaked bool
|
||||
client bool
|
||||
}
|
||||
|
||||
func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn {
|
||||
return &DockerTCPConn{
|
||||
conn: conn,
|
||||
options: &DockerConnOptions{},
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) SetOptionRawTerminal() {
|
||||
c.options.RawTerminal = true
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) GetOptions() *DockerConnOptions {
|
||||
if c.client && !c.handshaked {
|
||||
// Attempt to parse options encoded as a JSON dict and store
|
||||
// the reminder of what we read from the socket in a buffer.
|
||||
//
|
||||
// bufio (and its ReadBytes method) would have been nice here,
|
||||
// but if json.Unmarshal() fails (which will happen if we speak
|
||||
// to a version of docker that doesn't send any option), then
|
||||
// we can't put the data back in it for the next Read().
|
||||
c.handshaked = true
|
||||
buf := make([]byte, 4096)
|
||||
if n, _ := c.conn.Read(buf); n > 0 {
|
||||
buf = buf[:n]
|
||||
if nl := bytes.IndexByte(buf, '\n'); nl != -1 {
|
||||
if err := json.Unmarshal(buf[:nl], c.options); err == nil {
|
||||
buf = buf[nl+1:]
|
||||
}
|
||||
}
|
||||
c.optionsBuf = &buf
|
||||
}
|
||||
}
|
||||
|
||||
return c.options
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) Read(b []byte) (int, error) {
|
||||
if c.optionsBuf != nil {
|
||||
// Consume what we buffered in GetOptions() first:
|
||||
optionsBuf := *c.optionsBuf
|
||||
optionsBuflen := len(optionsBuf)
|
||||
copied := copy(b, optionsBuf)
|
||||
if copied < optionsBuflen {
|
||||
optionsBuf = optionsBuf[copied:]
|
||||
c.optionsBuf = &optionsBuf
|
||||
return copied, nil
|
||||
}
|
||||
c.optionsBuf = nil
|
||||
return copied, nil
|
||||
}
|
||||
return c.conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) Write(b []byte) (int, error) {
|
||||
optionsLen := 0
|
||||
if !c.client && !c.handshaked {
|
||||
c.handshaked = true
|
||||
options, _ := json.Marshal(c.options)
|
||||
options = append(options, '\n')
|
||||
if optionsLen, err := c.conn.Write(options); err != nil {
|
||||
return optionsLen, err
|
||||
}
|
||||
}
|
||||
n, err := c.conn.Write(b)
|
||||
return n + optionsLen, err
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) Close() error { return c.conn.Close() }
|
||||
|
||||
func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
|
||||
|
||||
func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
|
||||
|
||||
// Connect to a remote endpoint using protocol `proto` and address `addr`,
|
||||
// issue a single call, and return the result.
|
||||
// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
|
||||
func Call(proto, addr string, args ...string) (*net.TCPConn, error) {
|
||||
func Call(proto, addr string, args ...string) (DockerConn, error) {
|
||||
cmd, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := net.Dial(proto, addr)
|
||||
conn, err := dialDocker(proto, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn.(*net.TCPConn), nil
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Listen on `addr`, using protocol `proto`, for incoming rcli calls,
|
||||
|
@ -46,6 +129,10 @@ func ListenAndServe(proto, addr string, service Service) error {
|
|||
if conn, err := listener.Accept(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
conn, err := newDockerServerConn(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
if DEBUG_FLAG {
|
||||
CLIENT_SOCKET = conn
|
||||
|
@ -63,7 +150,7 @@ func ListenAndServe(proto, addr string, service Service) error {
|
|||
|
||||
// Parse an rcli call on a new connection, and pass it to `service` if it
|
||||
// is valid.
|
||||
func Serve(conn io.ReadWriter, service Service) error {
|
||||
func Serve(conn DockerConn, service Service) error {
|
||||
r := bufio.NewReader(conn)
|
||||
var args []string
|
||||
if line, err := r.ReadString('\n'); err != nil {
|
||||
|
|
|
@ -13,10 +13,49 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DockerConnOptions struct {
|
||||
RawTerminal bool
|
||||
}
|
||||
|
||||
type DockerConn interface {
|
||||
io.ReadWriteCloser
|
||||
CloseWrite() error
|
||||
CloseRead() error
|
||||
GetOptions() *DockerConnOptions
|
||||
SetOptionRawTerminal()
|
||||
}
|
||||
|
||||
var UnknownDockerProto = errors.New("Only TCP is actually supported by Docker at the moment")
|
||||
|
||||
func dialDocker(proto string, addr string) (DockerConn, error) {
|
||||
conn, err := net.Dial(proto, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch i := conn.(type) {
|
||||
case *net.TCPConn:
|
||||
return NewDockerTCPConn(i, true), nil
|
||||
}
|
||||
return nil, UnknownDockerProto
|
||||
}
|
||||
|
||||
func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
|
||||
switch i := conn.(type) {
|
||||
case *net.TCPConn:
|
||||
return NewDockerTCPConn(i, client), nil
|
||||
}
|
||||
return nil, UnknownDockerProto
|
||||
}
|
||||
|
||||
func newDockerServerConn(conn net.Conn) (DockerConn, error) {
|
||||
return newDockerFromConn(conn, false)
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
Name() string
|
||||
Help() string
|
||||
|
@ -26,11 +65,11 @@ type Cmd func(io.ReadCloser, io.Writer, ...string) error
|
|||
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
|
||||
|
||||
// FIXME: For reverse compatibility
|
||||
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
|
||||
return LocalCall(service, stdin, stdout, args...)
|
||||
}
|
||||
|
||||
func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
|
||||
if len(args) == 0 {
|
||||
args = []string{"help"}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue