golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/coordinator/internal/legacydash/dash.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build linux || darwin 6 7 // Package legacydash holds the serving code for the build dashboard 8 // (build.golang.org) and its remaining HTTP API endpoints. 9 // 10 // It's a code transplant of the previous app/appengine application, 11 // converted into a package that coordinator can import and use. 12 // A newer version of the build dashboard is in development in 13 // the golang.org/x/build/cmd/coordinator/internal/dashboard package. 14 package legacydash 15 16 import ( 17 "embed" 18 "net/http" 19 "sort" 20 "strings" 21 22 "cloud.google.com/go/datastore" 23 "github.com/NYTimes/gziphandler" 24 "golang.org/x/build/cmd/coordinator/internal/lucipoll" 25 "golang.org/x/build/maintner/maintnerd/apipb" 26 "golang.org/x/build/repos" 27 "google.golang.org/grpc" 28 ) 29 30 type luciClient interface { 31 PostSubmitSnapshot() lucipoll.Snapshot 32 } 33 34 type handler struct { 35 mux *http.ServeMux 36 37 // Datastore client to a GCP project where build results are stored. 38 // Typically this is the golang-org GCP project. 39 datastoreCl *datastore.Client 40 41 // Maintner client for the maintner service. 42 // Typically the one at maintner.golang.org. 43 maintnerCl apipb.MaintnerServiceClient 44 45 // LUCI is a client for LUCI, used for fetching build results from there. 46 LUCI luciClient 47 } 48 49 func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { h.mux.ServeHTTP(w, req) } 50 51 // fakeResults controls whether to make up fake random results. If true, datastore is not used. 52 const fakeResults = false 53 54 // Handler sets a datastore client, maintner client, builder master key and 55 // GRPC server at the package scope, and returns an HTTP mux for the legacy dashboard. 56 func Handler(dc *datastore.Client, mc apipb.MaintnerServiceClient, lc luciClient, key string, grpcServer *grpc.Server) http.Handler { 57 h := handler{ 58 mux: http.NewServeMux(), 59 datastoreCl: dc, 60 maintnerCl: mc, 61 LUCI: lc, 62 } 63 kc := keyCheck{masterKey: key} 64 65 // authenticated handlers 66 h.mux.Handle("/clear-results", hstsGzip(authHandler{kc, h.clearResultsHandler})) // called by coordinator for x/build/cmd/retrybuilds 67 h.mux.Handle("/result", hstsGzip(authHandler{kc, h.resultHandler})) // called by coordinator after build 68 69 // public handlers 70 h.mux.Handle("/", GRPCHandler(grpcServer, hstsGzip(http.HandlerFunc(h.uiHandler)))) // enables GRPC server for build.golang.org 71 h.mux.Handle("/log/", hstsGzip(http.HandlerFunc(h.logHandler))) 72 73 // static handler 74 fs := http.FileServer(http.FS(static)) 75 h.mux.Handle("/static/", hstsGzip(fs)) 76 77 return h 78 } 79 80 //go:embed static 81 var static embed.FS 82 83 // GRPCHandler creates handler which intercepts requests intended for a GRPC server and directs the calls to the server. 84 // All other requests are directed toward the passed in handler. 85 func GRPCHandler(gs *grpc.Server, h http.Handler) http.Handler { 86 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 87 if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") { 88 gs.ServeHTTP(w, r) 89 return 90 } 91 h.ServeHTTP(w, r) 92 }) 93 } 94 95 // hstsGzip is short for hstsHandler(GzipHandler(h)). 96 func hstsGzip(h http.Handler) http.Handler { 97 return hstsHandler(gziphandler.GzipHandler(h)) 98 } 99 100 // hstsHandler returns a Handler that sets the HSTS header but 101 // otherwise just wraps h. 102 func hstsHandler(h http.Handler) http.Handler { 103 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 104 w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload") 105 h.ServeHTTP(w, r) 106 }) 107 } 108 109 // Dashboard describes a unique build dashboard. 110 // 111 // (There used to be more than one dashboard, so this is now somewhat 112 // less important than it once was.) 113 type Dashboard struct { 114 Name string // This dashboard's name (always "Go" nowadays) 115 Packages []*Package // The project's packages to build 116 } 117 118 // packageWithPath returns the Package in d with the provided importPath, 119 // or nil if none is found. 120 func (d *Dashboard) packageWithPath(importPath string) *Package { 121 for _, p := range d.Packages { 122 if p.Path == importPath { 123 return p 124 } 125 } 126 return nil 127 } 128 129 // goDash is the dashboard for the main go repository. 130 var goDash = &Dashboard{ 131 Name: "Go", 132 Packages: []*Package{ 133 {Name: "Go"}, 134 }, 135 } 136 137 func init() { 138 var add []*Package 139 for _, r := range repos.ByGerritProject { 140 if !r.ShowOnDashboard() { 141 continue 142 } 143 add = append(add, &Package{ 144 Name: r.GoGerritProject, 145 Path: r.ImportPath, 146 }) 147 } 148 sort.Slice(add, func(i, j int) bool { 149 return add[i].Name < add[j].Name 150 }) 151 goDash.Packages = append(goDash.Packages, add...) 152 }