diff --git a/dashboard/buildlet/.gitignore b/dashboard/buildlet/.gitignore new file mode 100644 index 00000000000..3775742beb1 --- /dev/null +++ b/dashboard/buildlet/.gitignore @@ -0,0 +1,3 @@ +buildlet +buildlet.*-* +stage0/buildlet-stage0.* diff --git a/dashboard/buildlet/Makefile b/dashboard/buildlet/Makefile new file mode 100644 index 00000000000..689791049f4 --- /dev/null +++ b/dashboard/buildlet/Makefile @@ -0,0 +1,14 @@ +buildlet: buildlet.go + go build --tags=buildlet + +buildlet.openbsd-amd64: buildlet.go + GOOS=openbsd GOARCH=amd64 go build -o $@ --tags=buildlet + cat $@ | (cd ../coordinator/buildongce && go run create.go --write_object=go-builder-data/$@) + +buildlet.plan9-386: buildlet.go + GOOS=plan9 GOARCH=386 go build -o $@ --tags=buildlet + cat $@ | (cd ../coordinator/buildongce && go run create.go --write_object=go-builder-data/$@) + +buildlet.windows-amd64: buildlet.go + GOOS=windows GOARCH=amd64 go build -o $@ --tags=buildlet + cat $@ | (cd ../coordinator/buildongce && go run create.go --write_object=go-builder-data/$@) diff --git a/dashboard/buildlet/buildlet.go b/dashboard/buildlet/buildlet.go index bc356048ec6..3991c48ef14 100644 --- a/dashboard/buildlet/buildlet.go +++ b/dashboard/buildlet/buildlet.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build buildlet + // The buildlet is an HTTP server that untars content to disk and runs // commands it has untarred, streaming their output back over HTTP. // It is part of Go's continuous build system. @@ -29,7 +31,6 @@ import ( "io" "io/ioutil" "log" - "net" "net/http" "os" "os/exec" @@ -37,7 +38,8 @@ import ( "runtime" "strings" "sync" - "time" + + "google.golang.org/cloud/compute/metadata" ) var ( @@ -46,7 +48,7 @@ var ( ) func defaultListenAddr() string { - if OnGCE() { + if metadata.OnGCE() { // In production, default to return ":80" } @@ -55,7 +57,7 @@ func defaultListenAddr() string { func main() { flag.Parse() - if !OnGCE() && !strings.HasPrefix(*listenAddr, "localhost:") { + if !metadata.OnGCE() && !strings.HasPrefix(*listenAddr, "localhost:") { log.Printf("** WARNING *** This server is unsafe and offers no security. Be careful.") } if *scratchDir == "" { @@ -239,37 +241,3 @@ func (he httpError) httpStatus() int { return he.statusCode } func badRequest(msg string) error { return httpError{http.StatusBadRequest, msg} } - -// metaClient to fetch GCE metadata values. -var metaClient = &http.Client{ - Transport: &http.Transport{ - Dial: (&net.Dialer{ - Timeout: 750 * time.Millisecond, - KeepAlive: 30 * time.Second, - }).Dial, - ResponseHeaderTimeout: 750 * time.Millisecond, - }, -} - -var onGCE struct { - sync.Mutex - set bool - v bool -} - -// OnGCE reports whether this process is running on Google Compute Engine. -func OnGCE() bool { - defer onGCE.Unlock() - onGCE.Lock() - if onGCE.set { - return onGCE.v - } - onGCE.set = true - - res, err := metaClient.Get("http://metadata.google.internal") - if err != nil { - return false - } - onGCE.v = res.Header.Get("Metadata-Flavor") == "Google" - return onGCE.v -} diff --git a/dashboard/buildlet/stage0/Makefile b/dashboard/buildlet/stage0/Makefile new file mode 100644 index 00000000000..99abc63aeb6 --- /dev/null +++ b/dashboard/buildlet/stage0/Makefile @@ -0,0 +1,3 @@ +buildlet-stage0.windows-amd64: stage0.go + GOOS=windows GOARCH=amd64 go build -o $@ --tags=stage0 + cat $@ | (cd ../../coordinator/buildongce && go run create.go --write_object=go-builder-data/$@) diff --git a/dashboard/buildlet/stage0/stage0.go b/dashboard/buildlet/stage0/stage0.go new file mode 100644 index 00000000000..609c5c8e323 --- /dev/null +++ b/dashboard/buildlet/stage0/stage0.go @@ -0,0 +1,78 @@ +// 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. + +// +build stage0 + +// The stage0 command looks up the buildlet's URL from the GCE metadata +// service, downloads it, and runs it. It's used primarily by Windows, +// since it can be written in a couple lines of shell elsewhere. +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "time" + + "google.golang.org/cloud/compute/metadata" +) + +const attr = "buildlet-binary-url" + +func main() { + buildletURL, err := metadata.InstanceAttributeValue(attr) + if err != nil { + sleepFatalf("Failed to look up %q attribute value: %v", attr, err) + } + target := filepath.FromSlash("./buildlet.exe") + if err := download(target, buildletURL); err != nil { + sleepFatalf("Downloading %s: %v", buildletURL, 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) + } +} + +func sleepFatalf(format string, args ...interface{}) { + log.Printf(format, args...) + 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) + res, err := http.Get(url) + if err != nil { + return fmt.Errorf("Error fetching %v: %v", url, err) + } + 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 +}