Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed/finished basic pty support, added an example, and included tests #25

Merged
merged 8 commits into from
Feb 16, 2017
112 changes: 112 additions & 0 deletions _example/docker/docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"context"
"fmt"
"io"
"log"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"github.com/gliderlabs/ssh"
)

func main() {
ssh.Handle(func(sess ssh.Session) {
_, _, isTty := sess.Pty()
cfg := &container.Config{
Image: sess.User(),
Cmd: sess.Command(),
Env: sess.Environ(),
Tty: isTty,
OpenStdin: true,
AttachStderr: true,
AttachStdin: true,
AttachStdout: true,
StdinOnce: true,
Volumes: make(map[string]struct{}),
}
err, status, cleanup := dockerRun(cfg, sess)
defer cleanup()
if err != nil {
fmt.Fprintln(sess, err)
log.Println(err)
}
sess.Exit(int(status))
})

log.Println("starting ssh server on port 2222...")
log.Fatal(ssh.ListenAndServe(":2222", nil))
}

func dockerRun(cfg *container.Config, sess ssh.Session) (err error, status int64, cleanup func()) {
docker, err := client.NewEnvClient()
if err != nil {
panic(err)
}
status = 255
cleanup = func() {}
ctx := context.Background()
res, err := docker.ContainerCreate(ctx, cfg, nil, nil, "")
if err != nil {
return
}
cleanup = func() {
docker.ContainerRemove(ctx, res.ID, types.ContainerRemoveOptions{})
}
opts := types.ContainerAttachOptions{
Stdin: cfg.AttachStdin,
Stdout: cfg.AttachStdout,
Stderr: cfg.AttachStderr,
Stream: true,
}
stream, err := docker.ContainerAttach(ctx, res.ID, opts)
if err != nil {
return
}
cleanup = func() {
docker.ContainerRemove(ctx, res.ID, types.ContainerRemoveOptions{})
stream.Close()
}
outputErr := make(chan error)
go func() {
var err error
if cfg.Tty {
_, err = io.Copy(sess, stream.Conn)
} else {
_, err = stdcopy.StdCopy(sess, sess.Stderr(), stream.Reader)
}
outputErr <- err
}()
go func() {
defer stream.CloseWrite()
io.Copy(stream.Conn, sess)
}()
err = docker.ContainerStart(ctx, res.ID, types.ContainerStartOptions{})
if err != nil {
return
}
if cfg.Tty {
_, winCh, _ := sess.Pty()
go func() {
for win := range winCh {
err := docker.ContainerResize(ctx, res.ID, types.ResizeOptions{
Height: uint(win.Height),
Width: uint(win.Width),
})
if err != nil {
log.Println(err)
break
}
}
}()
}
status, err = docker.ContainerWait(ctx, res.ID)
if err != nil {
return
}
err = <-outputErr
return
}
48 changes: 48 additions & 0 deletions _example/pty/pty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"fmt"
"io"
"log"
"os"
"os/exec"
"syscall"
"unsafe"

"github.com/gliderlabs/ssh"
"github.com/kr/pty"
)

func setWinsize(f *os.File, w, h int) {
syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), uintptr(syscall.TIOCSWINSZ),
uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(h), uint16(w), 0, 0})))
}

func main() {
ssh.Handle(func(s ssh.Session) {
cmd := exec.Command("top")
ptyReq, winCh, isPty := s.Pty()
if isPty {
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
f, err := pty.Start(cmd)
if err != nil {
panic(err)
}
go func() {
for win := range winCh {
setWinsize(f, win.Width, win.Height)
}
}()
go func() {
io.Copy(f, s) // stdin
}()
io.Copy(s, f) // stdout
} else {
io.WriteString(s, "No PTY requested.\n")
s.Exit(1)
}
})

log.Println("starting ssh server on port 2222...")
log.Fatal(ssh.ListenAndServe(":2222", nil))
}
3 changes: 3 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test:
override:
- go test -v -race
23 changes: 13 additions & 10 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@ func (sess *session) Pty() (Pty, <-chan Window, bool) {

func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
for req := range reqs {
var width, height int
var ok bool
switch req.Type {
case "shell", "exec":
if sess.handled {
Expand All @@ -152,8 +150,9 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
var kv = struct{ Key, Value string }{}
gossh.Unmarshal(req.Payload, &kv)
sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
req.Reply(true, nil)
case "pty-req":
if sess.handled {
if sess.handled || sess.pty != nil {
req.Reply(false, nil)
continue
}
Expand All @@ -164,23 +163,27 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
continue
}
}
width, height, ok = parsePtyRequest(req.Payload)
ptyReq, ok := parsePtyRequest(req.Payload)
if ok {
sess.pty = &Pty{Window{width, height}}
sess.winch = make(chan Window)
sess.pty = &ptyReq
sess.winch = make(chan Window, 1)
sess.winch <- ptyReq.Window
defer func() {
close(sess.winch)
}()
}

req.Reply(ok, nil)
case "window-change":
if sess.pty == nil {
req.Reply(false, nil)
continue
}
width, height, ok = parseWinchRequest(req.Payload)
win, ok := parseWinchRequest(req.Payload)
if ok {
sess.pty.Window = Window{width, height}
sess.winch <- sess.pty.Window
sess.pty.Window = win
sess.winch <- win
}
req.Reply(ok, nil)
}
}
}
Loading