golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/upload/upload.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // The upload command writes a file to Google Cloud Storage. It's used
     6  // exclusively by the Makefiles in the Go project repos. Think of it
     7  // as a very light version of gsutil or gcloud, but with some
     8  // Go-specific configuration knowledge baked in.
     9  package main
    10  
    11  import (
    12  	"bytes"
    13  	"compress/gzip"
    14  	"context"
    15  	"crypto/md5"
    16  	"flag"
    17  	"fmt"
    18  	"io"
    19  	"log"
    20  	"net/http"
    21  	"os"
    22  	"strings"
    23  
    24  	"cloud.google.com/go/storage"
    25  )
    26  
    27  var (
    28  	public    = flag.Bool("public", false, "object should be world-readable")
    29  	cacheable = flag.Bool("cacheable", true, "object should be cacheable")
    30  	file      = flag.String("file", "", "read object from `file` ('-' for stdin)")
    31  	verbose   = flag.Bool("verbose", false, "verbose logging")
    32  	project   = flag.String("project", "", "GCE Project. If blank, it's automatically inferred from the bucket name for the common Go buckets.")
    33  	doGzip    = flag.Bool("gzip", false, "gzip the stored contents (not the upload's Content-Encoding); this forces the Content-Type to be application/octet-stream. To prevent misuse, the object name must also end in '.gz'")
    34  	extraEnv  = flag.String("env", "", "comma-separated list of addition KEY=val environment pairs to include in build environment when building a target to upload")
    35  )
    36  
    37  func main() {
    38  	flag.Usage = func() {
    39  		fmt.Fprintf(os.Stderr, "Usage: upload [flags] bucket/object\n")
    40  		flag.PrintDefaults()
    41  	}
    42  	flag.Parse()
    43  	if flag.NArg() != 1 {
    44  		flag.Usage()
    45  		os.Exit(1)
    46  	}
    47  	args := strings.SplitN(flag.Arg(0), "/", 2)
    48  	if len(args) != 2 {
    49  		flag.Usage()
    50  		os.Exit(1)
    51  	}
    52  	if strings.HasPrefix(*file, "go:") {
    53  		log.Fatalf("-file=go:target syntax is no longer supported")
    54  	}
    55  	bucket, object := args[0], args[1]
    56  
    57  	if *doGzip && !strings.HasSuffix(object, ".gz") {
    58  		log.Fatalf("-gzip flag requires object ending in .gz")
    59  	}
    60  
    61  	proj := *project
    62  	if proj == "" {
    63  		proj, _ = bucketProject[bucket]
    64  		if proj == "" {
    65  			log.Fatalf("bucket %q doesn't have an associated project in upload.go", bucket)
    66  		}
    67  	}
    68  
    69  	ctx := context.Background()
    70  	storageClient, err := storage.NewClient(ctx)
    71  	if err != nil {
    72  		log.Fatalf("storage.NewClient: %v", err)
    73  	}
    74  
    75  	if alreadyUploaded(storageClient, bucket, object) {
    76  		if *verbose {
    77  			log.Printf("gs://%s/%s up-to-date", bucket, object)
    78  		}
    79  		return
    80  	}
    81  
    82  	w := storageClient.Bucket(bucket).Object(object).NewWriter(ctx)
    83  	// If you don't give the owners access, the web UI seems to
    84  	// have a bug and doesn't have access to see that it's public, so
    85  	// won't render the "Shared Publicly" link. So we do that, even
    86  	// though it's dumb and unnecessary otherwise:
    87  	w.ACL = append(w.ACL, storage.ACLRule{Entity: storage.ACLEntity("project-owners-" + proj), Role: storage.RoleOwner})
    88  	if *public {
    89  		w.ACL = append(w.ACL, storage.ACLRule{Entity: storage.AllUsers, Role: storage.RoleReader})
    90  		if !*cacheable {
    91  			w.CacheControl = "no-cache"
    92  		}
    93  	}
    94  	var content io.Reader
    95  	switch {
    96  	case *file == "-":
    97  		content = os.Stdin
    98  	default:
    99  		content, err = os.Open(*file)
   100  		if err != nil {
   101  			log.Fatal(err)
   102  		}
   103  	}
   104  	if *doGzip {
   105  		var zbuf bytes.Buffer
   106  		zw := gzip.NewWriter(&zbuf)
   107  		if _, err := io.Copy(zw, content); err != nil {
   108  			log.Fatalf("compressing content: %v", err)
   109  		}
   110  		if err := zw.Close(); err != nil {
   111  			log.Fatalf("gzip.Close: %v", err)
   112  		}
   113  		content = &zbuf
   114  	}
   115  
   116  	const maxSlurp = 1 << 20
   117  	var buf bytes.Buffer
   118  	n, err := io.CopyN(&buf, content, maxSlurp)
   119  	if err != nil && err != io.EOF {
   120  		log.Fatalf("Error reading file: %v, %v", n, err)
   121  	}
   122  
   123  	if *doGzip {
   124  		w.ContentType = "application/octet-stream"
   125  	} else {
   126  		w.ContentType = http.DetectContentType(buf.Bytes())
   127  	}
   128  
   129  	_, err = io.Copy(w, io.MultiReader(&buf, content))
   130  	if cerr := w.Close(); cerr != nil && err == nil {
   131  		err = cerr
   132  	}
   133  	if err != nil {
   134  		log.Fatalf("Write error: %v", err)
   135  	}
   136  	if *verbose {
   137  		log.Printf("gs://%s/%s uploaded", bucket, object)
   138  	}
   139  }
   140  
   141  var bucketProject = map[string]string{
   142  	"dev-gccgo-builder-data": "gccgo-dashboard-dev",
   143  	"dev-go-builder-data":    "go-dashboard-dev",
   144  	"gccgo-builder-data":     "gccgo-dashboard-builders",
   145  	"go-builder-data":        "symbolic-datum-552",
   146  	"go-build-log":           "symbolic-datum-552",
   147  	"http2-demo-server-tls":  "symbolic-datum-552",
   148  	"gobuilder":              "999119582588", // deprecated
   149  	"golang":                 "999119582588",
   150  }
   151  
   152  // alreadyUploaded reports whether *file has already been uploaded and the correct contents
   153  // are on cloud storage already.
   154  func alreadyUploaded(storageClient *storage.Client, bucket, object string) bool {
   155  	if *file == "-" {
   156  		return false // don't know.
   157  	}
   158  	o, err := storageClient.Bucket(bucket).Object(object).Attrs(context.Background())
   159  	if err == storage.ErrObjectNotExist {
   160  		return false
   161  	}
   162  	if err != nil {
   163  		log.Printf("Warning: stat failure: %v", err)
   164  		return false
   165  	}
   166  	m5 := md5.New()
   167  	fi, err := os.Stat(*file)
   168  	if err != nil {
   169  		log.Fatal(err)
   170  	}
   171  	if fi.Size() != o.Size {
   172  		return false
   173  	}
   174  	f, err := os.Open(*file)
   175  	if err != nil {
   176  		log.Fatal(err)
   177  	}
   178  	defer f.Close()
   179  	n, err := io.Copy(m5, f)
   180  	if err != nil {
   181  		log.Fatal(err)
   182  	}
   183  	if n != fi.Size() {
   184  		log.Printf("Warning: file size of %v changed", *file)
   185  	}
   186  	return bytes.Equal(m5.Sum(nil), o.MD5)
   187  }