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 }