blob: 0004eb772d11fa698d136b0e45d469feb3960f4a [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.
/*
The gomote command is a client for the Go builder infrastructure.
It's a remote control for remote Go builder machines.
See https://golang.org/wiki/Gomote
Usage:
gomote [global-flags] cmd [cmd-flags]
For example,
$ gomote create openbsd-amd64-60
user-username-openbsd-amd64-60-0
$ gomote push user-username-openbsd-amd64-60-0
$ gomote run user-username-openbsd-amd64-60-0 go/src/make.bash
$ gomote run user-username-openbsd-amd64-60-0 go/bin/go test -v -short os
To list the subcommands, run "gomote" without arguments:
Commands:
create create a buildlet; with no args, list types of buildlets
destroy destroy a buildlet
gettar extract a tar.gz from a buildlet
list list active buildlets
ls list the contents of a directory on a buildlet
ping test whether a buildlet is alive and reachable
push sync your GOROOT directory to the buildlet
put put files on a buildlet
put14 put Go 1.4 in place
puttar extract a tar.gz to a buildlet
rm delete files or directories
rdp RDP (Remote Desktop Protocol) to a Windows buildlet
run run a command on a buildlet
ssh ssh to a buildlet
To list all the builder types available, run "create" with no arguments:
$ gomote create
(list tons of buildlet types)
The "gomote run" command has many of its own flags:
$ gomote run -h
run usage: gomote run [run-opts] <instance> <cmd> [args...]
-builderenv string
Optional alternate builder to act like. Must share the same
underlying buildlet host type, or it's an error. For
instance, linux-amd64-race is compatible
with linux-amd64, but openbsd-amd64 and openbsd-386 are
different hosts.
-debug
write debug info about the command's execution before it begins
-dir string
Directory to run from. Defaults to the directory of the
command, or the work directory if -system is true.
-e value
Environment variable KEY=value. The -e flag may be repeated
multiple times to add multiple things to the environment.
-path string
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.
-system
run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'
# Debugging buildlets directly
Using "gomote create" contacts the build coordinator
(farmer.golang.org) and requests that it create the buildlet on your
behalf. All subsequent commands (such as "gomote run" or "gomote ls")
then proxy your request via the coordinator. To access a buildlet
directly (for example, when working on the buildlet code), you can
skip the "gomote create" step and use the special builder name
"<build-config-name>@ip[:port>", such as "windows-amd64-2008@10.1.5.3".
# Groups
Instances may be managed in named groups, and commands are broadcast to all
instances in the group.
A group is specified either by the -group global flag or through the
GOMOTE_GROUP environment variable. The -group flag must always specify a
valid group, whereas GOMOTE_GROUP may contain an invalid group.
Instances may be part of more than one group.
Groups may be explicitly managed with the "group" subcommand, but there
are several short-cuts that make this unnecessary in most cases:
- The create command can create a new group for instances with the
-new-group flag.
- The create command will automatically create the group in GOMOTE_GROUP
if it does not exist and no other group is explicitly specified.
- The destroy command can destroy a group in addition to its instances
with the -destroy-group flag.
As a result, the easiest way to use groups is to just set the
GOMOTE_GROUP environment variable:
$ export GOMOTE_GROUP=debug
$ gomote create linux-amd64
$ GOROOT=/path/to/goroot gomote create linux-amd64
$ gomote run go/src/make.bash
As this example demonstrates, groups are useful even if the group
contains only a single instance: it can dramatically shorten most gomote
commands.
# Tips and tricks
- The create command accepts the -setup flag which also pushes a GOROOT
and runs the appropriate equivalent of "make.bash" for the instance.
- The create command accepts the -count flag for creating several
instances at once.
- The run command accepts the -collect flag for automatically writing
the output from the command to a file in $PWD, as well as a copy of
the full file tree from the instance. This command is useful for
capturing the output of long-running commands in a set-and-forget
manner.
- The run command accepts the -until flag for continuously executing
a command until the output of the command matches some pattern. Useful
for reproducing rare issues, and especially useful when used in tandem
with -collect.
- The run command always streams output to a temporary file regardless
of any additional flags to avoid losing output due to terminal
scrollback. It always prints the location of the file.
Using some of these tricks, it's straightforward to hammer at some test
to reproduce a rare failure, like so:
$ export GOMOTE_GROUP=debug
$ GOROOT=/path/to/goroot gomote create -setup -count=10 linux-amd64
$ gomote run -until='unexpected return pc' -collect go/bin/go run -run="MyFlakyTest" -count=100 runtime
*/
package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"sort"
"golang.org/x/build/buildenv"
"golang.org/x/build/buildlet"
"golang.org/x/build/internal/gomote/protos"
"golang.org/x/build/internal/iapclient"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var (
buildEnv *buildenv.Environment
activeGroup *groupData
)
type command struct {
name string
des string
run func([]string) error
}
var commands = map[string]command{}
func sortedCommands() []string {
s := make([]string, 0, len(commands))
for name := range commands {
s = append(s, name)
}
sort.Strings(s)
return s
}
func usage() {
fmt.Fprintf(os.Stderr, `Usage of gomote: gomote [global-flags] <cmd> [cmd-flags]
Global flags:
`)
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "Commands:\n\n")
for _, name := range sortedCommands() {
fmt.Fprintf(os.Stderr, " %-13s %s\n", name, commands[name].des)
}
os.Exit(1)
}
func registerCommand(name, des string, run func([]string) error) {
if _, dup := commands[name]; dup {
panic("duplicate registration of " + name)
}
commands[name] = command{
name: name,
des: des,
run: run,
}
}
func registerCommands() {
registerCommand("create", "create a buildlet; with no args, list types of buildlets", create)
registerCommand("destroy", "destroy a buildlet", destroy)
registerCommand("gettar", "extract a tar.gz from a buildlet", getTar)
registerCommand("group", "manage groups of instances", group)
registerCommand("ls", "list the contents of a directory on a buildlet", ls)
registerCommand("list", "list active buildlets", list)
registerCommand("ping", "test whether a buildlet is alive and reachable ", ping)
registerCommand("push", "sync your GOROOT directory to the buildlet", push)
registerCommand("put", "put files on a buildlet", put)
registerCommand("putbootstrap", "put bootstrap toolchain in place", putBootstrap)
registerCommand("puttar", "extract a tar.gz to a buildlet", putTar)
registerCommand("rdp", "Unimplimented: RDP (Remote Desktop Protocol) to a Windows buildlet", rdp)
registerCommand("rm", "delete files or directories", rm)
registerCommand("run", "run a command on a buildlet", run)
registerCommand("ssh", "ssh to a buildlet", ssh)
}
var (
serverAddr = flag.String("server", "build.golang.org:443", "Address for GRPC server")
)
func main() {
// Set up and parse global flags.
groupName := flag.String("group", os.Getenv("GOMOTE_GROUP"), "name of the gomote group to apply commands to (default is $GOMOTE_GROUP)")
buildlet.RegisterFlags()
registerCommands()
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
usage()
}
// Set up globals.
buildEnv = buildenv.FromFlags()
if *groupName != "" {
var err error
activeGroup, err = loadGroup(*groupName)
if os.Getenv("GOMOTE_GROUP") != *groupName {
// Only fail hard since it was specified by the flag.
if err != nil {
fmt.Fprintf(os.Stderr, "Failure: %v\n", err)
usage()
}
} else {
// With a valid group from GOMOTE_GROUP,
// make it explicit to the user that we're going
// ahead with it. We don't need this with the flag
// because it's explicit.
if err == nil {
fmt.Fprintf(os.Stderr, "# Using group %q from GOMOTE_GROUP\n", *groupName)
}
// Note that an invalid group in GOMOTE_GROUP is OK.
}
}
cmdName := args[0]
cmd, ok := commands[cmdName]
if !ok {
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmdName)
usage()
}
if err := cmd.run(args[1:]); err != nil {
logAndExitf("Error running %s: %v\n", cmdName, err)
}
}
// gomoteServerClient returns a gomote server client which can be used to interact with the gomote GRPC server.
// It will either retrieve a previously created authentication token or attempt to create a new one.
func gomoteServerClient(ctx context.Context) protos.GomoteServiceClient {
grpcClient, err := iapclient.GRPCClient(ctx, *serverAddr)
if err != nil {
logAndExitf("dialing the server=%s failed with: %s", *serverAddr, err)
}
return protos.NewGomoteServiceClient(grpcClient)
}
// logAndExitf is equivalent to Printf to Stderr followed by a call to os.Exit(1).
func logAndExitf(format string, v ...interface{}) {
fmt.Fprintf(os.Stderr, format, v...)
os.Exit(1)
}
func instanceDoesNotExist(err error) bool {
for err != nil {
if status.Code(err) == codes.NotFound {
return true
}
err = errors.Unwrap(err)
}
return false
}