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  }