From 70d2123efda0e92760b96b03ce27cb4f1fb61cb3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 23 May 2013 19:33:28 -0700 Subject: [PATCH 1/4] Add resize endpoint to api --- api.go | 24 ++++++++++++++++++++++++ container.go | 4 ++++ server.go | 7 +++++++ 3 files changed, 35 insertions(+) diff --git a/api.go b/api.go index 0a902c4043..6e2b425f04 100644 --- a/api.go +++ b/api.go @@ -48,6 +48,7 @@ func writeJson(w http.ResponseWriter, b []byte) { w.Write(b) } +// FIXME: Use stvconv.ParseBool() instead? func getBoolParam(value string) (bool, error) { if value == "1" || strings.ToLower(value) == "true" { return true, nil @@ -485,6 +486,28 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r * return nil } +func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + height, err := strconv.Atoi(r.Form.Get("h")) + if err != nil { + return err + } + width, err := strconv.Atoi(r.Form.Get("w")) + if err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + if err := srv.ContainerResize(name, height, width); err != nil { + return err + } + return nil +} + func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err @@ -620,6 +643,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { "/containers/{name:.*}/start": postContainersStart, "/containers/{name:.*}/stop": postContainersStop, "/containers/{name:.*}/wait": postContainersWait, + "/containers/{name:.*}/resize": postContainersResize, "/containers/{name:.*}/attach": postContainersAttach, }, "DELETE": { diff --git a/container.go b/container.go index a82ce0291a..8cba8f5985 100644 --- a/container.go +++ b/container.go @@ -754,6 +754,10 @@ func (container *Container) Wait() int { return container.State.ExitCode } +func (container *Container) Resize(h, w int) error { + return fmt.Errorf("Resize not yet implemented") +} + func (container *Container) ExportRw() (Archive, error) { return Tar(container.rwPath(), Uncompressed) } diff --git a/server.go b/server.go index 564b1c812d..144f180e41 100644 --- a/server.go +++ b/server.go @@ -776,6 +776,13 @@ func (srv *Server) ContainerWait(name string) (int, error) { return 0, fmt.Errorf("No such container: %s", name) } +func (srv *Server) ContainerResize(name string, h, w int) error { + if container := srv.runtime.Get(name); container != nil { + return container.Resize(h, w) + } + return fmt.Errorf("No such container: %s", name) +} + func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error { container := srv.runtime.Get(name) if container == nil { From deb9963e6e5871d53ab1d75c90bbf2da53ffcb36 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 24 May 2013 11:07:32 -0700 Subject: [PATCH 2/4] Catch sigwinch client --- commands.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 75ba562cd8..2b96f64f0e 100644 --- a/commands.go +++ b/commands.go @@ -15,10 +15,12 @@ import ( "net/http/httputil" "net/url" "os" + "os/signal" "path/filepath" "reflect" "strconv" "strings" + "syscall" "text/tabwriter" "time" "unicode" @@ -33,6 +35,19 @@ var ( func ParseCommands(args ...string) error { cli := NewDockerCli("0.0.0.0", 4243) + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGWINCH) + go func() { + for sig := range c { + if sig == syscall.SIGWINCH { + _, _, err := cli.call("GET", "/auth", nil) + if err != nil { + utils.Debugf("Error resize: %s", err) + } + } + } + }() + if len(args) > 0 { methodName := "Cmd" + strings.ToUpper(args[0][:1]) + strings.ToLower(args[0][1:]) method, exists := reflect.TypeOf(cli).MethodByName(methodName) @@ -1294,6 +1309,6 @@ func NewDockerCli(host string, port int) *DockerCli { } type DockerCli struct { - host string - port int + host string + port int } From 88ef309a940bcbb6f85a750372b8fdbc6569c3a7 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 24 May 2013 14:44:16 -0700 Subject: [PATCH 3/4] Finish resize implementation client and server --- commands.go | 42 +++++++++++++++++++++++++++++------------- container.go | 7 ++++++- term/term.go | 23 +++++++++++++++++++++-- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/commands.go b/commands.go index 2b96f64f0e..099c3686cc 100644 --- a/commands.go +++ b/commands.go @@ -35,19 +35,6 @@ var ( func ParseCommands(args ...string) error { cli := NewDockerCli("0.0.0.0", 4243) - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGWINCH) - go func() { - for sig := range c { - if sig == syscall.SIGWINCH { - _, _, err := cli.call("GET", "/auth", nil) - if err != nil { - utils.Debugf("Error resize: %s", err) - } - } - } - }() - if len(args) > 0 { methodName := "Cmd" + strings.ToUpper(args[0][:1]) + strings.ToLower(args[0][1:]) method, exists := reflect.TypeOf(cli).MethodByName(methodName) @@ -975,6 +962,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { v.Set("stderr", "1") v.Set("stdin", "1") + cli.monitorTtySize(cmd.Arg(0)) if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil { return err } @@ -1162,6 +1150,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } if config.AttachStdin || config.AttachStdout || config.AttachStderr { + cli.monitorTtySize(out.Id) if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil { return err } @@ -1295,6 +1284,33 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error { } +func (cli *DockerCli) resizeTty(id string) { + ws, err := term.GetWinsize(os.Stdin.Fd()) + if err != nil { + utils.Debugf("Error getting size: %s", err) + } + v := url.Values{} + v.Set("h", strconv.Itoa(int(ws.Height))) + v.Set("w", strconv.Itoa(int(ws.Width))) + if _, _, err := cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil); err != nil { + utils.Debugf("Error resize: %s", err) + } +} + +func (cli *DockerCli) monitorTtySize(id string) { + cli.resizeTty(id) + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGWINCH) + go func() { + for sig := range c { + if sig == syscall.SIGWINCH { + cli.resizeTty(id) + } + } + }() +} + func Subcmd(name, signature, description string) *flag.FlagSet { flags := flag.NewFlagSet(name, flag.ContinueOnError) flags.Usage = func() { diff --git a/container.go b/container.go index 8cba8f5985..c6b7c8a51c 100644 --- a/container.go +++ b/container.go @@ -4,6 +4,7 @@ import ( "encoding/json" "flag" "fmt" + "github.com/dotcloud/docker/term" "github.com/dotcloud/docker/utils" "github.com/kr/pty" "io" @@ -755,7 +756,11 @@ func (container *Container) Wait() int { } func (container *Container) Resize(h, w int) error { - return fmt.Errorf("Resize not yet implemented") + pty, ok := container.ptyMaster.(*os.File) + if !ok { + return fmt.Errorf("ptyMaster does not have Fd() method") + } + return term.SetWinsize(pty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) } func (container *Container) ExportRw() (Archive, error) { diff --git a/term/term.go b/term/term.go index 8c07b93356..d0f303f4e4 100644 --- a/term/term.go +++ b/term/term.go @@ -1,6 +1,7 @@ package term import ( + "github.com/dotcloud/docker/utils" "os" "os/signal" "syscall" @@ -109,17 +110,35 @@ type State struct { termios Termios } +type Winsize struct { + Width uint16 + Height uint16 + x uint16 + y uint16 +} + +func GetWinsize(fd uintptr) (*Winsize, error) { + ws := &Winsize{} + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws))) + return ws, err +} + +func SetWinsize(fd uintptr, ws *Winsize) error { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws))) + return err +} + // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(fd int) bool { var termios Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios))) return err == 0 } // Restore restores the terminal connected to the given file descriptor to a // previous state. func Restore(fd int, state *State) error { - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios))) return err } From b438565609917439cb4172717e5505c265c4e291 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 24 May 2013 14:48:13 -0700 Subject: [PATCH 4/4] Fix merge issue --- commands.go | 8 ++++---- term/term.go | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index 1b94fea45a..f429277328 100644 --- a/commands.go +++ b/commands.go @@ -71,7 +71,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { return nil } } - help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.addr, cli.port) + help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port) for cmd, description := range map[string]string{ "attach": "Attach to a running container", "build": "Build a container from Dockerfile or via stdin", @@ -1201,7 +1201,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, params = bytes.NewBuffer(buf) } - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.addr, cli.port, API_VERSION, path), params) + req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), params) if err != nil { return nil, -1, err } @@ -1233,7 +1233,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader([]byte{}) } - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.addr, cli.port, API_VERSION, path), in) + req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), in) if err != nil { return err } @@ -1269,7 +1269,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error { return err } req.Header.Set("Content-Type", "plain/text") - dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.addr, cli.port)) + dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port)) if err != nil { return err } diff --git a/term/term.go b/term/term.go index d0f303f4e4..290bf174ad 100644 --- a/term/term.go +++ b/term/term.go @@ -1,7 +1,6 @@ package term import ( - "github.com/dotcloud/docker/utils" "os" "os/signal" "syscall"