cmd/gomote: implements GRPC create command

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

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

Change-Id: I2a45364f6aac078c5356f60ddccb9e301985c9c1
Reviewed-on: https://go-review.googlesource.com/c/build/+/398054
Reviewed-by: Alex Rakoczy <alex@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Auto-Submit: Carlos Amedee <carlos@golang.org>
Run-TryBot: Carlos Amedee <carlos@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/gomote/create.go b/cmd/gomote/create.go
index 18d0d2c..cccf1e4 100644
--- a/cmd/gomote/create.go
+++ b/cmd/gomote/create.go
@@ -5,9 +5,11 @@
 package main
 
 import (
+	"context"
 	"encoding/json"
 	"flag"
 	"fmt"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -16,6 +18,7 @@
 	"time"
 
 	"golang.org/x/build/buildlet"
+	"golang.org/x/build/internal/gomote/protos"
 	"golang.org/x/build/types"
 )
 
@@ -74,7 +77,7 @@
 	return
 }
 
-func create(args []string) error {
+func legacyCreate(args []string) error {
 	fs := flag.NewFlagSet("create", flag.ContinueOnError)
 
 	fs.Usage = func() {
@@ -128,3 +131,56 @@
 	fmt.Println(client.RemoteName())
 	return nil
 }
+
+func create(args []string) error {
+	fs := flag.NewFlagSet("create", flag.ContinueOnError)
+
+	fs.Usage = func() {
+		fmt.Fprintln(os.Stderr, "create usage: gomote v2 create [create-opts] <type>")
+		fs.PrintDefaults()
+		fmt.Fprintln(os.Stderr, "\nValid types:")
+		for _, bt := range builders() {
+			var warn string
+			if bt.IsReverse {
+				if bt.ExpectNum > 0 {
+					warn = fmt.Sprintf("   [limited capacity: %d machines]", bt.ExpectNum)
+				} else {
+					warn = "   [limited capacity]"
+				}
+			}
+			fmt.Fprintf(os.Stderr, "  * %s%s\n", bt.Name, warn)
+		}
+		os.Exit(1)
+	}
+	var status bool
+	fs.BoolVar(&status, "status", true, "print regular status updates while waiting")
+
+	fs.Parse(args)
+	if fs.NArg() != 1 {
+		fs.Usage()
+	}
+	builderType := fs.Arg(0)
+	ctx := context.Background()
+	client := gomoteServerClient(ctx)
+
+	start := time.Now()
+	stream, err := client.CreateInstance(ctx, &protos.CreateInstanceRequest{BuilderType: builderType})
+	if err != nil {
+		return fmt.Errorf("failed to create buildlet: %v", statusFromError(err))
+	}
+	var instanceName string
+	for {
+		update, err := stream.Recv()
+		switch {
+		case err == io.EOF:
+			fmt.Println(instanceName)
+			return nil
+		case err != nil:
+			return fmt.Errorf("failed to create buildlet: %v", statusFromError(err))
+		case update.GetStatus() != protos.CreateInstanceResponse_COMPLETE && status:
+			fmt.Fprintf(os.Stderr, "# still creating %s after %v; %d requests ahead of you\n", builderType, time.Since(start).Round(time.Second), update.GetWaitersAhead())
+		case update.GetStatus() == protos.CreateInstanceResponse_COMPLETE:
+			instanceName = update.GetInstance().GetGomoteId()
+		}
+	}
+}
diff --git a/cmd/gomote/gomote.go b/cmd/gomote/gomote.go
index 8c6f404..d55bc6e 100644
--- a/cmd/gomote/gomote.go
+++ b/cmd/gomote/gomote.go
@@ -37,6 +37,7 @@
 	  rdp        RDP (Remote Desktop Protocol) to a Windows buildlet
 	  run        run a command on a buildlet
 	  ssh        ssh to a buildlet
+      v2         version 2 of the gomote API
 
 To list all the builder types available, run "create" with no arguments:
 
@@ -99,6 +100,7 @@
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials/oauth"
+	"google.golang.org/grpc/status"
 )
 
 var (
@@ -147,7 +149,7 @@
 }
 
 func registerCommands() {
-	registerCommand("create", "create a buildlet; with no args, list types of buildlets", create)
+	registerCommand("create", "create a buildlet; with no args, list types of buildlets", legacyCreate)
 	registerCommand("destroy", "destroy a buildlet", destroy)
 	registerCommand("gettar", "extract a tar.gz from a buildlet", getTar)
 	registerCommand("ls", "list the contents of a directory on a buildlet", ls)
@@ -161,6 +163,7 @@
 	registerCommand("rm", "delete files or directories", rm)
 	registerCommand("run", "run a command on a buildlet", run)
 	registerCommand("ssh", "ssh to a buildlet", ssh)
+	registerCommand("v2", "version 2 of the gomote commands", version2)
 }
 
 var (
@@ -185,8 +188,7 @@
 	}
 	err := cmd.run(args[1:])
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error running %s: %v\n", cmdName, err)
-		os.Exit(1)
+		logAndExitf("Error running %s: %v\n", cmdName, err)
 	}
 }
 
@@ -209,8 +211,31 @@
 	return protos.NewGomoteServiceClient(grpcClient)
 }
 
+type subCommand func([]string) error
+
+// version2 manages how version 2 subcommands are called.
+func version2(args []string) error {
+	cm := map[string]subCommand{
+		"create": create,
+	}
+	if len(args) == 0 {
+		usage()
+	}
+	subCmd := args[0]
+	sc, ok := cm[subCmd]
+	if !ok {
+		return fmt.Errorf("unknown sub-command %q\n", subCmd)
+	}
+	return sc(args[1:])
+}
+
 // logAndExitf is equivalent to Printf to Stderr followed by a call to os.Exit(1).
 func logAndExitf(format string, v ...interface{}) {
 	fmt.Fprintf(os.Stderr, format, v...)
 	os.Exit(1)
 }
+
+// statusFromError returns the message portion of a GRPC error.
+func statusFromError(err error) string {
+	return status.Convert(err).Message()
+}