mirror of
https://github.com/golang/go
synced 2024-11-18 14:04:45 -07:00
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>
This commit is contained in:
parent
3ecc311976
commit
3aad931e88
49
dashboard/auth/auth.go
Normal file
49
dashboard/auth/auth.go
Normal file
@ -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
|
||||
}
|
@ -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 "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 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()
|
||||
|
@ -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
|
||||
}
|
||||
|
132
dashboard/buildlet/keypair.go
Normal file
132
dashboard/buildlet/keypair.go
Normal file
@ -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
|
||||
}
|
@ -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/$@)
|
||||
|
@ -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)
|
||||
|
@ -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/$@)
|
||||
|
@ -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)
|
||||
|
@ -1113,7 +1113,7 @@ func hasComputeScope() bool {
|
||||
return false
|
||||
}
|
||||
for _, v := range scopes {
|
||||
if v == compute.DevstorageFull_controlScope {
|
||||
if v == compute.ComputeScope {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
87
dashboard/cmd/gomote/auth.go
Normal file
87
dashboard/cmd/gomote/auth.go
Normal file
@ -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)
|
||||
}
|
67
dashboard/cmd/gomote/create.go
Normal file
67
dashboard/cmd/gomote/create.go
Normal file
@ -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
|
||||
}
|
42
dashboard/cmd/gomote/destroy.go
Normal file
42
dashboard/cmd/gomote/destroy.go
Normal file
@ -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
|
||||
}
|
105
dashboard/cmd/gomote/gomote.go
Normal file
105
dashboard/cmd/gomote/gomote.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
57
dashboard/cmd/gomote/list.go
Normal file
57
dashboard/cmd/gomote/list.go
Normal file
@ -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)
|
||||
}
|
83
dashboard/cmd/gomote/put.go
Normal file
83
dashboard/cmd/gomote/put.go
Normal file
@ -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
|
||||
}
|
42
dashboard/cmd/gomote/run.go
Normal file
42
dashboard/cmd/gomote/run.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
proj, ok := bucketProject[bucket]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown project for bucket %q", bucket)
|
||||
}
|
||||
conf, err := google.JWTConfigFromJSON(jsonConf, storage.ScopeReadWrite)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading JSON config from %s: %v", fileName, err)
|
||||
}
|
||||
return conf.TokenSource(oauth2.NoContext), nil
|
||||
return auth.ProjectTokenSource(proj, storage.ScopeReadWrite)
|
||||
}
|
||||
|
2
dashboard/env/commit-watcher/Makefile
vendored
2
dashboard/env/commit-watcher/Makefile
vendored
@ -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)
|
||||
|
2
dashboard/env/linux-x86-base/Makefile
vendored
2
dashboard/env/linux-x86-base/Makefile
vendored
@ -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
|
||||
|
2
dashboard/env/linux-x86-clang/Makefile
vendored
2
dashboard/env/linux-x86-clang/Makefile
vendored
@ -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
|
||||
|
2
dashboard/env/linux-x86-gccgo/Makefile
vendored
2
dashboard/env/linux-x86-gccgo/Makefile
vendored
@ -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
|
||||
|
2
dashboard/env/linux-x86-nacl/Makefile
vendored
2
dashboard/env/linux-x86-nacl/Makefile
vendored
@ -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
|
||||
|
2
dashboard/env/linux-x86-sid/Makefile
vendored
2
dashboard/env/linux-x86-sid/Makefile
vendored
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user