func run(args []string) error { fs := flag.NewFlagSet("run", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "create usage: gomote run [run-opts] <instance> <cmd> [args...]") fs.PrintDefaults() os.Exit(1) } var sys bool fs.BoolVar(&sys, "system", false, "run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'") var debug bool fs.BoolVar(&debug, "debug", false, "write debug info about the command's execution before it begins") var env stringSlice fs.Var(&env, "e", "Environment variable KEY=value. The -e flag may be repeated multiple times to add multiple things to the environment.") var path string fs.StringVar(&path, "path", "", "Comma-separated list of ExecOpts.Path elements. The special string 'EMPTY' means to run without any $PATH. The empty string (default) does not modify the $PATH.") var dir string fs.StringVar(&dir, "dir", "", "Directory to run from. Defaults to the directory of the command, or the work directory if -system is true.") fs.Parse(args) if fs.NArg() < 2 { fs.Usage() } name, cmd := fs.Arg(0), fs.Arg(1) var conf dashboard.BuildConfig bc, conf, err := clientAndConf(name) if err != nil { return err } var pathOpt []string if path == "EMPTY" { pathOpt = []string{} // non-nil } else if path != "" { pathOpt = strings.Split(path, ",") } remoteErr, execErr := bc.Exec(cmd, buildlet.ExecOpts{ Dir: dir, SystemLevel: sys || strings.HasPrefix(cmd, "/"), Output: os.Stdout, Args: fs.Args()[2:], ExtraEnv: envutil.Dedup(conf.GOOS() == "windows", append(conf.Env(), []string(env)...)), Debug: debug, Path: pathOpt, }) if execErr != nil { return fmt.Errorf("Error trying to execute %s: %v", cmd, execErr) } return remoteErr }
func buildGoTarget() { target := strings.TrimPrefix(*file, "go:") var goos, goarch string if *osarch != "" { *osarch = (*osarch)[strings.LastIndex(*osarch, ".")+1:] v := strings.Split(*osarch, "-") if len(v) != 2 || v[0] == "" || v[1] == "" { log.Fatalf("invalid -osarch value %q", *osarch) } goos, goarch = v[0], v[1] } env := envutil.Dedup(runtime.GOOS == "windows", append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch)) cmd := exec.Command("go", "list", "-f", "{{.Target}}", target) cmd.Env = env out, err := cmd.Output() if err != nil { log.Fatalf("go list: %v", err) } outFile := string(bytes.TrimSpace(out)) fi0, err := os.Stat(outFile) if os.IsNotExist(err) { if *verbose { log.Printf("File %s doesn't exist; building...", outFile) } } version := os.Getenv("USER") + "-" + time.Now().Format(time.RFC3339) cmd = exec.Command("go", "install", "-x", "--ldflags=-X main.Version="+version, target) var stderr bytes.Buffer cmd.Stderr = &stderr if *verbose { cmd.Stdout = os.Stdout } cmd.Env = env if err := cmd.Run(); err != nil { log.Fatalf("go install %s: %v, %s", target, err, stderr.Bytes()) } fi1, err := os.Stat(outFile) if err != nil { log.Fatalf("Expected output file %s stat failure after go install %v: %v", outFile, target, err) } if !os.SameFile(fi0, fi1) { if *verbose { log.Printf("File %s rebuilt.", outFile) } } *file = outFile }
func handleExec(w http.ResponseWriter, r *http.Request) { cn := w.(http.CloseNotifier) clientGone := cn.CloseNotify() handlerDone := make(chan bool) defer close(handlerDone) if r.Method != "POST" { http.Error(w, "requires POST method", http.StatusBadRequest) return } if r.ProtoMajor*10+r.ProtoMinor < 11 { // We need trailers, only available in HTTP/1.1 or HTTP/2. http.Error(w, "HTTP/1.1 or higher required", http.StatusBadRequest) return } w.Header().Set("Trailer", hdrProcessState) // declare it so we can set it cmdPath := r.FormValue("cmd") // required absCmd := cmdPath dir := r.FormValue("dir") // optional sysMode := r.FormValue("mode") == "sys" debug, _ := strconv.ParseBool(r.FormValue("debug")) if sysMode { if cmdPath == "" { http.Error(w, "requires 'cmd' parameter", http.StatusBadRequest) return } if dir == "" { dir = *workDir } else { dir = filepath.FromSlash(dir) if !filepath.IsAbs(dir) { dir = filepath.Join(*workDir, dir) } } } else { if !validRelPath(cmdPath) { http.Error(w, "requires 'cmd' parameter", http.StatusBadRequest) return } absCmd = filepath.Join(*workDir, filepath.FromSlash(cmdPath)) if dir == "" { dir = filepath.Dir(absCmd) } else { if !validRelPath(dir) { http.Error(w, "bogus 'dir' parameter", http.StatusBadRequest) return } dir = filepath.Join(*workDir, filepath.FromSlash(dir)) } } if f, ok := w.(http.Flusher); ok { f.Flush() } env := append(baseEnv(), r.PostForm["env"]...) env = envutil.Dedup(runtime.GOOS == "windows", env) env = setPathEnv(env, r.PostForm["path"], *workDir) cmd := exec.Command(absCmd, r.PostForm["cmdArg"]...) cmd.Dir = dir cmdOutput := flushWriter{w} cmd.Stdout = cmdOutput cmd.Stderr = cmdOutput cmd.Env = env log.Printf("[%p] Running %s with args %q and env %q in dir %s", cmd, cmd.Path, cmd.Args, cmd.Env, cmd.Dir) if debug { fmt.Fprintf(cmdOutput, ":: Running %s with args %q and env %q in dir %s\n\n", cmd.Path, cmd.Args, cmd.Env, cmd.Dir) } t0 := time.Now() err := cmd.Start() if err == nil { go func() { select { case <-clientGone: err := killProcessTree(cmd.Process) if err != nil { log.Printf("Kill failed: %v", err) } case <-handlerDone: return } }() err = cmd.Wait() } state := "ok" if err != nil { if ps := cmd.ProcessState; ps != nil { state = ps.String() } else { state = err.Error() } } w.Header().Set(hdrProcessState, state) log.Printf("[%p] Run = %s, after %v", cmd, state, time.Since(t0)) }