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  }