cmd/gomote: implements GRPC puttar command

This change adds the implementation for GRPC puttar command to the
gomote client.

Updates golang/go#48737
For golang/go#47521

Change-Id: I9b500b2f3ca70c78c3f288d0280eba02a1c59554
Reviewed-on: https://go-review.googlesource.com/c/build/+/407878
Auto-Submit: Carlos Amedee <carlos@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Run-TryBot: Carlos Amedee <carlos@golang.org>
Reviewed-by: Alex Rakoczy <alex@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/gomote/gomote.go b/cmd/gomote/gomote.go
index 0553db1..684b635 100644
--- a/cmd/gomote/gomote.go
+++ b/cmd/gomote/gomote.go
@@ -158,7 +158,7 @@
 	registerCommand("push", "sync your GOROOT directory to the buildlet", push)
 	registerCommand("put", "put files on a buildlet", legacyPut)
 	registerCommand("put14", "put Go 1.4 in place", put14)
-	registerCommand("puttar", "extract a tar.gz to a buildlet", putTar)
+	registerCommand("puttar", "extract a tar.gz to a buildlet", legacyPutTar)
 	registerCommand("rdp", "RDP (Remote Desktop Protocol) to a Windows buildlet", rdp)
 	registerCommand("rm", "delete files or directories", legacyRm)
 	registerCommand("run", "run a command on a buildlet", legacyRun)
@@ -226,6 +226,7 @@
 		"rm":      rm,
 		"gettar":  getTar,
 		"put":     put,
+		"puttar":  putTar,
 	}
 	if len(args) == 0 {
 		usage()
diff --git a/cmd/gomote/put.go b/cmd/gomote/put.go
index 3315e6f..039744c 100644
--- a/cmd/gomote/put.go
+++ b/cmd/gomote/put.go
@@ -23,8 +23,8 @@
 	"golang.org/x/build/tarutil"
 )
 
-// put a .tar.gz
-func putTar(args []string) error {
+// legacyPutTar a .tar.gz
+func legacyPutTar(args []string) error {
 	fs := flag.NewFlagSet("put", flag.ContinueOnError)
 	fs.Usage = func() {
 		fmt.Fprintln(os.Stderr, "puttar usage: gomote puttar [put-opts] <buildlet-name> [tar.gz file or '-' for stdin]")
@@ -93,6 +93,106 @@
 	return bc.PutTar(ctx, tgz, dir)
 }
 
+// putTar a .tar.gz
+func putTar(args []string) error {
+	fs := flag.NewFlagSet("put", flag.ContinueOnError)
+	fs.Usage = func() {
+		fmt.Fprintln(os.Stderr, "puttar usage: gomote puttar [put-opts] <buildlet-name> [tar.gz file or '-' for stdin]")
+		fs.PrintDefaults()
+		os.Exit(1)
+	}
+	var rev string
+	fs.StringVar(&rev, "gorev", "", "If non-empty, git hash to download from gerrit and put to the buildlet. e.g. 886b02d705ff for Go 1.4.1. This just maps to the --URL flag, so the two options are mutually exclusive.")
+	var dir string
+	fs.StringVar(&dir, "dir", "", "relative directory from buildlet's work dir to extra tarball into")
+	var tarURL string
+	fs.StringVar(&tarURL, "url", "", "URL of tarball, instead of provided file.")
+
+	fs.Parse(args)
+	if fs.NArg() < 1 || fs.NArg() > 2 {
+		fs.Usage()
+	}
+	if rev != "" && tarURL != "" {
+		fmt.Fprintln(os.Stderr, "--gorev and --url are mutually exclusive")
+		fs.Usage()
+	}
+	name := fs.Arg(0)
+	ctx := context.Background()
+	client := gomoteServerClient(ctx)
+
+	if rev != "" {
+		tarURL = "https://go.googlesource.com/go/+archive/" + rev + ".tar.gz"
+	}
+	if tarURL != "" {
+		if fs.NArg() != 1 {
+			fs.Usage()
+		}
+		_, err := client.WriteTGZFromURL(ctx, &protos.WriteTGZFromURLRequest{
+			GomoteId:  name,
+			Directory: dir,
+			Url:       tarURL,
+		})
+		if err != nil {
+			return fmt.Errorf("unable to write tar to instance: %s", statusFromError(err))
+		}
+		if rev != "" {
+			// Put a VERSION file there too, to avoid git usage.
+			version := strings.NewReader("devel " + rev)
+			var vtar tarutil.FileList
+			vtar.AddRegular(&tar.Header{
+				Name: "VERSION",
+				Mode: 0644,
+				Size: int64(version.Len()),
+			}, int64(version.Len()), version)
+			tgz := vtar.TarGz()
+			defer tgz.Close()
+
+			resp, err := client.UploadFile(ctx, &protos.UploadFileRequest{})
+			if err != nil {
+				return fmt.Errorf("unable to request credentials for a file upload: %s", statusFromError(err))
+			}
+			if err := uploadToGCS(ctx, resp.GetFields(), tgz, resp.GetObjectName(), resp.GetUrl()); err != nil {
+				return fmt.Errorf("unable to upload version file to GCS: %s", err)
+			}
+			if _, err = client.WriteTGZFromURL(ctx, &protos.WriteTGZFromURLRequest{
+				GomoteId:  name,
+				Directory: dir,
+				Url:       fmt.Sprintf("%s%s", resp.GetUrl(), resp.GetObjectName()),
+			}); err != nil {
+				return fmt.Errorf("unable to write tar to instance: %s", statusFromError(err))
+			}
+		}
+		return nil
+	}
+	var tgz io.Reader = os.Stdin
+	if fs.NArg() != 2 {
+		fs.Usage()
+	}
+	if fs.Arg(1) != "-" {
+		f, err := os.Open(fs.Arg(1))
+		if err != nil {
+			return err
+		}
+		defer f.Close()
+		tgz = f
+	}
+	resp, err := client.UploadFile(ctx, &protos.UploadFileRequest{})
+	if err != nil {
+		return fmt.Errorf("unable to request credentials for a file upload: %s", statusFromError(err))
+	}
+	if err := uploadToGCS(ctx, resp.GetFields(), tgz, resp.GetObjectName(), resp.GetUrl()); err != nil {
+		return fmt.Errorf("unable to upload file to GCS: %s", err)
+	}
+	if _, err := client.WriteTGZFromURL(ctx, &protos.WriteTGZFromURLRequest{
+		GomoteId:  name,
+		Directory: dir,
+		Url:       fmt.Sprintf("%s%s", resp.GetUrl(), resp.GetObjectName()),
+	}); err != nil {
+		return fmt.Errorf("unable to write tar to instance: %s", statusFromError(err))
+	}
+	return nil
+}
+
 // put go1.4 in the workdir
 func put14(args []string) error {
 	fs := flag.NewFlagSet("put14", flag.ContinueOnError)