blob: a5f02b7d523fcb687f353de0abd82a737243dcdc [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 stage0 command looks up the buildlet's URL from its environment
// (GCE metadata service, scaleway, etc), downloads it, and runs
// it. If not on GCE, such as when in a Linux Docker container being
// developed and tested locally, the stage0 instead looks for the
// META_BUILDLET_BINARY_URL environment to have a URL to the buildlet
// binary.
package main
import (
"flag"
"fmt"
"log"
"net"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
"cloud.google.com/go/compute/metadata"
"golang.org/x/build/internal/httpdl"
"golang.org/x/build/internal/untar"
)
// This lets us be lazy and put the stage0 start-up in rc.local where
// it might race with the network coming up, rather than write proper
// upstart+systemd+init scripts:
var networkWait = flag.Duration("network-wait", 0, "if non-zero, the time to wait for the network to come up.")
const osArch = runtime.GOOS + "/" + runtime.GOARCH
const attr = "buildlet-binary-url"
// untar helper, for the Windows image prep script.
var (
untarFile = flag.String("untar-file", "", "if non-empty, tar.gz to untar to --untar-dest-dir")
untarDestDir = flag.String("untar-dest-dir", "", "destination directory to untar --untar-file to")
)
func main() {
flag.Parse()
if *untarFile != "" {
untarMode()
return
}
switch osArch {
case "linux/arm":
switch env := os.Getenv("GO_BUILDER_ENV"); env {
case "linux-arm-arm5spacemonkey", "host-linux-arm-scaleway":
// No setup currently.
default:
panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
}
case "linux/arm64":
switch env := os.Getenv("GO_BUILDER_ENV"); env {
case "host-linux-arm64-packet", "host-linux-arm64-linaro":
// No special setup.
default:
panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
}
case "linux/ppc64":
initOregonStatePPC64()
case "linux/ppc64le":
initOregonStatePPC64le()
}
if !awaitNetwork() {
sleepFatalf("network didn't become reachable")
}
// Note: we name it ".exe" for Windows, but the name also
// works fine on Linux, etc.
target := filepath.FromSlash("./buildlet.exe")
if err := download(target, buildletURL()); err != nil {
sleepFatalf("Downloading %s: %v", buildletURL, err)
}
if runtime.GOOS != "windows" {
if err := os.Chmod(target, 0755); err != nil {
log.Fatal(err)
}
}
env := os.Environ()
if isUnix() && os.Getuid() == 0 {
if os.Getenv("USER") == "" {
env = append(env, "USER=root")
}
if os.Getenv("HOME") == "" {
env = append(env, "HOME=/root")
}
}
cmd := exec.Command(target)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env
switch buildenv := os.Getenv("GO_BUILDER_ENV"); buildenv {
case "linux-arm-arm5spacemonkey":
cmd.Args = append(cmd.Args, legacyReverseBuildletArgs(buildenv)...)
case "host-linux-arm-scaleway":
scalewayArgs := append(
legacyReverseBuildletArgs(buildenv),
"--hostname="+os.Getenv("HOSTNAME"),
)
cmd.Args = append(cmd.Args,
scalewayArgs...,
)
}
switch osArch {
case "linux/s390x":
cmd.Args = append(cmd.Args, "--workdir=/data/golang/workdir")
cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-s390x-ibm")...)
case "linux/arm64":
switch v := os.Getenv("GO_BUILDER_ENV"); v {
case "host-linux-arm64-packet", "host-linux-arm64-linaro":
hostname := os.Getenv("HOSTNAME") // if empty, docker container name is used
cmd.Args = append(cmd.Args,
"--reverse-type="+v,
"--workdir=/workdir",
"--hostname="+hostname,
"--halt=false",
"--reboot=false",
"--coordinator=farmer.golang.org:443",
)
default:
panic(fmt.Sprintf("unknown/unspecified $GO_BUILDER_ENV value %q", env))
}
case "linux/ppc64":
cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-ppc64-buildlet")...)
case "linux/ppc64le":
cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("linux-ppc64le-buildlet")...)
case "solaris/amd64":
cmd.Args = append(cmd.Args, legacyReverseBuildletArgs("solaris-amd64-smartosbuildlet")...)
}
if err := cmd.Run(); err != nil {
sleepFatalf("Error running buildlet: %v", err)
}
}
// legacyReverseBuildletArgs passes builder as the deprecated --reverse flag.
// New code should use --reverse-type instead.
func legacyReverseBuildletArgs(builder string) []string {
return []string{
"--halt=false",
"--reverse=" + builder,
"--coordinator=farmer.golang.org:443",
}
}
// awaitNetwork reports whether the network came up within 30 seconds,
// determined somewhat arbitrarily via a DNS lookup for google.com.
func awaitNetwork() bool {
for deadline := time.Now().Add(30 * time.Second); time.Now().Before(deadline); time.Sleep(time.Second) {
if addrs, _ := net.LookupIP("google.com"); len(addrs) > 0 {
log.Printf("network is up.")
return true
}
log.Printf("waiting for network...")
}
log.Printf("gave up waiting for network")
return false
}
func buildletURL() string {
if os.Getenv("GO_BUILDER_ENV") == "linux-arm-arm5spacemonkey" {
return "https://storage.googleapis.com/go-builder-data/buildlet.linux-arm-arm5"
}
switch osArch {
case "linux/s390x":
return "https://storage.googleapis.com/go-builder-data/buildlet.linux-s390x"
case "linux/arm64":
return "https://storage.googleapis.com/go-builder-data/buildlet.linux-arm64"
case "linux/ppc64":
return "https://storage.googleapis.com/go-builder-data/buildlet.linux-ppc64"
case "linux/ppc64le":
return "https://storage.googleapis.com/go-builder-data/buildlet.linux-ppc64le"
case "solaris/amd64":
return "https://storage.googleapis.com/go-builder-data/buildlet.solaris-amd64"
}
// The buildlet download URL is located in an env var
// when the buildlet is not running on GCE, or is running
// on Kubernetes.
if !metadata.OnGCE() || os.Getenv("IN_KUBERNETES") == "1" {
if v := os.Getenv("META_BUILDLET_BINARY_URL"); v != "" {
return v
}
sleepFatalf("Not on GCE, and no META_BUILDLET_BINARY_URL specified.")
}
v, err := metadata.InstanceAttributeValue(attr)
if err != nil {
sleepFatalf("Failed to look up %q attribute value: %v", attr, err)
}
return v
}
func sleepFatalf(format string, args ...interface{}) {
log.Printf(format, args...)
if runtime.GOOS == "windows" {
log.Printf("(sleeping for 1 minute before failing)")
time.Sleep(time.Minute) // so user has time to see it in cmd.exe, maybe
}
os.Exit(1)
}
func download(file, url string) error {
log.Printf("Downloading %s to %s ...\n", url, file)
deadline := time.Now().Add(*networkWait)
for {
err := httpdl.Download(file, url)
if err == nil {
fi, _ := os.Stat(file)
log.Printf("Downloaded %s (%d bytes)", file, fi.Size())
return nil
}
// TODO(bradfitz): delete this whole download function
// and move this functionality into a "WaitNetwork"
// function somewhere?
if time.Now().Before(deadline) {
time.Sleep(1 * time.Second)
continue
}
return err
}
}
func aptGetInstall(pkgs ...string) {
args := append([]string{"--yes", "install"}, pkgs...)
cmd := exec.Command("apt-get", args...)
if out, err := cmd.CombinedOutput(); err != nil {
log.Fatalf("error running apt-get install: %s", out)
}
}
func initBootstrapDir(destDir, tgzCache string) {
if err := os.MkdirAll(destDir, 0755); err != nil {
log.Fatal(err)
}
// TODO(bradfitz): rewrite this to use Go instead of curl+tar
// if this ever gets used on platforms besides Unix. For
// Windows and Plan 9 we bake in the bootstrap tarball into
// the image anyway. So this works for now. Solaris might require
// tweaking to use gtar instead or something.
latestURL := fmt.Sprintf("https://storage.googleapis.com/go-builder-data/gobootstrap-%s-%s.tar.gz",
runtime.GOOS, runtime.GOARCH)
curl := exec.Command("/usr/bin/curl", "-R", "-o", tgzCache, "-z", tgzCache, latestURL)
out, err := curl.CombinedOutput()
if err != nil {
log.Fatalf("curl error fetching %s to %s: %s", latestURL, out, err)
}
tar := exec.Command("tar", "zxf", tgzCache)
tar.Dir = destDir
out, err = tar.CombinedOutput()
if err != nil {
log.Fatalf("error untarring %s to %s: %s", tgzCache, destDir, out)
}
}
func initOregonStatePPC64() {
aptGetInstall("gcc", "strace", "libc6-dev", "gdb")
initBootstrapDir("/usr/local/go-bootstrap", "/usr/local/go-bootstrap.tar.gz")
}
func initOregonStatePPC64le() {
aptGetInstall("gcc", "strace", "libc6-dev", "gdb")
initBootstrapDir("/usr/local/go-bootstrap", "/usr/local/go-bootstrap.tar.gz")
}
func isUnix() bool {
switch runtime.GOOS {
case "plan9", "windows":
return false
}
return true
}
func untarMode() {
if *untarDestDir == "" {
log.Fatal("--untar-dest-dir must not be empty")
}
if fi, err := os.Stat(*untarDestDir); err != nil || !fi.IsDir() {
if err != nil {
log.Fatalf("--untar-dest-dir %q: %v", *untarDestDir, err)
}
log.Fatalf("--untar-dest-dir %q not a directory.", *untarDestDir)
}
f, err := os.Open(*untarFile)
if err != nil {
log.Fatal(err)
}
defer f.Close()
if err := untar.Untar(f, *untarDestDir); err != nil {
log.Fatalf("Untarring %q to %q: %v", *untarFile, *untarDestDir, err)
}
}