[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) } }