github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/cmd/tip/tip.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  // Command tip is the tip.golang.org server,
     6  // serving the latest HEAD straight from the Git oven.
     7  package main
     8  
     9  import (
    10  	"bufio"
    11  	"encoding/json"
    12  	"errors"
    13  	"flag"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"log"
    18  	"net/http"
    19  	"net/http/httputil"
    20  	"net/url"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  const (
    29  	repoURL      = "https://go.googlesource.com/"
    30  	metaURL      = "https://go.googlesource.com/?b=master&format=JSON"
    31  	startTimeout = 10 * time.Minute
    32  )
    33  
    34  var startTime = time.Now()
    35  
    36  var (
    37  	autoCertDomain      = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt cert for this hostname")
    38  	autoCertCacheBucket = flag.String("autocert-bucket", "", "if non-empty, the Google Cloud Storage bucket in which to store the LetsEncrypt cache")
    39  )
    40  
    41  // Hooks that are set non-nil in cert.go if the "autocert" build tag
    42  // is used.
    43  var (
    44  	certInit    func()
    45  	runHTTPS    func(http.Handler) error
    46  	wrapHTTPMux func(http.Handler) http.Handler
    47  )
    48  
    49  func main() {
    50  	flag.Parse()
    51  
    52  	const k = "TIP_BUILDER"
    53  	var b Builder
    54  	switch os.Getenv(k) {
    55  	case "godoc":
    56  		b = godocBuilder{}
    57  	case "talks":
    58  		b = talksBuilder{}
    59  	default:
    60  		log.Fatalf("Unknown %v value: %q", k, os.Getenv(k))
    61  	}
    62  
    63  	if certInit != nil {
    64  		certInit()
    65  	}
    66  
    67  	p := &Proxy{builder: b}
    68  	go p.run()
    69  	mux := newServeMux(p)
    70  
    71  	log.Printf("Starting up tip server for builder %q", os.Getenv(k))
    72  
    73  	errc := make(chan error, 1)
    74  
    75  	go func() {
    76  		var httpMux http.Handler = mux
    77  		if wrapHTTPMux != nil {
    78  			httpMux = wrapHTTPMux(httpMux)
    79  		}
    80  		errc <- http.ListenAndServe(":8080", httpMux)
    81  	}()
    82  	if *autoCertDomain != "" {
    83  		if runHTTPS == nil {
    84  			errc <- errors.New("can't use --autocert without building binary with the autocert build tag")
    85  		} else {
    86  			go func() {
    87  				errc <- runHTTPS(mux)
    88  			}()
    89  		}
    90  		log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain)
    91  	}
    92  	if err := <-errc; err != nil {
    93  		p.stop()
    94  		log.Fatal(err)
    95  	}
    96  }
    97  
    98  // Proxy implements the tip.golang.org server: a reverse-proxy
    99  // that builds and runs godoc instances showing the latest docs.
   100  type Proxy struct {
   101  	builder Builder
   102  
   103  	mu       sync.Mutex // protects the followin'
   104  	proxy    http.Handler
   105  	cur      string    // signature of gorepo+toolsrepo
   106  	cmd      *exec.Cmd // live godoc instance, or nil for none
   107  	side     string
   108  	hostport string // host and port of the live instance
   109  	err      error
   110  }
   111  
   112  type Builder interface {
   113  	Signature(heads map[string]string) string
   114  	Init(dir, hostport string, heads map[string]string) (*exec.Cmd, error)
   115  	HealthCheck(hostport string) error
   116  }
   117  
   118  func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   119  	if r.URL.Path == "/_tipstatus" {
   120  		p.serveStatus(w, r)
   121  		return
   122  	}
   123  	p.mu.Lock()
   124  	proxy := p.proxy
   125  	err := p.err
   126  	p.mu.Unlock()
   127  	if proxy == nil {
   128  		s := "starting up"
   129  		if err != nil {
   130  			s = err.Error()
   131  		}
   132  		http.Error(w, s, http.StatusInternalServerError)
   133  		return
   134  	}
   135  	proxy.ServeHTTP(w, r)
   136  }
   137  
   138  func (p *Proxy) serveStatus(w http.ResponseWriter, r *http.Request) {
   139  	p.mu.Lock()
   140  	defer p.mu.Unlock()
   141  	fmt.Fprintf(w, "side=%v\ncurrent=%v\nerror=%v\nuptime=%v\n", p.side, p.cur, p.err, int(time.Since(startTime).Seconds()))
   142  }
   143  
   144  func (p *Proxy) serveHealthCheck(w http.ResponseWriter, r *http.Request) {
   145  	p.mu.Lock()
   146  	defer p.mu.Unlock()
   147  
   148  	// NOTE: (App Engine only; not GKE) Status 502, 503, 504 are
   149  	// the only status codes that signify an unhealthy app.  So
   150  	// long as this handler returns one of those codes, this
   151  	// instance will not be sent any requests.
   152  	if p.proxy == nil {
   153  		log.Printf("Health check: not ready")
   154  		http.Error(w, "Not ready", http.StatusServiceUnavailable)
   155  		return
   156  	}
   157  
   158  	if err := p.builder.HealthCheck(p.hostport); err != nil {
   159  		log.Printf("Health check failed: %v", err)
   160  		http.Error(w, "Health check failed", http.StatusServiceUnavailable)
   161  		return
   162  	}
   163  	io.WriteString(w, "ok")
   164  }
   165  
   166  // run runs in its own goroutine.
   167  func (p *Proxy) run() {
   168  	p.side = "a"
   169  	for {
   170  		p.poll()
   171  		time.Sleep(30 * time.Second)
   172  	}
   173  }
   174  
   175  func (p *Proxy) stop() {
   176  	p.mu.Lock()
   177  	defer p.mu.Unlock()
   178  	if p.cmd != nil {
   179  		p.cmd.Process.Kill()
   180  	}
   181  }
   182  
   183  // poll runs from the run loop goroutine.
   184  func (p *Proxy) poll() {
   185  	heads := gerritMetaMap()
   186  	if heads == nil {
   187  		return
   188  	}
   189  
   190  	sig := p.builder.Signature(heads)
   191  
   192  	p.mu.Lock()
   193  	changes := sig != p.cur
   194  	curSide := p.side
   195  	p.cur = sig
   196  	p.mu.Unlock()
   197  
   198  	if !changes {
   199  		return
   200  	}
   201  
   202  	newSide := "b"
   203  	if curSide == "b" {
   204  		newSide = "a"
   205  	}
   206  
   207  	dir := filepath.Join(os.TempDir(), "tip", newSide)
   208  	if err := os.MkdirAll(dir, 0755); err != nil {
   209  		p.err = err
   210  		return
   211  	}
   212  	hostport := "localhost:8081"
   213  	if newSide == "b" {
   214  		hostport = "localhost:8082"
   215  	}
   216  	cmd, err := p.builder.Init(dir, hostport, heads)
   217  	if err != nil {
   218  		err = fmt.Errorf("builder.Init: %v", err)
   219  	} else {
   220  		go func() {
   221  			// TODO(adg,bradfitz): be smarter about dead processes
   222  			if err := cmd.Wait(); err != nil {
   223  				log.Printf("process in %v exited: %v", dir, err)
   224  			}
   225  		}()
   226  		err = waitReady(p.builder, hostport)
   227  		if err != nil {
   228  			cmd.Process.Kill()
   229  			err = fmt.Errorf("waitReady: %v", err)
   230  		}
   231  	}
   232  
   233  	p.mu.Lock()
   234  	defer p.mu.Unlock()
   235  	if err != nil {
   236  		log.Println(err)
   237  		p.err = err
   238  		return
   239  	}
   240  
   241  	u, err := url.Parse(fmt.Sprintf("http://%v/", hostport))
   242  	if err != nil {
   243  		err = fmt.Errorf("parsing hostport: %v", err)
   244  		log.Println(err)
   245  		p.err = err
   246  		return
   247  	}
   248  	p.proxy = httputil.NewSingleHostReverseProxy(u)
   249  	p.side = newSide
   250  	p.hostport = hostport
   251  	if p.cmd != nil {
   252  		p.cmd.Process.Kill()
   253  	}
   254  	p.cmd = cmd
   255  }
   256  
   257  func newServeMux(p *Proxy) http.Handler {
   258  	mux := http.NewServeMux()
   259  	mux.Handle("/", httpsOnlyHandler{p})
   260  	mux.HandleFunc("/_ah/health", p.serveHealthCheck)
   261  	return mux
   262  }
   263  
   264  func waitReady(b Builder, hostport string) error {
   265  	var err error
   266  	deadline := time.Now().Add(startTimeout)
   267  	for time.Now().Before(deadline) {
   268  		if err = b.HealthCheck(hostport); err == nil {
   269  			return nil
   270  		}
   271  		time.Sleep(time.Second)
   272  	}
   273  	return fmt.Errorf("timed out waiting for process at %v: %v", hostport, err)
   274  }
   275  
   276  func runErr(cmd *exec.Cmd) error {
   277  	out, err := cmd.CombinedOutput()
   278  	if err != nil {
   279  		if len(out) == 0 {
   280  			return err
   281  		}
   282  		return fmt.Errorf("%s\n%v", out, err)
   283  	}
   284  	return nil
   285  }
   286  
   287  func checkout(repo, hash, path string) error {
   288  	// Clone git repo if it doesn't exist.
   289  	if _, err := os.Stat(filepath.Join(path, ".git")); os.IsNotExist(err) {
   290  		if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
   291  			return fmt.Errorf("mkdir: %v", err)
   292  		}
   293  		if err := runErr(exec.Command("git", "clone", repo, path)); err != nil {
   294  			return fmt.Errorf("clone: %v", err)
   295  		}
   296  	} else if err != nil {
   297  		return fmt.Errorf("stat .git: %v", err)
   298  	}
   299  
   300  	// Pull down changes and update to hash.
   301  	cmd := exec.Command("git", "fetch")
   302  	cmd.Dir = path
   303  	if err := runErr(cmd); err != nil {
   304  		return fmt.Errorf("fetch: %v", err)
   305  	}
   306  	cmd = exec.Command("git", "reset", "--hard", hash)
   307  	cmd.Dir = path
   308  	if err := runErr(cmd); err != nil {
   309  		return fmt.Errorf("reset: %v", err)
   310  	}
   311  	cmd = exec.Command("git", "clean", "-d", "-f", "-x")
   312  	cmd.Dir = path
   313  	if err := runErr(cmd); err != nil {
   314  		return fmt.Errorf("clean: %v", err)
   315  	}
   316  	return nil
   317  }
   318  
   319  var timeoutClient = &http.Client{Timeout: 10 * time.Second}
   320  
   321  // gerritMetaMap returns the map from repo name (e.g. "go") to its
   322  // latest master hash.
   323  // The returned map is nil on any transient error.
   324  func gerritMetaMap() map[string]string {
   325  	res, err := timeoutClient.Get(metaURL)
   326  	if err != nil {
   327  		log.Printf("Error getting Gerrit meta map: %v", err)
   328  		return nil
   329  	}
   330  	defer res.Body.Close()
   331  	defer io.Copy(ioutil.Discard, res.Body) // ensure EOF for keep-alive
   332  	if res.StatusCode != 200 {
   333  		return nil
   334  	}
   335  	var meta map[string]struct {
   336  		Branches map[string]string
   337  	}
   338  	br := bufio.NewReader(res.Body)
   339  	// For security reasons or something, this URL starts with ")]}'\n" before
   340  	// the JSON object. So ignore that.
   341  	// Shawn Pearce says it's guaranteed to always be just one line, ending in '\n'.
   342  	for {
   343  		b, err := br.ReadByte()
   344  		if err != nil {
   345  			return nil
   346  		}
   347  		if b == '\n' {
   348  			break
   349  		}
   350  	}
   351  	if err := json.NewDecoder(br).Decode(&meta); err != nil {
   352  		log.Printf("JSON decoding error from %v: %s", metaURL, err)
   353  		return nil
   354  	}
   355  	m := map[string]string{}
   356  	for repo, v := range meta {
   357  		if master, ok := v.Branches["master"]; ok {
   358  			m[repo] = master
   359  		}
   360  	}
   361  	return m
   362  }
   363  
   364  func getOK(url string) (body []byte, err error) {
   365  	res, err := timeoutClient.Get(url)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  	body, err = ioutil.ReadAll(res.Body)
   370  	res.Body.Close()
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  	if res.StatusCode != http.StatusOK {
   375  		return nil, errors.New(res.Status)
   376  	}
   377  	return body, nil
   378  }
   379  
   380  // httpsOnlyHandler redirects requests to "http://example.com/foo?bar" to
   381  // "https://example.com/foo?bar". It should be used when the server is listening
   382  // for HTTP traffic behind a proxy that terminates TLS traffic, not when the Go
   383  // server is terminating TLS directly.
   384  type httpsOnlyHandler struct {
   385  	h http.Handler
   386  }
   387  
   388  // isProxiedReq checks whether the server is running behind a proxy that may be
   389  // terminating TLS.
   390  func isProxiedReq(r *http.Request) bool {
   391  	if _, ok := r.Header["X-Appengine-Https"]; ok {
   392  		return true
   393  	}
   394  	if _, ok := r.Header["X-Forwarded-Proto"]; ok {
   395  		return true
   396  	}
   397  	return false
   398  }
   399  
   400  func (h httpsOnlyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   401  	if r.Header.Get("X-Appengine-Https") == "off" || r.Header.Get("X-Forwarded-Proto") == "http" ||
   402  		(!isProxiedReq(r) && r.TLS == nil) {
   403  		r.URL.Scheme = "https"
   404  		r.URL.Host = r.Host
   405  		http.Redirect(w, r, r.URL.String(), http.StatusFound)
   406  		return
   407  	}
   408  	if r.Header.Get("X-Appengine-Https") == "on" || r.Header.Get("X-Forwarded-Proto") == "https" ||
   409  		(!isProxiedReq(r) && r.TLS != nil) {
   410  		// Only set this header when we're actually in production.
   411  		w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload")
   412  	}
   413  	h.h.ServeHTTP(w, r)
   414  }