// Safe invokes the provided function and will attempt to ensure that when the // function returns (or a termination signal is sent) that the terminal state // is reset to the condition it was in prior to the function being invoked. If // t.Raw is true the terminal will be put into raw mode prior to calling the function. // If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file // will be opened (if available). func (t TTY) Safe(fn SafeFunc) error { in := t.In var hasFd bool var inFd uintptr if desc, ok := in.(fd); ok && in != nil { inFd = desc.Fd() hasFd = true } if t.TryDev && (!hasFd || !term.IsTerminal(inFd)) { if f, err := os.Open("/dev/tty"); err == nil { defer f.Close() inFd = f.Fd() hasFd = true } } if !hasFd || !term.IsTerminal(inFd) { return fn() } var state *term.State var err error if t.Raw { state, err = term.MakeRaw(inFd) } else { state, err = term.SaveState(inFd) } if err != nil { return err } return interrupt.Chain(t.Parent, func() { term.RestoreTerminal(inFd, state) }).Run(fn) }
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 }
// Safe invokes the provided function and will attempt to ensure that when the // function returns (or a termination signal is sent) that the terminal state // is reset to the condition it was in prior to the function being invoked. If // t.Raw is true the terminal will be put into raw mode prior to calling the function. // If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file // will be opened (if available). func (t TTY) Safe(fn SafeFunc) error { inFd, isTerminal := term.GetFdInfo(t.In) if !isTerminal && t.TryDev { if f, err := os.Open("/dev/tty"); err == nil { defer f.Close() inFd = f.Fd() isTerminal = term.IsTerminal(inFd) } } if !isTerminal { return fn() } var state *term.State var err error if t.Raw { state, err = term.MakeRaw(inFd) } else { state, err = term.SaveState(inFd) } if err != nil { return err } return interrupt.Chain(t.Parent, func() { if t.sizeQueue != nil { t.sizeQueue.stop() } term.RestoreTerminal(inFd, state) }).Run(fn) }
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 setRaw(t *testing.T, c *daemon.Container) *term.State { pty, err := c.GetPtyMaster() if err != nil { t.Fatal(err) } state, err := term.MakeRaw(pty.Fd()) if err != nil { t.Fatal(err) } return state }
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 }
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 }
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 }