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: