golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/debugnewvm/debugnewvm.go (about) 1 // Copyright 2017 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 // The debugnewvm command creates and destroys a VM-based buildlet 6 // with lots of logging for debugging. Nothing depends on this. 7 package main 8 9 import ( 10 "context" 11 "flag" 12 "fmt" 13 "log" 14 "net/http" 15 "os" 16 "path" 17 "regexp" 18 "strings" 19 "time" 20 21 "cloud.google.com/go/compute/metadata" 22 "golang.org/x/build/buildenv" 23 "golang.org/x/build/buildlet" 24 "golang.org/x/build/dashboard" 25 "golang.org/x/build/internal/buildgo" 26 "golang.org/x/build/internal/cloud" 27 "golang.org/x/build/internal/secret" 28 "golang.org/x/oauth2" 29 "golang.org/x/oauth2/google" 30 compute "google.golang.org/api/compute/v1" 31 ) 32 33 var ( 34 hostType = flag.String("host", "", "host type to create") 35 zone = flag.String("zone", "", "if non-empty, force a certain GCP zone") 36 overrideImage = flag.String("override-image", "", "if non-empty, an alternate GCE VM image or container image to use, depending on the host type") 37 serial = flag.Bool("serial", true, "watch serial. Supported for GCE VMs") 38 pauseAfterUp = flag.Duration("pause-after-up", 0, "pause for this duration before buildlet is destroyed") 39 sleepSec = flag.Int("sleep-test-secs", 0, "number of seconds to sleep when buildlet comes up, to test time source; OpenBSD only for now") 40 41 runBuild = flag.String("run-build", "", "optional builder name to run all.bash or make.bash for") 42 makeOnly = flag.Bool("make-only", false, "if a --run-build builder name is given, this controls whether make.bash or all.bash is run") 43 buildRev = flag.String("rev", "master", "if --run-build is specified, the git hash or branch name to build") 44 45 useIAPTunnel = flag.Bool("use-iap-tunnel", true, "use an IAP tunnel to connect to GCE builders") 46 47 awsKeyID = flag.String("aws-key-id", "", "if the builder runs on aws then key id is required. If executed on GCE, it will be retrieved from secrets.") 48 awsAccessKey = flag.String("aws-access-key", "", "if the builder runs on aws then the access key is required. If executed on GCE, it will be retrieved from secrets.") 49 awsRegion = flag.String("aws-region", "", "if non-empty and the requested builder is an EC2 instance, force an EC2 region.") 50 ) 51 52 var ( 53 computeSvc *compute.Service 54 env *buildenv.Environment 55 ) 56 57 func main() { 58 buildenv.RegisterFlags() 59 flag.Parse() 60 61 var bconf *dashboard.BuildConfig 62 if *runBuild != "" { 63 var ok bool 64 bconf, ok = dashboard.Builders[*runBuild] 65 if !ok { 66 log.Fatalf("unknown builder %q", *runBuild) 67 } 68 if *hostType == "" { 69 *hostType = bconf.HostType 70 } 71 } 72 73 if *hostType == "" { 74 log.Fatalf("missing --host (or --run-build)") 75 } 76 if *sleepSec != 0 && !strings.Contains(*hostType, "openbsd") { 77 log.Fatalf("The --sleep-test-secs is currently only supported for openbsd hosts.") 78 } 79 80 hconf, ok := dashboard.Hosts[*hostType] 81 if !ok { 82 log.Fatalf("unknown host type %q", *hostType) 83 } 84 if !hconf.IsVM() && !hconf.IsContainer() { 85 log.Fatalf("host type %q is type %q; want a VM or container host type", *hostType, hconf.PoolName()) 86 } 87 if hconf.IsEC2 && (*awsKeyID == "" || *awsAccessKey == "") { 88 if !metadata.OnGCE() { 89 log.Fatal("missing -aws-key-id and -aws-access-key params are required for builders on AWS") 90 } 91 var err error 92 *awsKeyID, *awsAccessKey, err = awsCredentialsFromSecrets() 93 if err != nil { 94 log.Fatalf("unable to retrieve AWS credentials: %s", err) 95 } 96 } 97 if img := *overrideImage; img != "" { 98 if hconf.IsContainer() { 99 hconf.ContainerImage = img 100 } else { 101 hconf.VMImage = img 102 } 103 } 104 vmImageSummary := fmt.Sprintf("%q", hconf.VMImage) 105 if hconf.IsContainer() { 106 containerHost := hconf.ContainerVMImage() 107 if containerHost == "" { 108 containerHost = "default container host" 109 } 110 vmImageSummary = fmt.Sprintf("%s, running container %q", containerHost, hconf.ContainerImage) 111 } 112 113 env = buildenv.FromFlags() 114 ctx := context.Background() 115 name := fmt.Sprintf("debug-temp-%d-%s", time.Now().Unix(), os.Getenv("USER")) 116 117 log.Printf("Creating %s (with VM image %s)", name, vmImageSummary) 118 var bc buildlet.Client 119 if hconf.IsEC2 { 120 region := env.AWSRegion 121 if *awsRegion != "" { 122 region = *awsRegion 123 } 124 awsC, err := cloud.NewAWSClient(region, *awsKeyID, *awsAccessKey) 125 if err != nil { 126 log.Fatalf("unable to create aws cloud client: %s", err) 127 } 128 ec2C := buildlet.NewEC2Client(awsC) 129 if err != nil { 130 log.Fatalf("unable to create ec2 client: %v", err) 131 } 132 bc, err = ec2Buildlet(context.Background(), ec2C, hconf, env, name, *hostType, *zone) 133 if err != nil { 134 log.Fatalf("Start EC2 VM: %v", err) 135 } 136 } else { 137 buildenv.CheckUserCredentials() 138 creds, err := env.Credentials(ctx) 139 if err != nil { 140 log.Fatal(err) 141 } 142 computeSvc, _ = compute.New(oauth2.NewClient(ctx, creds.TokenSource)) 143 bc, err = gceBuildlet(creds, env, name, *hostType, *zone) 144 if err != nil { 145 log.Fatalf("Start GCE VM: %v", err) 146 } 147 } 148 dir, err := bc.WorkDir(ctx) 149 log.Printf("WorkDir: %v, %v", dir, err) 150 151 if *sleepSec > 0 { 152 bc.Exec(ctx, "sysctl", buildlet.ExecOpts{ 153 Output: os.Stdout, 154 SystemLevel: true, 155 Args: []string{"kern.timecounter.hardware"}, 156 }) 157 bc.Exec(ctx, "bash", buildlet.ExecOpts{ 158 Output: os.Stdout, 159 SystemLevel: true, 160 Args: []string{"-c", "rdate -p -v time.nist.gov; sleep " + fmt.Sprint(*sleepSec) + "; rdate -p -v time.nist.gov"}, 161 }) 162 } 163 164 var buildFailed bool 165 if *runBuild != "" { 166 // Push GOROOT_BOOTSTRAP, if needed. 167 if u := bconf.GoBootstrapURL(env); u != "" { 168 log.Printf("Pushing 'go1.4' Go bootstrap dir from %s...", u) 169 const bootstrapDir = "go1.4" // might be newer; name is the default 170 if err := bc.PutTarFromURL(ctx, u, bootstrapDir); err != nil { 171 bc.Close() 172 log.Fatalf("Putting Go bootstrap: %v", err) 173 } 174 } 175 176 // Push Go code 177 log.Printf("Pushing 'go' dir...") 178 goTarGz := "https://go.googlesource.com/go/+archive/" + *buildRev + ".tar.gz" 179 if err := bc.PutTarFromURL(ctx, goTarGz, "go"); err != nil { 180 bc.Close() 181 log.Fatalf("Putting go code: %v", err) 182 } 183 184 // Push a synthetic VERSION file to prevent git usage: 185 if err := bc.PutTar(ctx, buildgo.VersionTgz(*buildRev), "go"); err != nil { 186 bc.Close() 187 log.Fatalf("Putting VERSION file: %v", err) 188 } 189 190 script := bconf.AllScript() 191 if *makeOnly { 192 script = bconf.MakeScript() 193 } 194 t0 := time.Now() 195 log.Printf("Running %s ...", script) 196 remoteErr, err := bc.Exec(ctx, path.Join("go", script), buildlet.ExecOpts{ 197 Output: os.Stdout, 198 ExtraEnv: bconf.Env(), 199 Debug: true, 200 Args: bconf.AllScriptArgs(), 201 }) 202 if err != nil { 203 log.Fatalf("error trying to run %s: %v", script, err) 204 } 205 if remoteErr != nil { 206 log.Printf("remote failure running %s: %v", script, remoteErr) 207 buildFailed = true 208 } else { 209 log.Printf("ran %s in %v", script, time.Since(t0).Round(time.Second)) 210 } 211 } 212 213 if *pauseAfterUp != 0 { 214 log.Printf("Sleeping for %v before shutting down...", *pauseAfterUp) 215 time.Sleep(*pauseAfterUp) 216 } 217 if err := bc.Close(); err != nil { 218 log.Fatalf("Close: %v", err) 219 } 220 log.Printf("done.") 221 time.Sleep(2 * time.Second) // wait for serial logging to catch up 222 223 if buildFailed { 224 os.Exit(1) 225 } 226 } 227 228 // watchSerial streams the named VM's serial port to log.Printf. It's roughly: 229 // 230 // gcloud compute connect-to-serial-port --zone=xxx $NAME 231 // 232 // but in Go and works. For some reason, gcloud doesn't work as a 233 // child process and has weird errors. 234 // TODO(golang.org/issue/39485) - investigate if this is possible for EC2 instances 235 func watchSerial(zone, name string) { 236 start := int64(0) 237 indent := strings.Repeat(" ", len("2017/07/25 06:37:14 SERIAL: ")) 238 for { 239 sout, err := computeSvc.Instances.GetSerialPortOutput(env.ProjectName, zone, name).Start(start).Do() 240 if err != nil { 241 log.Printf("serial output error: %v", err) 242 return 243 } 244 moved := sout.Next != start 245 start = sout.Next 246 contents := strings.Replace(strings.TrimSpace(sout.Contents), "\r\n", "\r\n"+indent, -1) 247 if contents != "" { 248 log.Printf("SERIAL: %s", contents) 249 } 250 if !moved { 251 time.Sleep(1 * time.Second) 252 } 253 } 254 } 255 256 // awsCredentialsFromSecrets retrieves AWS credentials from the secret management service. 257 // This function returns the key ID and the access key. 258 func awsCredentialsFromSecrets() (string, string, error) { 259 c, err := secret.NewClient() 260 if err != nil { 261 return "", "", fmt.Errorf("unable to create secret client: %w", err) 262 } 263 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 264 defer cancel() 265 keyID, err := c.Retrieve(ctx, secret.NameAWSKeyID) 266 if err != nil { 267 return "", "", fmt.Errorf("unable to retrieve key ID: %w", err) 268 } 269 accessKey, err := c.Retrieve(ctx, secret.NameAWSAccessKey) 270 if err != nil { 271 return "", "", fmt.Errorf("unable to retrieve access key: %w", err) 272 } 273 return keyID, accessKey, nil 274 } 275 276 func gceBuildlet(creds *google.Credentials, env *buildenv.Environment, name, hostType, zone string) (buildlet.Client, error) { 277 return buildlet.StartNewVM(creds, env, name, hostType, buildlet.VMOpts{ 278 Zone: zone, 279 OnInstanceRequested: func() { log.Printf("instance requested") }, 280 OnInstanceCreated: func() { 281 log.Printf("instance created") 282 }, 283 OnGotInstanceInfo: func(inst *compute.Instance) { 284 zone := inst.Zone 285 m := regexp.MustCompile(`/projects/([^/]+)/zones/([^/]+)`).FindStringSubmatch(inst.Zone) 286 if m != nil { 287 zone = m[2] 288 } 289 log.Printf("got instance info; running in %v (%v)", inst.Zone, zone) 290 if *serial { 291 go watchSerial(zone, name) 292 } 293 }, 294 OnBeginBuildletProbe: func(buildletURL string) { 295 log.Printf("About to hit %s to see if buildlet is up yet...", buildletURL) 296 }, 297 OnEndBuildletProbe: func(res *http.Response, err error) { 298 if err != nil { 299 log.Printf("client buildlet probe error: %v", err) 300 return 301 } 302 log.Printf("buildlet probe: %s", res.Status) 303 }, 304 UseIAPTunnel: *useIAPTunnel, 305 }) 306 } 307 308 func ec2Buildlet(ctx context.Context, ec2Client *buildlet.EC2Client, hconf *dashboard.HostConfig, env *buildenv.Environment, name, hostType, zone string) (buildlet.Client, error) { 309 kp, err := buildlet.NewKeyPair() 310 if err != nil { 311 log.Fatalf("key pair failed: %v", err) 312 } 313 return ec2Client.StartNewVM(ctx, env, hconf, name, hostType, &buildlet.VMOpts{ 314 TLS: kp, 315 Zone: zone, 316 OnInstanceRequested: func() { log.Printf("instance requested") }, 317 OnInstanceCreated: func() { 318 log.Printf("instance created") 319 }, 320 OnGotEC2InstanceInfo: func(inst *cloud.Instance) { 321 log.Printf("got instance info: running in %v", inst.Zone) 322 }, 323 OnBeginBuildletProbe: func(buildletURL string) { 324 log.Printf("About to hit %s to see if buildlet is up yet...", buildletURL) 325 }, 326 OnEndBuildletProbe: func(res *http.Response, err error) { 327 if err != nil { 328 log.Printf("client buildlet probe error: %v", err) 329 return 330 } 331 log.Printf("buildlet probe: %s", res.Status) 332 }, 333 }) 334 }