blob: 44f5a72707a2a1ca556d180b43fae31427b6d474 [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build upload
// ^ this is so we don't break the build of x/tools/... build due
// to missing depenencies on the builders. We don't want full builds
// needing to pull in dependencies outside of the x/tools repo.
// The upload command writes a file to Google Cloud Storage. It's used
// exclusively by the Makefiles in the Go project repos. Think of it
// as a very light version of gsutil or gcloud, but with some
// Go-specific configuration knowledge baked in.
package main
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/cloud"
"google.golang.org/cloud/storage"
)
var (
public = flag.Bool("public", false, "object should be world-readable")
file = flag.String("file", "-", "Filename to read object from, or '-' for stdin.")
verbose = flag.Bool("verbose", false, "verbose logging")
)
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: upload [--public] [--file=...] <bucket/object>\n")
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(1)
}
args := strings.SplitN(flag.Arg(0), "/", 2)
if len(args) != 2 {
flag.Usage()
os.Exit(1)
}
bucket, object := args[0], args[1]
proj, ok := bucketProject[bucket]
if !ok {
log.Fatalf("bucket %q doesn't have an associated project in upload.go")
}
ts, err := tokenSource(bucket)
if err != nil {
log.Fatalf("Failed to get an OAuth2 token source: %v", err)
}
httpClient := oauth2.NewClient(oauth2.NoContext, ts)
ctx := cloud.NewContext(proj, httpClient)
w := storage.NewWriter(ctx, bucket, object)
// If you don't give the owners access, the web UI seems to
// have a bug and doesn't have access to see that it's public, so
// won't render the "Shared Publicly" link. So we do that, even
// though it's dumb and unnecessary otherwise:
w.ACL = append(w.ACL, storage.ACLRule{Entity: storage.ACLEntity("project-owners-" + proj), Role: storage.RoleOwner})
if *public {
w.ACL = append(w.ACL, storage.ACLRule{Entity: storage.AllUsers, Role: storage.RoleReader})
}
var content io.Reader
if *file == "-" {
content = os.Stdin
} else {
content, err = os.Open(*file)
if err != nil {
log.Fatal(err)
}
}
const maxSlurp = 1 << 20
var buf bytes.Buffer
n, err := io.CopyN(&buf, content, maxSlurp)
if err != nil && err != io.EOF {
log.Fatalf("Error reading from stdin: %v, %v", n, err)
}
w.ContentType = http.DetectContentType(buf.Bytes())
_, err = io.Copy(w, io.MultiReader(&buf, content))
if cerr := w.Close(); cerr != nil && err == nil {
err = cerr
}
if err != nil {
log.Fatalf("Write error: %v", err)
}
if *verbose {
log.Printf("Wrote %v", object)
}
os.Exit(0)
}
var bucketProject = map[string]string{
"go-builder-data": "symbolic-datum-552",
"http2-demo-server-tls": "symbolic-datum-552",
"winstrap": "999119582588",
"gobuilder": "999119582588", // deprecated
}
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)
}
return conf.TokenSource(oauth2.NoContext), nil
}