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 }