cmd/releasebot: add support for security releases

Change-Id: I24f22a6101b550d459a4f4db1971e0c23ffae1d3
Reviewed-on: https://go-review.googlesource.com/c/153866
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/release/release.go b/cmd/release/release.go
index 4e9e289..53844bd 100644
--- a/cmd/release/release.go
+++ b/cmd/release/release.go
@@ -37,7 +37,8 @@
 	target = flag.String("target", "", "If specified, build specific target platform (e.g. 'linux-amd64'). Default is to build all.")
 	watch  = flag.Bool("watch", false, "Watch the build. Only compatible with -target")
 
-	rev       = flag.String("rev", "", "Go revision to build")
+	rev       = flag.String("rev", "", "Go revision to build, alternative to -tarball")
+	tarball   = flag.String("tarball", "", "Go tree tarball to build, alternative to -rev")
 	toolsRev  = flag.String("tools", "", "Tools revision to build")
 	tourRev   = flag.String("tour", "master", "Tour revision to include")
 	netRev    = flag.String("net", "master", "Net revision to include")
@@ -70,8 +71,8 @@
 		log.Fatalf("couldn't find releaselet source: %v", err)
 	}
 
-	if *rev == "" {
-		log.Fatal("must specify -rev flag")
+	if (*rev == "" && *tarball == "") || (*rev != "" && *tarball != "") {
+		log.Fatal("must specify one of -rev and -tarball")
 	}
 	if *toolsRev == "" {
 		log.Fatal("must specify -tools flag")
@@ -285,24 +286,38 @@
 		goPath = "gopath"
 		go14   = "go1.4"
 	)
+	if *tarball != "" {
+		tarFile, err := os.Open(*tarball)
+		if err != nil {
+			b.logf("failed to open tarball %q: %v", *tarball, err)
+			return err
+		}
+		if err := client.PutTar(tarFile, goDir); err != nil {
+			b.logf("failed to put tarball %q into dir %q: %v", *tarball, goDir, err)
+			return err
+		}
+		tarFile.Close()
+	} else {
+		tar := "https://go.googlesource.com/go/+archive/" + *rev + ".tar.gz"
+		if err := client.PutTarFromURL(tar, goDir); err != nil {
+			b.logf("failed to put tarball %q into dir %q: %v", tar, goDir, err)
+			return err
+		}
+	}
 	for _, r := range []struct {
 		repo, rev string
 	}{
-		{"go", *rev},
 		{"tools", *toolsRev},
 		{"tour", *tourRev},
 		{"net", *netRev},
 	} {
-		if b.Source && r.repo != "go" {
+		if b.Source {
 			continue
 		}
 		if r.repo == "tour" && !versionIncludesTour(*version) {
 			continue
 		}
-		dir := goDir
-		if r.repo != "go" {
-			dir = goPath + "/src/golang.org/x/" + r.repo
-		}
+		dir := goPath + "/src/golang.org/x/" + r.repo
 		tar := "https://go.googlesource.com/" + r.repo + "/+archive/" + r.rev + ".tar.gz"
 		if err := client.PutTarFromURL(tar, dir); err != nil {
 			b.logf("failed to put tarball %q into dir %q: %v", tar, dir, err)
diff --git a/cmd/releasebot/git.go b/cmd/releasebot/git.go
index 51b56a5..924d4cf 100644
--- a/cmd/releasebot/git.go
+++ b/cmd/releasebot/git.go
@@ -25,11 +25,16 @@
 		w.log.Panic(err)
 	}
 
+	origin := "https://go.googlesource.com/go"
+	if w.Security {
+		origin = "sso://team/golang/go-private"
+	}
+
 	// Check out a local mirror to work-mirror, to speed future checkouts for this point release.
 	mirror := filepath.Join(w.Dir, "gitmirror")
 	r := w.runner(mirror)
 	if _, err := os.Stat(mirror); err != nil {
-		w.runner(w.Dir).run("git", "clone", "https://go.googlesource.com/go", mirror)
+		w.runner(w.Dir).run("git", "clone", origin, mirror)
 		r.run("git", "config", "gc.auto", "0") // don't throw away refs we fetch
 	} else {
 		r.run("git", "fetch", "origin", "master")
@@ -41,7 +46,7 @@
 	if err := os.RemoveAll(gitDir); err != nil {
 		w.log.Panic(err)
 	}
-	w.runner(w.Dir).run("git", "clone", "--reference", mirror, "-b", w.ReleaseBranch, "https://go.googlesource.com/go", gitDir)
+	w.runner(w.Dir).run("git", "clone", "--reference", mirror, "-b", w.ReleaseBranch, origin, gitDir)
 	r = w.runner(gitDir)
 	r.run("git", "codereview", "change", "relwork")
 	r.run("git", "config", "gc.auto", "0") // don't throw away refs we fetch
diff --git a/cmd/releasebot/github.go b/cmd/releasebot/github.go
index 1389185..89c3544 100644
--- a/cmd/releasebot/github.go
+++ b/cmd/releasebot/github.go
@@ -85,7 +85,7 @@
 
 func (w *Work) findOrCreateReleaseIssue() {
 	w.log.Printf("Release status issue title: %q", w.releaseStatusTitle())
-	if dryRun {
+	if dryRun || w.Security {
 		return
 	}
 	if w.ReleaseIssue == 0 {
@@ -103,7 +103,7 @@
 // createGitHubIssue creates an issue in the release milestone and returns its number.
 func (w *Work) createGitHubIssue(title, msg string) (int, error) {
 	if dryRun {
-		return 0, errors.New("attemted write operation in dry-run mode")
+		return 0, errors.New("attempted write operation in dry-run mode")
 	}
 	var dup int
 	goRepo.ForeachIssue(func(gi *maintner.GitHubIssue) error {
@@ -150,7 +150,11 @@
 		if gi.Milestone == nil || gi.Milestone.Title != w.Milestone.Title {
 			return nil
 		}
-		if gi.Closed || gi.Title == w.releaseStatusTitle() {
+		if gi.Title == w.releaseStatusTitle() {
+			return nil
+		}
+		// All issues are unrelated if this is a security release.
+		if gi.Closed && !w.Security {
 			return nil
 		}
 		w.log.Printf("changing milestone of issue %d to %s", gi.Number, w.NextMilestone.Title)
diff --git a/cmd/releasebot/main.go b/cmd/releasebot/main.go
index 6f056d5..6825422 100644
--- a/cmd/releasebot/main.go
+++ b/cmd/releasebot/main.go
@@ -52,12 +52,7 @@
 }
 
 func usage() {
-	fmt.Fprintln(os.Stderr, "usage: releasebot -mode <release mode> [-dry-run] {go1.8.5|go1.10beta2|go1.11rc1}")
-	fmt.Fprintln(os.Stderr, "Release modes:")
-	fmt.Fprintln(os.Stderr)
-	for m := range releaseModes {
-		fmt.Fprintln(os.Stderr, m)
-	}
+	fmt.Fprintln(os.Stderr, "usage: releasebot -mode {prepare|release} [-security] [-dry-run] {go1.8.5|go1.10beta2|go1.11rc1}")
 	os.Exit(2)
 }
 
@@ -66,6 +61,7 @@
 func main() {
 	modeFlag := flag.String("mode", "", "release mode (prepare, release)")
 	flag.BoolVar(&dryRun, "dry-run", false, "only perform pre-flight checks, only log to terminal")
+	security := flag.Bool("security", false, "cut a security release from the internal Gerrit")
 	flag.Usage = usage
 	flag.Parse()
 	if *modeFlag == "" || !releaseModes[*modeFlag] || flag.NArg() == 0 {
@@ -84,6 +80,10 @@
 	var wg sync.WaitGroup
 	for _, release := range flag.Args() {
 		if strings.Contains(release, "beta") || strings.Contains(release, "rc") {
+			if *security {
+				log.Printf("error: only minor releases are supported in security mode")
+				usage()
+			}
 			w := &Work{
 				Prepare:     *modeFlag == "prepare",
 				Version:     release,
@@ -110,6 +110,7 @@
 					NextMilestone: nextM,
 					Prepare:       *modeFlag == "prepare",
 					Version:       release,
+					Security:      *security,
 				}
 				wg.Add(1)
 				go func() {
@@ -194,6 +195,7 @@
 	Prepare     bool // create the release commit and submit it for review
 	BetaRelease bool
 	RCRelease   bool
+	Security    bool // cut a security release from the internal Gerrit
 
 	ReleaseIssue  int    // Release status issue number
 	ReleaseBranch string // "master" for beta releases
@@ -312,6 +314,9 @@
 	} else {
 		shortRel := w.Version[:strings.LastIndex(w.Version, ".")]
 		w.ReleaseBranch = "release-branch." + shortRel
+		if w.Security {
+			w.ReleaseBranch += "-security"
+		}
 	}
 
 	w.checkSpelling()
@@ -326,7 +331,9 @@
 	if w.BetaRelease || w.RCRelease {
 		// TODO: go tool api -allow_new=false
 	} else {
-		w.checkReleaseBlockers()
+		if !w.Security {
+			w.checkReleaseBlockers()
+		}
 		w.checkDocs()
 	}
 	w.findOrCreateReleaseIssue()
@@ -389,6 +396,18 @@
 }
 
 func (w *Work) nextStepsPrepare(changeID string) {
+	if w.Security {
+		w.log.Printf(`
+
+The release is ready.
+
+Please review and submit https://team-review.git.corp.google.com/q/%s
+after testing it with all.bash and then run the release stage.
+
+`, changeID)
+		return
+	}
+
 	w.log.Printf(`
 
 The release is ready.
@@ -439,7 +458,8 @@
 
 	body := md.String()
 	fmt.Printf("%s", body)
-	if dryRun {
+	// Avoid the risk of leaking sensitive test failures on security releases.
+	if dryRun || w.Security {
 		return
 	}
 	err := postGithubComment(w.ReleaseIssue, body)
@@ -503,8 +523,10 @@
 	r.run("git", "commit", "-m", desc, "VERSION")
 	if dryRun {
 		fmt.Printf("\n### VERSION commit\n\n%s\n", r.runOut("git", "show", "HEAD"))
+	} else if w.Security {
+		r.run("git", "codereview", "mail")
 	} else {
-		r.run("git", "codereview", "mail", "-trybot", "HEAD")
+		r.run("git", "codereview", "mail", "-trybot")
 	}
 	return
 }
@@ -540,6 +562,22 @@
 	}
 	w.ReleaseInfo = make(map[string]*ReleaseInfo)
 
+	if w.Security {
+		fmt.Printf(`
+
+Please download
+
+	https://team.git.corp.google.com/golang/go-private/+archive/%s.tar.gz
+
+to %s and press enter.
+`, w.VersionCommit, filepath.Join(w.Dir, w.VersionCommit+".tar.gz"))
+
+		_, err := fmt.Scanln()
+		if err != nil {
+			w.log.Panic(err)
+		}
+	}
+
 	var wg sync.WaitGroup
 	for _, target := range releaseTargets {
 		func() {
@@ -627,9 +665,15 @@
 	} else {
 		failures := 0
 		for {
-			out, err := w.runner(filepath.Join(w.Dir, "release"), "GOPATH="+filepath.Join(w.Dir, "gopath")).runErr(
-				w.ReleaseBinary, "-target", target, "-user", gomoteUser, "-version", w.Version, "-rev", w.VersionCommit,
-				"-tools", w.ReleaseBranch, "-net", w.ReleaseBranch)
+			releaseBranch := strings.TrimSuffix(w.ReleaseBranch, "-security")
+			args := []string{w.ReleaseBinary, "-target", target, "-user", gomoteUser,
+				"-version", w.Version, "-tools", releaseBranch, "-net", releaseBranch}
+			if w.Security {
+				args = append(args, "-tarball", filepath.Join(w.Dir, w.VersionCommit+".tar.gz"))
+			} else {
+				args = append(args, "-rev", w.VersionCommit)
+			}
+			out, err := w.runner(filepath.Join(w.Dir, "release"), "GOPATH="+filepath.Join(w.Dir, "gopath")).runErr(args...)
 			// Exit code from release binary is apparently unreliable.
 			// Look to see if the files we expected were created instead.
 			failed := false