func (t *TarWriter) runJob(client controller.Client, app string, req *ct.NewJob, out io.Writer) error { // set deprecated Entrypoint and Cmd for old clusters if len(req.Args) > 0 { req.DeprecatedEntrypoint = []string{req.Args[0]} } if len(req.Args) > 1 { req.DeprecatedCmd = req.Args[1:] } rwc, err := client.RunJobAttached(app, req) if err != nil { return err } defer rwc.Close() attachClient := cluster.NewAttachClient(rwc) attachClient.CloseWrite() exit, err := attachClient.Receive(out, os.Stderr) if err != nil { return err } if exit != 0 { return fmt.Errorf("unexpected command exit status %d", exit) } return nil }
func (s *S) TestJobLogWait(c *C) { app := s.createTestApp(c, &ct.App{Name: "joblog-wait"}) hostID, jobID := random.UUID(), random.UUID() hc := tu.NewFakeHostClient(hostID) hc.SetAttachFunc(jobID, func(req *host.AttachReq, wait bool) (cluster.AttachClient, error) { if !wait { return nil, cluster.ErrWouldWait } return cluster.NewAttachClient(newFakeLog(strings.NewReader("foo"))), nil }) s.cc.SetHostClient(hostID, hc) req, err := http.NewRequest("GET", fmt.Sprintf("%s/apps/%s/jobs/%s-%s/log", s.srv.URL, app.ID, hostID, jobID), nil) c.Assert(err, IsNil) req.SetBasicAuth("", authKey) res, err := http.DefaultClient.Do(req) c.Assert(err, IsNil) res.Body.Close() c.Assert(res.StatusCode, Equals, 404) req, err = http.NewRequest("GET", fmt.Sprintf("%s/apps/%s/jobs/%s-%s/log?wait=true", s.srv.URL, app.ID, hostID, jobID), nil) c.Assert(err, IsNil) req.SetBasicAuth("", authKey) res, err = http.DefaultClient.Do(req) var buf bytes.Buffer _, err = buf.ReadFrom(res.Body) res.Body.Close() c.Assert(err, IsNil) c.Assert(buf.String(), Equals, "foo") }
func (s *TaffyDeploySuite) deployWithTaffy(t *c.C, app *ct.App, github map[string]string) { client := s.controllerClient(t) taffyRelease, err := client.GetAppRelease("taffy") t.Assert(err, c.IsNil) rwc, err := client.RunJobAttached("taffy", &ct.NewJob{ ReleaseID: taffyRelease.ID, ReleaseEnv: true, Cmd: []string{ app.Name, github["clone_url"], github["ref"], github["sha"], }, Meta: map[string]string{ "type": "github", "user_login": github["user_login"], "repo_name": github["repo_name"], "ref": github["ref"], "sha": github["sha"], "clone_url": github["clone_url"], "app": app.ID, }, }) t.Assert(err, c.IsNil) attachClient := cluster.NewAttachClient(rwc) var outBuf bytes.Buffer exit, err := attachClient.Receive(&outBuf, &outBuf) t.Log(outBuf.String()) t.Assert(exit, c.Equals, 0) t.Assert(err, c.IsNil) }
func (s *S) createLogTestApp(c *C, name string, stream io.Reader) (*ct.App, string, string) { app := s.createTestApp(c, &ct.App{Name: name}) hostID, jobID := random.UUID(), random.UUID() hc := tu.NewFakeHostClient(hostID) hc.SetAttach(jobID, cluster.NewAttachClient(newFakeLog(stream))) s.cc.SetHostClient(hostID, hc) return app, hostID, jobID }
func (t *TarWriter) runJob(client *controller.Client, app string, req *ct.NewJob, out io.Writer) error { rwc, err := client.RunJobAttached(app, req) if err != nil { return err } defer rwc.Close() attachClient := cluster.NewAttachClient(rwc) attachClient.CloseWrite() _, err = attachClient.Receive(out, os.Stderr) return err }
func runLog(args *docopt.Args, client *controller.Client) error { rc, err := client.GetJobLog(mustApp(), args.String["<job>"]) if err != nil { return err } var stderr io.Writer = os.Stdout if args.Bool["--split-stderr"] { stderr = os.Stderr } attachClient := cluster.NewAttachClient(struct { io.Writer io.ReadCloser }{nil, rc}) attachClient.Receive(os.Stdout, stderr) return nil }
func (s *ControllerSuite) TestResourceLimitsOneOffJob(t *c.C) { app, release := s.createApp(t) rwc, err := s.controllerClient(t).RunJobAttached(app.ID, &ct.NewJob{ ReleaseID: release.ID, Cmd: []string{"sh", "-c", resourceCmd}, Resources: testResources(), }) t.Assert(err, c.IsNil) attachClient := cluster.NewAttachClient(rwc) var out bytes.Buffer exit, err := attachClient.Receive(&out, &out) t.Assert(exit, c.Equals, 0) t.Assert(err, c.IsNil) assertResourceLimits(t, out.String()) }
func (t *TarWriter) runJob(client *controller.Client, app string, req *ct.NewJob, out io.Writer) error { rwc, err := client.RunJobAttached(app, req) if err != nil { return err } defer rwc.Close() attachClient := cluster.NewAttachClient(rwc) attachClient.CloseWrite() exit, err := attachClient.Receive(out, os.Stderr) if err != nil { return err } if exit != 0 { return fmt.Errorf("unexpected command exit status %d", exit) } return nil }
func (s *TaffyDeploySuite) deployWithTaffy(t *c.C, app *ct.App, env, meta, github map[string]string) { client := s.controllerClient(t) taffyRelease, err := client.GetAppRelease("taffy") t.Assert(err, c.IsNil) args := []string{ "/bin/taffy", app.Name, github["clone_url"], github["branch"], github["rev"], } for name, m := range map[string]map[string]string{"--env": env, "--meta": meta} { for k, v := range m { args = append(args, name) args = append(args, fmt.Sprintf("%s=%s", k, v)) } } rwc, err := client.RunJobAttached("taffy", &ct.NewJob{ ReleaseID: taffyRelease.ID, ReleaseEnv: true, Args: args, Meta: map[string]string{ "github": "true", "github_user": github["user"], "github_repo": github["repo"], "branch": github["branch"], "rev": github["rev"], "clone_url": github["clone_url"], "app": app.ID, }, Env: env, }) t.Assert(err, c.IsNil) attachClient := cluster.NewAttachClient(rwc) var outBuf bytes.Buffer exit, err := attachClient.Receive(&outBuf, &outBuf) t.Log(outBuf.String()) t.Assert(exit, c.Equals, 0) t.Assert(err, c.IsNil) }
func (s *S) TestRunJobAttached(c *C) { app := s.createTestApp(c, &ct.App{Name: "run-attached"}) hostID := fakeHostID() hc := tu.NewFakeHostClient(hostID, false) s.cc.AddHost(hc) input := make(chan string, 1) 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 inPipeR, inPipeW := io.Pipe() go func() { buf := make([]byte, 10) n, _ := inPipeR.Read(buf) input <- string(buf[:n]) }() outPipeR, outPipeW := io.Pipe() go outPipeW.Write([]byte("test out")) return cluster.NewAttachClient(struct { io.Reader io.WriteCloser }{outPipeR, inPipeW}), nil }) artifact := s.createTestArtifact(c, &ct.Artifact{}) release := s.createTestRelease(c, &ct.Release{ ArtifactIDs: []string{artifact.ID}, Env: map[string]string{"RELEASE": "true", "FOO": "bar"}, }) data := &ct.NewJob{ ReleaseID: release.ID, ReleaseEnv: true, Args: []string{"foo", "bar"}, Env: map[string]string{"JOB": "true", "FOO": "baz"}, Meta: map[string]string{"foo": "baz"}, TTY: true, Columns: 10, Lines: 20, } rwc, err := s.c.RunJobAttached(app.ID, data) c.Assert(err, IsNil) _, err = rwc.Write([]byte("test in")) c.Assert(err, IsNil) c.Assert(<-input, Equals, "test in") buf := make([]byte, 10) n, _ := rwc.Read(buf) c.Assert(err, IsNil) c.Assert(string(buf[:n]), Equals, "test out") rwc.Close() jobs, err := hc.ListJobs() c.Assert(err, IsNil) for _, j := range jobs { job := j.Job c.Assert(job.ID, Equals, jobID) c.Assert(job.Metadata, DeepEquals, map[string]string{ "flynn-controller.app": app.ID, "flynn-controller.app_name": app.Name, "flynn-controller.release": release.ID, "foo": "baz", }) c.Assert(job.Config.Args, DeepEquals, []string{"foo", "bar"}) c.Assert(job.Config.Env, DeepEquals, map[string]string{ "FLYNN_APP_ID": app.ID, "FLYNN_RELEASE_ID": release.ID, "FLYNN_PROCESS_TYPE": "", "FLYNN_JOB_ID": job.ID, "FOO": "baz", "JOB": "true", "RELEASE": "true", }) c.Assert(job.Config.Stdin, Equals, true) } }
func runJob(client controller.Client, config runConfig) error { req := &ct.NewJob{ Args: config.Args, TTY: config.Stdin == nil && config.Stdout == nil && term.IsTerminal(os.Stdin.Fd()) && term.IsTerminal(os.Stdout.Fd()) && !config.Detached, ReleaseID: config.Release, Env: config.Env, ReleaseEnv: config.ReleaseEnv, DisableLog: config.DisableLog, } // ensure slug apps from old clusters use /runner/init release, err := client.GetRelease(req.ReleaseID) if err != nil { return err } if release.IsGitDeploy() && (len(req.Args) == 0 || req.Args[0] != "/runner/init") { req.Args = append([]string{"/runner/init"}, req.Args...) } // set deprecated Entrypoint and Cmd for old clusters if len(req.Args) > 0 { req.DeprecatedEntrypoint = []string{req.Args[0]} } if len(req.Args) > 1 { req.DeprecatedCmd = req.Args[1:] } if config.Stdin == nil { config.Stdin = os.Stdin } if config.Stdout == nil { config.Stdout = os.Stdout } if config.Stderr == nil { config.Stderr = os.Stderr } if req.TTY { if req.Env == nil { req.Env = make(map[string]string) } ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } req.Columns = int(ws.Width) req.Lines = int(ws.Height) req.Env["COLUMNS"] = strconv.Itoa(int(ws.Width)) req.Env["LINES"] = strconv.Itoa(int(ws.Height)) req.Env["TERM"] = os.Getenv("TERM") } if config.Detached { job, err := client.RunJobDetached(config.App, req) if err != nil { return err } log.Println(job.ID) return nil } rwc, err := client.RunJobAttached(config.App, req) if err != nil { return err } defer rwc.Close() attachClient := cluster.NewAttachClient(rwc) var termState *term.State if req.TTY { termState, err = term.MakeRaw(os.Stdin.Fd()) if err != nil { return err } // Restore the terminal if we return without calling os.Exit defer term.RestoreTerminal(os.Stdin.Fd(), termState) go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, SIGWINCH) for range ch { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return } attachClient.ResizeTTY(ws.Height, ws.Width) attachClient.Signal(int(SIGWINCH)) } }() } go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) sig := <-ch attachClient.Signal(int(sig.(syscall.Signal))) time.Sleep(10 * time.Second) attachClient.Signal(int(syscall.SIGKILL)) }() go func() { io.Copy(attachClient, config.Stdin) attachClient.CloseWrite() }() childDone := make(chan struct{}) shutdown.BeforeExit(func() { <-childDone }) exitStatus, err := attachClient.Receive(config.Stdout, config.Stderr) close(childDone) if err != nil { return err } if req.TTY { term.RestoreTerminal(os.Stdin.Fd(), termState) } if config.Exit { shutdown.ExitWithCode(exitStatus) } if exitStatus != 0 { return RunExitError(exitStatus) } return nil }
func runRun(args *docopt.Args, client *controller.Client) error { runDetached := args.Bool["--detached"] runRelease := args.String["-r"] if runRelease == "" { release, err := client.GetAppRelease(mustApp()) if err == controller.ErrNotFound { return errors.New("No app release, specify a release with -release") } if err != nil { return err } runRelease = release.ID } req := &ct.NewJob{ Cmd: append([]string{args.String["<command>"]}, args.All["<argument>"].([]string)...), TTY: term.IsTerminal(os.Stdin) && term.IsTerminal(os.Stdout) && !runDetached, ReleaseID: runRelease, } if args.String["-e"] != "" { req.Entrypoint = []string{args.String["-e"]} } if req.TTY { cols, err := term.Cols() if err != nil { return err } lines, err := term.Lines() if err != nil { return err } req.Columns = cols req.Lines = lines req.Env = map[string]string{ "COLUMNS": strconv.Itoa(cols), "LINES": strconv.Itoa(lines), "TERM": os.Getenv("TERM"), } } if runDetached { job, err := client.RunJobDetached(mustApp(), req) if err != nil { return err } log.Println(job.ID) return nil } rwc, err := client.RunJobAttached(mustApp(), req) if err != nil { return err } defer rwc.Close() attachClient := cluster.NewAttachClient(rwc) if req.TTY { if err := term.MakeRaw(os.Stdin); err != nil { return err } defer term.Restore(os.Stdin) go func() { ch := make(chan os.Signal) signal.Notify(ch, SIGWINCH) <-ch height, err := term.Lines() if err != nil { return } width, err := term.Cols() if err != nil { return } attachClient.ResizeTTY(uint16(height), uint16(width)) attachClient.Signal(int(SIGWINCH)) }() } go func() { ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) sig := <-ch attachClient.Signal(int(sig.(syscall.Signal))) time.Sleep(10 * time.Second) attachClient.Signal(int(syscall.SIGKILL)) }() go func() { io.Copy(attachClient, os.Stdin) attachClient.CloseWrite() }() exitStatus, err := attachClient.Receive(os.Stdout, os.Stderr) if err != nil { return err } if req.TTY { term.Restore(os.Stdin) } os.Exit(exitStatus) panic("unreached") }
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) }
func runJob(client *controller.Client, config runConfig) error { req := &ct.NewJob{ Cmd: config.Args, TTY: config.Stdin == nil && config.Stdout == nil && term.IsTerminal(os.Stdin.Fd()) && term.IsTerminal(os.Stdout.Fd()) && !config.Detached, ReleaseID: config.Release, Entrypoint: config.Entrypoint, Env: config.Env, ReleaseEnv: config.ReleaseEnv, DisableLog: config.DisableLog, } if config.Stdin == nil { config.Stdin = os.Stdin } if config.Stdout == nil { config.Stdout = os.Stdout } if config.Stderr == nil { config.Stderr = os.Stderr } if req.TTY { if req.Env == nil { req.Env = make(map[string]string) } ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return err } req.Columns = int(ws.Width) req.Lines = int(ws.Height) req.Env["COLUMNS"] = strconv.Itoa(int(ws.Width)) req.Env["LINES"] = strconv.Itoa(int(ws.Height)) req.Env["TERM"] = os.Getenv("TERM") } if config.Detached { job, err := client.RunJobDetached(config.App, req) if err != nil { return err } log.Println(job.ID) return nil } rwc, err := client.RunJobAttached(config.App, req) if err != nil { return err } defer rwc.Close() attachClient := cluster.NewAttachClient(rwc) var termState *term.State if req.TTY { termState, err = term.MakeRaw(os.Stdin.Fd()) if err != nil { return err } // Restore the terminal if we return without calling os.Exit defer term.RestoreTerminal(os.Stdin.Fd(), termState) go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, SIGWINCH) for range ch { ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { return } attachClient.ResizeTTY(ws.Height, ws.Width) attachClient.Signal(int(SIGWINCH)) } }() } go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) sig := <-ch attachClient.Signal(int(sig.(syscall.Signal))) time.Sleep(10 * time.Second) attachClient.Signal(int(syscall.SIGKILL)) }() go func() { io.Copy(attachClient, config.Stdin) attachClient.CloseWrite() }() childDone := make(chan struct{}) shutdown.BeforeExit(func() { <-childDone }) exitStatus, err := attachClient.Receive(config.Stdout, config.Stderr) close(childDone) if err != nil { return err } if req.TTY { term.RestoreTerminal(os.Stdin.Fd(), termState) } shutdown.ExitWithCode(exitStatus) panic("unreached") }
func (s *S) TestRunJobAttached(c *C) { app := s.createTestApp(c, &ct.App{Name: "run-attached"}) hostID := fakeHostID() hc := tu.NewFakeHostClient(hostID) s.cc.AddHost(hc) 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 }) 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 := &ct.NewJob{ ReleaseID: release.ID, ReleaseEnv: true, Cmd: []string{"foo", "bar"}, Env: map[string]string{"JOB": "true", "FOO": "baz"}, Meta: map[string]string{"foo": "baz"}, TTY: true, Columns: 10, Lines: 20, } rwc, err := s.c.RunJobAttached(app.ID, data) 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 := hc.Jobs[0] c.Assert(job.ID, Equals, jobID) c.Assert(job.Metadata, DeepEquals, map[string]string{ "flynn-controller.app": app.ID, "flynn-controller.app_name": app.Name, "flynn-controller.release": release.ID, "foo": "baz", }) c.Assert(job.Config.Cmd, DeepEquals, []string{"foo", "bar"}) c.Assert(job.Config.Env, DeepEquals, map[string]string{ "FLYNN_APP_ID": app.ID, "FLYNN_RELEASE_ID": release.ID, "FLYNN_PROCESS_TYPE": "", "FLYNN_JOB_ID": job.ID, "FOO": "baz", "JOB": "true", "RELEASE": "true", }) c.Assert(job.Config.Stdin, Equals, true) }