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 }