github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/publish-artifacts/main.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package main 12 13 import ( 14 "archive/tar" 15 "archive/zip" 16 "bufio" 17 "bytes" 18 "compress/gzip" 19 "flag" 20 "fmt" 21 "go/build" 22 "io" 23 "log" 24 "mime" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "regexp" 29 "strings" 30 31 "github.com/aws/aws-sdk-go/aws" 32 "github.com/aws/aws-sdk-go/aws/session" 33 "github.com/aws/aws-sdk-go/service/s3" 34 "github.com/cockroachdb/cockroach/pkg/util/version" 35 "github.com/kr/pretty" 36 ) 37 38 const ( 39 awsAccessKeyIDKey = "AWS_ACCESS_KEY_ID" 40 awsSecretAccessKeyKey = "AWS_SECRET_ACCESS_KEY" 41 teamcityBuildBranchKey = "TC_BUILD_BRANCH" 42 ) 43 44 type s3putter interface { 45 PutObject(*s3.PutObjectInput) (*s3.PutObjectOutput, error) 46 } 47 48 // Overridden in testing. 49 var testableS3 = func() (s3putter, error) { 50 sess, err := session.NewSession(&aws.Config{ 51 Region: aws.String("us-east-1"), 52 }) 53 if err != nil { 54 return nil, err 55 } 56 return s3.New(sess), nil 57 } 58 59 var libsRe = func() *regexp.Regexp { 60 libs := strings.Join([]string{ 61 regexp.QuoteMeta("linux-vdso.so."), 62 regexp.QuoteMeta("librt.so."), 63 regexp.QuoteMeta("libpthread.so."), 64 regexp.QuoteMeta("libdl.so."), 65 regexp.QuoteMeta("libm.so."), 66 regexp.QuoteMeta("libc.so."), 67 regexp.QuoteMeta("libresolv.so."), 68 strings.Replace(regexp.QuoteMeta("ld-linux-ARCH.so."), "ARCH", ".*", -1), 69 }, "|") 70 return regexp.MustCompile(libs) 71 }() 72 73 var osVersionRe = regexp.MustCompile(`\d+(\.\d+)*-`) 74 75 var isRelease = flag.Bool("release", false, "build in release mode instead of bleeding-edge mode") 76 var destBucket = flag.String("bucket", "", "override default bucket") 77 78 var ( 79 noCache = "no-cache" 80 // TODO(tamird,benesch,bdarnell): make "latest" a website-redirect 81 // rather than a full key. This means that the actual artifact will no 82 // longer be named "-latest". 83 latestStr = "latest" 84 ) 85 86 const dotExe = ".exe" 87 88 func main() { 89 flag.Parse() 90 91 if _, ok := os.LookupEnv(awsAccessKeyIDKey); !ok { 92 log.Fatalf("AWS access key ID environment variable %s is not set", awsAccessKeyIDKey) 93 } 94 if _, ok := os.LookupEnv(awsSecretAccessKeyKey); !ok { 95 log.Fatalf("AWS secret access key environment variable %s is not set", awsSecretAccessKeyKey) 96 } 97 98 branch, ok := os.LookupEnv(teamcityBuildBranchKey) 99 if !ok { 100 log.Fatalf("VCS branch environment variable %s is not set", teamcityBuildBranchKey) 101 } 102 pkg, err := build.Import("github.com/cockroachdb/cockroach", "", build.FindOnly) 103 if err != nil { 104 log.Fatalf("unable to locate CRDB directory: %s", err) 105 } 106 107 var versionStr string 108 var isStableRelease bool 109 if *isRelease { 110 ver, err := version.Parse(branch) 111 if err != nil { 112 log.Fatalf("refusing to build release with invalid version name '%s' (err: %s)", branch, err) 113 } 114 115 // Prerelease returns anything after the `-` and before metadata. eg: `beta` for `1.0.1-beta+metadata` 116 if ver.PreRelease() == "" { 117 isStableRelease = true 118 } 119 versionStr = branch 120 } else { 121 cmd := exec.Command("git", "rev-parse", "HEAD") 122 cmd.Dir = pkg.Dir 123 log.Printf("%s %s", cmd.Env, cmd.Args) 124 out, err := cmd.Output() 125 if err != nil { 126 log.Fatalf("%s: out=%q err=%s", cmd.Args, out, err) 127 } 128 versionStr = string(bytes.TrimSpace(out)) 129 } 130 131 svc, err := testableS3() 132 if err != nil { 133 log.Fatalf("Creating AWS S3 session: %s", err) 134 } 135 136 var bucketName string 137 if len(*destBucket) > 0 { 138 bucketName = *destBucket 139 } else if *isRelease { 140 bucketName = "binaries.cockroachdb.com" 141 } else { 142 bucketName = "cockroach" 143 } 144 log.Printf("Using S3 bucket: %s", bucketName) 145 146 releaseVersionStrs := []string{versionStr} 147 // Only build `latest` tarballs for stable releases. 148 if isStableRelease { 149 releaseVersionStrs = append(releaseVersionStrs, latestStr) 150 } 151 152 if *isRelease { 153 buildArchive(svc, opts{ 154 PkgDir: pkg.Dir, 155 BucketName: bucketName, 156 ReleaseVersionStrs: releaseVersionStrs, 157 }) 158 } 159 160 for _, target := range []struct { 161 buildType string 162 suffix string 163 }{ 164 // TODO(tamird): consider shifting this information into the builder 165 // image; it's conceivable that we'll want to target multiple versions 166 // of a given triple. 167 {buildType: "darwin", suffix: ".darwin-10.9-amd64"}, 168 {buildType: "linux-gnu", suffix: ".linux-2.6.32-gnu-amd64"}, 169 {buildType: "windows", suffix: ".windows-6.2-amd64.exe"}, 170 } { 171 for i, extraArgs := range []struct { 172 goflags string 173 suffix string 174 tags string 175 }{ 176 {}, 177 // TODO(tamird): re-enable deadlock builds. This really wants its 178 // own install suffix; it currently pollutes the normal release 179 // build cache. 180 // 181 // {suffix: ".deadlock", tags: "deadlock"}, 182 {suffix: ".race", goflags: "-race"}, 183 } { 184 var o opts 185 o.ReleaseVersionStrs = releaseVersionStrs 186 o.PkgDir = pkg.Dir 187 o.Branch = branch 188 o.VersionStr = versionStr 189 o.BucketName = bucketName 190 o.Branch = branch 191 o.BuildType = target.buildType 192 o.GoFlags = extraArgs.goflags 193 o.Suffix = extraArgs.suffix + target.suffix 194 o.Tags = extraArgs.tags 195 196 log.Printf("building %s", pretty.Sprint(o)) 197 198 // TODO(tamird): build deadlock,race binaries for all targets? 199 if i > 0 && (*isRelease || !strings.HasSuffix(o.BuildType, "linux-gnu")) { 200 log.Printf("skipping auxiliary build") 201 continue 202 } 203 204 buildOneCockroach(svc, o) 205 } 206 } 207 208 if !*isRelease { 209 buildOneWorkload(svc, opts{ 210 PkgDir: pkg.Dir, 211 BucketName: bucketName, 212 Branch: branch, 213 VersionStr: versionStr, 214 }) 215 } 216 } 217 218 func buildArchive(svc s3putter, o opts) { 219 for _, releaseVersionStr := range o.ReleaseVersionStrs { 220 archiveBase := fmt.Sprintf("cockroach-%s", releaseVersionStr) 221 srcArchive := fmt.Sprintf("%s.%s", archiveBase, "src.tgz") 222 cmd := exec.Command( 223 "make", 224 "archive", 225 fmt.Sprintf("ARCHIVE_BASE=%s", archiveBase), 226 fmt.Sprintf("ARCHIVE=%s", srcArchive), 227 ) 228 cmd.Dir = o.PkgDir 229 cmd.Stdout = os.Stdout 230 cmd.Stderr = os.Stderr 231 log.Printf("%s %s", cmd.Env, cmd.Args) 232 if err := cmd.Run(); err != nil { 233 log.Fatalf("%s: %s", cmd.Args, err) 234 } 235 236 absoluteSrcArchivePath := filepath.Join(o.PkgDir, srcArchive) 237 f, err := os.Open(absoluteSrcArchivePath) 238 if err != nil { 239 log.Fatalf("os.Open(%s): %s", absoluteSrcArchivePath, err) 240 } 241 putObjectInput := s3.PutObjectInput{ 242 Bucket: &o.BucketName, 243 Key: &srcArchive, 244 Body: f, 245 } 246 if releaseVersionStr == latestStr { 247 putObjectInput.CacheControl = &noCache 248 } 249 if _, err := svc.PutObject(&putObjectInput); err != nil { 250 log.Fatalf("s3 upload %s: %s", absoluteSrcArchivePath, err) 251 } 252 if err := f.Close(); err != nil { 253 log.Fatal(err) 254 } 255 } 256 } 257 258 func buildOneCockroach(svc s3putter, o opts) { 259 defer func() { 260 log.Printf("done building cockroach: %s", pretty.Sprint(o)) 261 }() 262 263 { 264 args := []string{o.BuildType} 265 args = append(args, fmt.Sprintf("%s=%s", "GOFLAGS", o.GoFlags)) 266 args = append(args, fmt.Sprintf("%s=%s", "SUFFIX", o.Suffix)) 267 args = append(args, fmt.Sprintf("%s=%s", "TAGS", o.Tags)) 268 args = append(args, fmt.Sprintf("%s=%s", "BUILDCHANNEL", "official-binary")) 269 if *isRelease { 270 args = append(args, fmt.Sprintf("%s=%s", "BUILD_TAGGED_RELEASE", "true")) 271 } 272 cmd := exec.Command("mkrelease", args...) 273 cmd.Dir = o.PkgDir 274 cmd.Stdout = os.Stdout 275 cmd.Stderr = os.Stderr 276 log.Printf("%s %s", cmd.Env, cmd.Args) 277 if err := cmd.Run(); err != nil { 278 log.Fatalf("%s: %s", cmd.Args, err) 279 } 280 } 281 282 if strings.Contains(o.BuildType, "linux") { 283 binaryName := "./cockroach" + o.Suffix 284 285 cmd := exec.Command(binaryName, "version") 286 cmd.Dir = o.PkgDir 287 cmd.Env = append(cmd.Env, "MALLOC_CONF=prof:true") 288 cmd.Stdout = os.Stdout 289 cmd.Stderr = os.Stderr 290 log.Printf("%s %s", cmd.Env, cmd.Args) 291 if err := cmd.Run(); err != nil { 292 log.Fatalf("%s %s: %s", cmd.Env, cmd.Args, err) 293 } 294 295 cmd = exec.Command("ldd", binaryName) 296 cmd.Dir = o.PkgDir 297 log.Printf("%s %s", cmd.Env, cmd.Args) 298 out, err := cmd.Output() 299 if err != nil { 300 log.Fatalf("%s: out=%q err=%s", cmd.Args, out, err) 301 } 302 scanner := bufio.NewScanner(bytes.NewReader(out)) 303 for scanner.Scan() { 304 if line := scanner.Text(); !libsRe.MatchString(line) { 305 log.Fatalf("%s is not properly statically linked:\n%s", binaryName, out) 306 } 307 } 308 if err := scanner.Err(); err != nil { 309 log.Fatal(err) 310 } 311 } 312 313 o.Base = "cockroach" + o.Suffix 314 o.AbsolutePath = filepath.Join(o.PkgDir, o.Base) 315 { 316 var err error 317 o.Binary, err = os.Open(o.AbsolutePath) 318 319 if err != nil { 320 log.Fatalf("os.Open(%s): %s", o.AbsolutePath, err) 321 } 322 } 323 324 if !*isRelease { 325 putNonRelease(svc, o) 326 } else { 327 putRelease(svc, o) 328 } 329 if err := o.Binary.Close(); err != nil { 330 log.Fatal(err) 331 } 332 } 333 334 func buildOneWorkload(svc s3putter, o opts) { 335 defer func() { 336 log.Printf("done building workload: %s", pretty.Sprint(o)) 337 }() 338 339 if *isRelease { 340 log.Fatalf("refusing to build workload in release mode") 341 } 342 343 { 344 cmd := exec.Command("make", "bin/workload") 345 cmd.Dir = o.PkgDir 346 cmd.Stdout = os.Stdout 347 cmd.Stderr = os.Stderr 348 log.Printf("%s %s", cmd.Env, cmd.Args) 349 if err := cmd.Run(); err != nil { 350 log.Fatalf("%s: %s", cmd.Args, err) 351 } 352 } 353 354 o.Base = "workload" 355 o.AbsolutePath = filepath.Join(o.PkgDir, "bin", o.Base) 356 { 357 var err error 358 o.Binary, err = os.Open(o.AbsolutePath) 359 360 if err != nil { 361 log.Fatalf("os.Open(%s): %s", o.AbsolutePath, err) 362 } 363 } 364 putNonRelease(svc, o) 365 if err := o.Binary.Close(); err != nil { 366 log.Fatal(err) 367 } 368 } 369 370 type opts struct { 371 VersionStr string 372 Branch string 373 ReleaseVersionStrs []string 374 375 BuildType string 376 GoFlags string 377 Suffix string 378 Tags string 379 380 Base string 381 BucketName string 382 Binary *os.File 383 AbsolutePath string 384 PkgDir string 385 } 386 387 // TrimDotExe trims '.exe. from `name` and returns the result (and whether any 388 // trimming has occurred). 389 func TrimDotExe(name string) (string, bool) { 390 return strings.TrimSuffix(name, dotExe), strings.HasSuffix(name, dotExe) 391 } 392 393 func putNonRelease(svc s3putter, o opts) { 394 const repoName = "cockroach" 395 remoteName, hasExe := TrimDotExe(o.Base) 396 // TODO(tamird): do we want to keep doing this? No longer 397 // doing so requires updating cockroachlabs/production, and 398 // possibly cockroachdb/cockroach-go. 399 remoteName = osVersionRe.ReplaceAllLiteralString(remoteName, "") 400 401 fileName := fmt.Sprintf("%s.%s", remoteName, o.VersionStr) 402 if hasExe { 403 fileName += ".exe" 404 } 405 disposition := mime.FormatMediaType("attachment", map[string]string{"filename": fileName}) 406 407 // NB: The leading slash is required to make redirects work 408 // correctly since we reuse this key as the redirect location. 409 versionKey := fmt.Sprintf("/%s/%s", repoName, fileName) 410 if _, err := svc.PutObject(&s3.PutObjectInput{ 411 Bucket: &o.BucketName, 412 ContentDisposition: &disposition, 413 Key: &versionKey, 414 Body: o.Binary, 415 }); err != nil { 416 log.Fatalf("s3 upload %s: %s", o.AbsolutePath, err) 417 } 418 latestSuffix := o.Branch 419 if latestSuffix == "master" { 420 latestSuffix = "LATEST" 421 } 422 latestKey := fmt.Sprintf("%s/%s.%s", repoName, remoteName, latestSuffix) 423 if _, err := svc.PutObject(&s3.PutObjectInput{ 424 Bucket: &o.BucketName, 425 CacheControl: &noCache, 426 Key: &latestKey, 427 WebsiteRedirectLocation: &versionKey, 428 }); err != nil { 429 log.Fatalf("s3 redirect to %s: %s", versionKey, err) 430 } 431 } 432 433 func putRelease(svc s3putter, o opts) { 434 targetSuffix, hasExe := TrimDotExe(o.Suffix) 435 // TODO(tamird): remove this weirdness. Requires updating 436 // "users" e.g. docs, cockroachdb/cockroach-go, maybe others. 437 if strings.Contains(o.BuildType, "linux") { 438 targetSuffix = strings.Replace(targetSuffix, "gnu-", "", -1) 439 targetSuffix = osVersionRe.ReplaceAllLiteralString(targetSuffix, "") 440 } 441 442 // Stat the binary. Info is needed for archive headers. 443 binaryInfo, err := o.Binary.Stat() 444 if err != nil { 445 log.Fatal(err) 446 } 447 448 for _, releaseVersionStr := range o.ReleaseVersionStrs { 449 archiveBase := fmt.Sprintf("cockroach-%s", releaseVersionStr) 450 targetArchiveBase := archiveBase + targetSuffix 451 var targetArchive string 452 var body bytes.Buffer 453 if hasExe { 454 targetArchive = targetArchiveBase + ".zip" 455 zw := zip.NewWriter(&body) 456 457 // Set the zip header from the file info. Overwrite name. 458 zipHeader, err := zip.FileInfoHeader(binaryInfo) 459 if err != nil { 460 log.Fatal(err) 461 } 462 zipHeader.Name = filepath.Join(targetArchiveBase, "cockroach.exe") 463 464 zfw, err := zw.CreateHeader(zipHeader) 465 if err != nil { 466 log.Fatal(err) 467 } 468 if _, err := io.Copy(zfw, o.Binary); err != nil { 469 log.Fatal(err) 470 } 471 if err := zw.Close(); err != nil { 472 log.Fatal(err) 473 } 474 } else { 475 targetArchive = targetArchiveBase + ".tgz" 476 gzw := gzip.NewWriter(&body) 477 tw := tar.NewWriter(gzw) 478 479 // Set the tar header from the file info. Overwrite name. 480 tarHeader, err := tar.FileInfoHeader(binaryInfo, "") 481 if err != nil { 482 log.Fatal(err) 483 } 484 tarHeader.Name = filepath.Join(targetArchiveBase, "cockroach") 485 if err := tw.WriteHeader(tarHeader); err != nil { 486 log.Fatal(err) 487 } 488 489 if _, err := io.Copy(tw, o.Binary); err != nil { 490 log.Fatal(err) 491 } 492 if err := tw.Close(); err != nil { 493 log.Fatal(err) 494 } 495 if err := gzw.Close(); err != nil { 496 log.Fatal(err) 497 } 498 } 499 if _, err := o.Binary.Seek(0, 0); err != nil { 500 log.Fatal(err) 501 } 502 putObjectInput := s3.PutObjectInput{ 503 Bucket: &o.BucketName, 504 Key: &targetArchive, 505 Body: bytes.NewReader(body.Bytes()), 506 } 507 if releaseVersionStr == latestStr { 508 putObjectInput.CacheControl = &noCache 509 } 510 if _, err := svc.PutObject(&putObjectInput); err != nil { 511 log.Fatalf("s3 upload %s: %s", targetArchive, err) 512 } 513 } 514 }