blob: 7144b22a7f7d3b6ae775eb049449f157f2398b04 [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 the GCE
// metadata service, 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"
"io"
"log"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
"google.golang.org/cloud/compute/metadata"
)
// 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 attr = "buildlet-binary-url"
func main() {
flag.Parse()
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)
}
}
cmd := exec.Command(target)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
sleepFatalf("Error running buildlet: %v", err)
}
}
// 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 !metadata.OnGCE() {
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)
var res *http.Response
var err error
deadline := time.Now().Add(*networkWait)
for {
res, err = http.Get(url)
if err != nil {
if time.Now().Before(deadline) {
time.Sleep(1 * time.Second)
continue
}
return fmt.Errorf("Error fetching %v: %v", url, err)
}
break
}
if res.StatusCode != 200 {
return fmt.Errorf("HTTP status code of %s was %v", url, res.Status)
}
tmp := file + ".tmp"
os.Remove(tmp)
os.Remove(file)
f, err := os.Create(tmp)
if err != nil {
return err
}
n, err := io.Copy(f, res.Body)
res.Body.Close()
if err != nil {
return fmt.Errorf("Error reading %v: %v", url, err)
}
f.Close()
err = os.Rename(tmp, file)
if err != nil {
return err
}
log.Printf("Downloaded %s (%d bytes)", file, n)
return nil
}