blob: bed6c42c7b5e266cca77b7b564640b769c425c17 [file] [log] [blame]
// Copyright 2013 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"
"crypto/tls"
"log"
"net/http"
"os"
"sort"
"strings"
"golang.org/x/build/maintner/maintnerd/apipb"
"golang.org/x/build/repos"
"golang.org/x/net/http2"
"google.golang.org/appengine"
"grpc.go4.org" // simpler, uses x/net/http2.Transport; we use this elsewhere in x/build
)
var maintnerClient = createMaintnerClient()
func main() {
// authenticated handlers
handleFunc("/building", AuthHandler(buildingHandler)) // called by coordinator during builds
handleFunc("/clear-results", AuthHandler(clearResultsHandler)) // called by x/build/cmd/retrybuilds
handleFunc("/result", AuthHandler(resultHandler)) // called by coordinator after build
// public handlers
handleFunc("/", uiHandler)
handleFunc("/log/", logHandler)
// We used to use App Engine's static file handling support, declared in app.yaml,
// but it's currently broken with dev_appserver.py with the go111 runtime we use.
// So just do it ourselves. It doesn't buy us enough to be worth it.
fs := http.StripPrefix("/static", http.FileServer(http.Dir(staticDir())))
handleFunc("/static/", fs.ServeHTTP)
handleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://golang.org/favicon.ico", http.StatusFound)
})
appengine.Main()
}
func staticDir() string {
if pwd, _ := os.Getwd(); strings.HasSuffix(pwd, "app/appengine") {
return "static"
}
return "app/appengine/static"
}
func createMaintnerClient() apipb.MaintnerServiceClient {
addr := os.Getenv("MAINTNER_ADDR") // host[:port]
if addr == "" {
addr = "maintner.golang.org"
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
NextProtos: []string{"h2"},
InsecureSkipVerify: strings.HasPrefix(addr, "localhost:"),
},
}
hc := &http.Client{Transport: tr}
http2.ConfigureTransport(tr)
cc, err := grpc.NewClient(hc, "https://"+addr)
if err != nil {
log.Fatal(err)
}
return apipb.NewMaintnerServiceClient(cc)
}
func handleFunc(path string, h http.HandlerFunc) {
http.Handle(path, hstsHandler(h))
}
// hstsHandler wraps an http.HandlerFunc such that it sets the HSTS header.
func hstsHandler(fn http.HandlerFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload")
fn(w, r)
})
}
// Dashboard describes a unique build dashboard.
//
// (There used to be more than one dashboard, so this is now somewhat
// less important than it once was.)
type Dashboard struct {
Name string // This dashboard's name (always "Go" nowadays)
Packages []*Package // The project's packages to build
}
// packageWithPath returns the Package in d with the provided importPath,
// or nil if none is found.
func (d *Dashboard) packageWithPath(importPath string) *Package {
for _, p := range d.Packages {
if p.Path == importPath {
return p
}
}
return nil
}
// Context returns a namespaced context for this dashboard, or panics if it
// fails to create a new context.
func (d *Dashboard) Context(ctx context.Context) context.Context {
n, err := appengine.Namespace(ctx, "Git")
if err != nil {
panic(err)
}
return n
}
// goDash is the dashboard for the main go repository.
var goDash = &Dashboard{
Name: "Go",
Packages: []*Package{
{Name: "Go"},
},
}
func init() {
var add []*Package
for _, r := range repos.ByGerritProject {
if !r.ShowOnDashboard() {
continue
}
add = append(add, &Package{
Name: r.GoGerritProject,
Path: r.ImportPath,
})
}
sort.Slice(add, func(i, j int) bool {
return add[i].Name < add[j].Name
})
goDash.Packages = append(goDash.Packages, add...)
}