blob: d05409b37650385f9f021c33fcd04d708e08aa08 [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"flag"
"fmt"
"os"
"strings"
"golang.org/x/build/buildlet"
"golang.org/x/build/dashboard"
"golang.org/x/build/envutil"
)
func run(args []string) error {
fs := flag.NewFlagSet("run", flag.ContinueOnError)
fs.Usage = func() {
fmt.Fprintln(os.Stderr, "run 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 firewall bool
fs.BoolVar(&firewall, "firewall", false, "Enable outbound firewall on machine. This is on by default on many builders (where supported) but disabled by default on gomote for ease of debugging. Once any command has been run with the -firewall flag on, it's on for the lifetime of that gomote instance.")
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. Otherwise, the following expansions apply: the string '$PATH' expands to the current PATH element(s), the substring '$WORKDIR' expands to the buildlet's temp workdir.")
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.")
var builderEnv string
fs.StringVar(&builderEnv, "builderenv", "", "Optional alternate builder to act like. Must share the same underlying buildlet host type, or it's an error. For instance, linux-amd64-race or linux-386-387 are compatible with linux-amd64, but openbsd-amd64 and openbsd-386 are different hosts.")
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
}
if builderEnv != "" {
altConf, ok := dashboard.Builders[builderEnv]
if !ok {
return fmt.Errorf("unknown --builderenv=%q builder value", builderEnv)
}
if altConf.HostType != conf.HostType {
return fmt.Errorf("--builderEnv=%q has host type %q, which is not compatible with the named buildlet's host type %q",
builderEnv, altConf.HostType, conf.HostType)
}
conf = altConf
}
var pathOpt []string
if path == "EMPTY" {
pathOpt = []string{} // non-nil
} else if path != "" {
pathOpt = strings.Split(path, ",")
}
env = append(env, "GO_DISABLE_OUTBOUND_NETWORK="+fmt.Sprint(firewall))
remoteErr, execErr := bc.Exec(context.Background(), 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
}
// stringSlice implements flag.Value, specifically for storing environment
// variable key=value pairs.
type stringSlice []string
func (*stringSlice) String() string { return "" } // default value
func (ss *stringSlice) Set(v string) error {
if v != "" {
if !strings.Contains(v, "=") {
return fmt.Errorf("-e argument %q doesn't contains an '=' sign.", v)
}
*ss = append(*ss, v)
}
return nil
}