// RunJobAttached runs a new job under the specified app, attaching to the job // and returning a ReadWriteCloser stream, which can then be used for // communicating with the job. func (c *Client) RunJobAttached(appID string, job *ct.NewJob) (utils.ReadWriteCloser, error) { data, err := httpclient.ToJSON(job) if err != nil { return nil, err } req, err := http.NewRequest("POST", fmt.Sprintf("%s/apps/%s/jobs", c.URL, appID), data) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/vnd.flynn.attach") req.SetBasicAuth("", c.Key) var dial rpcplus.DialFunc if c.Dial != nil { dial = c.Dial } res, rwc, err := utils.HijackRequest(req, dial) if err != nil { res.Body.Close() return nil, err } return rwc, nil }
func (s *S) TestRunJobAttached(c *C) { app := s.createTestApp(c, &ct.App{Name: "run-attached"}) hostID := random.UUID() hc := tu.NewFakeHostClient(hostID) done := make(chan struct{}) var jobID string hc.SetAttachFunc("*", func(req *host.AttachReq, wait bool) (cluster.AttachClient, error) { c.Assert(wait, Equals, true) c.Assert(req.JobID, Not(Equals), "") c.Assert(req, DeepEquals, &host.AttachReq{ JobID: req.JobID, Flags: host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagStdin | host.AttachFlagStream, Height: 20, Width: 10, }) jobID = req.JobID pipeR, pipeW := io.Pipe() go func() { stdin, err := ioutil.ReadAll(pipeR) c.Assert(err, IsNil) c.Assert(string(stdin), Equals, "test in") close(done) }() return cluster.NewAttachClient(struct { io.Reader io.WriteCloser }{strings.NewReader("test out"), pipeW}), nil }) s.cc.SetHostClient(hostID, hc) s.cc.SetHosts(map[string]host.Host{hostID: {}}) artifact := s.createTestArtifact(c, &ct.Artifact{Type: "docker", URI: "docker://foo/bar"}) release := s.createTestRelease(c, &ct.Release{ ArtifactID: artifact.ID, Env: map[string]string{"RELEASE": "true", "FOO": "bar"}, }) data, _ := json.Marshal(&ct.NewJob{ ReleaseID: release.ID, Cmd: []string{"foo", "bar"}, Env: map[string]string{"JOB": "true", "FOO": "baz"}, TTY: true, Columns: 10, Lines: 20, }) req, err := http.NewRequest("POST", s.srv.URL+"/apps/"+app.ID+"/jobs", bytes.NewBuffer(data)) c.Assert(err, IsNil) req.SetBasicAuth("", authKey) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/vnd.flynn.attach") _, rwc, err := utils.HijackRequest(req, nil) c.Assert(err, IsNil) _, err = rwc.Write([]byte("test in")) c.Assert(err, IsNil) rwc.CloseWrite() stdout, err := ioutil.ReadAll(rwc) c.Assert(err, IsNil) c.Assert(string(stdout), Equals, "test out") rwc.Close() job := s.cc.GetHost(hostID).Jobs[0] c.Assert(job.ID, Equals, jobID) c.Assert(job.Metadata, DeepEquals, map[string]string{ "flynn-controller.app": app.ID, "flynn-controller.release": release.ID, }) c.Assert(job.Config.Cmd, DeepEquals, []string{"foo", "bar"}) c.Assert(job.Config.Env, DeepEquals, map[string]string{"FOO": "baz", "JOB": "true", "RELEASE": "true"}) c.Assert(job.Config.Stdin, Equals, true) }