func (s *Sender) Handle(job *Job) Status { cmd := append([]string{job.Name}, job.Args...) env := data.Encode(job.Env().MultiMap()) msg := data.Empty().Set("cmd", cmd...).Set("env", env) peer, err := beam.SendConn(s, msg.Bytes()) if err != nil { return job.Errorf("beamsend: %v", err) } defer peer.Close() var tasks sync.WaitGroup defer tasks.Wait() r := beam.NewRouter(nil) r.NewRoute().KeyStartsWith("cmd", "log", "stdout").HasAttachment().Handler(func(p []byte, stdout *os.File) error { tasks.Add(1) go func() { io.Copy(job.Stdout, stdout) stdout.Close() tasks.Done() }() return nil }) r.NewRoute().KeyStartsWith("cmd", "log", "stderr").HasAttachment().Handler(func(p []byte, stderr *os.File) error { tasks.Add(1) go func() { io.Copy(job.Stderr, stderr) stderr.Close() tasks.Done() }() return nil }) r.NewRoute().KeyStartsWith("cmd", "log", "stdin").HasAttachment().Handler(func(p []byte, stdin *os.File) error { go func() { io.Copy(stdin, job.Stdin) stdin.Close() }() return nil }) var status int r.NewRoute().KeyStartsWith("cmd", "status").Handler(func(p []byte, f *os.File) error { cmd := data.Message(p).Get("cmd") if len(cmd) != 2 { return fmt.Errorf("usage: %s <0-127>", cmd[0]) } s, err := strconv.ParseUint(cmd[1], 10, 8) if err != nil { return fmt.Errorf("usage: %s <0-127>", cmd[0]) } status = int(s) return nil }) if _, err := beam.Copy(r, peer); err != nil { return job.Errorf("%v", err) } return Status(status) }
// 1) Find a handler for the command (if no handler, fail) // 2) Attach new in & out pair to the handler // 3) [in the background] Copy handler output to our own output // 4) [in the background] Run the handler // 5) Recursively executeScript() all children commands and wait for them to complete // 6) Wait for handler to return and (shortly afterwards) output copy to complete // 7) Profit func executeCommand(out beam.Sender, cmd *dockerscript.Command) error { if flX { fmt.Printf("+ %v\n", strings.Replace(strings.TrimRight(cmd.String(), "\n"), "\n", "\n+ ", -1)) } Debugf("executeCommand(%s)\n", strings.Join(cmd.Args, " ")) defer Debugf("executeCommand(%s) DONE\n", strings.Join(cmd.Args, " ")) if len(cmd.Args) == 0 { return fmt.Errorf("empty command") } Debugf("[executeCommand] sending job '%s'\n", strings.Join(cmd.Args, " ")) job, err := beam.SendConn(out, data.Empty().Set("cmd", cmd.Args...).Set("type", "job").Bytes()) if err != nil { return fmt.Errorf("%v\n", err) } var tasks sync.WaitGroup tasks.Add(1) Debugf("[executeCommand] spawning background copy of the output of '%s'\n", strings.Join(cmd.Args, " ")) go func() { if out != nil { Debugf("[executeCommand] background copy of the output of '%s'\n", strings.Join(cmd.Args, " ")) n, err := beam.Copy(out, job) if err != nil { Fatalf("[executeCommand] [%s] error during background copy: %v\n", strings.Join(cmd.Args, " "), err) } Debugf("[executeCommand] background copy done of the output of '%s': copied %d messages\n", strings.Join(cmd.Args, " "), n) } tasks.Done() }() // depth-first execution of children commands // executeScript() blocks until all commands are completed Debugf("[executeCommand] recursively running children of '%s'\n", strings.Join(cmd.Args, " ")) executeScript(job, cmd.Children) Debugf("[executeCommand] DONE recursively running children of '%s'\n", strings.Join(cmd.Args, " ")) job.CloseWrite() Debugf("[executeCommand] closing the input of '%s' (all children are completed)\n", strings.Join(cmd.Args, " ")) Debugf("[executeCommand] waiting for background copy of '%s' to complete...\n", strings.Join(cmd.Args, " ")) tasks.Wait() Debugf("[executeCommand] background copy of '%s' complete! This means the job completed.\n", strings.Join(cmd.Args, " ")) return nil }