[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
This commit is contained in:
Chris Broadfoot 2017-08-07 10:30:36 -07:00
commit fa615f793b
39 changed files with 1496 additions and 77 deletions

5
cmd/getgo/.dockerignore Normal file
View File

@ -0,0 +1,5 @@
.git
.dockerignore
LICENSE
README.md
.gitignore

3
cmd/getgo/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build
testgetgo
getgo

20
cmd/getgo/Dockerfile Normal file
View File

@ -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

27
cmd/getgo/LICENSE Normal file
View File

@ -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.

71
cmd/getgo/README.md Normal file
View File

@ -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`.

182
cmd/getgo/download.go Normal file
View File

@ -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
}

View File

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

115
cmd/getgo/main.go Normal file
View File

@ -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
}

171
cmd/getgo/main_test.go Normal file
View File

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

13
cmd/getgo/make.bash Executable file
View File

@ -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"

153
cmd/getgo/path.go Normal file
View File

@ -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())
}
}

56
cmd/getgo/path_test.go Normal file
View File

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

View File

@ -0,0 +1,7 @@
# getgo server
## Deployment
```
gcloud app deploy --promote --project golang-org
```

View File

@ -0,0 +1,7 @@
runtime: go
service: get
api_version: go1
handlers:
- url: /.*
script: _go_app

61
cmd/getgo/server/main.go Normal file
View File

@ -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())
}

131
cmd/getgo/steps.go Normal file
View File

@ -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()
}

36
cmd/getgo/system.go Normal file
View File

@ -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
}

55
cmd/getgo/system_unix.go Normal file
View File

@ -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
}

View File

@ -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
}

19
cmd/getgo/upload.bash Executable file
View File

@ -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

View File

@ -5,11 +5,128 @@ RUN apt-get update && apt-get install --no-install-recommends -y -q build-essent
# golang puts its go install here (weird but true) # golang puts its go install here (weird but true)
ENV GOROOT_BOOTSTRAP /usr/local/go 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 # golang sets GOPATH=/go
ADD . /go/src/tip ADD . /go/src/tip
RUN go install tip RUN go install --tags=autocert tip
ENTRYPOINT ["/go/bin/tip"] ENTRYPOINT ["/go/bin/tip"]
# App Engine expects us to listen on port 8080 # App Engine expects us to listen on port 8080
EXPOSE 8080 EXPOSE 8080

View File

@ -2,7 +2,11 @@
# Use of this source code is governed by a BSD-style # Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file. # 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-prod: Dockerfile
docker build -f Dockerfile --tag=gcr.io/symbolic-datum-552/tip:$(VERSION) . docker build -f Dockerfile --tag=gcr.io/symbolic-datum-552/tip:$(VERSION) .
@ -10,6 +14,6 @@ docker-dev: Dockerfile
docker build -f Dockerfile --tag=gcr.io/go-dashboard-dev/tip:$(VERSION) . docker build -f Dockerfile --tag=gcr.io/go-dashboard-dev/tip:$(VERSION) .
push-prod: docker-prod 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 push-dev: docker-dev
gcloud docker push -- gcr.io/go-dashboard-dev/tip:$(VERSION) gcloud docker -- push gcr.io/go-dashboard-dev/tip:$(VERSION)

50
cmd/tip/cert.go Normal file
View File

@ -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("", "")
}

View File

@ -1,7 +1,7 @@
apiVersion: v1 apiVersion: v1
kind: ReplicationController kind: ReplicationController
metadata: metadata:
name: tipgodoc-v1 name: tipgodoc
spec: spec:
replicas: 1 replicas: 1
selector: selector:
@ -17,9 +17,9 @@ spec:
emptyDir: {} emptyDir: {}
containers: containers:
- name: gitmirror - name: gitmirror
image: gcr.io/symbolic-datum-552/tip:v1 image: gcr.io/symbolic-datum-552/tip:v2
imagePullPolicy: Always imagePullPolicy: Always
command: ["/go/bin/tip", "--autocert=tip.golang.org"] command: ["/go/bin/tip", "--autocert=tip.golang.org", "--autocert-bucket=golang-tip-autocert"]
env: env:
- name: TMPDIR - name: TMPDIR
value: /build value: /build

View File

@ -8,7 +8,6 @@ package main
import ( import (
"bufio" "bufio"
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"flag" "flag"
@ -24,8 +23,6 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"time" "time"
"golang.org/x/crypto/acme/autocert"
) )
const ( const (
@ -38,8 +35,13 @@ var startTime = time.Now()
var ( 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() { func main() {
flag.Parse() flag.Parse()
@ -60,26 +62,21 @@ func main() {
log.Printf("Starting up tip server for builder %q", os.Getenv(k)) log.Printf("Starting up tip server for builder %q", os.Getenv(k))
errc := make(chan error) errc := make(chan error, 1)
go func() { go func() {
errc <- http.ListenAndServe(":8080", mux) errc <- http.ListenAndServe(":8080", mux)
}() }()
if *autoCertDomain != "" { if *autoCertDomain != "" {
log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain) if runHTTPS == nil {
m := autocert.Manager{ errc <- errors.New("can't use --autocert without building binary with the autocert build tag")
Prompt: autocert.AcceptTOS, } else {
HostPolicy: autocert.HostWhitelist(*autoCertDomain),
}
s := &http.Server{
Addr: ":https",
Handler: mux,
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
}
go func() { go func() {
errc <- s.ListenAndServeTLS("", "") errc <- runHTTPS(mux)
}() }()
} }
log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain)
}
if err := <-errc; err != nil { if err := <-errc; err != nil {
p.stop() p.stop()
log.Fatal(err) log.Fatal(err)

View File

@ -10,7 +10,7 @@ binary. It can be tedious to recompile assets every time, but you can pass a
flag to load CSS/JS/templates from disk every time a page loads: 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 ## Recompiling static assets

View File

@ -32,7 +32,6 @@ import (
"google.golang.org/appengine/datastore" "google.golang.org/appengine/datastore"
"google.golang.org/appengine/log" "google.golang.org/appengine/log"
"google.golang.org/appengine/memcache" "google.golang.org/appengine/memcache"
"google.golang.org/appengine/user"
) )
const ( const (
@ -183,7 +182,6 @@ var featuredFiles = []Feature{
type listTemplateData struct { type listTemplateData struct {
Featured []Feature Featured []Feature
Stable, Unstable, Archive []Release Stable, Unstable, Archive []Release
LoginURL string
} }
var ( var (
@ -218,11 +216,6 @@ func listHandler(w http.ResponseWriter, r *http.Request) {
d.Featured = filesToFeatured(d.Stable[0].Files) 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} item := &memcache.Item{Key: cacheKey, Object: &d, Expiration: cacheDuration}
if err := memcache.Gob.Set(c, item); err != nil { if err := memcache.Gob.Set(c, item); err != nil {
log.Errorf(c, "cache set error: %v", err) log.Errorf(c, "cache set error: %v", err)

View File

@ -146,13 +146,6 @@ information about Go releases.
</div> </div>
{{end}} {{end}}
<!-- Disabled for now; there's no admin functionality yet.
<p>
<small><a href="{{.LoginURL}}">&pi;</a></small>
</p>
-->
<div id="footer"> <div id="footer">
<p> <p>
Except as Except as

View File

@ -423,7 +423,7 @@ func sanitizeFunc(src string) string {
type PageInfo struct { type PageInfo struct {
Dirname string // directory containing the package Dirname string // directory containing the package
Err error // error or nil Err error // error or nil
Share bool // show share button on examples GoogleCN bool // page is being served from golang.google.cn
Mode PageInfoMode // display metadata from query string Mode PageInfoMode // display metadata from query string
@ -461,17 +461,14 @@ func pkgLinkFunc(path string) string {
return "pkg/" + path return "pkg/" + path
} }
// srcToPkgLinkFunc builds an <a> tag linking to // srcToPkgLinkFunc builds an <a> tag linking to the package
// the package documentation of relpath. // documentation of relpath.
func srcToPkgLinkFunc(relpath string) string { func srcToPkgLinkFunc(relpath string) string {
relpath = pkgLinkFunc(relpath) relpath = pkgLinkFunc(relpath)
if relpath == "pkg/" { relpath = pathpkg.Dir(relpath)
if relpath == "pkg" {
return `<a href="/pkg">Index</a>` 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/"):]) return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
} }
@ -683,8 +680,8 @@ func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string
err := p.ExampleHTML.Execute(&buf, struct { err := p.ExampleHTML.Execute(&buf, struct {
Name, Doc, Code, Play, Output string Name, Doc, Code, Play, Output string
Share bool GoogleCN bool
}{eg.Name, eg.Doc, code, play, out, info.Share}) }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }

View File

@ -313,6 +313,8 @@ func TestSrcToPkgLinkFunc(t *testing.T) {
}{ }{
{"src/", `<a href="/pkg">Index</a>`}, {"src/", `<a href="/pkg">Index</a>`},
{"src/fmt/", `<a href="/pkg/fmt">fmt</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 { if got := srcToPkgLinkFunc(tc.path); got != tc.want {
t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want) t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)

View File

@ -9,6 +9,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
) )
// Page describes the contents of the top-level godoc webpage. // Page describes the contents of the top-level godoc webpage.
@ -19,7 +20,7 @@ type Page struct {
SrcPath string SrcPath string
Query string Query string
Body []byte Body []byte
Share bool GoogleCN bool // page is being served from golang.google.cn
// filled in by servePage // filled in by servePage
SearchBox bool SearchBox bool
@ -51,19 +52,25 @@ func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpat
Title: "File " + relpath, Title: "File " + relpath,
Subtitle: relpath, Subtitle: relpath,
Body: applyTemplate(p.ErrorHTML, "errorHTML", err), 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 { if !onAppengine {
return false
}
if strings.HasSuffix(r.Host, ".cn") {
return true return true
} }
switch r.Header.Get("X-AppEngine-Country") { switch r.Header.Get("X-AppEngine-Country") {
case "", "ZZ", "CN": case "", "ZZ", "CN":
return false
}
return true return true
}
return false
} }

