func sendTermSize(control *websocket.Conn) error { width, height, err := terminal.GetSize(int(syscall.Stdout)) if err != nil { return err } shared.Debugf("Window size is now: %dx%d", width, height) w, err := control.NextWriter(websocket.TextMessage) if err != nil { return err } msg := shared.ContainerExecControl{} msg.Command = "window-resize" msg.Args = make(map[string]string) msg.Args["width"] = strconv.Itoa(width) msg.Args["height"] = strconv.Itoa(height) buf, err := json.Marshal(msg) if err != nil { return err } _, err = w.Write(buf) w.Close() return err }
func (c *Client) Exec(name string, cmd []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error) { interactive := terminal.IsTerminal(int(stdin.Fd())) body := shared.Jmap{"command": cmd, "wait-for-websocket": true, "interactive": interactive, "environment": env} resp, err := c.post(fmt.Sprintf("containers/%s/exec", name), body, Async) if err != nil { return -1, err } md := execMd{} if err := json.Unmarshal(resp.Metadata, &md); err != nil { return -1, err } if interactive { if wsControl, ok := md.FDs["control"]; ok { go func() { control, err := c.websocket(resp.Operation, wsControl) if err != nil { return } for { width, height, err := terminal.GetSize(syscall.Stdout) if err != nil { continue } shared.Debugf("Window size is now: %dx%d", width, height) w, err := control.NextWriter(websocket.TextMessage) if err != nil { shared.Debugf("Got error getting next writer %s", err) break } msg := shared.ContainerExecControl{} msg.Command = "window-resize" msg.Args = make(map[string]string) msg.Args["width"] = strconv.Itoa(width) msg.Args["height"] = strconv.Itoa(height) buf, err := json.Marshal(msg) if err != nil { shared.Debugf("Failed to convert to json %s", err) break } _, err = w.Write(buf) w.Close() if err != nil { shared.Debugf("Got err writing %s", err) break } ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGWINCH) sig := <-ch shared.Debugf("Received '%s signal', updating window geometry.", sig) } closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") control.WriteMessage(websocket.CloseMessage, closeMsg) }() } conn, err := c.websocket(resp.Operation, md.FDs["0"]) if err != nil { return -1, err } shared.WebsocketSendStream(conn, stdin) <-shared.WebsocketRecvStream(stdout, conn) } else { sources := []*os.File{stdin, stdout, stderr} conns := make([]*websocket.Conn, 3) dones := make([]chan bool, 3) for i := 0; i < 3; i++ { conns[i], err = c.websocket(resp.Operation, md.FDs[strconv.Itoa(i)]) if err != nil { return -1, err } if i == 0 { dones[i] = shared.WebsocketSendStream(conns[i], sources[i]) } else { dones[i] = shared.WebsocketRecvStream(sources[i], conns[i]) } } /* * We'll get a read signal from each of stdout, stderr when they've * both died. We need to wait for these in addition to the operation, * because the server may indicate that the operation is done before we * can actually read the last bits of data off these sockets and print * it to the screen. * * We don't wait for stdin here, because if we're interactive, the user * may not have closed it (e.g. if the command exits but the user * didn't ^D). */ for i := 1; i < 3; i++ { <-dones[i] } // Once we're done, we explicitly close stdin, to signal the websockets // we're done. sources[0].Close() } // Now, get the operation's status too. op, err := c.WaitFor(resp.Operation) if err != nil { return -1, err } if op.StatusCode == shared.Failure { return -1, op.GetError() } if op.StatusCode != shared.Success { return -1, fmt.Errorf(gettext.Gettext("got bad op status %s"), op.Status) } opMd, err := op.MetadataAsMap() if err != nil { return -1, err } return opMd.GetInt("return") }