package terminal import ( "fmt" "github.com/ActiveState/termtest/conpty" "github.com/gliderlabs/ssh" "io" "log" "os" "syscall" ) var PtySpawner = Spawner(func(sshSession ssh.Session, env []string, name string, args ...string) (Process, error) { _, winCh, isPty := sshSession.Pty() if !isPty { return nil, fmt.Errorf("ssh session is not a pty") } cpty, err := conpty.New(80, 25) if err != nil { return nil, err } pid, _, err := cpty.Spawn( name, args, &syscall.ProcAttr{ Env: env, }, ) if err != nil { cpty.Close() return nil, err } log.Printf("New process with pid %d spawned\n", pid) process, err := os.FindProcess(pid) if err != nil { cpty.Close() return nil, fmt.Errorf("Failed to find process: %v", err) } go func() { for win := range winCh { err = cpty.Resize(uint16(win.Width), uint16(win.Height)) if err != nil { log.Printf("Failed to resize terminal to %d x %d", win.Width, win.Height) } } }() return ptyProcess{ cpty: cpty, process: process, }, nil }) type ptyProcess struct { cpty *conpty.ConPty process *os.Process } func (proc ptyProcess) Read(p []byte) (n int, err error) { return proc.cpty.OutPipe().Read(p) } func (proc ptyProcess) Write(p []byte) (n int, err error) { uintn, err := proc.cpty.Write(p) return int(uintn), err } func (p ptyProcess) Pipe() io.ReadWriter { return p } func (p ptyProcess) Kill() error { return p.process.Kill() } func (p ptyProcess) Wait() error { defer p.cpty.Close() ps, err := p.process.Wait() if err != nil { return fmt.Errorf("Error waiting for process: %v", err) } if ps.ExitCode() != 0 { return fmt.Errorf("exit code was: %d\n", ps.ExitCode()) } return nil }