func runJob(app *ct.App, newJob ct.NewJob, releases *ReleaseRepo, artifacts *ArtifactRepo, cl clusterClient, req *http.Request, w http.ResponseWriter, r ResponseHelper) { data, err := releases.Get(newJob.ReleaseID) if err != nil { r.Error(err) return } release := data.(*ct.Release) data, err = artifacts.Get(release.ArtifactID) if err != nil { r.Error(err) return } artifact := data.(*ct.Artifact) image, err := utils.DockerImage(artifact.URI) if err != nil { log.Println("error parsing artifact uri", err) r.Error(ct.ValidationError{ Field: "artifact.uri", Message: "is invalid", }) return } attach := strings.Contains(req.Header.Get("Accept"), "application/vnd.flynn.attach") job := &host.Job{ ID: cluster.RandomJobID(""), Attributes: map[string]string{ "flynn-controller.app": app.ID, "flynn-controller.release": release.ID, }, Config: &docker.Config{ Cmd: newJob.Cmd, Env: utils.FormatEnv(release.Env, newJob.Env), Image: image, AttachStdout: true, AttachStderr: true, }, } if newJob.TTY { job.Config.Tty = true } if attach { job.Config.AttachStdin = true job.Config.StdinOnce = true job.Config.OpenStdin = true } hosts, err := cl.ListHosts() if err != nil { r.Error(err) return } // pick a random host var hostID string for hostID = range hosts { break } if hostID == "" { r.Error(errors.New("no hosts found")) return } var attachConn cluster.ReadWriteCloser var attachWait func() error if attach { attachReq := &host.AttachReq{ JobID: job.ID, Flags: host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagStdin | host.AttachFlagStream, Height: newJob.Lines, Width: newJob.Columns, } client, err := cl.DialHost(hostID) if err != nil { r.Error(fmt.Errorf("lorne connect failed: %s", err.Error())) return } defer client.Close() attachConn, attachWait, err = client.Attach(attachReq, true) if err != nil { r.Error(fmt.Errorf("attach failed: %s", err.Error())) return } defer attachConn.Close() } _, err = cl.AddJobs(&host.AddJobsReq{HostJobs: map[string][]*host.Job{hostID: {job}}}) if err != nil { r.Error(fmt.Errorf("schedule failed: %s", err.Error())) return } if attach { if err := attachWait(); err != nil { r.Error(fmt.Errorf("attach wait failed: %s", err.Error())) return } w.Header().Set("Content-Type", "application/vnd.flynn.attach") w.Header().Set("Content-Length", "0") w.WriteHeader(http.StatusSwitchingProtocols) conn, _, err := w.(http.Hijacker).Hijack() if err != nil { panic(err) } defer conn.Close() done := make(chan struct{}, 2) cp := func(to cluster.ReadWriteCloser, from io.Reader) { io.Copy(to, from) to.CloseWrite() done <- struct{}{} } go cp(conn.(cluster.ReadWriteCloser), attachConn) go cp(attachConn, conn) <-done <-done return } else { r.JSON(200, &ct.Job{ ID: hostID + "-" + job.ID, ReleaseID: newJob.ReleaseID, Cmd: newJob.Cmd, }) } }