func (r *registry) execContainer(containerID string, req xfer.Request) xfer.Response { exec, err := r.client.CreateExec(docker_client.CreateExecOptions{ AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: true, Cmd: []string{"/bin/sh", "-c", "TERM=xterm exec $( (type getent > /dev/null 2>&1 && getent passwd root | cut -d: -f7 2>/dev/null) || echo /bin/sh)"}, Container: containerID, }) if err != nil { return xfer.ResponseError(err) } id, pipe, err := controls.NewPipe(r.pipes, req.AppID) if err != nil { return xfer.ResponseError(err) } local, _ := pipe.Ends() cw, err := r.client.StartExecNonBlocking(exec.ID, docker_client.StartExecOptions{ Tty: true, RawTerminal: true, InputStream: local, OutputStream: local, ErrorStream: local, }) if err != nil { return xfer.ResponseError(err) } pipe.OnClose(func() { if err := cw.Close(); err != nil { log.Errorf("Error closing exec: %v", err) return } log.Infof("Exec on container %s closed.", containerID) }) go func() { if err := cw.Wait(); err != nil { log.Errorf("Error waiting on exec: %v", err) } pipe.Close() }() return xfer.Response{ Pipe: id, RawTTY: true, } }
func (r *registry) attachContainer(containerID string, req xfer.Request) xfer.Response { c, ok := r.GetContainer(containerID) if !ok { return xfer.ResponseErrorf("Not found: %s", containerID) } hasTTY := c.HasTTY() id, pipe, err := controls.NewPipe(r.pipes, req.AppID) if err != nil { return xfer.ResponseError(err) } local, _ := pipe.Ends() cw, err := r.client.AttachToContainerNonBlocking(docker_client.AttachToContainerOptions{ Container: containerID, RawTerminal: hasTTY, Stream: true, Stdin: true, Stdout: true, Stderr: true, InputStream: local, OutputStream: local, ErrorStream: local, }) if err != nil { return xfer.ResponseError(err) } pipe.OnClose(func() { if err := cw.Close(); err != nil { log.Errorf("Error closing attachment: %v", err) return } log.Infof("Attachment to container %s closed.", containerID) }) go func() { if err := cw.Wait(); err != nil { log.Errorf("Error waiting on exec: %v", err) } pipe.Close() }() return xfer.Response{ Pipe: id, RawTTY: hasTTY, } }
func TestPipeClose(t *testing.T) { router := mux.NewRouter() pr := NewLocalPipeRouter() RegisterPipeRoutes(router, pr) defer pr.Stop() server := httptest.NewServer(router) defer server.Close() ip, port, err := net.SplitHostPort(strings.TrimPrefix(server.URL, "http://")) if err != nil { t.Fatal(err) } probeConfig := appclient.ProbeConfig{ ProbeID: "foo", } client, err := appclient.NewAppClient(probeConfig, ip+":"+port, ip+":"+port, nil) if err != nil { t.Fatal(err) } defer client.Stop() // this is the probe end of the pipe pipeID, pipe, err := controls.NewPipe(adapter{client}, "appid") if err != nil { t.Fatal(err) } // this is a client to the app pipeURL := fmt.Sprintf("ws://%s:%s/api/pipe/%s", ip, port, pipeID) conn, _, err := websocket.DefaultDialer.Dial(pipeURL, http.Header{}) if err != nil { t.Fatal(err) } // Send something from pipe -> app -> conn local, _ := pipe.Ends() msg := []byte("hello world") if _, err := local.Write(msg); err != nil { t.Fatal(err) } if _, buf, err := conn.ReadMessage(); err != nil { t.Fatal(err) } else if !bytes.Equal(buf, msg) { t.Fatalf("%v != %v", buf, msg) } // Send something from conn -> app -> probe msg = []byte("goodbye, cruel world") if err := conn.WriteMessage(websocket.BinaryMessage, msg); err != nil { t.Fatal(err) } buf := make([]byte, 1024) if n, err := local.Read(buf); err != nil { t.Fatal(err) } else if !bytes.Equal(msg, buf[:n]) { t.Fatalf("%v != %v", buf, msg) } // Now delete the pipe if err := pipe.Close(); err != nil { t.Fatal(err) } // the client backs off for 1 second before trying to reconnect the pipe, // so we need to wait for longer. test.Poll(t, 2*time.Second, true, func() interface{} { return pipe.Closed() }) }