[release-branch.go1.9] all: merge master into release-branch.go1.9
0f5d61c4 imports: print dir of candidates in addition to import path
a237aba5 godoc: fix out-of-bounds panic when serving top-level files
4e70a1b2 godoc: add GoogleCN property to pages
fcc44a63 cmd/getgo: add a user-agent to download requests
3fd990c6 cmd/tip: fix the build
d07a458d cmd/tip: add a cert cache, clean up Kubernetes config, use update-deps
9badcbe4 cmd/getgo: prompt warning if an earlier installation exists
6fdd948b godoc: remove disabled admin code
f2b3bb00 cmd/getgo: fix builds
001b4ec8 cmd/getgo: have consistent messages
29518d98 cmd/getgo: display that -i stands for interactive mode
5724bdc2 x/tools/godoc: fix redirect to Gerrit
ac1e4b19 cmd/getgo: initial commit
Change-Id: Ie58293a2621bbabbadea4f9431c6fe7bc4aa329f
diff --git a/cmd/getgo/.dockerignore b/cmd/getgo/.dockerignore
new file mode 100644
index 0000000..2b87ad9
--- /dev/null
+++ b/cmd/getgo/.dockerignore
@@ -0,0 +1,5 @@
+.git
+.dockerignore
+LICENSE
+README.md
+.gitignore
diff --git a/cmd/getgo/.gitignore b/cmd/getgo/.gitignore
new file mode 100644
index 0000000..d4984ab
--- /dev/null
+++ b/cmd/getgo/.gitignore
@@ -0,0 +1,3 @@
+build
+testgetgo
+getgo
diff --git a/cmd/getgo/Dockerfile b/cmd/getgo/Dockerfile
new file mode 100644
index 0000000..78fd956
--- /dev/null
+++ b/cmd/getgo/Dockerfile
@@ -0,0 +1,20 @@
+FROM golang:latest
+
+ENV SHELL /bin/bash
+ENV HOME /root
+WORKDIR $HOME
+
+COPY . /go/src/golang.org/x/tools/cmd/getgo
+
+RUN ( \
+ cd /go/src/golang.org/x/tools/cmd/getgo \
+ && go build \
+ && mv getgo /usr/local/bin/getgo \
+ )
+
+# undo the adding of GOPATH to env for testing
+ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+ENV GOPATH ""
+
+# delete /go and /usr/local/go for testing
+RUN rm -rf /go /usr/local/go
diff --git a/cmd/getgo/LICENSE b/cmd/getgo/LICENSE
new file mode 100644
index 0000000..32017f8
--- /dev/null
+++ b/cmd/getgo/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cmd/getgo/README.md b/cmd/getgo/README.md
new file mode 100644
index 0000000..e62a6c2
--- /dev/null
+++ b/cmd/getgo/README.md
@@ -0,0 +1,71 @@
+# getgo
+
+A proof-of-concept command-line installer for Go.
+
+This installer is designed to both install Go as well as do the initial configuration
+of setting up the right environment variables and paths.
+
+It will install the Go distribution (tools & stdlib) to "/.go" inside your home directory by default.
+
+It will setup "$HOME/go" as your GOPATH.
+This is where third party libraries and apps will be installed as well as where you will write your Go code.
+
+If Go is already installed via this installer it will upgrade it to the latest version of Go.
+
+Currently the installer supports Windows, \*nix and macOS on x86 & x64.
+It supports Bash and Zsh on all of these platforms as well as powershell & cmd.exe on Windows.
+
+## Usage
+
+Windows Powershell/cmd.exe:
+
+`(New-Object System.Net.WebClient).DownloadFile('https://get.golang.org/installer.exe', 'installer.exe'); Start-Process -Wait -NonewWindow installer.exe; Remove-Item installer.exe`
+
+Shell (Linux/macOS/Windows):
+
+`curl -LO https://get.golang.org/$(uname)/go_installer && chmod +x go_installer && ./go_installer && rm go_installer`
+
+## To Do
+
+* Check if Go is already installed (via a different method) and update it in place or at least notify the user
+* Lots of testing. It's only had limited testing so far.
+* Add support for additional shells.
+
+## Development instructions
+
+### Testing
+
+There are integration tests in [`main_test.go`](main_test.go). Please add more
+tests there.
+
+#### On unix/linux with the Dockerfile
+
+The Dockerfile automatically builds the binary, moves it to
+`/usr/local/bin/getgo` and then unsets `$GOPATH` and removes all `$GOPATH` from
+`$PATH`.
+
+```bash
+$ docker build --rm --force-rm -t getgo .
+...
+$ docker run --rm -it getgo bash
+root@78425260fad0:~# getgo -v
+Welcome to the Go installer!
+Downloading Go version go1.8.3 to /usr/local/go
+This may take a bit of time...
+Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin" to /root/.bashrc
+Downloaded!
+Setting up GOPATH
+Adding "export GOPATH=/root/go" to /root/.bashrc
+Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin" to /root/.bashrc
+GOPATH has been setup!
+root@78425260fad0:~# which go
+/usr/local/go/bin/go
+root@78425260fad0:~# echo $GOPATH
+/root/go
+root@78425260fad0:~# echo $PATH
+/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin
+```
+
+## Release instructions
+
+To upload a new release of getgo, run `./make.bash && ./upload.bash`.
diff --git a/cmd/getgo/download.go b/cmd/getgo/download.go
new file mode 100644
index 0000000..ae46549
--- /dev/null
+++ b/cmd/getgo/download.go
@@ -0,0 +1,182 @@
+// Copyright 2017 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 (
+ "archive/tar"
+ "archive/zip"
+ "compress/gzip"
+ "crypto/sha256"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+const (
+ currentVersionURL = "https://golang.org/VERSION?m=text"
+ downloadURLPrefix = "https://storage.googleapis.com/golang"
+)
+
+// downloadGoVersion downloads and upacks the specific go version to dest/go.
+func downloadGoVersion(version, ops, arch, dest string) error {
+ suffix := "tar.gz"
+ if ops == "windows" {
+ suffix = "zip"
+ }
+ uri := fmt.Sprintf("%s/%s.%s-%s.%s", downloadURLPrefix, version, ops, arch, suffix)
+
+ verbosef("Downloading %s", uri)
+
+ req, err := http.NewRequest("GET", uri, nil)
+ if err != nil {
+ return err
+ }
+ req.Header.Add("User-Agent", fmt.Sprintf("golang.org-getgo/%s", version))
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return fmt.Errorf("Downloading Go from %s failed: %v", uri, err)
+ }
+ if resp.StatusCode > 299 {
+ return fmt.Errorf("Downloading Go from %s failed with HTTP status %s", resp.Status)
+ }
+ defer resp.Body.Close()
+
+ tmpf, err := ioutil.TempFile("", "go")
+ if err != nil {
+ return err
+ }
+ defer os.Remove(tmpf.Name())
+
+ h := sha256.New()
+
+ w := io.MultiWriter(tmpf, h)
+ if _, err := io.Copy(w, resp.Body); err != nil {
+ return err
+ }
+
+ verbosef("Downloading SHA %s.sha256", uri)
+
+ sresp, err := http.Get(uri + ".sha256")
+ if err != nil {
+ return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed: %v", uri, err)
+ }
+ defer sresp.Body.Close()
+ if sresp.StatusCode > 299 {
+ return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed with HTTP status %s", sresp.Status)
+ }
+
+ shasum, err := ioutil.ReadAll(sresp.Body)
+ if err != nil {
+ return err
+ }
+
+ // Check the shasum.
+ sum := fmt.Sprintf("%x", h.Sum(nil))
+ if sum != string(shasum) {
+ return fmt.Errorf("Shasum mismatch %s vs. %s", sum, string(shasum))
+ }
+
+ unpackFunc := unpackTar
+ if ops == "windows" {
+ unpackFunc = unpackZip
+ }
+ if err := unpackFunc(tmpf.Name(), dest); err != nil {
+ return fmt.Errorf("Unpacking Go to %s failed: %v", dest, err)
+ }
+ return nil
+}
+
+func unpack(dest, name string, fi os.FileInfo, r io.Reader) error {
+ if strings.HasPrefix(name, "go/") {
+ name = name[len("go/"):]
+ }
+
+ path := filepath.Join(dest, name)
+ if fi.IsDir() {
+ return os.MkdirAll(path, fi.Mode())
+ }
+
+ f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ _, err = io.Copy(f, r)
+ return err
+}
+
+func unpackTar(src, dest string) error {
+ r, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ archive, err := gzip.NewReader(r)
+ if err != nil {
+ return err
+ }
+ defer archive.Close()
+
+ tarReader := tar.NewReader(archive)
+
+ for {
+ header, err := tarReader.Next()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return err
+ }
+
+ if err := unpack(dest, header.Name, header.FileInfo(), tarReader); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func unpackZip(src, dest string) error {
+ zr, err := zip.OpenReader(src)
+ if err != nil {
+ return err
+ }
+
+ for _, f := range zr.File {
+ fr, err := f.Open()
+ if err != nil {
+ return err
+ }
+ if err := unpack(dest, f.Name, f.FileInfo(), fr); err != nil {
+ return err
+ }
+ fr.Close()
+ }
+
+ return nil
+}
+
+func getLatestGoVersion() (string, error) {
+ resp, err := http.Get(currentVersionURL)
+ if err != nil {
+ return "", fmt.Errorf("Getting current Go version failed: %v", err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode > 299 {
+ b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 1024))
+ return "", fmt.Errorf("Could not get current Go version: HTTP %d: %q", resp.StatusCode, b)
+ }
+ version, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(version)), nil
+}
diff --git a/cmd/getgo/download_test.go b/cmd/getgo/download_test.go
new file mode 100644
index 0000000..1a47823
--- /dev/null
+++ b/cmd/getgo/download_test.go
@@ -0,0 +1,34 @@
+// Copyright 2017 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 (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestDownloadGoVersion(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("Skipping download in short mode")
+ }
+
+ tmpd, err := ioutil.TempDir("", "go")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpd)
+
+ if err := downloadGoVersion("go1.8.1", "linux", "amd64", filepath.Join(tmpd, "go")); err != nil {
+ t.Fatal(err)
+ }
+
+ // Ensure the VERSION file exists.
+ vf := filepath.Join(tmpd, "go", "VERSION")
+ if _, err := os.Stat(vf); os.IsNotExist(err) {
+ t.Fatalf("file %s does not exist and should", vf)
+ }
+}
diff --git a/cmd/getgo/main.go b/cmd/getgo/main.go
new file mode 100644
index 0000000..8d9fbd4
--- /dev/null
+++ b/cmd/getgo/main.go
@@ -0,0 +1,115 @@
+// Copyright 2017 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.
+
+// The getgo command installs Go to the user's system.
+package main
+
+import (
+ "bufio"
+ "context"
+ "errors"
+ "flag"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+var (
+ interactive = flag.Bool("i", false, "Interactive mode, prompt for inputs.")
+ verbose = flag.Bool("v", false, "Verbose.")
+ setupOnly = flag.Bool("skip-dl", false, "Don't download - only set up environment variables")
+ goVersion = flag.String("version", "", `Version of Go to install (e.g. "1.8.3"). If empty, uses the latest version.`)
+
+ version = "devel"
+)
+
+var exitCleanly error = errors.New("exit cleanly sentinel value")
+
+func main() {
+ flag.Parse()
+ if *goVersion != "" && !strings.HasPrefix(*goVersion, "go") {
+ *goVersion = "go" + *goVersion
+ }
+
+ ctx := context.Background()
+
+ verbosef("version " + version)
+
+ runStep := func(s step) {
+ err := s(ctx)
+ if err == exitCleanly {
+ os.Exit(0)
+ }
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+ }
+
+ if !*setupOnly {
+ runStep(welcome)
+ runStep(checkOthers)
+ runStep(chooseVersion)
+ runStep(downloadGo)
+ }
+
+ runStep(setupGOPATH)
+}
+
+func verbosef(format string, v ...interface{}) {
+ if !*verbose {
+ return
+ }
+
+ fmt.Printf(format+"\n", v...)
+}
+
+func prompt(ctx context.Context, query, defaultAnswer string) (string, error) {
+ if !*interactive {
+ return defaultAnswer, nil
+ }
+
+ fmt.Printf("%s [%s]: ", query, defaultAnswer)
+
+ type result struct {
+ answer string
+ err error
+ }
+ ch := make(chan result, 1)
+ go func() {
+ s := bufio.NewScanner(os.Stdin)
+ if !s.Scan() {
+ ch <- result{"", s.Err()}
+ return
+ }
+ answer := s.Text()
+ if answer == "" {
+ answer = defaultAnswer
+ }
+ ch <- result{answer, nil}
+ }()
+
+ select {
+ case r := <-ch:
+ return r.answer, r.err
+ case <-ctx.Done():
+ return "", ctx.Err()
+ }
+}
+
+func runCommand(ctx context.Context, prog string, args ...string) ([]byte, error) {
+ verbosef("Running command: %s %v", prog, args)
+
+ cmd := exec.CommandContext(ctx, prog, args...)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return nil, fmt.Errorf("running cmd '%s %s' failed: %s err: %v", prog, strings.Join(args, " "), string(out), err)
+ }
+ if out != nil && err == nil && len(out) != 0 {
+ verbosef("%s", out)
+ }
+
+ return out, nil
+}
diff --git a/cmd/getgo/main_test.go b/cmd/getgo/main_test.go
new file mode 100644
index 0000000..932a739
--- /dev/null
+++ b/cmd/getgo/main_test.go
@@ -0,0 +1,171 @@
+// Copyright 2017 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 (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "runtime"
+ "testing"
+)
+
+const (
+ testbin = "testgetgo"
+)
+
+var (
+ exeSuffix string // ".exe" on Windows
+)
+
+func init() {
+ if runtime.GOOS == "windows" {
+ exeSuffix = ".exe"
+ }
+}
+
+// TestMain creates a getgo command for testing purposes and
+// deletes it after the tests have been run.
+func TestMain(m *testing.M) {
+ if os.Getenv("GOGET_INTEGRATION") == "" {
+ fmt.Fprintln(os.Stderr, "main_test: Skipping integration tests with GOGET_INTEGRATION unset")
+ return
+ }
+
+ args := []string{"build", "-tags", testbin, "-o", testbin + exeSuffix}
+ out, err := exec.Command("go", args...).CombinedOutput()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "building %s failed: %v\n%s", testbin, err, out)
+ os.Exit(2)
+ }
+
+ // Don't let these environment variables confuse the test.
+ os.Unsetenv("GOBIN")
+ os.Unsetenv("GOPATH")
+ os.Unsetenv("GIT_ALLOW_PROTOCOL")
+ os.Unsetenv("PATH")
+
+ r := m.Run()
+
+ os.Remove(testbin + exeSuffix)
+
+ os.Exit(r)
+}
+
+func createTmpHome(t *testing.T) string {
+ tmpd, err := ioutil.TempDir("", "testgetgo")
+ if err != nil {
+ t.Fatalf("creating test tempdir failed: %v", err)
+ }
+
+ os.Setenv("HOME", tmpd)
+ return tmpd
+}
+
+// doRun runs the test getgo command, recording stdout and stderr and
+// returning exit status.
+func doRun(t *testing.T, args ...string) error {
+ var stdout, stderr bytes.Buffer
+ t.Logf("running %s %v", testbin, args)
+ cmd := exec.Command("./"+testbin+exeSuffix, args...)
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ cmd.Env = os.Environ()
+ status := cmd.Run()
+ if stdout.Len() > 0 {
+ t.Log("standard output:")
+ t.Log(stdout.String())
+ }
+ if stderr.Len() > 0 {
+ t.Log("standard error:")
+ t.Log(stderr.String())
+ }
+ return status
+}
+
+func TestCommandVerbose(t *testing.T) {
+ tmpd := createTmpHome(t)
+ defer os.RemoveAll(tmpd)
+
+ err := doRun(t, "-v")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // make sure things are in path
+ shellConfig, err := shellConfigFile()
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := ioutil.ReadFile(shellConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+ home, err := getHomeDir()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := fmt.Sprintf(`
+export PATH=$PATH:%s/.go/bin
+
+export GOPATH=%s/go
+
+export PATH=$PATH:%s/go/bin
+`, home, home, home)
+
+ if string(b) != expected {
+ t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
+ }
+}
+
+func TestCommandPathExists(t *testing.T) {
+ tmpd := createTmpHome(t)
+ defer os.RemoveAll(tmpd)
+
+ // run once
+ err := doRun(t, "-skip-dl")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // make sure things are in path
+ shellConfig, err := shellConfigFile()
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := ioutil.ReadFile(shellConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+ home, err := getHomeDir()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := fmt.Sprintf(`
+export GOPATH=%s/go
+
+export PATH=$PATH:%s/go/bin
+`, home, home)
+
+ if string(b) != expected {
+ t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
+ }
+
+ // run twice
+ if err := doRun(t, "-skip-dl"); err != nil {
+ t.Fatal(err)
+ }
+
+ b, err = ioutil.ReadFile(shellConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if string(b) != expected {
+ t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
+ }
+}
diff --git a/cmd/getgo/make.bash b/cmd/getgo/make.bash
new file mode 100755
index 0000000..cbc3685
--- /dev/null
+++ b/cmd/getgo/make.bash
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Copyright 2017 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.
+
+set -e -o -x
+
+LDFLAGS="-X main.version=$(git describe --always --dirty='*')"
+
+GOOS=windows GOARCH=386 go build -o build/installer.exe -ldflags="$LDFLAGS"
+GOOS=linux GOARCH=386 go build -o build/installer_linux -ldflags="$LDFLAGS"
+GOOS=darwin GOARCH=386 go build -o build/installer_darwin -ldflags="$LDFLAGS"
diff --git a/cmd/getgo/path.go b/cmd/getgo/path.go
new file mode 100644
index 0000000..551ac42
--- /dev/null
+++ b/cmd/getgo/path.go
@@ -0,0 +1,153 @@
+// Copyright 2017 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 (
+ "bufio"
+ "context"
+ "fmt"
+ "os"
+ "os/user"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+const (
+ bashConfig = ".bash_profile"
+ zshConfig = ".zshrc"
+)
+
+// appendToPATH adds the given path to the PATH environment variable and
+// persists it for future sessions.
+func appendToPATH(value string) error {
+ if isInPATH(value) {
+ return nil
+ }
+ return persistEnvVar("PATH", pathVar+envSeparator+value)
+}
+
+func isInPATH(dir string) bool {
+ p := os.Getenv("PATH")
+
+ paths := strings.Split(p, envSeparator)
+ for _, d := range paths {
+ if d == dir {
+ return true
+ }
+ }
+
+ return false
+}
+
+func getHomeDir() (string, error) {
+ home := os.Getenv(homeKey)
+ if home != "" {
+ return home, nil
+ }
+
+ u, err := user.Current()
+ if err != nil {
+ return "", err
+ }
+ return u.HomeDir, nil
+}
+
+func checkStringExistsFile(filename, value string) (bool, error) {
+ file, err := os.OpenFile(filename, os.O_RDONLY, 0600)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if line == value {
+ return true, nil
+ }
+ }
+
+ return false, scanner.Err()
+}
+
+func appendToFile(filename, value string) error {
+ verbosef("Adding %q to %s", value, filename)
+
+ ok, err := checkStringExistsFile(filename, value)
+ if err != nil {
+ return err
+ }
+ if ok {
+ // Nothing to do.
+ return nil
+ }
+
+ f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ _, err = f.WriteString(lineEnding + value + lineEnding)
+ return err
+}
+
+func isShell(name string) bool {
+ return strings.Contains(currentShell(), name)
+}
+
+// persistEnvVarWindows sets an environment variable in the Windows
+// registry.
+func persistEnvVarWindows(name, value string) error {
+ _, err := runCommand(context.Background(), "powershell", "-command",
+ fmt.Sprintf(`[Environment]::SetEnvironmentVariable("%s", "%s", "User")`, name, value))
+ return err
+}
+
+func persistEnvVar(name, value string) error {
+ if runtime.GOOS == "windows" {
+ if err := persistEnvVarWindows(name, value); err != nil {
+ return err
+ }
+
+ if isShell("cmd.exe") || isShell("powershell.exe") {
+ return os.Setenv(strings.ToUpper(name), value)
+ }
+ // User is in bash, zsh, etc.
+ // Also set the environment variable in their shell config.
+ }
+
+ rc, err := shellConfigFile()
+ if err != nil {
+ return err
+ }
+
+ line := fmt.Sprintf("export %s=%s", strings.ToUpper(name), value)
+ if err := appendToFile(rc, line); err != nil {
+ return err
+ }
+
+ return os.Setenv(strings.ToUpper(name), value)
+}
+
+func shellConfigFile() (string, error) {
+ home, err := getHomeDir()
+ if err != nil {
+ return "", err
+ }
+
+ switch {
+ case isShell("bash"):
+ return filepath.Join(home, bashConfig), nil
+ case isShell("zsh"):
+ return filepath.Join(home, zshConfig), nil
+ default:
+ return "", fmt.Errorf("%q is not a supported shell", currentShell())
+ }
+}
diff --git a/cmd/getgo/path_test.go b/cmd/getgo/path_test.go
new file mode 100644
index 0000000..4cf6647
--- /dev/null
+++ b/cmd/getgo/path_test.go
@@ -0,0 +1,56 @@
+// Copyright 2017 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 (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestAppendPath(t *testing.T) {
+ tmpd, err := ioutil.TempDir("", "go")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpd)
+
+ if err := os.Setenv("HOME", tmpd); err != nil {
+ t.Fatal(err)
+ }
+
+ GOPATH := os.Getenv("GOPATH")
+ if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
+ t.Fatal(err)
+ }
+
+ shellConfig, err := shellConfigFile()
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := ioutil.ReadFile(shellConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := "export PATH=" + pathVar + envSeparator + filepath.Join(GOPATH, "bin")
+ if strings.TrimSpace(string(b)) != expected {
+ t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
+ }
+
+ // Check that appendToPATH is idempotent.
+ if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
+ t.Fatal(err)
+ }
+ b, err = ioutil.ReadFile(shellConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.TrimSpace(string(b)) != expected {
+ t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
+ }
+}
diff --git a/cmd/getgo/server/README.md b/cmd/getgo/server/README.md
new file mode 100644
index 0000000..0cf629d
--- /dev/null
+++ b/cmd/getgo/server/README.md
@@ -0,0 +1,7 @@
+# getgo server
+
+## Deployment
+
+```
+gcloud app deploy --promote --project golang-org
+```
diff --git a/cmd/getgo/server/app.yaml b/cmd/getgo/server/app.yaml
new file mode 100644
index 0000000..0502c4e
--- /dev/null
+++ b/cmd/getgo/server/app.yaml
@@ -0,0 +1,7 @@
+runtime: go
+service: get
+api_version: go1
+
+handlers:
+- url: /.*
+ script: _go_app
diff --git a/cmd/getgo/server/main.go b/cmd/getgo/server/main.go
new file mode 100644
index 0000000..b599506
--- /dev/null
+++ b/cmd/getgo/server/main.go
@@ -0,0 +1,61 @@
+// Copyright 2017 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.
+
+// Command server serves get.golang.org, redirecting users to the appropriate
+// getgo installer based on the request path.
+package server
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+)
+
+const (
+ base = "https://storage.googleapis.com/golang/getgo/"
+ windowsInstaller = base + "installer.exe"
+ linuxInstaller = base + "installer_linux"
+ macInstaller = base + "installer_darwin"
+)
+
+// substring-based redirects.
+var stringMatch = map[string]string{
+ // via uname, from bash
+ "MINGW": windowsInstaller, // Reported as MINGW64_NT-10.0 in git bash
+ "Linux": linuxInstaller,
+ "Darwin": macInstaller,
+}
+
+func init() {
+ http.HandleFunc("/", handler)
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+ if containsIgnoreCase(r.URL.Path, "installer.exe") {
+ // cache bust
+ http.Redirect(w, r, windowsInstaller+cacheBust(), http.StatusFound)
+ return
+ }
+
+ for match, redirect := range stringMatch {
+ if containsIgnoreCase(r.URL.Path, match) {
+ http.Redirect(w, r, redirect, http.StatusFound)
+ return
+ }
+ }
+
+ http.NotFound(w, r)
+}
+
+func containsIgnoreCase(s, substr string) bool {
+ return strings.Contains(
+ strings.ToLower(s),
+ strings.ToLower(substr),
+ )
+}
+
+func cacheBust() string {
+ return fmt.Sprintf("?%d", time.Now().Nanosecond())
+}
diff --git a/cmd/getgo/steps.go b/cmd/getgo/steps.go
new file mode 100644
index 0000000..eac6517
--- /dev/null
+++ b/cmd/getgo/steps.go
@@ -0,0 +1,131 @@
+// Copyright 2017 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 (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+type step func(context.Context) error
+
+func welcome(ctx context.Context) error {
+ fmt.Println("Welcome to the Go installer!")
+ answer, err := prompt(ctx, "Would you like to install Go? Y/n", "Y")
+ if err != nil {
+ return err
+ }
+ if strings.ToLower(answer) != "y" {
+ fmt.Println("Exiting install.")
+ return exitCleanly
+ }
+
+ return nil
+}
+
+func checkOthers(ctx context.Context) error {
+ // TODO: if go is currently installed install new version over that
+ path, err := whichGo(ctx)
+ if err != nil {
+ fmt.Printf("Cannot check if Go is already installed:\n%v\n", err)
+ }
+ if path == "" {
+ return nil
+ }
+ if path != installPath {
+ fmt.Printf("Go is already installed at %v; remove it from your PATH.\n", path)
+ }
+ return nil
+}
+
+func chooseVersion(ctx context.Context) error {
+ if *goVersion != "" {
+ return nil
+ }
+
+ var err error
+ *goVersion, err = getLatestGoVersion()
+ if err != nil {
+ return err
+ }
+
+ answer, err := prompt(ctx, fmt.Sprintf("The latest Go version is %s, install that? Y/n", *goVersion), "Y")
+ if err != nil {
+ return err
+ }
+
+ if strings.ToLower(answer) != "y" {
+ // TODO: handle passing a version
+ fmt.Println("Aborting install.")
+ return exitCleanly
+ }
+
+ return nil
+}
+
+func downloadGo(ctx context.Context) error {
+ answer, err := prompt(ctx, fmt.Sprintf("Download Go version %s to %s? Y/n", *goVersion, installPath), "Y")
+ if err != nil {
+ return err
+ }
+
+ if strings.ToLower(answer) != "y" {
+ fmt.Println("Aborting install.")
+ return exitCleanly
+ }
+
+ fmt.Printf("Downloading Go version %s to %s\n", *goVersion, installPath)
+ fmt.Println("This may take a bit of time...")
+
+ if err := downloadGoVersion(*goVersion, runtime.GOOS, arch, installPath); err != nil {
+ return err
+ }
+
+ if err := appendToPATH(filepath.Join(installPath, "bin")); err != nil {
+ return err
+ }
+
+ fmt.Println("Downloaded!")
+ return nil
+}
+
+func setupGOPATH(ctx context.Context) error {
+ answer, err := prompt(ctx, "Would you like us to setup your GOPATH? Y/n", "Y")
+ if err != nil {
+ return err
+ }
+
+ if strings.ToLower(answer) != "y" {
+ fmt.Println("Exiting and not setting up GOPATH.")
+ return exitCleanly
+ }
+
+ fmt.Println("Setting up GOPATH")
+ home, err := getHomeDir()
+ if err != nil {
+ return err
+ }
+
+ gopath := os.Getenv("GOPATH")
+ if gopath == "" {
+ // set $GOPATH
+ gopath = filepath.Join(home, "go")
+ if err := persistEnvVar("GOPATH", gopath); err != nil {
+ return err
+ }
+ fmt.Println("GOPATH has been set up!")
+ } else {
+ verbosef("GOPATH is already set to %s", gopath)
+ }
+
+ if err := appendToPATH(filepath.Join(gopath, "bin")); err != nil {
+ return err
+ }
+ return persistEnvChangesForSession()
+}
diff --git a/cmd/getgo/system.go b/cmd/getgo/system.go
new file mode 100644
index 0000000..8b5b024
--- /dev/null
+++ b/cmd/getgo/system.go
@@ -0,0 +1,36 @@
+// Copyright 2017 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 (
+ "bytes"
+ "context"
+ "os/exec"
+ "runtime"
+ "strings"
+)
+
+// arch contains either amd64 or 386.
+var arch = func() string {
+ cmd := exec.Command("uname", "-m") // "x86_64"
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("powershell", "-command", "(Get-WmiObject -Class Win32_ComputerSystem).SystemType") // "x64-based PC"
+ }
+
+ out, err := cmd.Output()
+ if err != nil {
+ // a sensible default?
+ return "amd64"
+ }
+ if bytes.Contains(out, []byte("64")) {
+ return "amd64"
+ }
+ return "386"
+}()
+
+func findGo(ctx context.Context, cmd string) (string, error) {
+ out, err := exec.CommandContext(ctx, cmd, "go").CombinedOutput()
+ return strings.TrimSpace(string(out)), err
+}
diff --git a/cmd/getgo/system_unix.go b/cmd/getgo/system_unix.go
new file mode 100644
index 0000000..d2405c5
--- /dev/null
+++ b/cmd/getgo/system_unix.go
@@ -0,0 +1,55 @@
+// Copyright 2017 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.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+const (
+ envSeparator = ":"
+ homeKey = "HOME"
+ lineEnding = "\n"
+ pathVar = "$PATH"
+)
+
+var installPath = func() string {
+ home, err := getHomeDir()
+ if err != nil {
+ return "/usr/local/go"
+ }
+
+ return filepath.Join(home, ".go")
+}()
+
+func whichGo(ctx context.Context) (string, error) {
+ return findGo(ctx, "which")
+}
+
+func isWindowsXP() bool {
+ return false
+}
+
+func currentShell() string {
+ return os.Getenv("SHELL")
+}
+
+func persistEnvChangesForSession() error {
+ shellConfig, err := shellConfigFile()
+ if err != nil {
+ return err
+ }
+ fmt.Println()
+ fmt.Printf("One more thing! Run `source %s` to persist the\n", shellConfig)
+ fmt.Println("new environment variables to your current session, or open a")
+ fmt.Println("new shell prompt.")
+
+ return nil
+}
diff --git a/cmd/getgo/system_windows.go b/cmd/getgo/system_windows.go
new file mode 100644
index 0000000..d8a6191
--- /dev/null
+++ b/cmd/getgo/system_windows.go
@@ -0,0 +1,86 @@
+// Copyright 2017 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.
+
+// +build windows
+
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+const (
+ envSeparator = ";"
+ homeKey = "USERPROFILE"
+ lineEnding = "/r/n"
+ pathVar = "$env:Path"
+)
+
+var installPath = `c:\go`
+
+func isWindowsXP() bool {
+ v, err := syscall.GetVersion()
+ if err != nil {
+ log.Fatalf("GetVersion failed: %v", err)
+ }
+ major := byte(v)
+ return major < 6
+}
+
+func whichGo(ctx context.Context) (string, error) {
+ return findGo(ctx, "where")
+}
+
+// currentShell reports the current shell.
+// It might be "powershell.exe", "cmd.exe" or any of the *nix shells.
+//
+// Returns empty string if the shell is unknown.
+func currentShell() string {
+ shell := os.Getenv("SHELL")
+ if shell != "" {
+ return shell
+ }
+
+ pid := os.Getppid()
+ pe, err := getProcessEntry(pid)
+ if err != nil {
+ verbosef("getting shell from process entry failed: %v", err)
+ return ""
+ }
+
+ return syscall.UTF16ToString(pe.ExeFile[:])
+}
+
+func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
+ // From https://go.googlesource.com/go/+/go1.8.3/src/syscall/syscall_windows.go#941
+ snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
+ if err != nil {
+ return nil, err
+ }
+ defer syscall.CloseHandle(snapshot)
+
+ var procEntry syscall.ProcessEntry32
+ procEntry.Size = uint32(unsafe.Sizeof(procEntry))
+ if err = syscall.Process32First(snapshot, &procEntry); err != nil {
+ return nil, err
+ }
+
+ for {
+ if procEntry.ProcessID == uint32(pid) {
+ return &procEntry, nil
+ }
+
+ if err := syscall.Process32Next(snapshot, &procEntry); err != nil {
+ return nil, err
+ }
+ }
+}
+
+func persistEnvChangesForSession() error {
+ return nil
+}
diff --git a/cmd/getgo/upload.bash b/cmd/getgo/upload.bash
new file mode 100755
index 0000000..f52bb23
--- /dev/null
+++ b/cmd/getgo/upload.bash
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Copyright 2017 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.
+
+if ! command -v gsutil 2>&1 > /dev/null; then
+ echo "Install gsutil:"
+ echo
+ echo " https://cloud.google.com/storage/docs/gsutil_install#sdk-install"
+fi
+
+if [ ! -d build ]; then
+ echo "Run make.bash first"
+fi
+
+set -e -o -x
+
+gsutil -m cp -a public-read build/* gs://golang/getgo
diff --git a/cmd/tip/Dockerfile b/cmd/tip/Dockerfile
index 9c80137..86dfe36 100644
--- a/cmd/tip/Dockerfile
+++ b/cmd/tip/Dockerfile
@@ -5,11 +5,128 @@
# golang puts its go install here (weird but true)
ENV GOROOT_BOOTSTRAP /usr/local/go
-RUN go get -d golang.org/x/crypto/acme/autocert
+# BEGIN deps (run `make update-deps` to update)
+
+# Repo cloud.google.com/go at 76d607c (2017-07-20)
+ENV REV=76d607c4e7a2b9df49f1d1a58a3f3d2dd2614704
+RUN go get -d cloud.google.com/go/compute/metadata `#and 6 other pkgs` &&\
+ (cd /go/src/cloud.google.com/go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo github.com/golang/protobuf at 0a4f71a (2017-07-11)
+ENV REV=0a4f71a498b7c4812f64969510bcb4eca251e33a
+RUN go get -d github.com/golang/protobuf/proto `#and 6 other pkgs` &&\
+ (cd /go/src/github.com/golang/protobuf && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo github.com/googleapis/gax-go at 84ed267 (2017-06-10)
+ENV REV=84ed26760e7f6f80887a2fbfb50db3cc415d2cea
+RUN go get -d github.com/googleapis/gax-go &&\
+ (cd /go/src/github.com/googleapis/gax-go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/build at da1460b (2017-07-31)
+ENV REV=da1460b7c9c9b65383d1336593ed9ad346f6a1c5
+RUN go get -d golang.org/x/build/autocertcache &&\
+ (cd /go/src/golang.org/x/build && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/crypto at 6914964 (2017-07-20)
+ENV REV=6914964337150723782436d56b3f21610a74ce7b
+RUN go get -d golang.org/x/crypto/acme `#and 2 other pkgs` &&\
+ (cd /go/src/golang.org/x/crypto && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/net at ab54850 (2017-07-21)
+ENV REV=ab5485076ff3407ad2d02db054635913f017b0ed
+RUN go get -d golang.org/x/net/context `#and 8 other pkgs` &&\
+ (cd /go/src/golang.org/x/net && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/oauth2 at b53b38a (2017-07-19)
+ENV REV=b53b38ad8a6435bd399ea76d0fa74f23149cca4e
+RUN go get -d golang.org/x/oauth2 `#and 5 other pkgs` &&\
+ (cd /go/src/golang.org/x/oauth2 && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/text at 836efe4 (2017-07-14)
+ENV REV=836efe42bb4aa16aaa17b9c155d8813d336ed720
+RUN go get -d golang.org/x/text/secure/bidirule `#and 4 other pkgs` &&\
+ (cd /go/src/golang.org/x/text && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo google.golang.org/api at 295e4bb (2017-07-18)
+ENV REV=295e4bb0ade057ae2cfb9876ab0b54635dbfcea4
+RUN go get -d google.golang.org/api/gensupport `#and 9 other pkgs` &&\
+ (cd /go/src/google.golang.org/api && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo google.golang.org/genproto at b0a3dcf (2017-07-12)
+ENV REV=b0a3dcfcd1a9bd48e63634bd8802960804cf8315
+RUN go get -d google.golang.org/genproto/googleapis/api/annotations `#and 3 other pkgs` &&\
+ (cd /go/src/google.golang.org/genproto && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo google.golang.org/grpc at fa1cb32 (2017-07-31)
+ENV REV=fa1cb32dc4f81e23ab862dd5e7ac4f2920a33088
+RUN go get -d google.golang.org/grpc `#and 14 other pkgs` &&\
+ (cd /go/src/google.golang.org/grpc && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Optimization to speed up iterative development, not necessary for correctness:
+RUN go install cloud.google.com/go/compute/metadata \
+ cloud.google.com/go/iam \
+ cloud.google.com/go/internal \
+ cloud.google.com/go/internal/optional \
+ cloud.google.com/go/internal/version \
+ cloud.google.com/go/storage \
+ github.com/golang/protobuf/proto \
+ github.com/golang/protobuf/protoc-gen-go/descriptor \
+ github.com/golang/protobuf/ptypes \
+ github.com/golang/protobuf/ptypes/any \
+ github.com/golang/protobuf/ptypes/duration \
+ github.com/golang/protobuf/ptypes/timestamp \
+ github.com/googleapis/gax-go \
+ golang.org/x/build/autocertcache \
+ golang.org/x/crypto/acme \
+ golang.org/x/crypto/acme/autocert \
+ golang.org/x/net/context \
+ golang.org/x/net/context/ctxhttp \
+ golang.org/x/net/http2 \
+ golang.org/x/net/http2/hpack \
+ golang.org/x/net/idna \
+ golang.org/x/net/internal/timeseries \
+ golang.org/x/net/lex/httplex \
+ golang.org/x/net/trace \
+ golang.org/x/oauth2 \
+ golang.org/x/oauth2/google \
+ golang.org/x/oauth2/internal \
+ golang.org/x/oauth2/jws \
+ golang.org/x/oauth2/jwt \
+ golang.org/x/text/secure/bidirule \
+ golang.org/x/text/transform \
+ golang.org/x/text/unicode/bidi \
+ golang.org/x/text/unicode/norm \
+ google.golang.org/api/gensupport \
+ google.golang.org/api/googleapi \
+ google.golang.org/api/googleapi/internal/uritemplates \
+ google.golang.org/api/googleapi/transport \
+ google.golang.org/api/internal \
+ google.golang.org/api/iterator \
+ google.golang.org/api/option \
+ google.golang.org/api/storage/v1 \
+ google.golang.org/api/transport/http \
+ google.golang.org/genproto/googleapis/api/annotations \
+ google.golang.org/genproto/googleapis/iam/v1 \
+ google.golang.org/genproto/googleapis/rpc/status \
+ google.golang.org/grpc \
+ google.golang.org/grpc/codes \
+ google.golang.org/grpc/credentials \
+ google.golang.org/grpc/grpclb/grpc_lb_v1 \
+ google.golang.org/grpc/grpclog \
+ google.golang.org/grpc/internal \
+ google.golang.org/grpc/keepalive \
+ google.golang.org/grpc/metadata \
+ google.golang.org/grpc/naming \
+ google.golang.org/grpc/peer \
+ google.golang.org/grpc/stats \
+ google.golang.org/grpc/status \
+ google.golang.org/grpc/tap \
+ google.golang.org/grpc/transport
+# END deps.
# golang sets GOPATH=/go
ADD . /go/src/tip
-RUN go install tip
+RUN go install --tags=autocert tip
ENTRYPOINT ["/go/bin/tip"]
# App Engine expects us to listen on port 8080
EXPOSE 8080
diff --git a/cmd/tip/Makefile b/cmd/tip/Makefile
index 5844307..7d2f6ed 100644
--- a/cmd/tip/Makefile
+++ b/cmd/tip/Makefile
@@ -2,7 +2,11 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
-VERSION=v1
+VERSION=v2
+
+update-deps:
+ go install golang.org/x/build/cmd/gitlock
+ gitlock --update=Dockerfile --ignore=NONE golang.org/x/tools/cmd/tip
docker-prod: Dockerfile
docker build -f Dockerfile --tag=gcr.io/symbolic-datum-552/tip:$(VERSION) .
@@ -10,6 +14,6 @@
docker build -f Dockerfile --tag=gcr.io/go-dashboard-dev/tip:$(VERSION) .
push-prod: docker-prod
- gcloud docker push -- gcr.io/symbolic-datum-552/tip:$(VERSION)
+ gcloud docker -- push gcr.io/symbolic-datum-552/tip:$(VERSION)
push-dev: docker-dev
- gcloud docker push -- gcr.io/go-dashboard-dev/tip:$(VERSION)
+ gcloud docker -- push gcr.io/go-dashboard-dev/tip:$(VERSION)
diff --git a/cmd/tip/cert.go b/cmd/tip/cert.go
new file mode 100644
index 0000000..e00777b
--- /dev/null
+++ b/cmd/tip/cert.go
@@ -0,0 +1,50 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// +build autocert
+
+// This file contains autocert and cloud.google.com/go/storage
+// dependencies we want to hide by default from the Go build system,
+// which currently doesn't know how to fetch non-golang.org/x/*
+// dependencies. The Dockerfile builds the production binary
+// with this code using --tags=autocert.
+
+package main
+
+import (
+ "context"
+ "crypto/tls"
+ "log"
+ "net/http"
+
+ "cloud.google.com/go/storage"
+ "golang.org/x/build/autocertcache"
+ "golang.org/x/crypto/acme/autocert"
+)
+
+func init() {
+ runHTTPS = runHTTPSAutocert
+}
+
+func runHTTPSAutocert(h http.Handler) error {
+ var cache autocert.Cache
+ if b := *autoCertCacheBucket; b != "" {
+ sc, err := storage.NewClient(context.Background())
+ if err != nil {
+ log.Fatalf("storage.NewClient: %v", err)
+ }
+ cache = autocertcache.NewGoogleCloudStorageCache(sc, b)
+ }
+ m := autocert.Manager{
+ Prompt: autocert.AcceptTOS,
+ HostPolicy: autocert.HostWhitelist(*autoCertDomain),
+ Cache: cache,
+ }
+ s := &http.Server{
+ Addr: ":https",
+ Handler: h,
+ TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
+ }
+ return s.ListenAndServeTLS("", "")
+}
diff --git a/cmd/tip/tip-rc.yaml b/cmd/tip/tip-rc.yaml
index 139e614..82af3b7 100644
--- a/cmd/tip/tip-rc.yaml
+++ b/cmd/tip/tip-rc.yaml
@@ -1,7 +1,7 @@
apiVersion: v1
kind: ReplicationController
metadata:
- name: tipgodoc-v1
+ name: tipgodoc
spec:
replicas: 1
selector:
@@ -17,9 +17,9 @@
emptyDir: {}
containers:
- name: gitmirror
- image: gcr.io/symbolic-datum-552/tip:v1
+ image: gcr.io/symbolic-datum-552/tip:v2
imagePullPolicy: Always
- command: ["/go/bin/tip", "--autocert=tip.golang.org"]
+ command: ["/go/bin/tip", "--autocert=tip.golang.org", "--autocert-bucket=golang-tip-autocert"]
env:
- name: TMPDIR
value: /build
diff --git a/cmd/tip/tip.go b/cmd/tip/tip.go
index 428bcf0..6e0fd47 100644
--- a/cmd/tip/tip.go
+++ b/cmd/tip/tip.go
@@ -8,7 +8,6 @@
import (
"bufio"
- "crypto/tls"
"encoding/json"
"errors"
"flag"
@@ -24,8 +23,6 @@
"path/filepath"
"sync"
"time"
-
- "golang.org/x/crypto/acme/autocert"
)
const (
@@ -37,9 +34,14 @@
var startTime = time.Now()
var (
- autoCertDomain = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt cert for this hostname")
+ autoCertDomain = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt cert for this hostname")
+ autoCertCacheBucket = flag.String("autocert-bucket", "", "if non-empty, the Google Cloud Storage bucket in which to store the LetsEncrypt cache")
)
+// runHTTPS, if non-nil, specifies the function to serve HTTPS.
+// It is set non-nil in cert.go with the "autocert" build tag.
+var runHTTPS func(http.Handler) error
+
func main() {
flag.Parse()
@@ -60,25 +62,20 @@
log.Printf("Starting up tip server for builder %q", os.Getenv(k))
- errc := make(chan error)
+ errc := make(chan error, 1)
go func() {
errc <- http.ListenAndServe(":8080", mux)
}()
if *autoCertDomain != "" {
+ if runHTTPS == nil {
+ errc <- errors.New("can't use --autocert without building binary with the autocert build tag")
+ } else {
+ go func() {
+ errc <- runHTTPS(mux)
+ }()
+ }
log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain)
- m := autocert.Manager{
- Prompt: autocert.AcceptTOS,
- HostPolicy: autocert.HostWhitelist(*autoCertDomain),
- }
- s := &http.Server{
- Addr: ":https",
- Handler: mux,
- TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
- }
- go func() {
- errc <- s.ListenAndServeTLS("", "")
- }()
}
if err := <-errc; err != nil {
p.stop()
diff --git a/godoc/README.md b/godoc/README.md
index 1c495c8..52bc8a4 100644
--- a/godoc/README.md
+++ b/godoc/README.md
@@ -10,7 +10,7 @@
flag to load CSS/JS/templates from disk every time a page loads:
```
-godoc --templates=$GOPATH/src/golang.org/x/tools/godoc/static --http=:6060
+godoc -templates=$GOPATH/src/golang.org/x/tools/godoc/static -http=:6060
```
## Recompiling static assets
diff --git a/godoc/dl/dl.go b/godoc/dl/dl.go
index b4230b6..3d2f6c4 100644
--- a/godoc/dl/dl.go
+++ b/godoc/dl/dl.go
@@ -32,7 +32,6 @@
"google.golang.org/appengine/datastore"
"google.golang.org/appengine/log"
"google.golang.org/appengine/memcache"
- "google.golang.org/appengine/user"
)
const (
@@ -183,7 +182,6 @@
type listTemplateData struct {
Featured []Feature
Stable, Unstable, Archive []Release
- LoginURL string
}
var (
@@ -218,11 +216,6 @@
d.Featured = filesToFeatured(d.Stable[0].Files)
}
- d.LoginURL, _ = user.LoginURL(c, "/dl")
- if user.Current(c) != nil {
- d.LoginURL, _ = user.LogoutURL(c, "/dl")
- }
-
item := &memcache.Item{Key: cacheKey, Object: &d, Expiration: cacheDuration}
if err := memcache.Gob.Set(c, item); err != nil {
log.Errorf(c, "cache set error: %v", err)
diff --git a/godoc/dl/tmpl.go b/godoc/dl/tmpl.go
index e92f9b7..440917a 100644
--- a/godoc/dl/tmpl.go
+++ b/godoc/dl/tmpl.go
@@ -106,7 +106,7 @@
</p>
<p>
-If you are building from source,
+If you are building from source,
follow the <a href="/doc/install/source">source installation instructions</a>.
</p>
@@ -146,13 +146,6 @@
</div>
{{end}}
-
-<!-- Disabled for now; there's no admin functionality yet.
-<p>
-<small><a href="{{.LoginURL}}">π</a></small>
-</p>
--->
-
<div id="footer">
<p>
Except as
diff --git a/godoc/godoc.go b/godoc/godoc.go
index a9e8b3b..d6c27d0 100644
--- a/godoc/godoc.go
+++ b/godoc/godoc.go
@@ -421,9 +421,9 @@
}
type PageInfo struct {
- Dirname string // directory containing the package
- Err error // error or nil
- Share bool // show share button on examples
+ Dirname string // directory containing the package
+ Err error // error or nil
+ GoogleCN bool // page is being served from golang.google.cn
Mode PageInfoMode // display metadata from query string
@@ -461,17 +461,14 @@
return "pkg/" + path
}
-// srcToPkgLinkFunc builds an <a> tag linking to
-// the package documentation of relpath.
+// srcToPkgLinkFunc builds an <a> tag linking to the package
+// documentation of relpath.
func srcToPkgLinkFunc(relpath string) string {
relpath = pkgLinkFunc(relpath)
- if relpath == "pkg/" {
+ relpath = pathpkg.Dir(relpath)
+ if relpath == "pkg" {
return `<a href="/pkg">Index</a>`
}
- if i := strings.LastIndex(relpath, "/"); i != -1 {
- // Remove filename after last slash.
- relpath = relpath[:i]
- }
return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
}
@@ -683,8 +680,8 @@
err := p.ExampleHTML.Execute(&buf, struct {
Name, Doc, Code, Play, Output string
- Share bool
- }{eg.Name, eg.Doc, code, play, out, info.Share})
+ GoogleCN bool
+ }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
if err != nil {
log.Print(err)
}
diff --git a/godoc/godoc_test.go b/godoc/godoc_test.go
index dca1c95..c1d631c 100644
--- a/godoc/godoc_test.go
+++ b/godoc/godoc_test.go
@@ -313,6 +313,8 @@
}{
{"src/", `<a href="/pkg">Index</a>`},
{"src/fmt/", `<a href="/pkg/fmt">fmt</a>`},
+ {"pkg/", `<a href="/pkg">Index</a>`},
+ {"pkg/LICENSE", `<a href="/pkg">Index</a>`},
} {
if got := srcToPkgLinkFunc(tc.path); got != tc.want {
t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
diff --git a/godoc/page.go b/godoc/page.go
index 0c7bf00..10e86e5 100644
--- a/godoc/page.go
+++ b/godoc/page.go
@@ -9,6 +9,7 @@
"os"
"path/filepath"
"runtime"
+ "strings"
)
// Page describes the contents of the top-level godoc webpage.
@@ -19,7 +20,7 @@
SrcPath string
Query string
Body []byte
- Share bool
+ GoogleCN bool // page is being served from golang.google.cn
// filled in by servePage
SearchBox bool
@@ -51,19 +52,25 @@
Title: "File " + relpath,
Subtitle: relpath,
Body: applyTemplate(p.ErrorHTML, "errorHTML", err),
- Share: allowShare(r),
+ GoogleCN: googleCN(r),
})
}
-var onAppengine = false // overriden in appengine.go when on app engine
+var onAppengine = false // overridden in appengine.go when on app engine
-func allowShare(r *http.Request) bool {
+func googleCN(r *http.Request) bool {
+ if r.FormValue("googlecn") != "" {
+ return true
+ }
if !onAppengine {
+ return false
+ }
+ if strings.HasSuffix(r.Host, ".cn") {
return true
}
switch r.Header.Get("X-AppEngine-Country") {
case "", "ZZ", "CN":
- return false
+ return true
}
- return true
+ return false
}
diff --git a/godoc/proxy/proxy.go b/godoc/proxy/proxy.go
index e8cb18e..0ddaa00 100644
--- a/godoc/proxy/proxy.go
+++ b/godoc/proxy/proxy.go
@@ -19,6 +19,7 @@
"net/http"
"net/http/httputil"
"net/url"
+ "strings"
"time"
"golang.org/x/net/context"
@@ -147,8 +148,8 @@
}
func share(w http.ResponseWriter, r *http.Request) {
- if !allowShare(r) {
- http.Error(w, "Forbidden", http.StatusForbidden)
+ if googleCN(r) {
+ http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
}
target, _ := url.Parse(playgroundURL)
@@ -157,13 +158,19 @@
p.ServeHTTP(w, r)
}
-func allowShare(r *http.Request) bool {
+func googleCN(r *http.Request) bool {
+ if r.FormValue("googlecn") != "" {
+ return true
+ }
if appengine.IsDevAppServer() {
+ return false
+ }
+ if strings.HasSuffix(r.Host, ".cn") {
return true
}
switch r.Header.Get("X-AppEngine-Country") {
case "", "ZZ", "CN":
- return false
+ return true
}
- return true
+ return false
}
diff --git a/godoc/redirect/redirect.go b/godoc/redirect/redirect.go
index c81d05f..f86e0ad 100644
--- a/godoc/redirect/redirect.go
+++ b/godoc/redirect/redirect.go
@@ -196,7 +196,7 @@
if n, err := strconv.Atoi(id); err == nil && n > 150000 {
target = "https://codereview.appspot.com/" + id
} else {
- target = "https://go-review.googlesource.com/r/" + id
+ target = "https://go-review.googlesource.com/" + id
}
http.Redirect(w, r, target, http.StatusFound)
}
diff --git a/godoc/redirect/redirect_test.go b/godoc/redirect/redirect_test.go
index 8a02de3..cfc2f97 100644
--- a/godoc/redirect/redirect_test.go
+++ b/godoc/redirect/redirect_test.go
@@ -58,8 +58,8 @@
"/design/123-foo": {302, "https://github.com/golang/proposal/blob/master/design/123-foo.md"},
"/design/text/123-foo": {302, "https://github.com/golang/proposal/blob/master/design/text/123-foo.md"},
- "/cl/1": {302, "https://go-review.googlesource.com/r/1"},
- "/cl/1/": {302, "https://go-review.googlesource.com/r/1"},
+ "/cl/1": {302, "https://go-review.googlesource.com/1"},
+ "/cl/1/": {302, "https://go-review.googlesource.com/1"},
"/cl/267120043": {302, "https://codereview.appspot.com/267120043"},
"/cl/267120043/": {302, "https://codereview.appspot.com/267120043"},
}
diff --git a/godoc/search.go b/godoc/search.go
index 63d9e76..d61193f 100644
--- a/godoc/search.go
+++ b/godoc/search.go
@@ -126,7 +126,7 @@
Tabtitle: query,
Query: query,
Body: body.Bytes(),
- Share: allowShare(r),
+ GoogleCN: googleCN(r),
})
}
diff --git a/godoc/server.go b/godoc/server.go
index c9b4056..3b452e5 100644
--- a/godoc/server.go
+++ b/godoc/server.go
@@ -312,13 +312,13 @@
info.TypeInfoIndex[ti.Name] = i
}
- info.Share = allowShare(r)
+ info.GoogleCN = googleCN(r)
h.p.ServePage(w, Page{
Title: title,
Tabtitle: tabtitle,
Subtitle: subtitle,
Body: applyTemplate(h.p.PackageHTML, "packageHTML", info),
- Share: info.Share,
+ GoogleCN: info.GoogleCN,
})
}
@@ -583,7 +583,7 @@
SrcPath: relpath,
Tabtitle: relpath,
Body: buf.Bytes(),
- Share: allowShare(r),
+ GoogleCN: googleCN(r),
})
}
@@ -654,7 +654,7 @@
SrcPath: relpath,
Tabtitle: relpath,
Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list),
- Share: allowShare(r),
+ GoogleCN: googleCN(r),
})
}
@@ -683,7 +683,7 @@
page := Page{
Title: meta.Title,
Subtitle: meta.Subtitle,
- Share: allowShare(r),
+ GoogleCN: googleCN(r),
}
// evaluate as template if indicated
diff --git a/godoc/static/example.html b/godoc/static/example.html
index 3bc0eb9..1e86b25 100644
--- a/godoc/static/example.html
+++ b/godoc/static/example.html
@@ -13,7 +13,7 @@
<div class="buttons">
<a class="run" title="Run this code [shift-enter]">Run</a>
<a class="fmt" title="Format this code">Format</a>
- {{if $.Share}}
+ {{if not $.GoogleCN}}
<a class="share" title="Share this code">Share</a>
{{end}}
</div>
diff --git a/godoc/static/godoc.html b/godoc/static/godoc.html
index 92b10aa..b7f6c11 100644
--- a/godoc/static/godoc.html
+++ b/godoc/static/godoc.html
@@ -55,7 +55,7 @@
<div class="buttons">
<a class="run" title="Run this code [shift-enter]">Run</a>
<a class="fmt" title="Format this code">Format</a>
- {{if $.Share}}
+ {{if not $.GoogleCN}}
<a class="share" title="Share this code">Share</a>
{{end}}
</div>
@@ -95,7 +95,7 @@
the content of this page is licensed under the
Creative Commons Attribution 3.0 License,
and code is licensed under a <a href="/LICENSE">BSD license</a>.<br>
-<a href="/doc/tos.html">Terms of Service</a> |
+<a href="/doc/tos.html">Terms of Service</a> |
<a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
</div>
diff --git a/imports/fix.go b/imports/fix.go
index ac7f4b0..e246084 100644
--- a/imports/fix.go
+++ b/imports/fix.go
@@ -776,7 +776,7 @@
sort.Sort(byImportPathShortLength(candidates))
if Debug {
for i, pkg := range candidates {
- log.Printf("%s candidate %d/%d: %v", pkgName, i+1, len(candidates), pkg.importPathShort)
+ log.Printf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), pkg.importPathShort, pkg.dir)
}
}