View File

@ -19,6 +19,7 @@ import (
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"strings"
"time" "time"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -147,8 +148,8 @@ func cacheKey(body string) string {
} }
func share(w http.ResponseWriter, r *http.Request) { func share(w http.ResponseWriter, r *http.Request) {
if !allowShare(r) { if googleCN(r) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return return
} }
target, _ := url.Parse(playgroundURL) target, _ := url.Parse(playgroundURL)
@ -157,13 +158,19 @@ func share(w http.ResponseWriter, r *http.Request) {
p.ServeHTTP(w, r) 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() { if appengine.IsDevAppServer() {
return false
}
if strings.HasSuffix(r.Host, ".cn") {
return true return true
} }
switch r.Header.Get("X-AppEngine-Country") { switch r.Header.Get("X-AppEngine-Country") {
case "", "ZZ", "CN": case "", "ZZ", "CN":
return false
}
return true return true
}
return false
} }

View File

@ -196,7 +196,7 @@ func clHandler(w http.ResponseWriter, r *http.Request) {
if n, err := strconv.Atoi(id); err == nil && n > 150000 { if n, err := strconv.Atoi(id); err == nil && n > 150000 {
target = "https://codereview.appspot.com/" + id target = "https://codereview.appspot.com/" + id
} else { } else {
target = "https://go-review.googlesource.com/r/" + id target = "https://go-review.googlesource.com/" + id
} }
http.Redirect(w, r, target, http.StatusFound) http.Redirect(w, r, target, http.StatusFound)
} }

