func jobLog(req *http.Request, app *ct.App, params martini.Params, hc cluster.Host, w http.ResponseWriter, r ResponseHelper) { attachReq := &host.AttachReq{ JobID: params["jobs_id"], Flags: host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagLogs, } tail := req.FormValue("tail") != "" if tail { attachReq.Flags |= host.AttachFlagStream } wait := req.FormValue("wait") != "" attachClient, err := hc.Attach(attachReq, wait) if err != nil { if err == cluster.ErrWouldWait { w.WriteHeader(404) } else { r.Error(err) } return } if cn, ok := w.(http.CloseNotifier); ok { go func() { <-cn.CloseNotify() attachClient.Close() }() } else { defer attachClient.Close() } sse := strings.Contains(req.Header.Get("Accept"), "text/event-stream") if sse { w.Header().Set("Content-Type", "text/event-stream; charset=utf-8") } else { w.Header().Set("Content-Type", "application/vnd.flynn.attach") } w.WriteHeader(200) // Send headers right away if tailing if wf, ok := w.(http.Flusher); ok && tail { wf.Flush() } fw := flushWriter{w, tail} if sse { ssew := NewSSELogWriter(w) exit, err := attachClient.Receive(flushWriter{ssew.Stream("stdout"), tail}, flushWriter{ssew.Stream("stderr"), tail}) if err != nil { fw.Write([]byte("event: error\ndata: {}\n\n")) return } if tail { fmt.Fprintf(fw, "event: exit\ndata: {\"status\": %d}\n\n", exit) return } fw.Write([]byte("event: eof\ndata: {}\n\n")) } else { io.Copy(fw, attachClient.Conn()) } }
func jobLog(req *http.Request, app *ct.App, params martini.Params, hc cluster.Host, w http.ResponseWriter, r ResponseHelper) { attachReq := &host.AttachReq{ JobID: params["jobs_id"], Flags: host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagLogs, } tail := req.FormValue("tail") != "" if tail { attachReq.Flags |= host.AttachFlagStream } wait := req.FormValue("wait") != "" attachClient, err := hc.Attach(attachReq, wait) if err != nil { if err == cluster.ErrWouldWait { w.WriteHeader(404) } else { r.Error(err) } return } defer attachClient.Close() sse := strings.Contains(req.Header.Get("Accept"), "text/event-stream") if sse { w.Header().Set("Content-Type", "text/event-stream; charset=utf-8") } else { w.Header().Set("Content-Type", "application/vnd.flynn.attach") } w.WriteHeader(200) // Send headers right away if tailing if wf, ok := w.(http.Flusher); ok && tail { wf.Flush() } // TODO: use http.CloseNotifier to clean up when client disconnects if sse { ssew := NewSSELogWriter(w) attachClient.Receive(flushWriter{ssew.Stream("stdout"), tail}, flushWriter{ssew.Stream("stderr"), tail}) // TODO: include exit code here if tailing flushWriter{w, tail}.Write([]byte("event: eof\ndata: {}\n\n")) } else { io.Copy(flushWriter{w, tail}, attachClient.Conn()) } }
func runLog(args *docopt.Args, client cluster.Host) error { attachReq := &host.AttachReq{ JobID: args.String["ID"], Flags: host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagLogs, } if args.Bool["-f"] || args.Bool["--follow"] { attachReq.Flags |= host.AttachFlagStream } attachClient, err := client.Attach(attachReq, false) if err != nil { if err == cluster.ErrWouldWait { return fmt.Errorf("no such job") } return err } defer attachClient.Close() attachClient.Receive(os.Stdout, os.Stderr) return nil }