Skip to content

Commit

Permalink
cmd/coordinator/internal/legacydash: refactor package-scoped variables
Browse files Browse the repository at this point in the history
I was on the of fence whether to take this on so late in the game; it
seems to favor slightly towards doing it anyway. This makes it easier
to see where the variables are used, and will make the future changes
easier to reason about.

For golang/go#65913.

Change-Id: I3c274d2aa7174a9fbd6be91869ce4b1da0dfecaa
Reviewed-on: https://go-review.googlesource.com/c/build/+/567499
Reviewed-by: Michael Knyszek <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Auto-Submit: Dmitri Shuralyov <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
  • Loading branch information
dmitshur authored and gopherbot committed Feb 29, 2024
1 parent f15cdea commit efa540e
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 75 deletions.
6 changes: 2 additions & 4 deletions cmd/coordinator/internal/legacydash/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.16

package legacydash

import (
Expand Down Expand Up @@ -449,13 +447,13 @@ func (l *Log) Text() ([]byte, error) {
return b, nil
}

func PutLog(c context.Context, text string) (hash string, err error) {
func (h handler) putLog(c context.Context, text string) (hash string, err error) {
b := new(bytes.Buffer)
z, _ := gzip.NewWriterLevel(b, gzip.BestCompression)
io.WriteString(z, text)
z.Close()
hash = loghash.New(text)
key := dsKey("Log", hash, nil)
_, err = datastoreClient.Put(c, key, &Log{b.Bytes()})
_, err = h.datastoreCl.Put(c, key, &Log{b.Bytes()})
return
}
42 changes: 19 additions & 23 deletions cmd/coordinator/internal/legacydash/dash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.16

// Package legacydash holds the serving code for the build dashboard
// (build.golang.org) and its remaining HTTP API endpoints.
//
Expand All @@ -26,48 +24,46 @@ import (
"google.golang.org/grpc"
)

var (
type handler struct {
mux *http.ServeMux

// Datastore client to a GCP project where build results are stored.
// Typically this is the golang-org GCP project.
datastoreClient *datastore.Client
datastoreCl *datastore.Client

// Maintner client for the maintner service.
// Typically the one at maintner.golang.org.
maintnerClient apipb.MaintnerServiceClient
maintnerCl apipb.MaintnerServiceClient
}

// The builder master key.
masterKey string

// TODO(golang.org/issue/38337): Keep moving away from package scope
// variables during future refactors.
)
func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { h.mux.ServeHTTP(w, req) }

// fakeResults controls whether to make up fake random results. If true, datastore is not used.
const fakeResults = false

// Handler sets a datastore client, maintner client, builder master key and
// GRPC server at the package scope, and returns an HTTP mux for the legacy dashboard.
func Handler(dc *datastore.Client, mc apipb.MaintnerServiceClient, key string, grpcServer *grpc.Server) http.Handler {
datastoreClient = dc
maintnerClient = mc
masterKey = key
grpcServer = grpcServer

mux := http.NewServeMux()
h := handler{
mux: http.NewServeMux(),
datastoreCl: dc,
maintnerCl: mc,
}
kc := keyCheck{masterKey: key}

// authenticated handlers
mux.Handle("/clear-results", hstsGzip(AuthHandler(clearResultsHandler))) // called by coordinator for x/build/cmd/retrybuilds
mux.Handle("/result", hstsGzip(AuthHandler(resultHandler))) // called by coordinator after build
h.mux.Handle("/clear-results", hstsGzip(authHandler{kc, h.clearResultsHandler})) // called by coordinator for x/build/cmd/retrybuilds
h.mux.Handle("/result", hstsGzip(authHandler{kc, h.resultHandler})) // called by coordinator after build

// public handlers
mux.Handle("/", GRPCHandler(grpcServer, hstsGzip(http.HandlerFunc(uiHandler)))) // enables GRPC server for build.golang.org
mux.Handle("/log/", hstsGzip(http.HandlerFunc(logHandler)))
h.mux.Handle("/", GRPCHandler(grpcServer, hstsGzip(http.HandlerFunc(h.uiHandler)))) // enables GRPC server for build.golang.org
h.mux.Handle("/log/", hstsGzip(http.HandlerFunc(h.logHandler)))

// static handler
fs := http.FileServer(http.FS(static))
mux.Handle("/static/", hstsGzip(fs))
h.mux.Handle("/static/", hstsGzip(fs))

return mux
return h
}

//go:embed static
Expand Down
61 changes: 35 additions & 26 deletions cmd/coordinator/internal/legacydash/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.16

package legacydash

import (
"context"
"crypto/hmac"
"crypto/md5"
"encoding/json"
Expand All @@ -32,7 +29,7 @@ const (
// creates a new Result entity, and creates or updates the relevant Commit entity.
// If the Log field is not empty, resultHandler creates a new Log entity
// and updates the LogHash field before putting the Commit entity.
func resultHandler(r *http.Request) (interface{}, error) {
func (h handler) resultHandler(r *http.Request) (interface{}, error) {
if r.Method != "POST" {
return nil, errBadMethod(r.Method)
}
Expand All @@ -54,7 +51,7 @@ func resultHandler(r *http.Request) (interface{}, error) {
}
// store the Log text if supplied
if len(res.Log) > 0 {
hash, err := PutLog(ctx, res.Log)
hash, err := h.putLog(ctx, res.Log)
if err != nil {
return nil, fmt.Errorf("putting Log: %v", err)
}
Expand All @@ -75,23 +72,27 @@ func resultHandler(r *http.Request) (interface{}, error) {
}
return nil
}
_, err := datastoreClient.RunInTransaction(ctx, tx)
_, err := h.datastoreCl.RunInTransaction(ctx, tx)
return nil, err
}

// logHandler displays log text for a given hash.
// It handles paths like "/log/hash".
func logHandler(w http.ResponseWriter, r *http.Request) {
func (h handler) logHandler(w http.ResponseWriter, r *http.Request) {
if h.datastoreCl == nil {
http.Error(w, "no datastore client", http.StatusNotFound)
return
}
c := r.Context()
hash := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
key := dsKey("Log", hash, nil)
l := new(Log)
if err := datastoreClient.Get(c, key, l); err != nil {
if err := h.datastoreCl.Get(c, key, l); err != nil {
if err == datastore.ErrNoSuchEntity {
// Fall back to default namespace;
// maybe this was on the old dashboard.
key.Namespace = ""
err = datastoreClient.Get(c, key, l)
err = h.datastoreCl.Get(c, key, l)
}
if err != nil {
log.Printf("Error: %v", err)
Expand All @@ -111,7 +112,7 @@ func logHandler(w http.ResponseWriter, r *http.Request) {

// clearResultsHandler purge a single build failure from the dashboard.
// It currently only supports the main Go repo.
func clearResultsHandler(r *http.Request) (interface{}, error) {
func (h handler) clearResultsHandler(r *http.Request) (interface{}, error) {
if r.Method != "POST" {
return nil, errBadMethod(r.Method)
}
Expand All @@ -126,7 +127,7 @@ func clearResultsHandler(r *http.Request) (interface{}, error) {

ctx := r.Context()

_, err := datastoreClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
_, err := h.datastoreCl.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
c := &Commit{
PackagePath: "", // TODO(adg): support clearing sub-repos
Hash: hash,
Expand Down Expand Up @@ -186,12 +187,15 @@ func builderKeyRevoked(builder string) bool {
return false
}

// AuthHandler wraps an http.HandlerFunc with a handler that validates the
// supplied key and builder query parameters.
func AuthHandler(h dashHandler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c := r.Context()
// authHandler wraps an http.HandlerFunc with a handler that validates the
// supplied key and builder query parameters with the provided key checker.
type authHandler struct {
kc keyCheck
h dashHandler
}

func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
{ // Block to improve diff readability. Can be unnested later.
// Put the URL Query values into r.Form to avoid parsing the
// request body when calling r.FormValue.
r.Form = r.URL.Query()
Expand All @@ -202,13 +206,13 @@ func AuthHandler(h dashHandler) http.Handler {
// Validate key query parameter for POST requests only.
key := r.FormValue("key")
builder := r.FormValue("builder")
if r.Method == "POST" && !validKey(c, key, builder) {
if r.Method == "POST" && !a.kc.ValidKey(key, builder) {
err = fmt.Errorf("invalid key %q for builder %q", key, builder)
}

// Call the original HandlerFunc and return the response.
if err == nil {
resp, err = h(r)
resp, err = a.h(r)
}

// Write JSON response.
Expand All @@ -221,7 +225,7 @@ func AuthHandler(h dashHandler) http.Handler {
if err = json.NewEncoder(w).Encode(dashResp); err != nil {
log.Printf("encoding response: %v", err)
}
})
}
}

// validHash reports whether hash looks like a valid git commit hash.
Expand All @@ -231,22 +235,27 @@ func validHash(hash string) bool {
return hash != ""
}

func validKey(c context.Context, key, builder string) bool {
if isMasterKey(c, key) {
type keyCheck struct {
// The builder master key.
masterKey string
}

func (kc keyCheck) ValidKey(key, builder string) bool {
if kc.isMasterKey(key) {
return true
}
if builderKeyRevoked(builder) {
return false
}
return key == builderKey(c, builder)
return key == kc.builderKey(builder)
}

func isMasterKey(ctx context.Context, k string) bool {
return k == masterKey
func (kc keyCheck) isMasterKey(k string) bool {
return k == kc.masterKey
}

func builderKey(ctx context.Context, builder string) string {
h := hmac.New(md5.New, []byte(masterKey))
func (kc keyCheck) builderKey(builder string) string {
h := hmac.New(md5.New, []byte(kc.masterKey))
h.Write([]byte(builder))
return fmt.Sprintf("%x", h.Sum(nil))
}
Expand Down
28 changes: 9 additions & 19 deletions cmd/coordinator/internal/legacydash/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.16

package legacydash

import (
Expand Down Expand Up @@ -34,7 +32,7 @@ import (
)

// uiHandler is the HTTP handler for the https://build.golang.org/.
func uiHandler(w http.ResponseWriter, r *http.Request) {
func (h handler) uiHandler(w http.ResponseWriter, r *http.Request) {
view, err := viewForRequest(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
Expand All @@ -55,7 +53,7 @@ func uiHandler(w http.ResponseWriter, r *http.Request) {
var rpcs errgroup.Group
rpcs.Go(func() error {
var err error
tb.res, err = maintnerClient.GetDashboard(ctx, dashReq)
tb.res, err = h.maintnerCl.GetDashboard(ctx, dashReq)
return err
})
if view.ShowsActiveBuilds() {
Expand All @@ -68,7 +66,7 @@ func uiHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "maintner.GetDashboard: "+err.Error(), httpStatusOfErr(err))
return
}
data, err := tb.buildTemplateData(ctx)
data, err := tb.buildTemplateData(ctx, h.datastoreCl)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
Expand Down Expand Up @@ -152,7 +150,7 @@ func (tb *uiTemplateDataBuilder) getCommitsToLoad() map[commitInPackage]bool {
// want map. The returned map is keyed by the git hash and may not
// contain items that didn't exist in the datastore. (It is not an
// error if 1 or all don't exist.)
func (tb *uiTemplateDataBuilder) loadDatastoreCommits(ctx context.Context, want map[commitInPackage]bool) (map[string]*Commit, error) {
func (tb *uiTemplateDataBuilder) loadDatastoreCommits(ctx context.Context, cl *datastore.Client, want map[commitInPackage]bool) (map[string]*Commit, error) {
ret := map[string]*Commit{}

// Allow tests to fake what the datastore would've loaded, and
Expand All @@ -174,7 +172,7 @@ func (tb *uiTemplateDataBuilder) loadDatastoreCommits(ctx context.Context, want
}).Key()
keys = append(keys, key)
}
commits, err := fetchCommits(ctx, keys)
commits, err := fetchCommits(ctx, cl, keys)
if err != nil {
return nil, fmt.Errorf("fetchCommits: %v", err)
}
Expand Down Expand Up @@ -277,8 +275,8 @@ func repoImportPath(rh *apipb.DashRepoHead) string {
return ri.ImportPath
}

func (tb *uiTemplateDataBuilder) buildTemplateData(ctx context.Context) (*uiTemplateData, error) {
dsCommits, err := tb.loadDatastoreCommits(ctx, tb.getCommitsToLoad())
func (tb *uiTemplateDataBuilder) buildTemplateData(ctx context.Context, datastoreCl *datastore.Client) (*uiTemplateData, error) {
dsCommits, err := tb.loadDatastoreCommits(ctx, datastoreCl, tb.getCommitsToLoad())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -325,14 +323,6 @@ func (tb *uiTemplateDataBuilder) buildTemplateData(ctx context.Context) (*uiTemp
}
}

// Release Branches
var releaseBranches []string
for _, gr := range tb.res.Releases {
if gr.BranchName != "master" {
releaseBranches = append(releaseBranches, gr.BranchName)
}
}

gerritProject := "go"
if repo := repos.ByImportPath[tb.req.Repo]; repo != nil {
gerritProject = repo.GoGerritProject
Expand Down Expand Up @@ -561,7 +551,7 @@ type Pagination struct {
// It is not an error if a commit doesn't exist.
// Only commits that were found in datastore are returned,
// in an unspecified order.
func fetchCommits(ctx context.Context, keys []*datastore.Key) ([]*Commit, error) {
func fetchCommits(ctx context.Context, cl *datastore.Client, keys []*datastore.Key) ([]*Commit, error) {
if len(keys) == 0 {
return nil, nil
}
Expand All @@ -570,7 +560,7 @@ func fetchCommits(ctx context.Context, keys []*datastore.Key) ([]*Commit, error)
out[i] = new(Commit)
}

err := datastoreClient.GetMulti(ctx, keys, out)
err := cl.GetMulti(ctx, keys, out)
err = filterDatastoreError(err)
err = filterNoSuchEntity(err)
if err != nil {
Expand Down
4 changes: 1 addition & 3 deletions cmd/coordinator/internal/legacydash/ui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.16

package legacydash

import (
Expand Down Expand Up @@ -327,7 +325,7 @@ func TestUITemplateDataBuilder(t *testing.T) {
activeBuilds: tt.activeBuilds,
testCommitData: tt.testCommitData,
}
data, err := tb.buildTemplateData(context.Background())
data, err := tb.buildTemplateData(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit efa540e

Please sign in to comment.