View File

@ -58,8 +58,8 @@ func TestRedirects(t *testing.T) {
"/design/123-foo": {302, "https://github.com/golang/proposal/blob/master/design/123-foo.md"}, "/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"}, "/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/1"},
"/cl/1/": {302, "https://go-review.googlesource.com/r/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"},
"/cl/267120043/": {302, "https://codereview.appspot.com/267120043"}, "/cl/267120043/": {302, "https://codereview.appspot.com/267120043"},
} }

View File

@ -126,7 +126,7 @@ func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) {
Tabtitle: query, Tabtitle: query,
Query: query, Query: query,
Body: body.Bytes(), Body: body.Bytes(),
Share: allowShare(r), GoogleCN: googleCN(r),
}) })
} }

View File

@ -312,13 +312,13 @@ func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
info.TypeInfoIndex[ti.Name] = i info.TypeInfoIndex[ti.Name] = i
} }
info.Share = allowShare(r) info.GoogleCN = googleCN(r)
h.p.ServePage(w, Page{ h.p.ServePage(w, Page{
Title: title, Title: title,
Tabtitle: tabtitle, Tabtitle: tabtitle,
Subtitle: subtitle, Subtitle: subtitle,
Body: applyTemplate(h.p.PackageHTML, "packageHTML", info), Body: applyTemplate(h.p.PackageHTML, "packageHTML", info),
Share: info.Share, GoogleCN: info.GoogleCN,
}) })
} }
@ -583,7 +583,7 @@ func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abs
SrcPath: relpath, SrcPath: relpath,
Tabtitle: relpath, Tabtitle: relpath,
Body: buf.Bytes(), Body: buf.Bytes(),
Share: allowShare(r), GoogleCN: googleCN(r),
}) })
} }
@ -654,7 +654,7 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab
SrcPath: relpath, SrcPath: relpath,
Tabtitle: relpath, Tabtitle: relpath,
Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list), Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list),
Share: allowShare(r), GoogleCN: googleCN(r),
}) })
} }
@ -683,7 +683,7 @@ func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, absp
page := Page{ page := Page{
Title: meta.Title, Title: meta.Title,
Subtitle: meta.Subtitle, Subtitle: meta.Subtitle,
Share: allowShare(r), GoogleCN: googleCN(r),
} }
// evaluate as template if indicated // evaluate as template if indicated

