func (client *NativeClient) Shell(args ...string) error { var ( termWidth, termHeight int ) conn, err := ssh.Dial("tcp", net.JoinHostPort(client.Hostname, strconv.Itoa(client.Port)), &client.Config) if err != nil { return err } defer closeConn(conn) session, err := conn.NewSession() if err != nil { return err } defer session.Close() session.Stdout = os.Stdout session.Stderr = os.Stderr session.Stdin = os.Stdin modes := ssh.TerminalModes{ ssh.ECHO: 1, } fd := os.Stdin.Fd() if term.IsTerminal(fd) { oldState, err := term.MakeRaw(fd) if err != nil { return err } defer term.RestoreTerminal(fd, oldState) winsize, err := term.GetWinsize(fd) if err != nil { termWidth = 80 termHeight = 24 } else { termWidth = int(winsize.Width) termHeight = int(winsize.Height) } } if err := session.RequestPty("xterm", termHeight, termWidth, modes); err != nil { return err } if len(args) == 0 { if err := session.Shell(); err != nil { return err } session.Wait() } else { session.Run(strings.Join(args, " ")) } return nil }
func (t *DockerTerminal) GetWindowWidth() (uint16, error) { winsize, err := term.GetWinsize(os.Stdout.Fd()) if err != nil { return 0, err } return winsize.Width, nil }
func (*DockerTerm) GetWinsize(fd uintptr) (width, height int) { winSize, err := term.GetWinsize(fd) if err != nil { return 80, 43 } return int(winSize.Width), int(winSize.Height) }
// GetSize returns the current size of the terminal associated with fd. func GetSize(fd uintptr) *Size { winsize, err := term.GetWinsize(fd) if err != nil { runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err)) return nil } return &Size{Width: winsize.Width, Height: winsize.Height} }
func (client NativeClient) Shell() error { var ( termWidth, termHeight int ) conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Hostname, client.Port), &client.Config) if err != nil { return err } session, err := conn.NewSession() if err != nil { return err } defer session.Close() session.Stdout = os.Stdout session.Stderr = os.Stderr session.Stdin = os.Stdin modes := ssh.TerminalModes{ ssh.ECHO: 1, } fd := os.Stdin.Fd() if term.IsTerminal(fd) { oldState, err := term.MakeRaw(fd) if err != nil { return err } defer term.RestoreTerminal(fd, oldState) winsize, err := term.GetWinsize(fd) if err != nil { termWidth = 80 termHeight = 24 } else { termWidth = int(winsize.Width) termHeight = int(winsize.Height) } } if err := session.RequestPty("xterm", termHeight, termWidth, modes); err != nil { return err } if err := session.Shell(); err != nil { return err } session.Wait() return nil }
func (p *process) resizePty() error { if p.pty == nil { return nil } ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } return term.SetWinsize(p.pty.Fd(), ws) }
func (t *tty) resize() error { if t.console == nil { return nil } ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } return term.SetWinsize(t.console.Fd(), ws) }
// GetTTYSize func GetTTYSize(fd uintptr) (int, int) { ws, err := term.GetWinsize(fd) if err != nil { config.Fatal("[util/server/exec] term.GetWinsize() failed", err.Error()) } // return int(ws.Width), int(ws.Height) }
func (p *JSONProgress) String() string { var ( width = 200 pbBox string numbersBox string timeLeftBox string ) ws, err := term.GetWinsize(p.terminalFd) if err == nil { width = int(ws.Width) } if p.Current <= 0 && p.Total <= 0 { return "" } current := units.HumanSize(float64(p.Current)) if p.Total <= 0 { return fmt.Sprintf("%8v", current) } total := units.HumanSize(float64(p.Total)) percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 if percentage > 50 { percentage = 50 } if width > 110 { // this number can't be negetive gh#7136 numSpaces := 0 if 50-percentage > 0 { numSpaces = 50 - percentage - 1 } pbBox = fmt.Sprintf("[%s%s%s] ", strings.Repeat("~", percentage), p.Animal, strings.Repeat("~", numSpaces)) } numbersBox = fmt.Sprintf("%8v/%v", current, total) if p.Current > p.Total { // remove total display if the reported current is wonky. numbersBox = fmt.Sprintf("%8v", current) } if p.Current > 0 && p.Start > 0 && percentage < 50 { fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0)) perEntry := fromStart / time.Duration(p.Current) left := time.Duration(p.Total-p.Current) * perEntry left = (left / time.Second) * time.Second if width > 50 { timeLeftBox = " " + left.String() } } return pbBox + numbersBox + timeLeftBox }
func (cli *DockerCli) getTtySize() (int, int) { if !cli.isTerminalOut { return 0, 0 } ws, err := term.GetWinsize(cli.outFd) if err != nil { logrus.Debugf("Error getting size: %s", err) if ws == nil { return 0, 0 } } return int(ws.Height), int(ws.Width) }
// GetTtySize returns the height and width in characters of the tty func (o *OutStream) GetTtySize() (uint, uint) { if !o.isTerminal { return 0, 0 } ws, err := term.GetWinsize(o.fd) if err != nil { logrus.Debugf("Error getting size: %s", err) if ws == nil { return 0, 0 } } return uint(ws.Height), uint(ws.Width) }
func (builder *Builder) getTtySize() (int, int) { if !builder.isTerminalOut { return 0, 0 } ws, err := term.GetWinsize(builder.fdOut) if err != nil { fmt.Fprintf(builder.OutStream, "Error getting TTY size: %s\n", err) if ws == nil { return 0, 0 } } return int(ws.Height), int(ws.Width) }
// ResizeTTY resizes the tty size of docker connection so output looks normal func (c *DockerClient) ResizeTTY(execID string) error { ws, err := term.GetWinsize(os.Stdout.Fd()) if err != nil { c.logger.Debugln("Error getting term size: %s", err) return err } err = c.ResizeExecTTY(execID, int(ws.Height), int(ws.Width)) if err != nil { c.logger.Debugln("Error resizing term: %s", err) return err } return nil }
func resizeTty(master *os.File) { if master == nil { return } ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return } if err := term.SetWinsize(master.Fd(), ws); err != nil { return } }
func resize(id, pid string, c types.APIClient) error { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } if _, err := c.UpdateProcess(netcontext.Background(), &types.UpdateProcessRequest{ Id: id, Pid: "init", Width: uint32(ws.Width), Height: uint32(ws.Height), }); err != nil { return err } return nil }
func TestProgress(t *testing.T) { termsz, err := term.GetWinsize(0) if err != nil { // we can safely ignore the err here termsz = nil } jp := JSONProgress{} if jp.String() != "" { t.Fatalf("Expected empty string, got '%s'", jp.String()) } expected := " 1 B" jp2 := JSONProgress{Current: 1} if jp2.String() != expected { t.Fatalf("Expected %q, got %q", expected, jp2.String()) } expectedStart := "[==========> ] 20 B/100 B" if termsz != nil && termsz.Width <= 110 { expectedStart = " 20 B/100 B" } jp3 := JSONProgress{Current: 20, Total: 100, Start: time.Now().Unix()} // Just look at the start of the string // (the remaining time is really hard to test -_-) if jp3.String()[:len(expectedStart)] != expectedStart { t.Fatalf("Expected to start with %q, got %q", expectedStart, jp3.String()) } expected = "[=========================> ] 50 B/100 B" if termsz != nil && termsz.Width <= 110 { expected = " 50 B/100 B" } jp4 := JSONProgress{Current: 50, Total: 100} if jp4.String() != expected { t.Fatalf("Expected %q, got %q", expected, jp4.String()) } // this number can't be negative gh#7136 expected = "[==================================================>] 50 B" if termsz != nil && termsz.Width <= 110 { expected = " 50 B" } jp5 := JSONProgress{Current: 50, Total: 40} if jp5.String() != expected { t.Fatalf("Expected %q, got %q", expected, jp5.String()) } }
func (c *DockerClient) getTtySize(out io.Writer) (int, int) { var ( fdOut, isTerminalOut = term.GetFdInfo(out) ) if !isTerminalOut { return 0, 0 } ws, err := term.GetWinsize(fdOut) if err != nil { log.Errorf("Error getting TTY size: %s\n", err) if ws == nil { return 0, 0 } } return int(ws.Height), int(ws.Width) }
func (p *JSONProgress) String() string { var ( width = 200 pbBox string numbersBox string timeLeftBox string ) ws, err := term.GetWinsize(p.terminalFd) if err == nil { width = int(ws.Width) } if p.Current <= 0 && p.Total <= 0 { return "" } current := units.HumanSize(int64(p.Current)) if p.Total <= 0 { return fmt.Sprintf("%8v", current) } total := units.HumanSize(int64(p.Total)) percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 if width > 110 { pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", 50-percentage)) } numbersBox = fmt.Sprintf("%8v/%v", current, total) if p.Current > 0 && p.Start > 0 && percentage < 50 { fromStart := time.Now().UTC().Sub(time.Unix(int64(p.Start), 0)) perEntry := fromStart / time.Duration(p.Current) left := time.Duration(p.Total-p.Current) * perEntry left = (left / time.Second) * time.Second if width > 50 { timeLeftBox = " " + left.String() } } return pbBox + numbersBox + timeLeftBox }
func sshConnect(user string, host string, method ssh.AuthMethod) error { sshc := &ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{method}, } conn, err := ssh.Dial("tcp", host, sshc) if err != nil { return err } defer conn.Close() session, err := conn.NewSession() if err != nil { return err } defer session.Close() session.Stdout = os.Stdout session.Stderr = os.Stderr session.Stdin = os.Stdin var ( termWidth, termHeight int ) fd := os.Stdin.Fd() oldState, err := term.MakeRaw(fd) if err != nil { return err } defer term.RestoreTerminal(fd, oldState) winsize, err := term.GetWinsize(fd) if err != nil { termWidth = 80 termHeight = 24 } else { termWidth = int(winsize.Width) termHeight = int(winsize.Height) } modes := ssh.TerminalModes{ ssh.ECHO: 1, } if err := session.RequestPty("xterm", termWidth, termHeight, modes); err != nil { return err } if err == nil { err = session.Shell() } if err != nil { return err } err = session.Wait() if _, ok := err.(*ssh.ExitError); ok { return nil } if err == io.EOF { return nil } return err }
func runRun(cmd *Command, args []string) { if len(args) == 0 { cmd.PrintUsage() os.Exit(2) } appname := mustApp() w, err := term.GetWinsize(inFd) if err != nil { // If syscall.TIOCGWINSZ is not supported by the device, we're // probably trying to run tests. Set w to some sensible default. if err.Error() == "operation not supported by device" { w = &term.Winsize{ Height: 20, Width: 80, } } else { printFatal(err.Error()) } } attached := !detachedRun opts := heroku.DynoCreateOpts{Attach: &attached} if attached { env := map[string]string{ "COLUMNS": strconv.Itoa(int(w.Width)), "LINES": strconv.Itoa(int(w.Height)), "TERM": os.Getenv("TERM"), } opts.Env = &env } if dynoSize != "" { if !strings.HasSuffix(dynoSize, "X") { cmd.PrintUsage() os.Exit(2) } opts.Size = &dynoSize } command := strings.Join(args, " ") if detachedRun { dyno, err := client.DynoCreate(appname, command, &opts) must(err) log.Printf("Ran `%s` on %s as %s, detached.", dyno.Command, appname, dyno.Name) return } params := struct { Command string `json:"command"` Attach *bool `json:"attach,omitempty"` Env *map[string]string `json:"env,omitempty"` Size *string `json:"size,omitempty"` }{ Command: command, Attach: opts.Attach, Env: opts.Env, Size: opts.Size, } req, err := client.NewRequest("POST", "/apps/"+appname+"/dynos", params) must(err) u, err := url.Parse(apiURL) must(err) proto, address := dialParams(u) var dial net.Conn if proto == "tls" { dial, err = tlsDial("tcp", address, &tls.Config{}) if err != nil { printFatal(err.Error()) } } else { dial, err = net.Dial(proto, address) if err != nil { printFatal(err.Error()) } } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() _, err = clientconn.Do(req) if err != nil && err != httputil.ErrPersistEOF { printFatal(err.Error()) } rwc, br := clientconn.Hijack() defer rwc.Close() if isTerminalIn && isTerminalOut { state, err := term.SetRawTerminal(inFd) if err != nil { printFatal(err.Error()) } defer term.RestoreTerminal(inFd, state) } errChanOut := make(chan error, 1) errChanIn := make(chan error, 1) exit := make(chan bool) go func() { defer close(exit) defer close(errChanOut) var err error _, err = io.Copy(os.Stdout, br) errChanOut <- err }() go func() { _, err := io.Copy(rwc, os.Stdin) errChanIn <- err rwc.(interface { CloseWrite() error }).CloseWrite() }() <-exit select { case err = <-errChanIn: must(err) case err = <-errChanOut: must(err) } }
func (t *terminalHelper) GetWinsize(fd uintptr) (*term.Winsize, error) { return term.GetWinsize(fd) }
func runJob(client controller.Client, config runConfig) error { req := &ct.NewJob{ Args: config.Args, TTY: config.Stdin == nil && config.Stdout == nil && term.IsTerminal(os.Stdin.Fd()) && term.IsTerminal(os.Stdout.Fd()) && !config.Detached, ReleaseID: config.Release, Env: config.Env, ReleaseEnv: config.ReleaseEnv, DisableLog: config.DisableLog, } // ensure slug apps from old clusters use /runner/init release, err := client.GetRelease(req.ReleaseID) if err != nil { return err } if release.IsGitDeploy() && (len(req.Args) == 0 || req.Args[0] != "/runner/init") { req.Args = append([]string{"/runner/init"}, req.Args...) } // set deprecated Entrypoint and Cmd for old clusters if len(req.Args) > 0 { req.DeprecatedEntrypoint = []string{req.Args[0]} } if len(req.Args) > 1 { req.DeprecatedCmd = req.Args[1:] } if config.Stdin == nil { config.Stdin = os.Stdin } if config.Stdout == nil { config.Stdout = os.Stdout } if config.Stderr == nil { config.Stderr = os.Stderr } if req.TTY { if req.Env == nil { req.Env = make(map[string]string) } ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } req.Columns = int(ws.Width) req.Lines = int(ws.Height) req.Env["COLUMNS"] = strconv.Itoa(int(ws.Width)) req.Env["LINES"] = strconv.Itoa(int(ws.Height)) req.Env["TERM"] = os.Getenv("TERM") } if config.Detached { job, err := client.RunJobDetached(config.App, req) if err != nil { return err } log.Println(job.ID) return nil } rwc, err := client.RunJobAttached(config.App, req) if err != nil { return err } defer rwc.Close() attachClient := cluster.NewAttachClient(rwc) var termState *term.State if req.TTY { termState, err = term.MakeRaw(os.Stdin.Fd()) if err != nil { return err } // Restore the terminal if we return without calling os.Exit defer term.RestoreTerminal(os.Stdin.Fd(), termState) go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, SIGWINCH) for range ch { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return } attachClient.ResizeTTY(ws.Height, ws.Width) attachClient.Signal(int(SIGWINCH)) } }() } go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) sig := <-ch attachClient.Signal(int(sig.(syscall.Signal))) time.Sleep(10 * time.Second) attachClient.Signal(int(syscall.SIGKILL)) }() go func() { io.Copy(attachClient, config.Stdin) attachClient.CloseWrite() }() childDone := make(chan struct{}) shutdown.BeforeExit(func() { <-childDone }) exitStatus, err := attachClient.Receive(config.Stdout, config.Stderr) close(childDone) if err != nil { return err } if req.TTY { term.RestoreTerminal(os.Stdin.Fd(), termState) } if config.Exit { shutdown.ExitWithCode(exitStatus) } if exitStatus != 0 { return RunExitError(exitStatus) } return nil }
// Open opens a secure console to a code or database service. For code // services, a command is required. This command is executed as root in the // context of the application root directory. For database services, no command // is needed - instead, the appropriate command for the database type is run. // For example, for a postgres database, psql is run. func (c *SConsole) Open(command string, service *models.Service) error { stdin, stdout, _ := term.StdStreams() fdIn, isTermIn := term.GetFdInfo(stdin) if !isTermIn { return errors.New("StdIn is not a terminal") } var size *term.Winsize var err error if runtime.GOOS != "windows" { size, err = term.GetWinsize(fdIn) } else { fdOut, _ := term.GetFdInfo(stdout) size, err = term.GetWinsize(fdOut) } if err != nil { return err } if size.Width != 80 { logrus.Warnln("Your terminal width is not 80 characters. Please resize your terminal to be exactly 80 characters wide to avoid line wrapping issues.") } else { logrus.Warnln("Keep your terminal width at 80 characters. Resizing your terminal will introduce line wrapping issues.") } logrus.Printf("Opening console to %s (%s)", service.Name, service.ID) job, err := c.Request(command, service) if err != nil { return err } // all because logrus treats print, println, and printf the same logrus.StandardLogger().Out.Write([]byte(fmt.Sprintf("Waiting for the console (job ID = %s) to be ready. This might take a minute.", job.ID))) validStatuses := []string{"running", "finished", "failed"} status, err := c.Jobs.PollForStatus(validStatuses, job.ID, service.ID) if err != nil { return err } found := false for _, validStatus := range validStatuses { if status == validStatus { found = true break } } if !found { return fmt.Errorf("\nCould not open a console connection. Entered state '%s'", status) } job.Status = status defer c.Destroy(job.ID, service) creds, err := c.RetrieveTokens(job.ID, service) if err != nil { return err } creds.URL = strings.Replace(creds.URL, "http", "ws", 1) logrus.Println("\nConnecting...") // BEGIN websocket impl config, _ := websocket.NewConfig(creds.URL, "ws://localhost:9443/") config.TlsConfig = &tls.Config{ MinVersion: tls.VersionTLS12, } config.Header["X-Console-Token"] = []string{creds.Token} ws, err := websocket.DialConfig(config) if err != nil { return err } defer ws.Close() logrus.Println("Connection opened") oldState, err := term.SetRawTerminal(fdIn) if err != nil { return err } defer term.RestoreTerminal(fdIn, oldState) signal.Notify(make(chan os.Signal, 1), os.Interrupt) done := make(chan struct{}, 2) go readWS(ws, stdout, done) go readStdin(stdin, ws, done) <-done return nil }
func (TermPkg) GetWinsize(fd uintptr) (*term.Winsize, error) { return term.GetWinsize(fd) }
func runRun(args *docopt.Args, client *cluster.Client) error { cmd := exec.Cmd{ ImageArtifact: exec.DockerImage(args.String["<image>"]), Job: &host.Job{ Config: host.ContainerConfig{ Args: append([]string{args.String["<command>"]}, args.All["<argument>"].([]string)...), TTY: term.IsTerminal(os.Stdin.Fd()) && term.IsTerminal(os.Stdout.Fd()), Stdin: true, DisableLog: true, }, }, HostID: args.String["--host"], Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } if cmd.Job.Config.TTY { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } cmd.TermHeight = ws.Height cmd.TermWidth = ws.Width cmd.Env = map[string]string{ "COLUMNS": strconv.Itoa(int(ws.Width)), "LINES": strconv.Itoa(int(ws.Height)), "TERM": os.Getenv("TERM"), } } if specs := args.String["--bind"]; specs != "" { mounts := strings.Split(specs, ",") cmd.Job.Config.Mounts = make([]host.Mount, len(mounts)) for i, m := range mounts { s := strings.SplitN(m, ":", 2) cmd.Job.Config.Mounts[i] = host.Mount{ Target: s[0], Location: s[1], Writeable: true, } } } var termState *term.State if cmd.Job.Config.TTY { var err error termState, err = term.MakeRaw(os.Stdin.Fd()) if err != nil { return err } // Restore the terminal if we return without calling os.Exit defer term.RestoreTerminal(os.Stdin.Fd(), termState) go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGWINCH) for range ch { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return } cmd.ResizeTTY(ws.Height, ws.Width) cmd.Signal(int(syscall.SIGWINCH)) } }() } go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) sig := <-ch cmd.Signal(int(sig.(syscall.Signal))) time.Sleep(10 * time.Second) cmd.Signal(int(syscall.SIGKILL)) }() err := cmd.Run() if status, ok := err.(exec.ExitError); ok { if cmd.Job.Config.TTY { // The deferred restore doesn't happen due to the exit below term.RestoreTerminal(os.Stdin.Fd(), termState) } os.Exit(int(status)) } return err }