golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/gomote/gomote.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6  The gomote command is a client for the Go builder infrastructure.
     7  It's a remote control for remote Go builder machines.
     8  
     9  See https://golang.org/wiki/Gomote
    10  
    11  Usage:
    12  
    13  	gomote [global-flags] cmd [cmd-flags]
    14  
    15  	For example,
    16  	$ gomote create openbsd-amd64-60
    17  	user-username-openbsd-amd64-60-0
    18  	$ gomote push user-username-openbsd-amd64-60-0
    19  	$ gomote run user-username-openbsd-amd64-60-0 go/src/make.bash
    20  	$ gomote run user-username-openbsd-amd64-60-0 go/bin/go test -v -short os
    21  
    22  To list the subcommands, run "gomote" without arguments:
    23  
    24  	Commands:
    25  
    26  	  create     create a buildlet; with no args, list types of buildlets
    27  	  destroy    destroy a buildlet
    28  	  gettar     extract a tar.gz from a buildlet
    29  	  list       list active buildlets
    30  	  ls         list the contents of a directory on a buildlet
    31  	  ping       test whether a buildlet is alive and reachable
    32  	  push       sync your GOROOT directory to the buildlet
    33  	  put        put files on a buildlet
    34  	  put14      put Go 1.4 in place
    35  	  puttar     extract a tar.gz to a buildlet
    36  	  rm         delete files or directories
    37  	  rdp        RDP (Remote Desktop Protocol) to a Windows buildlet
    38  	  run        run a command on a buildlet
    39  	  ssh        ssh to a buildlet
    40  
    41  To list all the builder types available, run "create" with no arguments:
    42  
    43  	$ gomote create
    44  	(list tons of buildlet types)
    45  
    46  The "gomote run" command has many of its own flags:
    47  
    48  	$ gomote run -h
    49  	run usage: gomote run [run-opts] <instance> <cmd> [args...]
    50  	  -builderenv string
    51  	        Optional alternate builder to act like. Must share the same
    52  	        underlying buildlet host type, or it's an error. For
    53  	        instance, linux-amd64-race is compatible
    54  	        with linux-amd64, but openbsd-amd64 and openbsd-386 are
    55  	        different hosts.
    56  	  -debug
    57  	        write debug info about the command's execution before it begins
    58  	  -dir string
    59  	        Directory to run from. Defaults to the directory of the
    60  	        command, or the work directory if -system is true.
    61  	  -e value
    62  	        Environment variable KEY=value. The -e flag may be repeated
    63  	        multiple times to add multiple things to the environment.
    64  	  -path string
    65  	        Comma-separated list of ExecOpts.Path elements. The special
    66  	        string 'EMPTY' means to run without any $PATH. The empty
    67  	        string (default) does not modify the $PATH. Otherwise, the
    68  	        following expansions apply: the string '$PATH' expands to
    69  	        the current PATH element(s), the substring '$WORKDIR'
    70  	        expands to the buildlet's temp workdir.
    71  	  -system
    72  	        run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'
    73  
    74  # Debugging buildlets directly
    75  
    76  Using "gomote create" contacts the build coordinator
    77  (farmer.golang.org) and requests that it create the buildlet on your
    78  behalf. All subsequent commands (such as "gomote run" or "gomote ls")
    79  then proxy your request via the coordinator.  To access a buildlet
    80  directly (for example, when working on the buildlet code), you can
    81  skip the "gomote create" step and use the special builder name
    82  "<build-config-name>@ip[:port>", such as "windows-amd64-2008@10.1.5.3".
    83  
    84  # Groups
    85  
    86  Instances may be managed in named groups, and commands are broadcast to all
    87  instances in the group.
    88  
    89  A group is specified either by the -group global flag or through the
    90  GOMOTE_GROUP environment variable. The -group flag must always specify a
    91  valid group, whereas GOMOTE_GROUP may contain an invalid group.
    92  Instances may be part of more than one group.
    93  
    94  Groups may be explicitly managed with the "group" subcommand, but there
    95  are several short-cuts that make this unnecessary in most cases:
    96  
    97    - The create command can create a new group for instances with the
    98      -new-group flag.
    99    - The create command will automatically create the group in GOMOTE_GROUP
   100      if it does not exist and no other group is explicitly specified.
   101    - The destroy command can destroy a group in addition to its instances
   102      with the -destroy-group flag.
   103  
   104  As a result, the easiest way to use groups is to just set the
   105  GOMOTE_GROUP environment variable:
   106  
   107  	$ export GOMOTE_GROUP=debug
   108  	$ gomote create linux-amd64
   109  	$ GOROOT=/path/to/goroot gomote create linux-amd64
   110  	$ gomote run go/src/make.bash
   111  
   112  As this example demonstrates, groups are useful even if the group
   113  contains only a single instance: it can dramatically shorten most gomote
   114  commands.
   115  
   116  # Tips and tricks
   117  
   118    - The create command accepts the -setup flag which also pushes a GOROOT
   119      and runs the appropriate equivalent of "make.bash" for the instance.
   120    - The create command accepts the -count flag for creating several
   121      instances at once.
   122    - The run command accepts the -collect flag for automatically writing
   123      the output from the command to a file in $PWD, as well as a copy of
   124      the full file tree from the instance. This command is useful for
   125      capturing the output of long-running commands in a set-and-forget
   126      manner.
   127    - The run command accepts the -until flag for continuously executing
   128      a command until the output of the command matches some pattern. Useful
   129      for reproducing rare issues, and especially useful when used in tandem
   130      with -collect.
   131    - The run command always streams output to a temporary file regardless
   132      of any additional flags to avoid losing output due to terminal
   133      scrollback. It always prints the location of the file.
   134  
   135  Using some of these tricks, it's straightforward to hammer at some test
   136  to reproduce a rare failure, like so:
   137  
   138  	$ export GOMOTE_GROUP=debug
   139  	$ GOROOT=/path/to/goroot gomote create -setup -count=10 linux-amd64
   140  	$ gomote run -until='unexpected return pc' -collect go/bin/go run -run="MyFlakyTest" -count=100 runtime
   141  
   142  # Legacy Infrastructure
   143  
   144  Setting the GOMOTEDISABLELUCI environmental variable equal to true will set the gomote client to communicate with
   145  the coordinator instead of the gomote server.
   146  */
   147  package main
   148  
   149  import (
   150  	"context"
   151  	"errors"
   152  	"flag"
   153  	"fmt"
   154  	"os"
   155  	"sort"
   156  	"strconv"
   157  
   158  	"golang.org/x/build/buildenv"
   159  	"golang.org/x/build/buildlet"
   160  	"golang.org/x/build/internal/gomote/protos"
   161  	"golang.org/x/build/internal/iapclient"
   162  	"google.golang.org/grpc/codes"
   163  	"google.golang.org/grpc/status"
   164  )
   165  
   166  var (
   167  	buildEnv    *buildenv.Environment
   168  	activeGroup *groupData
   169  )
   170  
   171  type command struct {
   172  	name string
   173  	des  string
   174  	run  func([]string) error
   175  }
   176  
   177  var commands = map[string]command{}
   178  
   179  func sortedCommands() []string {
   180  	s := make([]string, 0, len(commands))
   181  	for name := range commands {
   182  		s = append(s, name)
   183  	}
   184  	sort.Strings(s)
   185  	return s
   186  }
   187  
   188  func usage() {
   189  	fmt.Fprintf(os.Stderr, `Usage of gomote: gomote [global-flags] <cmd> [cmd-flags]
   190  
   191  Global flags:
   192  `)
   193  	flag.PrintDefaults()
   194  	fmt.Fprintf(os.Stderr, "Commands:\n\n")
   195  	for _, name := range sortedCommands() {
   196  		fmt.Fprintf(os.Stderr, "  %-13s %s\n", name, commands[name].des)
   197  	}
   198  	os.Exit(1)
   199  }
   200  
   201  func registerCommand(name, des string, run func([]string) error) {
   202  	if _, dup := commands[name]; dup {
   203  		panic("duplicate registration of " + name)
   204  	}
   205  	commands[name] = command{
   206  		name: name,
   207  		des:  des,
   208  		run:  run,
   209  	}
   210  }
   211  
   212  func registerCommands() {
   213  	registerCommand("create", "create a buildlet; with no args, list types of buildlets", create)
   214  	registerCommand("destroy", "destroy a buildlet", destroy)
   215  	registerCommand("gettar", "extract a tar.gz from a buildlet", getTar)
   216  	registerCommand("group", "manage groups of instances", group)
   217  	registerCommand("ls", "list the contents of a directory on a buildlet", ls)
   218  	registerCommand("list", "list active buildlets", list)
   219  	registerCommand("ping", "test whether a buildlet is alive and reachable ", ping)
   220  	registerCommand("push", "sync your GOROOT directory to the buildlet", push)
   221  	registerCommand("put", "put files on a buildlet", put)
   222  	registerCommand("putbootstrap", "put bootstrap toolchain in place", putBootstrap)
   223  	registerCommand("puttar", "extract a tar.gz to a buildlet", putTar)
   224  	registerCommand("rdp", "Unimplimented: RDP (Remote Desktop Protocol) to a Windows buildlet", rdp)
   225  	registerCommand("rm", "delete files or directories", rm)
   226  	registerCommand("run", "run a command on a buildlet", run)
   227  	registerCommand("ssh", "ssh to a buildlet", ssh)
   228  }
   229  
   230  var (
   231  	serverAddr = flag.String("server", "gomote.golang.org:443", "Address for GRPC server")
   232  )
   233  
   234  func main() {
   235  	// Set up and parse global flags.
   236  	groupName := flag.String("group", os.Getenv("GOMOTE_GROUP"), "name of the gomote group to apply commands to (default is $GOMOTE_GROUP)")
   237  	buildlet.RegisterFlags()
   238  	registerCommands()
   239  	flag.Usage = usage
   240  	flag.Parse()
   241  	args := flag.Args()
   242  	if len(args) == 0 {
   243  		usage()
   244  	}
   245  	if luciDisabled() {
   246  		*serverAddr = "build.golang.org:443"
   247  	}
   248  	// Set up globals.
   249  	buildEnv = buildenv.FromFlags()
   250  	if *groupName != "" {
   251  		var err error
   252  		activeGroup, err = loadGroup(*groupName)
   253  		if os.Getenv("GOMOTE_GROUP") != *groupName {
   254  			// Only fail hard since it was specified by the flag.
   255  			if err != nil {
   256  				fmt.Fprintf(os.Stderr, "Failure: %v\n", err)
   257  				usage()
   258  			}
   259  		} else {
   260  			// With a valid group from GOMOTE_GROUP,
   261  			// make it explicit to the user that we're going
   262  			// ahead with it. We don't need this with the flag
   263  			// because it's explicit.
   264  			if err == nil {
   265  				fmt.Fprintf(os.Stderr, "# Using group %q from GOMOTE_GROUP\n", *groupName)
   266  			}
   267  			// Note that an invalid group in GOMOTE_GROUP is OK.
   268  		}
   269  	}
   270  
   271  	cmdName := args[0]
   272  	cmd, ok := commands[cmdName]
   273  	if !ok {
   274  		fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmdName)
   275  		usage()
   276  	}
   277  	if err := cmd.run(args[1:]); err != nil {
   278  		logAndExitf("Error running %s: %v\n", cmdName, err)
   279  	}
   280  }
   281  
   282  // gomoteServerClient returns a gomote server client which can be used to interact with the gomote GRPC server.
   283  // It will either retrieve a previously created authentication token or attempt to create a new one.
   284  func gomoteServerClient(ctx context.Context) protos.GomoteServiceClient {
   285  	grpcClient, err := iapclient.GRPCClient(ctx, *serverAddr)
   286  	if err != nil {
   287  		logAndExitf("dialing the server=%s failed with: %s", *serverAddr, err)
   288  	}
   289  	return protos.NewGomoteServiceClient(grpcClient)
   290  }
   291  
   292  // logAndExitf is equivalent to Printf to Stderr followed by a call to os.Exit(1).
   293  func logAndExitf(format string, v ...interface{}) {
   294  	fmt.Fprintf(os.Stderr, format, v...)
   295  	os.Exit(1)
   296  }
   297  
   298  func instanceDoesNotExist(err error) bool {
   299  	for err != nil {
   300  		if status.Code(err) == codes.NotFound {
   301  			return true
   302  		}
   303  		err = errors.Unwrap(err)
   304  	}
   305  	return false
   306  }
   307  
   308  func luciDisabled() bool {
   309  	on, _ := strconv.ParseBool(os.Getenv("GOMOTEDISABLELUCI"))
   310  	return on
   311  }