View File

@ -13,7 +13,7 @@
<div class="buttons"> <div class="buttons">
<a class="run" title="Run this code [shift-enter]">Run</a> <a class="run" title="Run this code [shift-enter]">Run</a>
<a class="fmt" title="Format this code">Format</a> <a class="fmt" title="Format this code">Format</a>
{{if $.Share}} {{if not $.GoogleCN}}
<a class="share" title="Share this code">Share</a> <a class="share" title="Share this code">Share</a>
{{end}} {{end}}
</div> </div>

View File

@ -55,7 +55,7 @@ func main() {
<div class="buttons"> <div class="buttons">
<a class="run" title="Run this code [shift-enter]">Run</a> <a class="run" title="Run this code [shift-enter]">Run</a>
<a class="fmt" title="Format this code">Format</a> <a class="fmt" title="Format this code">Format</a>
{{if $.Share}} {{if not $.GoogleCN}}
<a class="share" title="Share this code">Share</a> <a class="share" title="Share this code">Share</a>
{{end}} {{end}}
</div> </div>

View File

@ -776,7 +776,7 @@ func findImportGoPath(pkgName string, symbols map[string]bool, filename string)
sort.Sort(byImportPathShortLength(candidates)) sort.Sort(byImportPathShortLength(candidates))
if Debug { if Debug {
for i, pkg := range candidates { 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)
} }
} }