aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@golang.org>2015-01-16 12:59:14 -0800
committerBrad Fitzpatrick <bradfitz@golang.org>2015-01-20 03:05:44 +0000
commit3aad931e88ab402274a53e272899de64ca74eb63 (patch)
tree0cb15a6e3b26ea21fa14c05579a9f1f25d26b2f2
parent3ecc311976cc3f7c7b7a50314929bdc1b07c4c9d (diff)
downloadtools-3aad931e88ab402274a53e272899de64ca74eb63.tar.gz
dashboard: start of cmd/gomote buildlet client, more packification
Change-Id: I874f4f5ef253cf7f1d6d5073d7c81e76fa1de863 Reviewed-on: https://go-review.googlesource.com/2981 Reviewed-by: Andrew Gerrand <adg@golang.org>
-rw-r--r--dashboard/auth/auth.go49
-rw-r--r--dashboard/buildlet/buildletclient.go74
-rw-r--r--dashboard/buildlet/gce.go145
-rw-r--r--dashboard/buildlet/keypair.go132
-rw-r--r--dashboard/cmd/buildlet/Makefile12
-rw-r--r--dashboard/cmd/buildlet/buildlet.go65
-rw-r--r--dashboard/cmd/buildlet/stage0/Makefile2
-rw-r--r--dashboard/cmd/coordinator/Makefile2
-rw-r--r--dashboard/cmd/coordinator/coordinator.go2
-rw-r--r--dashboard/cmd/gomote/auth.go87
-rw-r--r--dashboard/cmd/gomote/create.go67
-rw-r--r--dashboard/cmd/gomote/destroy.go42
-rw-r--r--dashboard/cmd/gomote/gomote.go105
-rw-r--r--dashboard/cmd/gomote/list.go57
-rw-r--r--dashboard/cmd/gomote/put.go83
-rw-r--r--dashboard/cmd/gomote/run.go42
-rw-r--r--dashboard/cmd/upload/upload.go21
-rw-r--r--dashboard/env/commit-watcher/Makefile2
-rw-r--r--dashboard/env/linux-x86-base/Makefile2
-rw-r--r--dashboard/env/linux-x86-clang/Makefile2
-rw-r--r--dashboard/env/linux-x86-gccgo/Makefile2
-rw-r--r--dashboard/env/linux-x86-nacl/Makefile2
-rw-r--r--dashboard/env/linux-x86-sid/Makefile2
23 files changed, 904 insertions, 95 deletions
diff --git a/dashboard/auth/auth.go b/dashboard/auth/auth.go
new file mode 100644
index 0000000..35fa71d
--- /dev/null
+++ b/dashboard/auth/auth.go
@@ -0,0 +1,49 @@
+// 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 extdep
+
+// Package auth contains shared code related to OAuth2 and obtaining
+// tokens for a project.
+package auth
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+)
+
+func homedir() string {
+ if runtime.GOOS == "windows" {
+ return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
+ }
+ return os.Getenv("HOME")
+}
+
+// ProjectTokenSource returns an OAuth2 TokenSource for the given Google Project ID.
+func ProjectTokenSource(proj string, scopes ...string) (oauth2.TokenSource, error) {
+ // TODO(bradfitz): try different strategies too, like
+ // three-legged flow if the service account doesn't exist, and
+ // then cache the token file on disk somewhere. Or maybe that should be an
+ // option, for environments without stdin/stdout available to the user.
+ // We'll figure it out as needed.
+ fileName := filepath.Join(homedir(), "keys", proj+".key.json")
+ jsonConf, err := ioutil.ReadFile(fileName)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, fmt.Errorf("Missing JSON key configuration. Download the Service Account JSON key from https://console.developers.google.com/project/%s/apiui/credential and place it at %s", proj, fileName)
+ }
+ return nil, err
+ }
+ conf, err := google.JWTConfigFromJSON(jsonConf, scopes...)
+ if err != nil {
+ return nil, fmt.Errorf("reading JSON config from %s: %v", fileName, err)
+ }
+ return conf.TokenSource(oauth2.NoContext), nil
+}
diff --git a/dashboard/buildlet/buildletclient.go b/dashboard/buildlet/buildletclient.go
index 3ab091f..d9ecbd1 100644
--- a/dashboard/buildlet/buildletclient.go
+++ b/dashboard/buildlet/buildletclient.go
@@ -18,42 +18,44 @@ import (
"strings"
)
-// KeyPair is the TLS public certificate PEM file and its associated
-// private key PEM file that a builder will use for its HTTPS
-// server. The zero value means no HTTPs, which is used by the
-// coordinator for machines running within a firewall.
-type KeyPair struct {
- CertPEM string
- KeyPEM string
-}
-
-// NoKeyPair is used by the coordinator to speak http directly to buildlets,
-// inside their firewall, without TLS.
-var NoKeyPair = KeyPair{}
-
// NewClient returns a *Client that will manipulate ipPort,
// authenticated using the provided keypair.
//
// This constructor returns immediately without testing the host or auth.
-func NewClient(ipPort string, tls KeyPair) *Client {
+func NewClient(ipPort string, kp KeyPair) *Client {
return &Client{
- ipPort: ipPort,
- tls: tls,
+ ipPort: ipPort,
+ tls: kp,
+ password: kp.Password(),
+ httpClient: &http.Client{
+ Transport: &http.Transport{
+ DialTLS: kp.tlsDialer(),
+ },
+ },
}
}
// A Client interacts with a single buildlet.
type Client struct {
- ipPort string
- tls KeyPair
+ ipPort string
+ tls KeyPair
+ password string // basic auth password or empty for none
+ httpClient *http.Client
}
// URL returns the buildlet's URL prefix, without a trailing slash.
func (c *Client) URL() string {
- if c.tls != NoKeyPair {
- return "http://" + strings.TrimSuffix(c.ipPort, ":80")
+ if !c.tls.IsZero() {
+ return "https://" + strings.TrimSuffix(c.ipPort, ":443")
+ }
+ return "http://" + strings.TrimSuffix(c.ipPort, ":80")
+}
+
+func (c *Client) do(req *http.Request) (*http.Response, error) {
+ if c.password != "" {
+ req.SetBasicAuth("gomote", c.password)
}
- return "https://" + strings.TrimSuffix(c.ipPort, ":443")
+ return c.httpClient.Do(req)
}
// PutTarball writes files to the remote buildlet.
@@ -63,7 +65,7 @@ func (c *Client) PutTarball(r io.Reader) error {
if err != nil {
return err
}
- res, err := http.DefaultClient.Do(req)
+ res, err := c.do(req)
if err != nil {
return err
}
@@ -95,7 +97,15 @@ type ExecOpts struct {
// seen to completition. If execErr is non-nil, the remoteErr is
// meaningless.
func (c *Client) Exec(cmd string, opts ExecOpts) (remoteErr, execErr error) {
- res, err := http.PostForm(c.URL()+"/exec", url.Values{"cmd": {cmd}})
+ form := url.Values{
+ "cmd": {cmd},
+ }
+ req, err := http.NewRequest("POST", c.URL()+"/exec", strings.NewReader(form.Encode()))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ res, err := c.do(req)
if err != nil {
return nil, err
}
@@ -130,6 +140,24 @@ func (c *Client) Exec(cmd string, opts ExecOpts) (remoteErr, execErr error) {
return nil, nil
}
+// Destroy shuts down the buildlet, destroying all state immediately.
+func (c *Client) Destroy() error {
+ req, err := http.NewRequest("POST", c.URL()+"/halt", nil)
+ if err != nil {
+ return err
+ }
+ res, err := c.do(req)
+ if err != nil {
+ return err
+ }
+ defer res.Body.Close()
+ if res.StatusCode != http.StatusOK {
+ slurp, _ := ioutil.ReadAll(io.LimitReader(res.Body, 4<<10))
+ return fmt.Errorf("buildlet: HTTP status %v: %s", res.Status, slurp)
+ }
+ return nil
+}
+
func condRun(fn func()) {
if fn != nil {
fn()
diff --git a/dashboard/buildlet/gce.go b/dashboard/buildlet/gce.go
index 3e7366f..3bd1d92 100644
--- a/dashboard/buildlet/gce.go
+++ b/dashboard/buildlet/gce.go
@@ -38,6 +38,7 @@ type VMOpts struct {
Meta map[string]string
// DeleteIn optionally specifies a duration at which
+
// to delete the VM.
DeleteIn time.Duration
@@ -102,18 +103,7 @@ func StartNewVM(ts oauth2.TokenSource, instName, builderType string, opts VMOpts
// The https-server is authenticated, though.
Items: []string{"https-server"},
},
- Metadata: &compute.Metadata{
- Items: []*compute.MetadataItems{
- // The buildlet-binary-url is the URL of the buildlet binary
- // which the VMs are configured to download at boot and run.
- // This lets us/ update the buildlet more easily than
- // rebuilding the whole VM image.
- {
- Key: "buildlet-binary-url",
- Value: "http://storage.googleapis.com/go-builder-data/buildlet." + conf.GOOS() + "-" + conf.GOARCH(),
- },
- },
- },
+ Metadata: &compute.Metadata{},
NetworkInterfaces: []*compute.NetworkInterface{
&compute.NetworkInterface{
AccessConfigs: []*compute.AccessConfig{
@@ -126,6 +116,24 @@ func StartNewVM(ts oauth2.TokenSource, instName, builderType string, opts VMOpts
},
},
}
+ addMeta := func(key, value string) {
+ instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{
+ Key: key,
+ Value: value,
+ })
+ }
+ // The buildlet-binary-url is the URL of the buildlet binary
+ // which the VMs are configured to download at boot and run.
+ // This lets us/ update the buildlet more easily than
+ // rebuilding the whole VM image.
+ addMeta("buildlet-binary-url",
+ "http://storage.googleapis.com/go-builder-data/buildlet."+conf.GOOS()+"-"+conf.GOARCH())
+ addMeta("builder-type", builderType)
+ if !opts.TLS.IsZero() {
+ addMeta("tls-cert", opts.TLS.CertPEM)
+ addMeta("tls-key", opts.TLS.KeyPEM)
+ addMeta("password", opts.TLS.Password())
+ }
if opts.DeleteIn != 0 {
// In case the VM gets away from us (generally: if the
@@ -134,16 +142,11 @@ func StartNewVM(ts oauth2.TokenSource, instName, builderType string, opts VMOpts
// we can kill it later when the coordinator is
// restarted. The cleanUpOldVMs goroutine loop handles
// that killing.
- instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{
- Key: "delete-at",
- Value: fmt.Sprint(time.Now().Add(opts.DeleteIn).Unix()),
- })
+ addMeta("delete-at", fmt.Sprint(time.Now().Add(opts.DeleteIn).Unix()))
}
+
for k, v := range opts.Meta {
- instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{
- Key: k,
- Value: v,
- })
+ addMeta(k, v)
}
op, err := computeService.Instances.Insert(projectID, zone, instance).Do()
@@ -183,26 +186,24 @@ OpLoop:
return nil, fmt.Errorf("Error getting instance %s details after creation: %v", instName, err)
}
- // Find its internal IP.
- var ip string
- for _, iface := range inst.NetworkInterfaces {
- if strings.HasPrefix(iface.NetworkIP, "10.") {
- ip = iface.NetworkIP
- }
- }
- if ip == "" {
- return nil, errors.New("didn't find its internal IP address")
- }
+ // Finds its internal and/or external IP addresses.
+ intIP, extIP := instanceIPs(inst)
// Wait for it to boot and its buildlet to come up.
var buildletURL string
var ipPort string
- if opts.TLS != NoKeyPair {
- buildletURL = "https://" + ip
- ipPort = ip + ":443"
+ if !opts.TLS.IsZero() {
+ if extIP == "" {
+ return nil, errors.New("didn't find its external IP address")
+ }
+ buildletURL = "https://" + extIP
+ ipPort = extIP + ":443"
} else {
- buildletURL = "http://" + ip
- ipPort = ip + ":80"
+ if intIP == "" {
+ return nil, errors.New("didn't find its internal IP address")
+ }
+ buildletURL = "http://" + intIP
+ ipPort = intIP + ":80"
}
condRun(opts.OnGotInstanceInfo)
@@ -238,3 +239,77 @@ OpLoop:
return NewClient(ipPort, opts.TLS), nil
}
+
+// DestroyVM sends a request to delete a VM. Actual VM description is
+// currently (2015-01-19) very slow for no good reason. This function
+// returns once it's been requested, not when it's done.
+func DestroyVM(ts oauth2.TokenSource, proj, zone, instance string) error {
+ computeService, _ := compute.New(oauth2.NewClient(oauth2.NoContext, ts))
+ _, err := computeService.Instances.Delete(proj, zone, instance).Do()
+ return err
+}
+
+type VM struct {
+ // Name is the name of the GCE VM instance.
+ // For example, it's of the form "mote-bradfitz-plan9-386-foo",
+ // and not "plan9-386-foo".
+ Name string
+ IPPort string
+ TLS KeyPair
+ Type string
+}
+
+// ListVMs lists all VMs.
+func ListVMs(ts oauth2.TokenSource, proj, zone string) ([]VM, error) {
+ var vms []VM
+ computeService, _ := compute.New(oauth2.NewClient(oauth2.NoContext, ts))
+
+ // TODO(bradfitz): paging over results if more than 500
+ list, err := computeService.Instances.List(proj, zone).Do()
+ if err != nil {
+ return nil, err
+ }
+ for _, inst := range list.Items {
+ if inst.Metadata == nil {
+ // Defensive. Not seen in practice.
+ continue
+ }
+ meta := map[string]string{}
+ for _, it := range inst.Metadata.Items {
+ meta[it.Key] = it.Value
+ }
+ builderType := meta["builder-type"]
+ if builderType == "" {
+ continue
+ }
+ vm := VM{
+ Name: inst.Name,
+ Type: builderType,
+ TLS: KeyPair{
+ CertPEM: meta["tls-cert"],
+ KeyPEM: meta["tls-key"],
+ },
+ }
+ _, extIP := instanceIPs(inst)
+ if extIP == "" || vm.TLS.IsZero() {
+ continue
+ }
+ vm.IPPort = extIP + ":443"
+ vms = append(vms, vm)
+ }
+ return vms, nil
+}
+
+func instanceIPs(inst *compute.Instance) (intIP, extIP string) {
+ for _, iface := range inst.NetworkInterfaces {
+ if strings.HasPrefix(iface.NetworkIP, "10.") {
+ intIP = iface.NetworkIP
+ }
+ for _, accessConfig := range iface.AccessConfigs {
+ if accessConfig.Type == "ONE_TO_ONE_NAT" {
+ extIP = accessConfig.NatIP
+ }
+ }
+ }
+ return
+}
diff --git a/dashboard/buildlet/keypair.go b/dashboard/buildlet/keypair.go
new file mode 100644
index 0000000..3dc9439
--- /dev/null
+++ b/dashboard/buildlet/keypair.go
@@ -0,0 +1,132 @@
+// 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 extdep
+
+package buildlet
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha1"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "math/big"
+ "net"
+ "time"
+)
+
+// KeyPair is the TLS public certificate PEM file and its associated
+// private key PEM file that a builder will use for its HTTPS
+// server. The zero value means no HTTPs, which is used by the
+// coordinator for machines running within a firewall.
+type KeyPair struct {
+ CertPEM string
+ KeyPEM string
+}
+
+func (kp KeyPair) IsZero() bool { return kp == KeyPair{} }
+
+// Password returns the SHA1 of the KeyPEM. This is used as the HTTP
+// Basic Auth password.
+func (kp KeyPair) Password() string {
+ if kp.KeyPEM != "" {
+ return fmt.Sprintf("%x", sha1.Sum([]byte(kp.KeyPEM)))
+ }
+ return ""
+}
+
+// tlsDialer returns a TLS dialer for http.Transport.DialTLS that expects
+// exactly our TLS cert.
+func (kp KeyPair) tlsDialer() func(network, addr string) (net.Conn, error) {
+ if kp.IsZero() {
+ // Unused.
+ return nil
+ }
+ wantCert, _ := tls.X509KeyPair([]byte(kp.CertPEM), []byte(kp.KeyPEM))
+ var wantPubKey *rsa.PublicKey = &wantCert.PrivateKey.(*rsa.PrivateKey).PublicKey
+
+ return func(network, addr string) (net.Conn, error) {
+ if network != "tcp" {
+ return nil, fmt.Errorf("unexpected network %q", network)
+ }
+ plainConn, err := net.Dial("tcp", addr)
+ if err != nil {
+ return nil, err
+ }
+ tlsConn := tls.Client(plainConn, &tls.Config{InsecureSkipVerify: true})
+ if err := tlsConn.Handshake(); err != nil {
+ return nil, err
+ }
+ certs := tlsConn.ConnectionState().PeerCertificates
+ if len(certs) < 1 {
+ return nil, errors.New("no server peer certificate")
+ }
+ cert := certs[0]
+ peerPubRSA, ok := cert.PublicKey.(*rsa.PublicKey)
+ if !ok {
+ return nil, fmt.Errorf("peer cert was a %T; expected RSA", cert.PublicKey)
+ }
+ if peerPubRSA.N.Cmp(wantPubKey.N) != 0 {
+ return nil, fmt.Errorf("unexpected TLS certificate")
+ }
+ return tlsConn, nil
+ }
+}
+
+// NoKeyPair is used by the coordinator to speak http directly to buildlets,
+// inside their firewall, without TLS.
+var NoKeyPair = KeyPair{}
+
+func NewKeyPair() (KeyPair, error) {
+ fail := func(err error) (KeyPair, error) { return KeyPair{}, err }
+ failf := func(format string, args ...interface{}) (KeyPair, error) { return fail(fmt.Errorf(format, args...)) }
+
+ priv, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return failf("rsa.GenerateKey: %s", err)
+ }
+
+ notBefore := time.Now()
+ notAfter := notBefore.Add(5 * 365 * 24 * time.Hour) // 5 years
+
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ return failf("failed to generate serial number: %s", err)
+ }
+
+ template := x509.Certificate{
+ SerialNumber: serialNumber,
+ Subject: pkix.Name{
+ Organization: []string{"Gopher Co"},
+ },
+ NotBefore: notBefore,
+ NotAfter: notAfter,
+
+ KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ BasicConstraintsValid: true,
+ DNSNames: []string{"localhost"},
+ }
+
+ derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
+ if err != nil {
+ return failf("Failed to create certificate: %s", err)
+ }
+
+ var certOut bytes.Buffer
+ pem.Encode(&certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+ var keyOut bytes.Buffer
+ pem.Encode(&keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
+ return KeyPair{
+ CertPEM: certOut.String(),
+ KeyPEM: keyOut.String(),
+ }, nil
+}
diff --git a/dashboard/cmd/buildlet/Makefile b/dashboard/cmd/buildlet/Makefile
index 833a17c..078a4e9 100644
--- a/dashboard/cmd/buildlet/Makefile
+++ b/dashboard/cmd/buildlet/Makefile
@@ -3,24 +3,24 @@ buildlet: buildlet.go
buildlet.linux-amd64: buildlet.go
GOOS=linux GOARCH=amd64 go build -o $@ --tags=extdep
- cat $@ | (cd ../upload && go run upload.go --public go-builder-data/$@)
+ cat $@ | (cd ../upload && go run --tags=extdep upload.go --public go-builder-data/$@)
buildlet.openbsd-amd64: buildlet.go
GOOS=openbsd GOARCH=amd64 go build -o $@ --tags=extdep
- cat $@ | (cd ../upload && go run upload.go --public go-builder-data/$@)
+ cat $@ | (cd ../upload && go run --tags=extdep upload.go --public go-builder-data/$@)
buildlet.plan9-386: buildlet.go
GOOS=plan9 GOARCH=386 go build -o $@ --tags=extdep
- cat $@ | (cd ../upload && go run upload.go --public go-builder-data/$@)
+ cat $@ | (cd ../upload && go run --tags=extdep upload.go --public go-builder-data/$@)
buildlet.windows-amd64: buildlet.go
GOOS=windows GOARCH=amd64 go build -o $@ --tags=extdep
- cat $@ | (cd ../upload && go run upload.go --public go-builder-data/$@)
+ cat $@ | (cd ../upload && go run --tags=extdep upload.go --public go-builder-data/$@)
buildlet.darwin-amd64: buildlet.go
GOOS=darwin GOARCH=amd64 go build -o $@ --tags=extdep
- cat $@ | (cd ../upload && go run upload.go --public go-builder-data/$@)
+ cat $@ | (cd ../upload && go run --tags=extdep upload.go --public go-builder-data/$@)
buildlet.netbsd-amd64: buildlet.go
GOOS=netbsd GOARCH=amd64 go build -o $@ --tags=extdep
- cat $@ | (cd ../upload && go run upload.go --public go-builder-data/$@)
+ cat $@ | (cd ../upload && go run --tags=extdep upload.go --public go-builder-data/$@)
diff --git a/dashboard/cmd/buildlet/buildlet.go b/dashboard/cmd/buildlet/buildlet.go
index 29c60ee..63ec7f4 100644
--- a/dashboard/cmd/buildlet/buildlet.go
+++ b/dashboard/cmd/buildlet/buildlet.go
@@ -18,6 +18,7 @@ import (
"archive/tar"
"compress/gzip"
"crypto/tls"
+ "errors"
"flag"
"fmt"
"io"
@@ -37,8 +38,9 @@ import (
)
var (
- scratchDir = flag.String("scratchdir", "", "Temporary directory to use. The contents of this directory may be deleted at any time. If empty, TempDir is used to create one.")
- listenAddr = flag.String("listen", defaultListenAddr(), "address to listen on. Warning: this service is inherently insecure and offers no protection of its own. Do not expose this port to the world.")
+ haltEntireOS = flag.Bool("halt", true, "halt OS in /halt handler. If false, the buildlet process just ends.")
+ scratchDir = flag.String("scratchdir", "", "Temporary directory to use. The contents of this directory may be deleted at any time. If empty, TempDir is used to create one.")
+ listenAddr = flag.String("listen", defaultListenAddr(), "address to listen on. Warning: this service is inherently insecure and offers no protection of its own. Do not expose this port to the world.")
)
func defaultListenAddr() string {
@@ -59,6 +61,8 @@ func defaultListenAddr() string {
return ":80"
}
+var osHalt func() // set by some machines
+
func main() {
flag.Parse()
if !metadata.OnGCE() && !strings.HasPrefix(*listenAddr, "localhost:") {
@@ -87,8 +91,12 @@ func main() {
http.HandleFunc("/", handleRoot)
password := metadataValue("password")
- http.Handle("/writetgz", requirePassword{http.HandlerFunc(handleWriteTGZ), password})
- http.Handle("/exec", requirePassword{http.HandlerFunc(handleExec), password})
+ requireAuth := func(handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
+ return requirePasswordHandler{http.HandlerFunc(handler), password}
+ }
+ http.Handle("/writetgz", requireAuth(handleWriteTGZ))
+ http.Handle("/exec", requireAuth(handleExec))
+ http.Handle("/halt", requireAuth(handleHalt))
// TODO: removeall
tlsCert, tlsKey := metadataValue("tls-cert"), metadataValue("tls-key")
@@ -293,6 +301,51 @@ func handleExec(w http.ResponseWriter, r *http.Request) {
log.Printf("Run = %s", state)
}
+func handleHalt(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "POST" {
+ http.Error(w, "requires POST method", http.StatusBadRequest)
+ return
+ }
+ log.Printf("Halting in 1 second.")
+ // do the halt in 1 second, to give the HTTP response time to complete:
+ time.AfterFunc(1*time.Second, haltMachine)
+}
+
+func haltMachine() {
+ if !*haltEntireOS {
+ log.Printf("Ending buildlet process due to halt.")
+ os.Exit(0)
+ return
+ }
+ log.Printf("Halting machine.")
+ time.AfterFunc(5*time.Second, func() { os.Exit(0) })
+ if osHalt != nil {
+ // TODO: Windows: http://msdn.microsoft.com/en-us/library/windows/desktop/aa376868%28v=vs.85%29.aspx
+ osHalt()
+ os.Exit(0)
+ }
+ // Backup mechanism, if exec hangs for any reason:
+ var err error
+ switch runtime.GOOS {
+ case "openbsd":
+ // Quick, no fs flush, and power down:
+ err = exec.Command("halt", "-q", "-n", "-p").Run()
+ case "freebsd":
+ // Power off (-p), via halt (-o), now.
+ err = exec.Command("shutdown", "-p", "-o", "now").Run()
+ case "linux":
+ // Don't sync (-n), force without shutdown (-f), and power off (-p).
+ err = exec.Command("/bin/halt", "-n", "-f", "-p").Run()
+ case "plan9":
+ err = exec.Command("fshalt").Run()
+ default:
+ err = errors.New("No system-specific halt command run; will just end buildlet process.")
+ }
+ log.Printf("Shutdown: %v", err)
+ log.Printf("Ending buildlet process post-halt")
+ os.Exit(0)
+}
+
// flushWriter is an io.Writer wrapper that writes to w and
// Flushes the output immediately, if w is an http.Flusher.
type flushWriter struct {
@@ -336,12 +389,12 @@ func badRequest(msg string) error {
// requirePassword is an http.Handler auth wrapper that enforces a
// HTTP Basic password. The username is ignored.
-type requirePassword struct {
+type requirePasswordHandler struct {
h http.Handler
password string // empty means no password
}
-func (h requirePassword) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+func (h requirePasswordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_, gotPass, _ := r.BasicAuth()
if h.password != "" && h.password != gotPass {
http.Error(w, "invalid password", http.StatusForbidden)
diff --git a/dashboard/cmd/buildlet/stage0/Makefile b/dashboard/cmd/buildlet/stage0/Makefile
index b69cacf..bf66be3 100644
--- a/dashboard/cmd/buildlet/stage0/Makefile
+++ b/dashboard/cmd/buildlet/stage0/Makefile
@@ -1,3 +1,3 @@
buildlet-stage0.windows-amd64: stage0.go
GOOS=windows GOARCH=amd64 go build -o $@ --tags=extdep
- cat $@ | (cd ../../upload && go run upload.go --public go-builder-data/$@)
+ cat $@ | (cd ../../upload && go run --tags=extdep upload.go --public go-builder-data/$@)
diff --git a/dashboard/cmd/coordinator/Makefile b/dashboard/cmd/coordinator/Makefile
index d004fa8..56887c0 100644
--- a/dashboard/cmd/coordinator/Makefile
+++ b/dashboard/cmd/coordinator/Makefile
@@ -6,4 +6,4 @@ coordinator: coordinator.go
# And watch its logs with:
# sudo journalctl -f -u gobuild.service
upload: coordinator
- cat coordinator | (cd ../upload && go run upload.go --public go-builder-data/coordinator)
+ cat coordinator | (cd ../upload && go run --tags=extdep upload.go --public go-builder-data/coordinator)
diff --git a/dashboard/cmd/coordinator/coordinator.go b/dashboard/cmd/coordinator/coordinator.go
index e21f7f7..900b848 100644
--- a/dashboard/cmd/coordinator/coordinator.go
+++ b/dashboard/cmd/coordinator/coordinator.go
@@ -1113,7 +1113,7 @@ func hasComputeScope() bool {
return false
}
for _, v := range scopes {
- if v == compute.DevstorageFull_controlScope {
+ if v == compute.ComputeScope {
return true
}
}
diff --git a/dashboard/cmd/gomote/auth.go b/dashboard/cmd/gomote/auth.go
new file mode 100644
index 0000000..56ca0d4
--- /dev/null
+++ b/dashboard/cmd/gomote/auth.go
@@ -0,0 +1,87 @@
+// 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 extdep
+
+package main
+
+import (
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "runtime"
+
+ "golang.org/x/oauth2"
+ "golang.org/x/tools/dashboard/auth"
+ "golang.org/x/tools/dashboard/buildlet"
+ "google.golang.org/api/compute/v1"
+)
+
+func username() string {
+ if runtime.GOOS == "windows" {
+ return os.Getenv("USERNAME")
+ }
+ return os.Getenv("USER")
+}
+
+func homeDir() string {
+ if runtime.GOOS == "windows" {
+ return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
+ }
+ return os.Getenv("HOME")
+}
+
+func configDir() string {
+ if runtime.GOOS == "windows" {
+ return filepath.Join(os.Getenv("APPDATA"), "Gomote")
+ }
+ if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" {
+ return filepath.Join(xdg, "gomote")
+ }
+ return filepath.Join(homeDir(), ".config", "gomote")
+}
+
+func projTokenSource() oauth2.TokenSource {
+ ts, err := auth.ProjectTokenSource(*proj, compute.ComputeScope)
+ if err != nil {
+ log.Fatalf("Failed to get OAuth2 token source for project %s: %v", *proj, err)
+ }
+ return ts
+}
+
+func userKeyPair() buildlet.KeyPair {
+ keyDir := configDir()
+ crtFile := filepath.Join(keyDir, "gomote.crt")
+ keyFile := filepath.Join(keyDir, "gomote.key")
+ _, crtErr := os.Stat(crtFile)
+ _, keyErr := os.Stat(keyFile)
+ if crtErr == nil && keyErr == nil {
+ return buildlet.KeyPair{
+ CertPEM: slurpString(crtFile),
+ KeyPEM: slurpString(keyFile),
+ }
+ }
+ check := func(what string, err error) {
+ if err != nil {
+ log.Printf("%s: %v", what, err)
+ }
+ }
+ check("making key dir", os.MkdirAll(keyDir, 0700))
+ kp, err := buildlet.NewKeyPair()
+ if err != nil {
+ log.Fatalf("Error generating new key pair: %v", err)
+ }
+ check("writing cert file: ", ioutil.WriteFile(crtFile, []byte(kp.CertPEM), 0600))
+ check("writing key file: ", ioutil.WriteFile(keyFile, []byte(kp.KeyPEM), 0600))
+ return kp
+}
+
+func slurpString(f string) string {
+ slurp, err := ioutil.ReadFile(f)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return string(slurp)
+}
diff --git a/dashboard/cmd/gomote/create.go b/dashboard/cmd/gomote/create.go
new file mode 100644
index 0000000..bb25a8e
--- /dev/null
+++ b/dashboard/cmd/gomote/create.go
@@ -0,0 +1,67 @@
+// 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 extdep
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "sort"
+ "time"
+
+ "golang.org/x/tools/dashboard"
+ "golang.org/x/tools/dashboard/buildlet"
+)
+
+func create(args []string) error {
+ fs := flag.NewFlagSet("create", flag.ContinueOnError)
+ fs.Usage = func() {
+ fmt.Fprintln(os.Stderr, "create usage: gomote create [create-opts] <type>\n\n")
+ fs.PrintDefaults()
+ os.Exit(1)
+ }
+ var timeout time.Duration
+ fs.DurationVar(&timeout, "timeout", 60*time.Minute, "how long the VM will live before being deleted.")
+
+ fs.Parse(args)
+ if fs.NArg() != 1 {
+ fs.Usage()
+ }
+ builderType := fs.Arg(0)
+ conf, ok := dashboard.Builders[builderType]
+ if !ok || !conf.UsesVM() {
+ var valid []string
+ for k, conf := range dashboard.Builders {
+ if conf.UsesVM() {
+ valid = append(valid, k)
+ }
+ }
+ sort.Strings(valid)
+ return fmt.Errorf("Invalid builder type %q. Valid options include: %q", builderType, valid)
+ }
+
+ instName := fmt.Sprintf("mote-%s-%s", username(), builderType)
+ client, err := buildlet.StartNewVM(projTokenSource(), instName, builderType, buildlet.VMOpts{
+ Zone: *zone,
+ ProjectID: *proj,
+ TLS: userKeyPair(),
+ DeleteIn: timeout,
+ Description: fmt.Sprintf("gomote buildlet for %s", username()),
+ OnInstanceRequested: func() {
+ log.Printf("Sent create request. Waiting for operation.")
+ },
+ OnInstanceCreated: func() {
+ log.Printf("Instance created.")
+ },
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create VM: %v", err)
+ }
+ fmt.Printf("%s\t%s\n", builderType, client.URL())
+ return nil
+}
diff --git a/dashboard/cmd/gomote/destroy.go b/dashboard/cmd/gomote/destroy.go
new file mode 100644
index 0000000..ca9b4de
--- /dev/null
+++ b/dashboard/cmd/gomote/destroy.go
@@ -0,0 +1,42 @@
+// 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 extdep
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+
+ "golang.org/x/tools/dashboard/buildlet"
+)
+
+func destroy(args []string) error {
+ fs := flag.NewFlagSet("destroy", flag.ContinueOnError)
+ fs.Usage = func() {
+ fmt.Fprintln(os.Stderr, "create usage: gomote destroy <instance>\n\n")
+ fs.PrintDefaults()
+ os.Exit(1)
+ }
+
+ fs.Parse(args)
+ if fs.NArg() != 1 {
+ fs.Usage()
+ }
+ name := fs.Arg(0)
+ bc, err := namedClient(name)
+ if err != nil {
+ return err
+ }
+
+ // First ask it to kill itself, and then tell GCE to kill it too:
+ shutErr := bc.Destroy()
+ gceErr := buildlet.DestroyVM(projTokenSource(), *proj, *zone, fmt.Sprintf("mote-%s-%s", username(), name))
+ if shutErr != nil {
+ return shutErr
+ }
+ return gceErr
+}
diff --git a/dashboard/cmd/gomote/gomote.go b/dashboard/cmd/gomote/gomote.go
new file mode 100644
index 0000000..7ede494
--- /dev/null
+++ b/dashboard/cmd/gomote/gomote.go
@@ -0,0 +1,105 @@
+// 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 extdep
+
+/*
+The gomote command is a client for the Go builder infrastructure.
+It's a remote control for remote Go builder machines.
+
+Usage:
+
+ gomote [global-flags] cmd [cmd-flags]
+
+ For example,
+ $ gomote create openbsd-amd64-gce56
+ $ gomote push
+ $ gomote run openbsd-amd64-gce56 src/make.bash
+
+TODO: document more, and figure out the CLI interface more.
+*/
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "sort"
+)
+
+var (
+ proj = flag.String("project", "symbolic-datum-552", "GCE project owning builders")
+ zone = flag.String("zone", "us-central1-a", "GCE zone")
+)
+
+type command struct {
+ name string
+ des string
+ run func([]string) error
+}
+
+var commands = map[string]command{}
+
+func sortedCommands() []string {
+ s := make([]string, 0, len(commands))
+ for name := range commands {
+ s = append(s, name)
+ }
+ sort.Strings(s)
+ return s
+}
+
+func usage() {
+ fmt.Fprintf(os.Stderr, `Usage of gomote: gomote [global-flags] <cmd> [cmd-flags]
+
+Global flags:
+`)
+ flag.PrintDefaults()
+ fmt.Fprintf(os.Stderr, "Commands:\n\n")
+ for _, name := range sortedCommands() {
+ fmt.Fprintf(os.Stderr, " %-10s %s\n", name, commands[name].des)
+ }
+ os.Exit(1)
+}
+
+func registerCommand(name, des string, run func([]string) error) {
+ if _, dup := commands[name]; dup {
+ panic("duplicate registration of " + name)
+ }
+ commands[name] = command{
+ name: name,
+ des: des,
+ run: run,
+ }
+}
+
+func registerCommands() {
+ registerCommand("create", "create a buildlet", create)
+ registerCommand("destroy", "destroy a buildlet", destroy)
+ registerCommand("list", "list buildlets", list)
+ registerCommand("run", "run a command on a buildlet", run)
+ registerCommand("put", "put files on a buildlet", put)
+ registerCommand("puttar", "extract a tar.gz to a buildlet", putTar)
+}
+
+func main() {
+ registerCommands()
+ flag.Usage = usage
+ flag.Parse()
+ args := flag.Args()
+ if len(args) == 0 {
+ usage()
+ }
+ cmdName := args[0]
+ cmd, ok := commands[cmdName]
+ if !ok {
+ fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmdName)
+ usage()
+ }
+ err := cmd.run(args[1:])
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error running %s: %v\n", cmdName, err)
+ os.Exit(1)
+ }
+}
diff --git a/dashboard/cmd/gomote/list.go b/dashboard/cmd/gomote/list.go
new file mode 100644
index 0000000..cd3d1f8
--- /dev/null
+++ b/dashboard/cmd/gomote/list.go
@@ -0,0 +1,57 @@
+// 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 extdep
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+
+ "golang.org/x/tools/dashboard/buildlet"
+)
+
+func list(args []string) error {
+ fs := flag.NewFlagSet("list", flag.ContinueOnError)
+ fs.Usage = func() {
+ fmt.Fprintln(os.Stderr, "list usage: gomote list\n\n")
+ fs.PrintDefaults()
+ os.Exit(1)
+ }
+ fs.Parse(args)
+ if fs.NArg() != 0 {
+ fs.Usage()
+ }
+
+ prefix := fmt.Sprintf("mote-%s-", username())
+ vms, err := buildlet.ListVMs(projTokenSource(), *proj, *zone)
+ if err != nil {
+ return fmt.Errorf("failed to list VMs: %v", err)
+ }
+ for _, vm := range vms {
+ if !strings.HasPrefix(vm.Name, prefix) {
+ continue
+ }
+ fmt.Printf("%s\thttps://%s\n", vm.Type, strings.TrimSuffix(vm.IPPort, ":443"))
+ }
+ return nil
+}
+
+func namedClient(name string) (*buildlet.Client, error) {
+ // TODO(bradfitz): cache the list on disk and avoid the API call?
+ vms, err := buildlet.ListVMs(projTokenSource(), *proj, *zone)
+ if err != nil {
+ return nil, fmt.Errorf("error listing VMs while looking up %q: %v", name, err)
+ }
+ wantName := fmt.Sprintf("mote-%s-%s", username(), name)
+ for _, vm := range vms {
+ if vm.Name == wantName {
+ return buildlet.NewClient(vm.IPPort, vm.TLS), nil
+ }
+ }
+ return nil, fmt.Errorf("buildlet %q not running", name)
+}
diff --git a/dashboard/cmd/gomote/put.go b/dashboard/cmd/gomote/put.go
new file mode 100644
index 0000000..31595c2
--- /dev/null
+++ b/dashboard/cmd/gomote/put.go
@@ -0,0 +1,83 @@
+// 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 extdep
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+)
+
+// put a .tar.gz
+func putTar(args []string) error {
+ fs := flag.NewFlagSet("put", flag.ContinueOnError)
+ fs.Usage = func() {
+ fmt.Fprintln(os.Stderr, "create usage: gomote puttar [put-opts] <buildlet-name> [tar.gz file or '-' for stdin]\n")
+ fs.PrintDefaults()
+ os.Exit(1)
+ }
+ var rev string
+ fs.StringVar(&rev, "gorev", "", "If non-empty, git hash to download from gerrit and put to the buildlet. e.g. 886b02d705ff for Go 1.4.1")
+
+ fs.Parse(args)
+ if fs.NArg() < 1 || fs.NArg() > 2 {
+ fs.Usage()
+ }
+
+ name := fs.Arg(0)
+ bc, err := namedClient(name)
+ if err != nil {
+ return err
+ }
+
+ var tgz io.Reader = os.Stdin
+ if rev != "" {
+ if fs.NArg() != 1 {
+ fs.Usage()
+ }
+ // TODO(bradfitz): tell the buildlet to do this
+ // itself, to avoid network to & from home networks.
+ // Staying Google<->Google will be much faster.
+ res, err := http.Get("https://go.googlesource.com/go/+archive/" + rev + ".tar.gz")
+ if err != nil {
+ return fmt.Errorf("Error fetching rev %s from Gerrit: %v", rev, err)
+ }
+ defer res.Body.Close()
+ if res.StatusCode != 200 {
+ return fmt.Errorf("Error fetching rev %s from Gerrit: %v", rev, res.Status)
+ }
+ tgz = res.Body
+ } else if fs.NArg() == 2 && fs.Arg(1) != "-" {
+ f, err := os.Open(fs.Arg(1))
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ tgz = f
+ }
+ return bc.PutTarball(tgz)
+}
+
+// put single files
+func put(args []string) error {
+ fs := flag.NewFlagSet("put", flag.ContinueOnError)
+ fs.Usage = func() {
+ fmt.Fprintln(os.Stderr, "create usage: gomote put [put-opts] <type>\n\n")
+ fs.PrintDefaults()
+ os.Exit(1)
+ }
+ fs.Parse(args)
+ if fs.NArg() != 1 {
+ fs.Usage()
+ }
+ return fmt.Errorf("TODO")
+ builderType := fs.Arg(0)
+ _ = builderType
+ return nil
+}
diff --git a/dashboard/cmd/gomote/run.go b/dashboard/cmd/gomote/run.go
new file mode 100644
index 0000000..6be9346
--- /dev/null
+++ b/dashboard/cmd/gomote/run.go
@@ -0,0 +1,42 @@
+// 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 extdep
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+
+ "golang.org/x/tools/dashboard/buildlet"
+)
+
+func run(args []string) error {
+ fs := flag.NewFlagSet("run", flag.ContinueOnError)
+ fs.Usage = func() {
+ fmt.Fprintln(os.Stderr, "create usage: gomote run [run-opts] <buildlet-name> <cmd> [args...]")
+ fs.PrintDefaults()
+ os.Exit(1)
+ }
+
+ fs.Parse(args)
+ if fs.NArg() < 2 {
+ fs.Usage()
+ }
+ name, cmd := fs.Arg(0), fs.Arg(1)
+ bc, err := namedClient(name)
+ if err != nil {
+ return err
+ }
+
+ remoteErr, execErr := bc.Exec(cmd, buildlet.ExecOpts{
+ Output: os.Stdout,
+ })
+ if execErr != nil {
+ return fmt.Errorf("Error trying to execute %s: %v", cmd, execErr)
+ }
+ return remoteErr
+}
diff --git a/dashboard/cmd/upload/upload.go b/dashboard/cmd/upload/upload.go
index 880b879..087be49 100644
--- a/dashboard/cmd/upload/upload.go
+++ b/dashboard/cmd/upload/upload.go
@@ -15,15 +15,13 @@ import (
"flag"
"fmt"
"io"
- "io/ioutil"
"log"
"net/http"
"os"
- "path/filepath"
"strings"
"golang.org/x/oauth2"
- "golang.org/x/oauth2/google"
+ "golang.org/x/tools/dashboard/auth"
"google.golang.org/cloud"
"google.golang.org/cloud/storage"
)
@@ -111,18 +109,9 @@ var bucketProject = map[string]string{
}
func tokenSource(bucket string) (oauth2.TokenSource, error) {
- proj := bucketProject[bucket]
- fileName := filepath.Join(os.Getenv("HOME"), "keys", proj+".key.json")
- jsonConf, err := ioutil.ReadFile(fileName)
- if err != nil {
- if os.IsNotExist(err) {
- return nil, fmt.Errorf("Missing JSON key configuration. Download the Service Account JSON key from https://console.developers.google.com/project/%s/apiui/credential and place it at %s", proj, fileName)
- }
- return nil, err
- }
- conf, err := google.JWTConfigFromJSON(jsonConf, storage.ScopeReadWrite)
- if err != nil {
- return nil, fmt.Errorf("reading JSON config from %s: %v", fileName, err)
+ proj, ok := bucketProject[bucket]
+ if !ok {
+ return nil, fmt.Errorf("unknown project for bucket %q", bucket)
}
- return conf.TokenSource(oauth2.NoContext), nil
+ return auth.ProjectTokenSource(proj, storage.ScopeReadWrite)
}
diff --git a/dashboard/env/commit-watcher/Makefile b/dashboard/env/commit-watcher/Makefile
index d154c51..f844ecc 100644
--- a/dashboard/env/commit-watcher/Makefile
+++ b/dashboard/env/commit-watcher/Makefile
@@ -6,4 +6,4 @@ docker: Dockerfile
docker build -t go-commit-watcher .
docker-commit-watcher.tar.gz: docker
- docker save go-commit-watcher | gzip | (cd ../../cmd/upload && go run upload.go --public go-builder-data/docker-commit-watcher.tar.gz)
+ docker save go-commit-watcher | gzip | (cd ../../cmd/upload && go run --tags=extdep upload.go --public go-builder-data/docker-commit-watcher.tar.gz)
diff --git a/dashboard/env/linux-x86-base/Makefile b/dashboard/env/linux-x86-base/Makefile
index 981675c..ecfee99 100644
--- a/dashboard/env/linux-x86-base/Makefile
+++ b/dashboard/env/linux-x86-base/Makefile
@@ -6,7 +6,7 @@ docker: Dockerfile
docker build -t gobuilders/linux-x86-base .
docker-linux.base.tar.gz: docker
- docker save gobuilders/linux-x86-base | gzip | (cd ../../cmd/upload && go run upload.go --public go-builder-data/docker-linux.base.tar.gz)
+ docker save gobuilders/linux-x86-base | gzip | (cd ../../cmd/upload && go run --tags=extdep upload.go --public go-builder-data/docker-linux.base.tar.gz)
check: docker
docker run -e GOROOT_BOOTSTRAP=/go1.4-amd64/go gobuilders/linux-x86-base /usr/local/bin/builder -rev=20a10e7ddd1 -buildroot=/ -v -report=false linux-amd64-temp
diff --git a/dashboard/env/linux-x86-clang/Makefile b/dashboard/env/linux-x86-clang/Makefile
index 2c945bb..0ae0fd0 100644
--- a/dashboard/env/linux-x86-clang/Makefile
+++ b/dashboard/env/linux-x86-clang/Makefile
@@ -6,7 +6,7 @@ docker: Dockerfile
docker build -t gobuilders/linux-x86-clang .
docker-linux.clang.tar.gz: docker
- docker save gobuilders/linux-x86-clang | gzip | (cd ../../cmd/upload && go run upload.go --public go-builder-data/docker-linux.clang.tar.gz)
+ docker save gobuilders/linux-x86-clang | gzip | (cd ../../cmd/upload && go run --tags=extdep upload.go --public go-builder-data/docker-linux.clang.tar.gz)
check: docker
docker run -e GOROOT_BOOTSTRAP=/go1.4-amd64/go gobuilders/linux-x86-clang /usr/local/bin/builder -rev=20a10e7ddd1b -buildroot=/ -v -report=false linux-amd64-temp
diff --git a/dashboard/env/linux-x86-gccgo/Makefile b/dashboard/env/linux-x86-gccgo/Makefile
index e114b3d..a84ce89 100644
--- a/dashboard/env/linux-x86-gccgo/Makefile
+++ b/dashboard/env/linux-x86-gccgo/Makefile
@@ -6,7 +6,7 @@ docker: Dockerfile
docker build -t gobuilders/linux-x86-gccgo .
docker-linux.gccgo.tar.gz: docker
- docker save gobuilders/linux-x86-gccgo | gzip | (cd ../../cmd/upload && go run upload.go --public go-builder-data/docker-linux.gccgo.tar.gz)
+ docker save gobuilders/linux-x86-gccgo | gzip | (cd ../../cmd/upload && go run --tags=extdep upload.go --public go-builder-data/docker-linux.gccgo.tar.gz)
check: docker
docker run gobuilders/linux-x86-gccgo /usr/local/bin/builder -tool="gccgo" -rev=b9151e911a54 -v -cmd='make RUNTESTFLAGS="--target_board=unix/-m64" check-go' -report=false linux-amd64-gccgo-temp
diff --git a/dashboard/env/linux-x86-nacl/Makefile b/dashboard/env/linux-x86-nacl/Makefile
index d2f76ed..398221b 100644
--- a/dashboard/env/linux-x86-nacl/Makefile
+++ b/dashboard/env/linux-x86-nacl/Makefile
@@ -6,7 +6,7 @@ docker: Dockerfile
docker build -t gobuilders/linux-x86-nacl .
upload: docker
- docker save gobuilders/linux-x86-nacl | gzip | (cd ../../cmd/upload && go run upload.go --public go-builder-data/docker-linux.nacl.tar.gz)
+ docker save gobuilders/linux-x86-nacl | gzip | (cd ../../cmd/upload && go run --tags=extdep upload.go --public go-builder-data/docker-linux.nacl.tar.gz)
check: docker
docker run gobuilders/linux-x86-nacl /usr/local/bin/builder -rev=77e96c9208d0 -buildroot=/ -v -cmd=/usr/local/bin/build-command.pl -report=false nacl-amd64p32
diff --git a/dashboard/env/linux-x86-sid/Makefile b/dashboard/env/linux-x86-sid/Makefile
index df7b2cf..d40634a 100644
--- a/dashboard/env/linux-x86-sid/Makefile
+++ b/dashboard/env/linux-x86-sid/Makefile
@@ -6,7 +6,7 @@ docker: Dockerfile
docker build -t gobuilders/linux-x86-sid .
docker-linux.sid.tar.gz: docker
- docker save gobuilders/linux-x86-sid | gzip | (cd ../../cmd/upload && go run upload.go --public go-builder-data/docker-linux.sid.tar.gz)
+ docker save gobuilders/linux-x86-sid | gzip | (cd ../../cmd/upload && go run --tags=extdep upload.go --public go-builder-data/docker-linux.sid.tar.gz)
check: docker
docker run -e GOROOT_BOOTSTRAP=/go1.4-amd64/go gobuilders/linux-x86-sid /usr/local/bin/builder -rev=20a10e7ddd1b -buildroot=/ -v -report=false linux-amd64-sid