mirror of
https://github.com/golang/go.git
synced 2025-05-07 00:23:03 +00:00
At least in theory. We don't totally have it working yet. It does run locally in the dev environment, though, which should be the same as production, since it builds the Docker container locally. But we're getting problems when pushing it to production. Also some minor tweaks to the code with Andrew. Change-Id: Id192669dbc8d3f86d9c8dad79764abd66e983895 Reviewed-on: https://go-review.googlesource.com/1761 Reviewed-by: Andrew Gerrand <adg@golang.org>
259 lines
5.8 KiB
Go
259 lines
5.8 KiB
Go
// Copyright 2014 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.
|
|
|
|
// Command tipgodoc is the beginning of the new tip.golang.org server,
|
|
// serving the latest HEAD straight from the Git oven.
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const metaURL = "https://go.googlesource.com/?b=master&format=JSON"
|
|
|
|
func init() {
|
|
p := new(Proxy)
|
|
go p.run()
|
|
http.Handle("/", p)
|
|
}
|
|
|
|
type Proxy struct {
|
|
mu sync.Mutex // protects the followin'
|
|
proxy *httputil.ReverseProxy
|
|
cur string // signature of gorepo+toolsrepo
|
|
side string
|
|
}
|
|
|
|
// run runs in its own goroutine.
|
|
func (p *Proxy) run() {
|
|
p.side = "a"
|
|
for {
|
|
p.poll()
|
|
time.Sleep(30 * time.Second)
|
|
}
|
|
}
|
|
|
|
// poll runs from the run loop goroutine.
|
|
func (p *Proxy) poll() {
|
|
heads := gerritMetaMap()
|
|
if heads == nil {
|
|
return
|
|
}
|
|
|
|
sig := heads["go"] + "-" + heads["tools"]
|
|
|
|
p.mu.Lock()
|
|
changes := sig != p.cur
|
|
curSide := p.side
|
|
p.cur = sig
|
|
p.mu.Unlock()
|
|
|
|
if !changes {
|
|
return
|
|
}
|
|
|
|
newSide := "b"
|
|
if curSide == "b" {
|
|
newSide = "a"
|
|
}
|
|
|
|
hostport, err := initSide(newSide, heads["go"], heads["tools"])
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
u, err := url.Parse(fmt.Sprintf("http://%v/", hostport))
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
p.side = newSide
|
|
p.proxy = httputil.NewSingleHostReverseProxy(u)
|
|
}
|
|
|
|
func initSide(side, goHash, toolsHash string) (hostport string, err error) {
|
|
dir := filepath.Join(os.TempDir(), "tipgodoc", side)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
goDir := filepath.Join(dir, "go")
|
|
toolsDir := filepath.Join(dir, "gopath/src/golang.org/x/tools")
|
|
if err := checkout("https://go.googlesource.com/go", goHash, goDir); err != nil {
|
|
return "", err
|
|
}
|
|
if err := checkout("https://go.googlesource.com/tools", toolsHash, toolsDir); err != nil {
|
|
return "", err
|
|
|
|
}
|
|
|
|
env := []string{"GOROOT=" + goDir, "GOPATH=" + filepath.Join(dir, "gopath")}
|
|
|
|
make := exec.Command(filepath.Join(goDir, "src/make.bash"))
|
|
make.Stdout = os.Stdout
|
|
make.Stderr = os.Stderr
|
|
make.Dir = filepath.Join(goDir, "src")
|
|
if err := make.Run(); err != nil {
|
|
return "", err
|
|
}
|
|
goBin := filepath.Join(goDir, "bin/go")
|
|
install := exec.Command(goBin, "install", "golang.org/x/tools/cmd/godoc")
|
|
install.Stdout = os.Stdout
|
|
install.Stderr = os.Stderr
|
|
install.Env = env
|
|
if err := install.Run(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
godocBin := filepath.Join(goDir, "bin/godoc")
|
|
hostport = "localhost:8081"
|
|
if side == "b" {
|
|
hostport = "localhost:8082"
|
|
}
|
|
godoc := exec.Command(godocBin, "-http="+hostport)
|
|
godoc.Env = env
|
|
godoc.Stdout = os.Stdout
|
|
godoc.Stderr = os.Stderr
|
|
if err := godoc.Start(); err != nil {
|
|
return "", err
|
|
}
|
|
go func() {
|
|
// TODO(bradfitz): tell the proxy that this side is dead
|
|
if err := godoc.Wait(); err != nil {
|
|
log.Printf("side %v exited: %v", side, err)
|
|
}
|
|
}()
|
|
|
|
for i := 0; i < 15; i++ {
|
|
time.Sleep(time.Second)
|
|
var res *http.Response
|
|
res, err = http.Get(fmt.Sprintf("http://%v/", hostport))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
res.Body.Close()
|
|
if res.StatusCode == http.StatusOK {
|
|
return hostport, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("timed out waiting for side %v at %v (%v)", side, hostport, err)
|
|
}
|
|
|
|
func checkout(repo, hash, path string) error {
|
|
// Clone git repo if it doesn't exist.
|
|
if _, err := os.Stat(filepath.Join(path, ".git")); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(filepath.Base(path), 0755); err != nil {
|
|
return err
|
|
}
|
|
cmd := exec.Command("git", "clone", repo, path)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
// TODO(bradfitz): capture the standard error output
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd := exec.Command("git", "fetch")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Dir = path
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
cmd = exec.Command("git", "reset", "--hard", hash)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Dir = path
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
cmd = exec.Command("git", "clean", "-d", "-f", "-x")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Dir = path
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/_tipstatus" {
|
|
p.serveStatus(w, r)
|
|
return
|
|
}
|
|
p.mu.Lock()
|
|
proxy := p.proxy
|
|
p.mu.Unlock()
|
|
if proxy == nil {
|
|
http.Error(w, "tip.golang.org is currently starting up, compiling tip", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
proxy.ServeHTTP(w, r)
|
|
}
|
|
|
|
func (p *Proxy) serveStatus(w http.ResponseWriter, r *http.Request) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
fmt.Fprintf(w, "side=%v\ncurrent=%v\n", p.side, p.cur)
|
|
}
|
|
|
|
// gerritMetaMap returns the map from repo name (e.g. "go") to its
|
|
// latest master hash.
|
|
// The returned map is nil on any transient error.
|
|
func gerritMetaMap() map[string]string {
|
|
res, err := http.Get(metaURL)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer res.Body.Close()
|
|
defer io.Copy(ioutil.Discard, res.Body) // ensure EOF for keep-alive
|
|
if res.StatusCode != 200 {
|
|
return nil
|
|
}
|
|
var meta map[string]struct {
|
|
Branches map[string]string
|
|
}
|
|
br := bufio.NewReader(res.Body)
|
|
// For security reasons or something, this URL starts with ")]}'\n" before
|
|
// the JSON object. So ignore that.
|
|
// Shawn Pearce says it's guaranteed to always be just one line, ending in '\n'.
|
|
for {
|
|
b, err := br.ReadByte()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if b == '\n' {
|
|
break
|
|
}
|
|
}
|
|
if err := json.NewDecoder(br).Decode(&meta); err != nil {
|
|
log.Printf("JSON decoding error from %v: %s", metaURL, err)
|
|
return nil
|
|
}
|
|
m := map[string]string{}
|
|
for repo, v := range meta {
|
|
if master, ok := v.Branches["master"]; ok {
|
|
m[repo] = master
|
|
}
|
|
}
|
|
return m
|
|
}
|