build: use buildlet on Windows

Also adds "gomote rmall" command.

Fixes golang/go#8640
Fixes golang/go#9799

Change-Id: I41b62b16df5e855e1367a9cc1c370bb1f928c242
Reviewed-on: https://go-review.googlesource.com/4132
Reviewed-by: Andrew Gerrand <adg@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
diff --git a/cmd/buildlet/buildlet.go b/cmd/buildlet/buildlet.go
index 53778ef..fa9840e 100644
--- a/cmd/buildlet/buildlet.go
+++ b/cmd/buildlet/buildlet.go
@@ -77,11 +77,21 @@
 		fixMTU()
 	}
 	if *workDir == "" {
-		dir, err := ioutil.TempDir("", "buildlet-scatch")
-		if err != nil {
-			log.Fatalf("error creating workdir with ioutil.TempDir: %v", err)
+		switch runtime.GOOS {
+		case "windows":
+			// We want a short path on Windows, due to
+			// Windows issues with maximum path lengths.
+			*workDir = `C:\workdir`
+			if err := os.MkdirAll(*workDir, 0755); err != nil {
+				log.Fatalf("error creating workdir: %v", err)
+			}
+		default:
+			dir, err := ioutil.TempDir("", "buildlet-scatch")
+			if err != nil {
+				log.Fatalf("error creating workdir with ioutil.TempDir: %v", err)
+			}
+			*workDir = dir
 		}
-		*workDir = dir
 	}
 	// This is hard-coded because the client-supplied environment has
 	// no way to expand relative paths from the workDir.
@@ -300,7 +310,7 @@
 		if err != nil {
 			return err
 		}
-		rel := strings.TrimPrefix(strings.TrimPrefix(path, base), "/")
+		rel := strings.TrimPrefix(filepath.ToSlash(strings.TrimPrefix(path, base)), "/")
 		var linkName string
 		if fi.Mode()&os.ModeSymlink != 0 {
 			linkName, err = os.Readlink(path)
@@ -507,7 +517,11 @@
 	cmdOutput := flushWriter{w}
 	cmd.Stdout = cmdOutput
 	cmd.Stderr = cmdOutput
-	cmd.Env = append(os.Environ(), r.PostForm["env"]...)
+	cmd.Env = append(baseEnv(), r.PostForm["env"]...)
+
+	fmt.Fprintf(cmdOutput, ":: Running %s with args %q and env %q in dir %s\n\n",
+		cmd.Path, cmd.Args, cmd.Env, cmd.Dir)
+
 	err := cmd.Start()
 	if err == nil {
 		go func() {
@@ -532,6 +546,58 @@
 	log.Printf("Run = %s", state)
 }
 
+func baseEnv() []string {
+	if runtime.GOOS == "windows" {
+		return windowsBaseEnv()
+	}
+	return os.Environ()
+}
+
+func windowsBaseEnv() (e []string) {
+	e = append(e, "GOBUILDEXIT=1") // exit all.bat with completion status
+	btype, err := metadata.InstanceAttributeValue("builder-type")
+	if err != nil {
+		log.Fatalf("Failed to get builder-type: %v", err)
+		return nil
+	}
+	is64 := strings.HasPrefix(btype, "windows-amd64")
+	for _, pair := range os.Environ() {
+		const pathEq = "PATH="
+		if hasPrefixFold(pair, pathEq) {
+			e = append(e, "PATH="+windowsPath(pair[len(pathEq):], is64))
+		} else {
+			e = append(e, pair)
+		}
+	}
+	return e
+}
+
+// hasPrefixFold is a case-insensitive strings.HasPrefix.
+func hasPrefixFold(s, prefix string) bool {
+	return len(s) >= len(prefix) && strings.EqualFold(s[:len(prefix)], prefix)
+}
+
+// windowsPath cleans the windows %PATH% environment.
+// is64Bit is whether this is a windows-amd64-* builder.
+// The PATH is assumed to be that of the image described in env/windows/README.
+func windowsPath(old string, is64Bit bool) string {
+	vv := filepath.SplitList(old)
+	newPath := make([]string, 0, len(vv))
+	for _, v := range vv {
+		// The base VM image has both the 32-bit and 64-bit gcc installed.
+		// They're both in the environment, so scrub the one
+		// we don't want (TDM-GCC-64 or TDM-GCC-32).
+		if strings.Contains(v, "TDM-GCC-") {
+			gcc64 := strings.Contains(v, "TDM-GCC-64")
+			if is64Bit != gcc64 {
+				continue
+			}
+		}
+		newPath = append(newPath, v)
+	}
+	return strings.Join(newPath, string(filepath.ListSeparator))
+}
+
 func handleHalt(w http.ResponseWriter, r *http.Request) {
 	if r.Method != "POST" {
 		http.Error(w, "requires POST method", http.StatusBadRequest)
diff --git a/cmd/gomote/gomote.go b/cmd/gomote/gomote.go
index ccd7eb3..e5aaf49 100644
--- a/cmd/gomote/gomote.go
+++ b/cmd/gomote/gomote.go
@@ -75,12 +75,13 @@
 func registerCommands() {
 	registerCommand("create", "create a buildlet", create)
 	registerCommand("destroy", "destroy a buildlet", destroy)
-	registerCommand("list", "list buildlets", list)
-	registerCommand("run", "run a command on a buildlet", run)
-	registerCommand("put", "put files on a buildlet", put)
-	registerCommand("puttar", "extract a tar.gz to a buildlet", putTar)
-	registerCommand("put14", "put Go 1.4 in place", put14)
 	registerCommand("gettar", "extract a tar.gz from a buildlet", getTar)
+	registerCommand("list", "list buildlets", list)
+	registerCommand("put", "put files on a buildlet", put)
+	registerCommand("put14", "put Go 1.4 in place", put14)
+	registerCommand("puttar", "extract a tar.gz to a buildlet", putTar)
+	registerCommand("rm", "delete files or directories", rm)
+	registerCommand("run", "run a command on a buildlet", run)
 }
 
 func main() {
diff --git a/cmd/gomote/put.go b/cmd/gomote/put.go
index 4f5c7cf..fc77420 100644
--- a/cmd/gomote/put.go
+++ b/cmd/gomote/put.go
@@ -102,7 +102,7 @@
 		return fmt.Errorf("unknown builder %q", name)
 	}
 	if conf.Go14URL == "" {
-		fmt.Println("No Go14URL field defined for %q; ignoring. (may be baked into image)", name)
+		fmt.Printf("No Go14URL field defined for %q; ignoring. (may be baked into image)\n", name)
 		return nil
 	}
 	bc, err := namedClient(name)
diff --git a/cmd/gomote/rm.go b/cmd/gomote/rm.go
new file mode 100644
index 0000000..c8a206c
--- /dev/null
+++ b/cmd/gomote/rm.go
@@ -0,0 +1,33 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+)
+
+func rm(args []string) error {
+	fs := flag.NewFlagSet("rm", flag.ContinueOnError)
+	fs.Usage = func() {
+		fmt.Fprintln(os.Stderr, "create usage: gomote rm <instance> <file-or-dir>+")
+		fmt.Fprintln(os.Stderr, "              gomote rm <instance> .  (to delete everything)")
+		fs.PrintDefaults()
+		os.Exit(1)
+	}
+	fs.Parse(args)
+
+	if fs.NArg() < 2 {
+		fs.Usage()
+	}
+	name := fs.Arg(0)
+	args = fs.Args()[1:]
+	bc, err := namedClient(name)
+	if err != nil {
+		return err
+	}
+	return bc.RemoveAll(args...)
+}
diff --git a/dashboard/builders.go b/dashboard/builders.go
index 51ade10..076d564 100644
--- a/dashboard/builders.go
+++ b/dashboard/builders.go
@@ -303,7 +303,28 @@
 		// and we'll stop timing out on tests.
 		machineType: "n1-highcpu-2",
 	})
-
+	addBuilder(BuildConfig{
+		Name:        "windows-amd64-gce",
+		VMImage:     "windows-buildlet",
+		machineType: "n1-highcpu-2",
+		Go14URL:     "https://storage.googleapis.com/go-builder-data/go1.4-windows-amd64.tar.gz",
+		env:         []string{"GOARCH=amd64", "GOHOSTARCH=amd64"},
+	})
+	addBuilder(BuildConfig{
+		Name:        "windows-amd64-race",
+		VMImage:     "windows-buildlet",
+		machineType: "n1-highcpu-4",
+		Go14URL:     "https://storage.googleapis.com/go-builder-data/go1.4-windows-amd64.tar.gz",
+		env:         []string{"GOARCH=amd64", "GOHOSTARCH=amd64"},
+	})
+	addBuilder(BuildConfig{
+		Name:        "windows-386-gce",
+		VMImage:     "windows-buildlet",
+		machineType: "n1-highcpu-2",
+		buildletURL: "http://storage.googleapis.com/go-builder-data/buildlet.windows-amd64",
+		Go14URL:     "https://storage.googleapis.com/go-builder-data/go1.4-windows-386.tar.gz",
+		env:         []string{"GOARCH=386", "GOHOSTARCH=386"},
+	})
 }
 
 func addBuilder(c BuildConfig) {
diff --git a/env/windows/README b/env/windows/README
index c8a2e39..e50b3b5 100644
--- a/env/windows/README
+++ b/env/windows/README
@@ -3,8 +3,13 @@
 builder image is prepared by hand, following this list of
 instructions:
 
--- in GCE, create a new Windows 2008 server instance. Set its initial username
-   to "wingopher" and the password to something.
+-- In the GCE project's Networks > Default network, edit the "RDP" rule and
+   set its "Target tag" to "allow-rdp".  (VMs will require the "allow-rdp" tag
+   to have any RDP access from the outside)
+
+-- in GCE, create a new Windows 2008 server instance. Set its initial
+   username to "wingopher" and the password to something. Be sure to
+   uncheck "Delete the boot disk when instance terminates".
 
 -- boot it. first boot is slow.
 
@@ -16,14 +21,13 @@
      the builds later. And we don't care about security since this isn't
      going to be Internet-facing. No ports will be accessible
    * disable Windows Error Report (“Not participating”)
-
-TODO: disable the Windows firewall too. might break some outgoing net
-      tests? Figure out where to do this.
+   * disable the Windows firewall (GCE provides its own)
 
 -- Enable auto-login:
    * Start > Run > Open: "control userpasswords2" [OK]
    * Uncheck the "Users must enter a user name..." box (it already was
      unchecked for me)
+   * Click to highlight the sole user (this makes it be the auto-login user)
    * Press "OK"
    * Enter password twice.
 
@@ -31,7 +35,7 @@
    * Bring up cmd.exe (Start > Run > "cmd" [OK])
    * Copy/paste (Right click: paste) this into cmd.exe:
 
-     bitsadmin /transfer mydownloadjob  /download /priority normal https://storage.googleapis.com/winstrap/winstrap-2015-01-03-440bb77.exe c:\users\wingopher\Desktop\winstrap.exe
+     bitsadmin /transfer mydownloadjob  /download /priority normal https://storage.googleapis.com/winstrap/winstrap-2015-01-03-43d7fa3.exe c:\users\wingopher\Desktop\winstrap.exe
 
    * It should appear on the desktop.
 
@@ -52,16 +56,26 @@
    c:\TDM-GCC-32.  Don’t pick other locations. The gobuilder adds
    those to your path.
 
-TODO(bradfitz): this document is incomplete. Cover gcesysprep,
-regedit(?), windows-startup-script-cmd, etc.  And I think winstrap
-will need to write things to something outside of C:\Users, since
-sysprep on reboot nukes all users, at least on GCE. We probably need
-to run the buildlet's stage0 from the windows-startup-script-cmd. Find
-& ask Windows experts.
+-- disable GCE service so things boot quickly and don't kill the user
+   we've configured on boot.
+   - Run Regedit,
+   - Go to the registry entry "HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services"
+   - delete the Google Compute Engine folder.
 
--- 
+-- disable GCEStartupTask in the "Task Scheduler", for the same reason.
+   Maybe only one of these two is required. Might as well do both.
 
-Docs:
+-- shutdown the machine
+
+-- delete the GCE image (but not its boot disk)
+
+-- Go to GCE Compute > Images > New Image and create an image named "windows-buildlet" with
+   "Source Type" of "Disk" and pick the Source Disk from the previously-deleted image.
+   You can now delete the old disk if you want.
+
+
+** Misc docs:
+
 http://www.win2008workstation.com/configure-auto-logon/
 https://cloud.google.com/compute/docs/operating-systems/windows
   -- notably "windows-startup-script-cmd" ala: