app/appengine, app/key: delete
The appengine command is no longer needed since
the coordinator is serving the build dashboard.
The key package is now unused.
For golang/go#34744.
Change-Id: I74218bac6b7457434975d13842e59e150fc078be
Reviewed-on: https://go-review.googlesource.com/c/build/+/336792
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
diff --git a/app/appengine/.gcloudignore b/app/appengine/.gcloudignore
deleted file mode 100644
index 199e6d9..0000000
--- a/app/appengine/.gcloudignore
+++ /dev/null
@@ -1,25 +0,0 @@
-# This file specifies files that are *not* uploaded to Google Cloud Platform
-# using gcloud. It follows the same syntax as .gitignore, with the addition of
-# "#!include" directives (which insert the entries of the given .gitignore-style
-# file at that point).
-#
-# For more information, run:
-# $ gcloud topic gcloudignore
-#
-.gcloudignore
-# If you would like to upload your .git directory, .gitignore file or files
-# from your .gitignore file, remove the corresponding line
-# below:
-.git
-.gitignore
-
-# Binaries for programs and plugins
-*.exe
-*.exe~
-*.dll
-*.so
-*.dylib
-# Test binary, build with `go test -c`
-*.test
-# Output of the go coverage tool, specifically when used with LiteIDE
-*.out
\ No newline at end of file
diff --git a/app/appengine/Makefile b/app/appengine/Makefile
deleted file mode 100644
index c9d55ba..0000000
--- a/app/appengine/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-usage:
- echo "See Makefile for usage"
- exit 1
-
-deploy-prod:
- go install golang.org/x/build/cmd/xb
- GO111MODULE=on gcloud app --account=$$(xb google-email) --project=golang-org deploy app.yaml
-
-deploy-test:
- go install golang.org/x/build/cmd/xb
- GO111MODULE=on gcloud app --account=$$(xb google-email) --project=golang-org deploy --no-promote app.yaml
diff --git a/app/appengine/README.md b/app/appengine/README.md
deleted file mode 100644
index bdff720..0000000
--- a/app/appengine/README.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# build.golang.org App Engine App
-
-This is the code that runs https://build.golang.org/
-
-## Local development
-
-To use production maintner data (for the GetDashboard RPC containing
-the list of commits, etc) and production active builds (from the
-coordinator), both of which are open to anybody, use:
-
-```
-go run . --dev --fake-results
-```
-
-If you also want to use the production datastore for real commit data,
-or you want to work on the handlers that mutate data in the datastore,
-use:
-
-```
-go run . --dev
-```
-
-That requires access to the "golang-org" GCP project's datastore.
-
-Environment variables you can change:
-
-* `PORT`: plain port number or Go-style listen address
-* `DATASTORE_PROJECT_ID`: defaults to `"golang-org"` in dev mode
-* `MAINTNER_ADDR`: defaults to "maintner.golang.org"
-
-## Deploying a test version
-
-To deploy to the production project but to a version that's not promoted to the default URL:
-
-```sh
-make deploy-test
-```
-
-It will tell you what URL it deployed to. You can then check it and
-either delete it or promote it with either the gcloud or web UIs. Or
-just ignore it. They'll scale to zero and are only visual clutter
-until somebody deletes a batch of old ones.
-
-## Deploying to production
-
-To deploy to https://build.golang.org:
-
-```sh
-make deploy-prod
-```
-
diff --git a/app/appengine/app.yaml b/app/appengine/app.yaml
deleted file mode 100644
index 057f29d..0000000
--- a/app/appengine/app.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-runtime: go113
-service: build
-
-handlers:
- - url: /.*
- script: auto
- secure: always
diff --git a/app/appengine/build.go b/app/appengine/build.go
deleted file mode 100644
index d2ced21..0000000
--- a/app/appengine/build.go
+++ /dev/null
@@ -1,456 +0,0 @@
-// Copyright 2011 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 (
- "bytes"
- "compress/gzip"
- "context"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "math/rand"
- pathpkg "path"
- "strings"
-
- "cloud.google.com/go/datastore"
- "golang.org/x/build/dashboard"
- "golang.org/x/build/internal/loghash"
-)
-
-const (
- maxDatastoreStringLen = 500
-)
-
-func dsKey(kind, name string, parent *datastore.Key) *datastore.Key {
- dk := datastore.NameKey(kind, name, parent)
- dk.Namespace = "Git"
- return dk
-}
-
-// A Package describes a package that is listed on the dashboard.
-type Package struct {
- Name string // "Go", "arch", "net", ...
- Path string // empty for the main Go tree, else "golang.org/x/foo"
-}
-
-func (p *Package) String() string {
- return fmt.Sprintf("%s: %q", p.Path, p.Name)
-}
-
-func (p *Package) Key() *datastore.Key {
- key := p.Path
- if key == "" {
- key = "go"
- }
- return dsKey("Package", key, nil)
-}
-
-// filterDatastoreError returns err, unless it's just about datastore
-// not being able to load an entity with old legacy struct fields into
-// the Commit type that has since removed those fields.
-func filterDatastoreError(err error) error {
- return filterAppEngineError(err, func(err error) bool {
- if em, ok := err.(*datastore.ErrFieldMismatch); ok {
- switch em.FieldName {
- case "NeedsBenchmarking", "TryPatch", "FailNotificationSent":
- // Removed in CLs 208397 and 208324.
- return true
- case "PackagePath", "ParentHash", "Num", "User", "Desc", "Time", "Branch", "NextNum", "Kind":
- // Removed in move to maintner in CL 208697.
- return true
- }
- }
- return false
- })
-}
-
-// filterNoSuchEntity returns err, unless it's just about datastore
-// not being able to load an entity because it doesn't exist.
-func filterNoSuchEntity(err error) error {
- return filterAppEngineError(err, func(err error) bool {
- return err == datastore.ErrNoSuchEntity
- })
-}
-
-// filterAppEngineError returns err, unless ignore(err) is true,
-// in which case it returns nil. If err is an datastore.MultiError,
-// it returns either nil (if all errors are ignored) or a deep copy
-// with the non-ignored errors.
-func filterAppEngineError(err error, ignore func(error) bool) error {
- if err == nil || ignore(err) {
- return nil
- }
- if me, ok := err.(datastore.MultiError); ok {
- me2 := make(datastore.MultiError, 0, len(me))
- for _, err := range me {
- if e2 := filterAppEngineError(err, ignore); e2 != nil {
- me2 = append(me2, e2)
- }
- }
- if len(me2) == 0 {
- return nil
- }
- return me2
- }
- return err
-}
-
-// getOrMakePackageInTx fetches a Package by path from the datastore,
-// creating it if necessary.
-func getOrMakePackageInTx(ctx context.Context, tx *datastore.Transaction, path string) (*Package, error) {
- p := &Package{Path: path}
- if path != "" {
- p.Name = pathpkg.Base(path)
- } else {
- p.Name = "Go"
- }
- err := tx.Get(p.Key(), p)
- err = filterDatastoreError(err)
- if err == datastore.ErrNoSuchEntity {
- if _, err := tx.Put(p.Key(), p); err != nil {
- return nil, err
- }
- return p, nil
- }
- if err != nil {
- return nil, err
- }
- return p, nil
-}
-
-type builderAndGoHash struct {
- builder, goHash string
-}
-
-// A Commit describes an individual commit in a package.
-//
-// Each Commit entity is a descendant of its associated Package entity.
-// In other words, all Commits with the same PackagePath belong to the same
-// datastore entity group.
-type Commit struct {
- PackagePath string // (empty for main repo commits)
- Hash string
-
- // ResultData is the Data string of each build Result for this Commit.
- // For non-Go commits, only the Results for the current Go tip, weekly,
- // and release Tags are stored here. This is purely de-normalized data.
- // The complete data set is stored in Result entities.
- ResultData []string `datastore:",noindex"`
-}
-
-func (com *Commit) Key() *datastore.Key {
- if com.Hash == "" {
- panic("tried Key on Commit with empty Hash")
- }
- p := Package{Path: com.PackagePath}
- key := com.PackagePath + "|" + com.Hash
- return dsKey("Commit", key, p.Key())
-}
-
-// Valid reports whether the commit is valid.
-func (c *Commit) Valid() bool {
- // Valid really just means the hash is populated.
- return validHash(c.Hash)
-}
-
-// each result line is approx 105 bytes. This constant is a tradeoff between
-// build history and the AppEngine datastore limit of 1mb.
-const maxResults = 1000
-
-// AddResult adds the denormalized Result data to the Commit's
-// ResultData field.
-func (com *Commit) AddResult(tx *datastore.Transaction, r *Result) error {
- err := tx.Get(com.Key(), com)
- if err == datastore.ErrNoSuchEntity {
- // If it doesn't exist, we create it below.
- } else {
- err = filterDatastoreError(err)
- if err != nil {
- return fmt.Errorf("Commit.AddResult, getting Commit: %v", err)
- }
- }
-
- var resultExists bool
- for i, s := range com.ResultData {
- // if there already exists result data for this builder at com, overwrite it.
- if strings.HasPrefix(s, r.Builder+"|") && strings.HasSuffix(s, "|"+r.GoHash) {
- resultExists = true
- com.ResultData[i] = r.Data()
- }
- }
- if !resultExists {
- // otherwise, add the new result data for this builder.
- com.ResultData = trim(append(com.ResultData, r.Data()), maxResults)
- }
- if !com.Valid() {
- return errors.New("putting Commit: commit is not valid")
- }
- if _, err := tx.Put(com.Key(), com); err != nil {
- return fmt.Errorf("putting Commit: %v", err)
- }
- return nil
-}
-
-// removeResult removes the denormalized Result data from the ResultData field
-// for the given builder and go hash.
-// It must be called from within the datastore transaction that gets and puts
-// the Commit. Note this is slightly different to AddResult, above.
-func (com *Commit) RemoveResult(r *Result) {
- var rd []string
- for _, s := range com.ResultData {
- if strings.HasPrefix(s, r.Builder+"|") && strings.HasSuffix(s, "|"+r.GoHash) {
- continue
- }
- rd = append(rd, s)
- }
- com.ResultData = rd
-}
-
-func trim(s []string, n int) []string {
- l := min(len(s), n)
- return s[len(s)-l:]
-}
-
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
-
-// Result returns the build Result for this Commit for the given builder/goHash.
-//
-// For the main Go repo, goHash is the empty string.
-func (c *Commit) Result(builder, goHash string) *Result {
- return result(c.ResultData, c.Hash, c.PackagePath, builder, goHash)
-}
-
-// Result returns the build Result for this commit for the given builder/goHash.
-//
-// For the main Go repo, goHash is the empty string.
-func (c *CommitInfo) Result(builder, goHash string) *Result {
- if r := result(c.ResultData, c.Hash, c.PackagePath, builder, goHash); r != nil {
- return r
- }
- if u, ok := c.BuildingURLs[builderAndGoHash{builder, goHash}]; ok {
- return &Result{
- Builder: builder,
- BuildingURL: u,
- Hash: c.Hash,
- GoHash: goHash,
- }
- }
- if *fakeResults {
- switch rand.Intn(3) {
- default:
- return nil
- case 1:
- return &Result{
- Builder: builder,
- Hash: c.Hash,
- GoHash: goHash,
- OK: true,
- }
- case 2:
- return &Result{
- Builder: builder,
- Hash: c.Hash,
- GoHash: goHash,
- LogHash: "fakefailureurl",
- }
- }
- }
- return nil
-}
-
-func result(resultData []string, hash, packagePath, builder, goHash string) *Result {
- for _, r := range resultData {
- if !strings.HasPrefix(r, builder) {
- // Avoid strings.SplitN alloc in the common case.
- continue
- }
- p := strings.SplitN(r, "|", 4)
- if len(p) != 4 || p[0] != builder || p[3] != goHash {
- continue
- }
- return partsToResult(hash, packagePath, p)
- }
- return nil
-}
-
-// isUntested reports whether a cell in the build.golang.org grid is
-// an untested configuration.
-//
-// repo is "go", "net", etc.
-// branch is the branch of repo "master" or "release-branch.go1.12"
-// goBranch applies only if repo != "go" and is of form "master" or "release-branch.go1.N"
-//
-// As a special case, "tip" is an alias for "master", since this app
-// still uses a bunch of hg terms from when we used hg.
-func isUntested(builder, repo, branch, goBranch string) bool {
- if branch == "tip" {
- branch = "master"
- }
- if goBranch == "tip" {
- goBranch = "master"
- }
- bc, ok := dashboard.Builders[builder]
- if !ok {
- // Unknown builder, so not tested.
- return true
- }
- return !bc.BuildsRepoPostSubmit(repo, branch, goBranch)
-}
-
-// knownIssue returns a known issue for the named builder,
-// or zero if there isn't a known issue.
-func knownIssue(builder string) int {
- bc, ok := dashboard.Builders[builder]
- if !ok {
- // Unknown builder.
- return 0
- }
- return bc.KnownIssue
-}
-
-// Results returns the build Results for this Commit.
-func (c *CommitInfo) Results() (results []*Result) {
- for _, r := range c.ResultData {
- p := strings.SplitN(r, "|", 4)
- if len(p) != 4 {
- continue
- }
- results = append(results, partsToResult(c.Hash, c.PackagePath, p))
- }
- return
-}
-
-// ResultGoHashes, for non-go repos, returns the list of Go hashes that
-// this repo has been (or should be) built at.
-//
-// For the main Go repo it always returns a slice with 1 element: the
-// empty string.
-func (c *CommitInfo) ResultGoHashes() []string {
- // For the main repo, just return the empty string
- // (there's no corresponding main repo hash for a main repo Commit).
- // This function is only really useful for sub-repos.
- if c.PackagePath == "" {
- return []string{""}
- }
- var hashes []string
- for _, r := range c.ResultData {
- p := strings.SplitN(r, "|", 4)
- if len(p) != 4 {
- continue
- }
- // Append only new results (use linear scan to preserve order).
- if !contains(hashes, p[3]) {
- hashes = append(hashes, p[3])
- }
- }
- // Return results in reverse order (newest first).
- reverse(hashes)
- return hashes
-}
-
-func contains(t []string, s string) bool {
- for _, s2 := range t {
- if s2 == s {
- return true
- }
- }
- return false
-}
-
-func reverse(s []string) {
- for i := 0; i < len(s)/2; i++ {
- j := len(s) - i - 1
- s[i], s[j] = s[j], s[i]
- }
-}
-
-// partsToResult creates a Result from ResultData substrings.
-func partsToResult(hash, packagePath string, p []string) *Result {
- return &Result{
- Builder: p[0],
- Hash: hash,
- PackagePath: packagePath,
- GoHash: p[3],
- OK: p[1] == "true",
- LogHash: p[2],
- }
-}
-
-// A Result describes a build result for a Commit on an OS/architecture.
-//
-// Each Result entity is a descendant of its associated Package entity.
-type Result struct {
- Builder string // "os-arch[-note]"
- PackagePath string // (empty for Go commits, else "golang.org/x/foo")
- Hash string
-
- // The Go Commit this was built against (when PackagePath != ""; empty for Go commits).
- GoHash string
-
- BuildingURL string `datastore:"-"` // non-empty if currently building
- OK bool
- Log string `datastore:"-"` // for JSON unmarshaling only
- LogHash string `datastore:",noindex"` // Key to the Log record.
-
- RunTime int64 // time to build+test in nanoseconds
-}
-
-func (r *Result) Key() *datastore.Key {
- p := Package{Path: r.PackagePath}
- key := r.Builder + "|" + r.PackagePath + "|" + r.Hash + "|" + r.GoHash
- return dsKey("Result", key, p.Key())
-}
-
-func (r *Result) Valid() error {
- if !validHash(r.Hash) {
- return errors.New("invalid Hash")
- }
- if r.PackagePath != "" && !validHash(r.GoHash) {
- return errors.New("invalid GoHash")
- }
- return nil
-}
-
-// Data returns the Result in string format
-// to be stored in Commit's ResultData field.
-func (r *Result) Data() string {
- return fmt.Sprintf("%v|%v|%v|%v", r.Builder, r.OK, r.LogHash, r.GoHash)
-}
-
-// A Log is a gzip-compressed log file stored under the SHA1 hash of the
-// uncompressed log text.
-type Log struct {
- CompressedLog []byte `datastore:",noindex"`
-}
-
-func (l *Log) Text() ([]byte, error) {
- d, err := gzip.NewReader(bytes.NewBuffer(l.CompressedLog))
- if err != nil {
- return nil, fmt.Errorf("reading log data: %v", err)
- }
- b, err := ioutil.ReadAll(d)
- if err != nil {
- return nil, fmt.Errorf("reading log data: %v", err)
- }
- return b, nil
-}
-
-func 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()})
- return
-}
diff --git a/app/appengine/dash.go b/app/appengine/dash.go
deleted file mode 100644
index 691efaa..0000000
--- a/app/appengine/dash.go
+++ /dev/null
@@ -1,203 +0,0 @@
-// 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"
- crand "crypto/rand"
- "crypto/tls"
- "flag"
- "fmt"
- "log"
- "net/http"
- "os"
- "sort"
- "strings"
-
- "cloud.google.com/go/datastore"
- "github.com/NYTimes/gziphandler"
- "golang.org/x/build/maintner/maintnerd/apipb"
- "golang.org/x/build/repos"
- "golang.org/x/net/http2"
- "grpc.go4.org" // simpler, uses x/net/http2.Transport; we use this elsewhere in x/build
-)
-
-var (
- maintnerClient = createMaintnerClient()
- datastoreClient *datastore.Client // not done at init as createDatastoreClient fails under test environments
-)
-
-var (
- dev = flag.Bool("dev", false, "whether to run in local development mode")
- fakeResults = flag.Bool("fake-results", false, "dev mode option: whether to make up fake random results. If true, datastore is not used.")
-)
-
-func main() {
- flag.Parse()
- if *fakeResults && !*dev {
- log.Fatalf("--fake-results requires --dev mode")
- }
- if *dev {
- randBytes := make([]byte, 20)
- if _, err := crand.Read(randBytes[:]); err != nil {
- panic(err)
- }
- devModeMasterKey = fmt.Sprintf("%x", randBytes)
- if !*fakeResults {
- log.Printf("Running in dev mode. Temporary master key is %v", devModeMasterKey)
- if os.Getenv("DATASTORE_PROJECT_ID") == "" {
- log.Printf("DATASTORE_PROJECT_ID not set; defaulting to production golang-org")
- os.Setenv("DATASTORE_PROJECT_ID", "golang-org")
- }
- }
- }
-
- datastoreClient = createDatastoreClient()
-
- if *dev && !*fakeResults {
- // Test early whether user has datastore access.
- key := dsKey("Log", "bogus-want-no-such-entity", nil)
- if err := datastoreClient.Get(context.Background(), key, new(Log)); err != datastore.ErrNoSuchEntity {
- log.Printf("Failed to access datastore: %v", err)
- log.Printf("Run with --fake-results to avoid hitting a real datastore.")
- os.Exit(1)
- }
- }
-
- // authenticated handlers
- 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)
- })
-
- listen := os.Getenv("PORT")
- if listen == "" {
- listen = "8080"
- }
- if !strings.Contains(listen, ":") {
- listen = ":" + listen
- }
-
- log.Printf("Serving dashboard on %s", listen)
- if err := http.ListenAndServe(listen, nil); err != nil {
- log.Fatal(err)
- }
-}
-
-func staticDir() string {
- if pwd, _ := os.Getwd(); strings.HasSuffix(pwd, "app/appengine") {
- return "static"
- }
- return "app/appengine/static"
-}
-
-func createDatastoreClient() *datastore.Client {
- if *fakeResults {
- return nil
- }
- // First try with an empty project ID, so $DATASTORE_PROJECT_ID will be respected
- // if set.
- c, err := datastore.NewClient(context.Background(), "")
- if err == nil {
- return c
- }
- // Otherwise auto-detect it from the environment (that is,
- // work automatically in prod).
- c, err = datastore.NewClient(context.Background(), datastore.DetectProjectID)
- if err != nil {
- log.Fatalf("datastore.NewClient: %v", err)
- }
- return c
-}
-
-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(gziphandler.GzipHandler(h)))
-}
-
-// hstsHandler returns a Handler that sets the HSTS header but
-// otherwise just wraps h.
-func hstsHandler(h http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload")
- h.ServeHTTP(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
-}
-
-// 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...)
-}
diff --git a/app/appengine/handler.go b/app/appengine/handler.go
deleted file mode 100644
index 0283bd6..0000000
--- a/app/appengine/handler.go
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2011 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/hmac"
- "crypto/md5"
- "encoding/json"
- "errors"
- "fmt"
- "html"
- "log"
- "net/http"
- "strconv"
- "strings"
- "unicode/utf8"
-
- "cloud.google.com/go/datastore"
- "golang.org/x/build/app/key"
-)
-
-const (
- commitsPerPage = 30
- builderVersion = 1 // must match x/build/cmd/coordinator/dash.go's value
-)
-
-// resultHandler records a build result.
-// It reads a JSON-encoded Result value from the request body,
-// 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) {
- if r.Method != "POST" {
- return nil, errBadMethod(r.Method)
- }
-
- v, _ := strconv.Atoi(r.FormValue("version"))
- if v != builderVersion {
- return nil, fmt.Errorf("rejecting POST from builder; need version %v instead of %v",
- builderVersion, v)
- }
-
- ctx := r.Context()
- res := new(Result)
- defer r.Body.Close()
- if err := json.NewDecoder(r.Body).Decode(res); err != nil {
- return nil, fmt.Errorf("decoding Body: %v", err)
- }
- if err := res.Valid(); err != nil {
- return nil, fmt.Errorf("validating Result: %v", err)
- }
- // store the Log text if supplied
- if len(res.Log) > 0 {
- hash, err := PutLog(ctx, res.Log)
- if err != nil {
- return nil, fmt.Errorf("putting Log: %v", err)
- }
- res.LogHash = hash
- }
- tx := func(tx *datastore.Transaction) error {
- if _, err := getOrMakePackageInTx(ctx, tx, res.PackagePath); err != nil {
- return fmt.Errorf("GetPackage: %v", err)
- }
- // put Result
- if _, err := tx.Put(res.Key(), res); err != nil {
- return fmt.Errorf("putting Result: %v", err)
- }
- // add Result to Commit
- com := &Commit{PackagePath: res.PackagePath, Hash: res.Hash}
- if err := com.AddResult(tx, res); err != nil {
- return fmt.Errorf("AddResult: %v", err)
- }
- return nil
- }
- _, err := datastoreClient.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) {
- w.Header().Set("Content-type", "text/plain; charset=utf-8")
- 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 == datastore.ErrNoSuchEntity {
- // Fall back to default namespace;
- // maybe this was on the old dashboard.
- key.Namespace = ""
- err = datastoreClient.Get(c, key, l)
- }
- if err != nil {
- logErr(w, r, err)
- return
- }
- }
- b, err := l.Text()
- if err != nil {
- logErr(w, r, err)
- return
- }
- w.Write(b)
-}
-
-// clearResultsHandler purge a single build failure from the dashboard.
-// It currently only supports the main Go repo.
-func clearResultsHandler(r *http.Request) (interface{}, error) {
- if r.Method != "POST" {
- return nil, errBadMethod(r.Method)
- }
- builder := r.FormValue("builder")
- hash := r.FormValue("hash")
- if builder == "" {
- return nil, errors.New("missing 'builder'")
- }
- if hash == "" {
- return nil, errors.New("missing 'hash'")
- }
-
- ctx := r.Context()
-
- _, err := datastoreClient.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
- c := &Commit{
- PackagePath: "", // TODO(adg): support clearing sub-repos
- Hash: hash,
- }
- err := tx.Get(c.Key(), c)
- err = filterDatastoreError(err)
- if err == datastore.ErrNoSuchEntity {
- // Doesn't exist, so no build to clear.
- return nil
- }
- if err != nil {
- return err
- }
-
- r := c.Result(builder, "")
- if r == nil {
- // No result, so nothing to clear.
- return nil
- }
- c.RemoveResult(r)
- _, err = tx.Put(c.Key(), c)
- if err != nil {
- return err
- }
- return tx.Delete(r.Key())
- })
- return nil, err
-}
-
-type dashHandler func(*http.Request) (interface{}, error)
-
-type dashResponse struct {
- Response interface{}
- Error string
-}
-
-// errBadMethod is returned by a dashHandler when
-// the request has an unsuitable method.
-type errBadMethod string
-
-func (e errBadMethod) Error() string {
- return "bad method: " + string(e)
-}
-
-func builderKeyRevoked(builder string) bool {
- switch builder {
- case "plan9-amd64-mischief":
- // Broken and unmaintained for months.
- // It's polluting the dashboard.
- return true
- case "linux-arm-onlinenet":
- // Requested to be revoked by Dave Cheney.
- // The machine is in a fail+report loop
- // and can't be accessed. Revoke it for now.
- return true
- }
- return false
-}
-
-// AuthHandler wraps a http.HandlerFunc with a handler that validates the
-// supplied key and builder query parameters.
-func AuthHandler(h dashHandler) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- c := r.Context()
-
- // Put the URL Query values into r.Form to avoid parsing the
- // request body when calling r.FormValue.
- r.Form = r.URL.Query()
-
- var err error
- var resp interface{}
-
- // Validate key query parameter for POST requests only.
- key := r.FormValue("key")
- builder := r.FormValue("builder")
- if r.Method == "POST" && !validKey(c, 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)
- }
-
- // Write JSON response.
- dashResp := &dashResponse{Response: resp}
- if err != nil {
- log.Printf("%v", err)
- dashResp.Error = err.Error()
- }
- w.Header().Set("Content-Type", "application/json")
- 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.
-func validHash(hash string) bool {
- // TODO: correctly validate a hash: check that it's exactly 40
- // lowercase hex digits. But this is what we historically did:
- return hash != ""
-}
-
-func validKey(c context.Context, key, builder string) bool {
- if isMasterKey(c, key) {
- return true
- }
- if builderKeyRevoked(builder) {
- return false
- }
- return key == builderKey(c, builder)
-}
-
-var devModeMasterKey string
-
-func masterKey(ctx context.Context) string {
- if *dev {
- return devModeMasterKey
- }
- return key.Secret(ctx, datastoreClient)
-}
-
-func isMasterKey(ctx context.Context, k string) bool {
- return k == masterKey(ctx)
-}
-
-func builderKey(ctx context.Context, builder string) string {
- h := hmac.New(md5.New, []byte(masterKey(ctx)))
- h.Write([]byte(builder))
- return fmt.Sprintf("%x", h.Sum(nil))
-}
-
-func logErr(w http.ResponseWriter, r *http.Request, err error) {
- log.Printf("Error: %v", err)
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprint(w, "Error: ", html.EscapeString(err.Error()))
-}
-
-// limitStringLength essentially does return s[:max],
-// but it ensures that we dot not split UTF-8 rune in half.
-// Otherwise appengine python scripts will break badly.
-func limitStringLength(s string, max int) string {
- if len(s) <= max {
- return s
- }
- for {
- s = s[:max]
- r, size := utf8.DecodeLastRuneInString(s)
- if r != utf8.RuneError || size != 1 {
- return s
- }
- max--
- }
-}
diff --git a/app/appengine/static/status_alert.gif b/app/appengine/static/status_alert.gif
deleted file mode 100644
index 495d9d2..0000000
--- a/app/appengine/static/status_alert.gif
+++ /dev/null
Binary files differ
diff --git a/app/appengine/static/status_good.gif b/app/appengine/static/status_good.gif
deleted file mode 100644
index ef9c5a8..0000000
--- a/app/appengine/static/status_good.gif
+++ /dev/null
Binary files differ
diff --git a/app/appengine/static/style.css b/app/appengine/static/style.css
deleted file mode 100644
index 34c6603..0000000
--- a/app/appengine/static/style.css
+++ /dev/null
@@ -1,317 +0,0 @@
-* { box-sizing: border-box; }
-
-.dashboards {
- padding: 0.5em;
-}
-.dashboards > a {
- padding: 0.5em;
- background: #eee;
- color: blue;
-}
-
-body {
- margin: 0;
- font-family: sans-serif;
- padding: 0; margin: 0;
- color: #222;
- display: inline-block;
- min-width: 100%;
-}
-
-.container {
- max-width: 900px;
- margin: 0 auto;
-}
-
-p, pre, ul, ol { margin: 20px; }
-
-h1, h2, h3, h4 {
- margin: 20px 0;
- padding: 0;
- color: #375EAB;
- font-weight: bold;
-}
-
-h1 { font-size: 24px; }
-h2 { font-size: 20px; }
-h3 { font-size: 20px; }
-h4 { font-size: 16px; }
-
-h2 { background: #E0EBF5; padding: 2px 5px; }
-h3, h4 { margin: 20px 5px; }
-
-dl, dd { font-size: 14px; }
-dl { margin: 20px; }
-dd { margin: 2px 20px; }
-
-.clear {
- clear: both;
-}
-
-.button {
- padding: 10px;
-
- color: #222;
- border: 1px solid #375EAB;
- background: #E0EBF5;
-
- border-radius: 5px;
-
- cursor: pointer;
-
- margin-left: 60px;
-}
-
-/* navigation bar */
-
-#topbar {
- padding: 10px 10px;
- background: #E0EBF5;
-}
-
-#topbar a {
- color: #222;
-}
-#topbar h1 {
- float: left;
- margin: 0;
- padding-top: 5px;
-}
-
-#topbar nav {
- float: left;
- margin-left: 20px;
-}
-#topbar nav a {
- display: inline-block;
- padding: 10px;
-
- margin: 0;
- margin-right: 5px;
-
- color: white;
- background: #375EAB;
-
- text-decoration: none;
- font-size: 16px;
-
- border: 1px solid #375EAB;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
-}
-
-.page {
- margin-top: 20px;
-}
-
-/* settings panels */
-aside {
- margin-top: 5px;
-}
-
-.panel {
- border: 1px solid #aaa;
- border-radius: 5px;
- margin-bottom: 5px;
-}
-
-.panel h1 {
- font-size: 16px;
- margin: 0;
- padding: 2px 8px;
-}
-
-.panel select {
- padding: 5px;
- border: 0;
- width: 100%;
-}
-
-/* results table */
-
-table {
- margin: 5px;
- border-collapse: collapse;
- font-size: 11px;
-}
-
-table td, table th, table td, table th {
- vertical-align: top;
- padding: 2px 6px;
-}
-
-table tr:nth-child(2n+1) {
- background: #F4F4F4;
-}
-
-table tr.commit:hover {
- background-color: #ffff99 !important;
-}
-
-table thead tr {
- background: #fff !important;
-}
-
-/* build results */
-
-.build td, .build th, .packages td, .packages th {
- vertical-align: top;
- padding: 2px 4px;
- font-size: 10pt;
-}
-
-.build .hash {
- font-family: monospace;
- font-size: 9pt;
-}
-
-.build .result {
- text-align: center;
- width: 2em;
-}
-
-.build .col-desc, .build .col-result, .build .col-metric, .build .col-numresults {
- border-right: 1px solid #ccc;
-}
-
-.build .row-commit {
- border-top: 2px solid #ccc;
-}
-
-.build .arch {
- font-size: 83%;
- font-weight: normal;
-}
-
-.build .time {
- color: #666;
-}
-
-.build .ok {
- font-size: 83%;
-}
-
-.build .desc, .build .time, .build .user {
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
-}
-
-.build .desc {
- max-width: 150px;
-}
-
-.build .user {
- max-width: 50px;
-}
-
-tr.subheading2 th {
- max-width: 4em;
- overflow: hidden;
- word-wrap: none;
-}
-
-.good { text-decoration: none; color: #000000; border: 2px solid #00E700}
-.bad { text-decoration: none; text-shadow: 1px 1px 0 #000000; color: #FFFFFF; background: #E70000;}
-.noise { text-decoration: none; color: #888; }
-.fail { color: #C00; }
-
-/* pagination */
-
-.paginate nav {
- padding: 0.5em;
- margin: 10px 0;
-}
-
-.paginate nav a {
- padding: 0.5em;
- background: #E0EBF5;
- color: blue;
-
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
-}
-
-.paginate nav a.inactive {
- color: #888;
- cursor: default;
- text-decoration: none;
-}
-
-/* diffs */
-
-.diff-meta {
- font-family: monospace;
- margin-bottom: 10px;
-}
-
-.diff-container {
- padding: 10px;
-}
-
-.diff table .metric {
- font-weight: bold;
-}
-
-.diff {
- border: 1px solid #aaa;
- border-radius: 5px;
- margin-bottom: 5px;
- margin-right: 10px;
- float: left;
-}
-
-.diff h1 {
- font-size: 16px;
- margin: 0;
- padding: 2px 8px;
-}
-
-/* positioning elements */
-
-.page {
- position: relative;
- width: 100%;
-}
-
-aside {
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- width: 200px;
-}
-
-.main-content {
- position: absolute;
- top: 0;
- left: 210px;
- right: 5px;
- min-height: 200px;
- overflow: hidden;
-}
-
-@media only screen and (max-width: 900px) {
- aside {
- position: relative;
- display: block;
- width: auto;
- }
-
- .main-content {
- position: static;
- padding: 0;
- }
-
- aside .panel {
- float: left;
- width: auto;
- margin-right: 5px;
- }
- aside .button {
- float: left;
- margin: 0;
- }
-}
diff --git a/app/appengine/ui.go b/app/appengine/ui.go
deleted file mode 100644
index 0083a3c..0000000
--- a/app/appengine/ui.go
+++ /dev/null
@@ -1,972 +0,0 @@
-// Copyright 2011 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 (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "html/template"
- "log"
- "net/http"
- "os"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "time"
-
- "cloud.google.com/go/datastore"
- "golang.org/x/build/dashboard"
- "golang.org/x/build/maintner/maintnerd/apipb"
- "golang.org/x/build/repos"
- "golang.org/x/build/types"
- "golang.org/x/sync/errgroup"
- "grpc.go4.org"
- "grpc.go4.org/codes"
-)
-
-// uiHandler is the HTTP handler for the https://build.golang.org/.
-func uiHandler(w http.ResponseWriter, r *http.Request) {
- view, err := viewForRequest(r)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- dashReq, err := dashboardRequest(view, r)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- ctx := r.Context()
- tb := &uiTemplateDataBuilder{
- view: view,
- req: dashReq,
- }
- var rpcs errgroup.Group
- rpcs.Go(func() error {
- var err error
- tb.res, err = maintnerClient.GetDashboard(ctx, dashReq)
- return err
- })
- if view.ShowsActiveBuilds() {
- rpcs.Go(func() error {
- tb.activeBuilds = getActiveBuilds(ctx)
- return nil
- })
- }
- if err := rpcs.Wait(); err != nil {
- http.Error(w, "maintner.GetDashboard: "+err.Error(), httpStatusOfErr(err))
- return
- }
- data, err := tb.buildTemplateData(ctx)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- view.ServeDashboard(w, r, data)
-}
-
-// dashboardView is something that can render uiTemplateData.
-// See viewForRequest.
-type dashboardView interface {
- ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData)
- // ShowsActiveBuilds reports whether this view uses
- // information about the currently active builds.
- ShowsActiveBuilds() bool
-}
-
-// viewForRequest selects the dashboardView based on the HTTP
-// request's "mode" parameter. Any error should be considered
-// an HTTP 400 Bad Request.
-func viewForRequest(r *http.Request) (dashboardView, error) {
- if r.Method != "GET" && r.Method != "HEAD" {
- return nil, errors.New("unsupported method")
- }
- switch r.FormValue("mode") {
- case "failures":
- return failuresView{}, nil
- case "json":
- return jsonView{}, nil
- case "":
- return htmlView{}, nil
- }
- return nil, errors.New("unsupported mode argument")
-}
-
-type commitInPackage struct {
- packagePath string // "" for Go, else package import path
- commit string // git commit hash
-}
-
-// uiTemplateDataBuilder builds the uiTemplateData used by the various
-// dashboardViews. That is, it maps the maintner protobuf response to
-// the data structure needed by the dashboardView/template.
-type uiTemplateDataBuilder struct {
- view dashboardView
- req *apipb.DashboardRequest
- res *apipb.DashboardResponse
- activeBuilds []types.ActivePostSubmitBuild // optional; for blue gopher links
-
- // testCommitData, if non-nil, provides an alternate data
- // source to use for testing instead of making real datastore
- // calls. The keys are stringified datastore.Keys.
- testCommitData map[string]*Commit
-}
-
-// getCommitsToLoad returns a set (all values are true) of which commits to load from
-// the datastore.
-func (tb *uiTemplateDataBuilder) getCommitsToLoad() map[commitInPackage]bool {
- if *fakeResults {
- return nil
- }
- m := make(map[commitInPackage]bool)
- add := func(packagePath, commit string) {
- m[commitInPackage{packagePath: packagePath, commit: commit}] = true
- }
-
- for _, dc := range tb.res.Commits {
- add(tb.req.Repo, dc.Commit)
- }
- // We also want to load the Commits for the x/repo heads.
- if tb.showXRepoSection() {
- for _, rh := range tb.res.RepoHeads {
- if path := repoImportPath(rh); path != "" {
- add(path, rh.Commit.Commit)
- }
- }
- }
- return m
-}
-
-// loadDatastoreCommits loads the commits given in the keys of the
-// 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) {
- ret := map[string]*Commit{}
-
- // Allow tests to fake what the datastore would've loaded, and
- // thus also allow tests to be run without a real (or
- // dev_appserver-based fake) datastore.
- if m := tb.testCommitData; m != nil {
- for k := range want {
- if c, ok := m[k.commit]; ok {
- ret[k.commit] = c
- }
- }
- return ret, nil
- }
-
- var keys []*datastore.Key
- for k := range want {
- key := (&Commit{
- PackagePath: k.packagePath,
- Hash: k.commit,
- }).Key()
- keys = append(keys, key)
- }
- commits, err := fetchCommits(ctx, keys)
- if err != nil {
- return nil, fmt.Errorf("fetchCommits: %v", err)
- }
- for _, c := range commits {
- ret[c.Hash] = c
- }
- return ret, nil
-}
-
-// formatGitAuthor formats the git author name and email (as split by
-// maintner) back into the unified string how they're stored in a git
-// commit, so the shortUser func (used by the HTML template) can parse
-// back out the email part's username later. Maybe we could plumb down
-// the parsed proto into the template later.
-func formatGitAuthor(name, email string) string {
- name = strings.TrimSpace(name)
- email = strings.TrimSpace(email)
- if name != "" && email != "" {
- return fmt.Sprintf("%s <%s>", name, email)
- }
- if name != "" {
- return name
- }
- return "<" + email + ">"
-}
-
-// newCommitInfo returns a new CommitInfo populated for the template
-// data given a repo name and a dashboard commit from that repo, using
-// previously loaded datastore commit info in tb.
-func (tb *uiTemplateDataBuilder) newCommitInfo(dsCommits map[string]*Commit, repo string, dc *apipb.DashCommit) *CommitInfo {
- branch := dc.Branch
- if branch == "" {
- branch = "master"
- }
- ci := &CommitInfo{
- Hash: dc.Commit,
- PackagePath: repo,
- User: formatGitAuthor(dc.AuthorName, dc.AuthorEmail),
- Desc: cleanTitle(dc.Title, tb.req.Branch),
- Time: time.Unix(dc.CommitTimeSec, 0),
- Branch: branch,
- }
- if dsc, ok := dsCommits[dc.Commit]; ok {
- ci.ResultData = dsc.ResultData
- }
- // For non-go repos, add the rows for the Go commits that were
- // at HEAD overlapping in time with dc.Commit.
- if !tb.isGoRepo() {
- if dc.GoCommitAtTime != "" {
- ci.addEmptyResultGoHash(dc.GoCommitAtTime)
- }
- if dc.GoCommitLatest != "" && dc.GoCommitLatest != dc.GoCommitAtTime {
- ci.addEmptyResultGoHash(dc.GoCommitLatest)
- }
- }
- return ci
-}
-
-// showXRepoSection reports whether the dashboard should show the state of the x/foo repos at the bottom of
-// the page in the three branches (master, latest release branch, two releases ago).
-func (tb *uiTemplateDataBuilder) showXRepoSection() bool {
- return tb.req.Page == 0 &&
- (tb.branch() == "master" || tb.req.Branch == "mixed") &&
- tb.isGoRepo()
-}
-
-func (tb *uiTemplateDataBuilder) isGoRepo() bool { return tb.req.Repo == "" || tb.req.Repo == "go" }
-
-// repoGerritProj returns the Gerrit project name on go.googlesource.com for
-// the repo requested, or empty if unknown.
-func (tb *uiTemplateDataBuilder) repoGerritProj() string {
- if tb.isGoRepo() {
- return "go"
- }
- if r, ok := repos.ByImportPath[tb.req.Repo]; ok {
- return r.GoGerritProject
- }
- return ""
-}
-
-// branch returns the request branch, or "master" if empty.
-func (tb *uiTemplateDataBuilder) branch() string {
- if tb.req.Branch == "" {
- return "master"
- }
- return tb.req.Branch
-}
-
-// repoImportPath returns the import path for rh, unless rh is the
-// main "go" repo or is configured to be hidden from the dashboard, in
-// which case it returns the empty string.
-func repoImportPath(rh *apipb.DashRepoHead) string {
- if rh.GerritProject == "go" {
- return ""
- }
- ri, ok := repos.ByGerritProject[rh.GerritProject]
- if !ok || !ri.ShowOnDashboard() {
- return ""
- }
- return ri.ImportPath
-}
-
-func (tb *uiTemplateDataBuilder) buildTemplateData(ctx context.Context) (*uiTemplateData, error) {
- dsCommits, err := tb.loadDatastoreCommits(ctx, tb.getCommitsToLoad())
- if err != nil {
- return nil, err
- }
-
- var commits []*CommitInfo
- for _, dc := range tb.res.Commits {
- ci := tb.newCommitInfo(dsCommits, tb.req.Repo, dc)
- commits = append(commits, ci)
- }
-
- // x/ repo sections at bottom (each is a "TagState", for historical reasons)
- var xRepoSections []*TagState
- if tb.showXRepoSection() {
- for _, gorel := range tb.res.Releases {
- ts := &TagState{
- Name: gorel.BranchName,
- Tag: &CommitInfo{ // only a minimally populated version is needed by the template
- Hash: gorel.BranchCommit,
- },
- }
- for _, rh := range tb.res.RepoHeads {
- path := repoImportPath(rh)
- if path == "" {
- continue
- }
- ts.Packages = append(ts.Packages, &PackageState{
- Package: &Package{
- Name: rh.GerritProject,
- Path: path,
- },
- Commit: tb.newCommitInfo(dsCommits, path, rh.Commit),
- })
- }
- builders := map[string]bool{}
- for _, pkg := range ts.Packages {
- addBuilders(builders, pkg.Package.Name, ts.Branch())
- }
- ts.Builders = builderKeys(builders)
-
- sort.Slice(ts.Packages, func(i, j int) bool {
- return ts.Packages[i].Package.Name < ts.Packages[j].Package.Name
- })
- xRepoSections = append(xRepoSections, ts)
- }
- }
-
- // 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
- }
-
- data := &uiTemplateData{
- Dashboard: goDash,
- Package: goDash.packageWithPath(tb.req.Repo),
- Commits: commits,
- TagState: xRepoSections,
- Pagination: &Pagination{},
- Branches: tb.res.Branches,
- Branch: tb.req.Branch,
- Repo: gerritProject,
- }
-
- builders := buildersOfCommits(commits)
- if tb.branch() == "mixed" {
- for _, gr := range tb.res.Releases {
- addBuilders(builders, tb.repoGerritProj(), gr.BranchName)
- }
- } else {
- addBuilders(builders, tb.repoGerritProj(), tb.branch())
- }
- data.Builders = builderKeys(builders)
-
- if tb.res.CommitsTruncated {
- data.Pagination.Next = int(tb.req.Page) + 1
- }
- if tb.req.Page > 0 {
- data.Pagination.Prev = int(tb.req.Page) - 1
- data.Pagination.HasPrev = true
- }
-
- if tb.view.ShowsActiveBuilds() {
- // Populate building URLs for the HTML UI only.
- data.populateBuildingURLs(ctx, tb.activeBuilds)
- }
-
- return data, nil
-}
-
-// htmlView renders the HTML (default) form of https://build.golang.org/ with no mode parameter.
-type htmlView struct{}
-
-func (htmlView) ShowsActiveBuilds() bool { return true }
-func (htmlView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
- var buf bytes.Buffer
- if err := uiTemplate.Execute(&buf, data); err != nil {
- logErr(w, r, err)
- return
- }
- buf.WriteTo(w)
-}
-
-// dashboardRequest is a pure function that maps the provided HTTP
-// request to a maintner DashboardRequest and lightly validates the
-// HTTP request for the root dashboard handler. (It does not validate
-// that, say, branches or repos are valid.)
-// Any returned error is an HTTP 400 Bad Request.
-func dashboardRequest(view dashboardView, r *http.Request) (*apipb.DashboardRequest, error) {
- page := 0
- if s := r.FormValue("page"); s != "" {
- var err error
- page, err = strconv.Atoi(r.FormValue("page"))
- if err != nil {
- return nil, fmt.Errorf("invalid page value %q", s)
- }
- if page < 0 {
- return nil, errors.New("negative page")
- }
- }
-
- repo := r.FormValue("repo") // empty for main go repo, else e.g. "golang.org/x/net"
-
- branch := r.FormValue("branch")
- if branch == "" {
- branch = "master"
- }
- return &apipb.DashboardRequest{
- Page: int32(page),
- Branch: branch,
- Repo: repo,
- MaxCommits: commitsPerPage,
- }, nil
-}
-
-// cleanTitle returns a cleaned version of the provided title for
-// users viewing the provided viewBranch.
-func cleanTitle(title, viewBranch string) string {
- // Don't rewrite anything for master and mixed.
- if viewBranch == "master" || viewBranch == "mixed" {
- return title
- }
- // Strip the "[release-branch.go1.n]" prefixes from commit messages
- // when looking at a branch.
- if strings.HasPrefix(title, "[") {
- if i := strings.IndexByte(title, ']'); i != -1 {
- return strings.TrimSpace(title[i+1:])
- }
- }
- return title
-}
-
-// failuresView renders https://build.golang.org/?mode=failures, where it outputs
-// one line per failure on the front page, in the form:
-// hash builder failure-url
-type failuresView struct{}
-
-func (failuresView) ShowsActiveBuilds() bool { return false }
-func (failuresView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
- w.Header().Set("Content-Type", "text/plain")
- for _, c := range data.Commits {
- for _, b := range data.Builders {
- res := c.Result(b, "")
- if res == nil || res.OK || res.LogHash == "" {
- continue
- }
- url := fmt.Sprintf("https://%v/log/%v", r.Host, res.LogHash)
- fmt.Fprintln(w, c.Hash, b, url)
- }
- }
- // TODO: this doesn't include the TagState commit. It would be
- // needed if we want to do golang.org/issue/36131, to permit
- // the retrybuilds command to wipe flaky non-go builds.
-}
-
-// jsonView renders https://build.golang.org/?mode=json.
-// The output is a types.BuildStatus JSON object.
-type jsonView struct{}
-
-func (jsonView) ShowsActiveBuilds() bool { return false }
-func (jsonView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
- res := toBuildStatus(r.Host, data)
- v, _ := json.MarshalIndent(res, "", "\t")
- w.Header().Set("Content-Type", "text/json; charset=utf-8")
- w.Write(v)
-}
-
-func toBuildStatus(host string, data *uiTemplateData) types.BuildStatus {
- // cell returns one of "" (no data), "ok", or a failure URL.
- cell := func(res *Result) string {
- switch {
- case res == nil:
- return ""
- case res.OK:
- return "ok"
- }
- return fmt.Sprintf("https://%v/log/%v", host, res.LogHash)
- }
-
- builders := data.allBuilders()
-
- var res types.BuildStatus
- res.Builders = builders
-
- // First the commits from the main section (the requested repo)
- for _, c := range data.Commits {
- // The logic below works for both the go repo and other subrepos: if c is
- // in the main go repo, ResultGoHashes returns a slice of length 1
- // containing the empty string.
- for _, h := range c.ResultGoHashes() {
- rev := types.BuildRevision{
- Repo: data.Repo,
- Results: make([]string, len(res.Builders)),
- GoRevision: h,
- }
- commitToBuildRevision(c, &rev)
- for i, b := range res.Builders {
- rev.Results[i] = cell(c.Result(b, h))
- }
- res.Revisions = append(res.Revisions, rev)
- }
- }
-
- // Then the one commit each for the subrepos for each of the tracked tags.
- // (tip, Go 1.4, etc)
- for _, ts := range data.TagState {
- for _, pkgState := range ts.Packages {
- goRev := ts.Tag.Hash
- goBranch := ts.Name
- if goBranch == "tip" {
- // Normalize old hg terminology into
- // our git branch name.
- goBranch = "master"
- }
- rev := types.BuildRevision{
- Repo: pkgState.Package.Name,
- GoRevision: goRev,
- Results: make([]string, len(res.Builders)),
- GoBranch: goBranch,
- }
- commitToBuildRevision(pkgState.Commit, &rev)
- for i, b := range res.Builders {
- rev.Results[i] = cell(pkgState.Commit.Result(b, goRev))
- }
- res.Revisions = append(res.Revisions, rev)
- }
- }
- return res
-}
-
-// commitToBuildRevision fills in the fields of BuildRevision rev that
-// are derived from Commit c.
-func commitToBuildRevision(c *CommitInfo, rev *types.BuildRevision) {
- rev.Revision = c.Hash
- rev.Date = c.Time.Format(time.RFC3339)
- rev.Author = c.User
- rev.Desc = c.Desc
- rev.Branch = c.Branch
-}
-
-type Pagination struct {
- Next, Prev int
- HasPrev bool
-}
-
-// fetchCommits loads any commits that exist given by keys.
-// 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) {
- if len(keys) == 0 {
- return nil, nil
- }
- out := make([]*Commit, len(keys))
- for i := range keys {
- out[i] = new(Commit)
- }
-
- err := datastoreClient.GetMulti(ctx, keys, out)
- err = filterDatastoreError(err)
- err = filterNoSuchEntity(err)
- if err != nil {
- return nil, err
- }
- filtered := out[:0]
- for _, c := range out {
- if c.Valid() { // that is, successfully loaded
- filtered = append(filtered, c)
- }
- }
- return filtered, nil
-}
-
-// buildersOfCommits returns the set of builders that provided
-// Results for the provided commits.
-func buildersOfCommits(commits []*CommitInfo) map[string]bool {
- m := make(map[string]bool)
- for _, commit := range commits {
- for _, r := range commit.Results() {
- if r.Builder != "" {
- m[r.Builder] = true
- }
- }
- }
- return m
-}
-
-// addBuilders adds builders to the provide map that should be active for
-// the named Gerrit project & branch. (Issue 19930)
-func addBuilders(builders map[string]bool, gerritProj, branch string) {
- for name, bc := range dashboard.Builders {
- if bc.BuildsRepoPostSubmit(gerritProj, branch, branch) {
- builders[name] = true
- }
- }
-}
-
-func builderKeys(m map[string]bool) (s []string) {
- s = make([]string, 0, len(m))
- for k := range m {
- s = append(s, k)
- }
- sort.Sort(builderOrder(s))
- return
-}
-
-// builderOrder implements sort.Interface, sorting builder names
-// ("darwin-amd64", etc) first by builderPriority and then alphabetically.
-type builderOrder []string
-
-func (s builderOrder) Len() int { return len(s) }
-func (s builderOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s builderOrder) Less(i, j int) bool {
- pi, pj := builderPriority(s[i]), builderPriority(s[j])
- if pi == pj {
- return s[i] < s[j]
- }
- return pi < pj
-}
-
-func builderPriority(builder string) (p int) {
- // Group race builders together.
- if isRace(builder) {
- return 2
- }
- // If the OS has a specified priority, use it.
- if p, ok := osPriority[builderOS(builder)]; ok {
- return p
- }
- // The rest.
- return 10
-}
-
-func isRace(s string) bool {
- return strings.Contains(s, "-race-") || strings.HasSuffix(s, "-race")
-}
-
-func unsupported(builder string) bool {
- return unsupportedOS(builderOS(builder))
-}
-
-func unsupportedOS(os string) bool {
- if os == "race" || os == "android" || os == "all" {
- return false
- }
- p, ok := osPriority[os]
- return !ok || p > 1
-}
-
-// Priorities for specific operating systems.
-var osPriority = map[string]int{
- "all": 0,
- "darwin": 1,
- "freebsd": 1,
- "linux": 1,
- "windows": 1,
- // race == 2
- "android": 3,
- "openbsd": 4,
- "netbsd": 5,
- "dragonfly": 6,
-}
-
-// TagState represents the state of all Packages at a branch.
-type TagState struct {
- Name string // Go branch name: "master", "release-branch.go1.4", etc
- Tag *CommitInfo // current Go commit on the Name branch
- Packages []*PackageState
- Builders []string
-}
-
-// Branch returns the git branch name, converting from the old
-// terminology we used from Go's hg days into git terminology.
-func (ts *TagState) Branch() string {
- if ts.Name == "tip" {
- return "master"
- }
- return ts.Name
-}
-
-// PackageState represents the state of a Package (x/foo repo) for given Go branch.
-type PackageState struct {
- Package *Package
- Commit *CommitInfo
-}
-
-// A CommitInfo is a struct for use by html/template package.
-// It is not stored in the datastore.
-type CommitInfo struct {
- Hash string
-
- // ResultData is a copy of the Commit.ResultData field from datastore.
- ResultData []string
-
- // BuildingURLs contains the status URL values for builds that
- // are currently in progress for this commit.
- BuildingURLs map[builderAndGoHash]string
-
- PackagePath string // (empty for main repo commits)
- User string // "Foo Bar <foo@bar.com>"
- Desc string // git commit title
- Time time.Time // commit time
- Branch string // "master", "release-branch.go1.14"
-}
-
-// addEmptyResultGoHash adds an empty result containing goHash to
-// ci.ResultData, unless ci already contains a result for that hash.
-// This is used for non-go repos to show the go commits (both earliest
-// and latest) that correspond to this repo's commit time. We add an
-// empty result so it shows up on the dashboard (both for humans, and
-// in JSON form for the coordinator to pick up as work). Once the
-// coordinator does that work and posts its result, then ResultData
-// will be populate and this turns into a no-op.
-func (ci *CommitInfo) addEmptyResultGoHash(goHash string) {
- for _, exist := range ci.ResultData {
- if strings.Contains(exist, goHash) {
- return
- }
- }
- ci.ResultData = append(ci.ResultData, (&Result{GoHash: goHash}).Data())
-}
-
-type uiTemplateData struct {
- Dashboard *Dashboard
- Package *Package
- Commits []*CommitInfo
- Builders []string // builders for just the main section; not the "TagState" sections
- TagState []*TagState // x/foo repo overviews at master + last two releases
- Pagination *Pagination
- Branches []string
- Branch string
- Repo string // the repo gerrit project name. "go" if unspecified in the request.
-}
-
-// getActiveBuilds returns the builds that coordinator is currently doing.
-// This isn't critical functionality so errors are logged but otherwise ignored for now.
-// Once this is merged into the coordinator we won't need to make an RPC to get
-// this info. See https://github.com/golang/go/issues/34744#issuecomment-563398753.
-func getActiveBuilds(ctx context.Context) (builds []types.ActivePostSubmitBuild) {
- ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
- defer cancel()
- req, _ := http.NewRequest("GET", "https://farmer.golang.org/status/post-submit-active.json", nil)
- req = req.WithContext(ctx)
- res, err := http.DefaultClient.Do(req)
- if err != nil {
- log.Printf("getActiveBuilds: Do: %v", err)
- return
- }
- defer res.Body.Close()
- if res.StatusCode != 200 {
- log.Printf("getActiveBuilds: %v", res.Status)
- return
- }
- if err := json.NewDecoder(res.Body).Decode(&builds); err != nil {
- log.Printf("getActiveBuilds: JSON decode: %v", err)
- }
- return builds
-}
-
-// populateBuildingURLs populates each commit in Commits' buildingURLs map with the
-// URLs of builds which are currently in progress.
-func (td *uiTemplateData) populateBuildingURLs(ctx context.Context, activeBuilds []types.ActivePostSubmitBuild) {
- // active maps from a build record with its status URL zeroed
- // out to to the actual value of that status URL.
- active := map[types.ActivePostSubmitBuild]string{}
- for _, rec := range activeBuilds {
- statusURL := rec.StatusURL
- rec.StatusURL = ""
- active[rec] = statusURL
- }
-
- condAdd := func(c *CommitInfo, rec types.ActivePostSubmitBuild) {
- su, ok := active[rec]
- if !ok {
- return
- }
- if c.BuildingURLs == nil {
- c.BuildingURLs = make(map[builderAndGoHash]string)
- }
- c.BuildingURLs[builderAndGoHash{rec.Builder, rec.GoCommit}] = su
- }
-
- for _, b := range td.Builders {
- for _, c := range td.Commits {
- condAdd(c, types.ActivePostSubmitBuild{Builder: b, Commit: c.Hash})
- }
- }
-
- // Gather pending commits for sub-repos.
- for _, ts := range td.TagState {
- goHash := ts.Tag.Hash
- for _, b := range td.Builders {
- for _, pkg := range ts.Packages {
- c := pkg.Commit
- condAdd(c, types.ActivePostSubmitBuild{
- Builder: b,
- Commit: c.Hash,
- GoCommit: goHash,
- })
- }
- }
- }
-}
-
-// allBuilders returns the list of builders, unified over the main
-// section and any x/foo branch overview (TagState) sections.
-func (td *uiTemplateData) allBuilders() []string {
- m := map[string]bool{}
- for _, b := range td.Builders {
- m[b] = true
- }
- for _, ts := range td.TagState {
- for _, b := range ts.Builders {
- m[b] = true
- }
- }
- return builderKeys(m)
-}
-
-var uiTemplate = template.Must(
- template.New("ui.html").Funcs(tmplFuncs).ParseFiles(templateFile("ui.html")),
-)
-
-var tmplFuncs = template.FuncMap{
- "builderSpans": builderSpans,
- "builderSubheading": builderSubheading,
- "builderSubheading2": builderSubheading2,
- "shortDesc": shortDesc,
- "shortHash": shortHash,
- "shortUser": shortUser,
- "unsupported": unsupported,
- "isUntested": isUntested,
- "knownIssue": knownIssue,
- "formatTime": formatTime,
-}
-
-func formatTime(t time.Time) string {
- if t.Year() != time.Now().Year() {
- return t.Format("02 Jan 06")
- }
- return t.Format("02 Jan 15:04")
-}
-
-func splitDash(s string) (string, string) {
- i := strings.Index(s, "-")
- if i >= 0 {
- return s[:i], s[i+1:]
- }
- return s, ""
-}
-
-// builderOS returns the os tag for a builder string
-func builderOS(s string) string {
- os, _ := splitDash(s)
- return os
-}
-
-// builderOSOrRace returns the builder OS or, if it is a race builder, "race".
-func builderOSOrRace(s string) string {
- if isRace(s) {
- return "race"
- }
- return builderOS(s)
-}
-
-// builderArch returns the arch tag for a builder string
-func builderArch(s string) string {
- _, arch := splitDash(s)
- arch, _ = splitDash(arch) // chop third part
- return arch
-}
-
-// builderSubheading returns a short arch tag for a builder string
-// or, if it is a race builder, the builder OS.
-func builderSubheading(s string) string {
- if isRace(s) {
- return builderOS(s)
- }
- return builderArch(s)
-}
-
-// builderSubheading2 returns any third part of a hyphenated builder name.
-// For instance, for "linux-amd64-nocgo", it returns "nocgo".
-// For race builders it returns the empty string.
-func builderSubheading2(s string) string {
- if isRace(s) {
- return ""
- }
- _, secondThird := splitDash(s)
- _, third := splitDash(secondThird)
- return third
-}
-
-type builderSpan struct {
- N int
- OS string
- Unsupported bool
-}
-
-// builderSpans creates a list of tags showing
-// the builder's operating system names, spanning
-// the appropriate number of columns.
-func builderSpans(s []string) []builderSpan {
- var sp []builderSpan
- for len(s) > 0 {
- i := 1
- os := builderOSOrRace(s[0])
- u := unsupportedOS(os)
- for i < len(s) && builderOSOrRace(s[i]) == os {
- i++
- }
- sp = append(sp, builderSpan{i, os, u})
- s = s[i:]
- }
- return sp
-}
-
-// shortDesc returns the first line of a description.
-func shortDesc(desc string) string {
- if i := strings.Index(desc, "\n"); i != -1 {
- desc = desc[:i]
- }
- return limitStringLength(desc, 100)
-}
-
-// shortHash returns a short version of a hash.
-func shortHash(hash string) string {
- if len(hash) > 7 {
- hash = hash[:7]
- }
- return hash
-}
-
-// shortUser returns a shortened version of a user string.
-func shortUser(user string) string {
- if i, j := strings.Index(user, "<"), strings.Index(user, ">"); 0 <= i && i < j {
- user = user[i+1 : j]
- }
- if i := strings.Index(user, "@"); i >= 0 {
- return user[:i]
- }
- return user
-}
-
-// templateFile returns the path to the provided HTML template file,
-// conditionally prepending a relative path depending on the
-// environment.
-func templateFile(base string) string {
- // In tests the current directory is ".", but in prod it's up
- // two levels. So just look to see if it's in . first.
- if _, err := os.Stat(base); err == nil {
- return base
- }
- return filepath.Join("app/appengine", base)
-}
-
-func httpStatusOfErr(err error) int {
- fmt.Fprintf(os.Stderr, "Got error: %#v, code %v\n", err, grpc.Code(err))
- switch grpc.Code(err) {
- case codes.NotFound:
- return http.StatusNotFound
- case codes.InvalidArgument:
- return http.StatusBadRequest
- default:
- return http.StatusInternalServerError
- }
-}
diff --git a/app/appengine/ui.html b/app/appengine/ui.html
deleted file mode 100644
index 3ff7ac8..0000000
--- a/app/appengine/ui.html
+++ /dev/null
@@ -1,250 +0,0 @@
-<!DOCTYPE HTML>
-<html>
- <head>
- <title>{{$.Dashboard.Name}} Build Dashboard</title>
- <link rel="stylesheet" href="/static/style.css"/>
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
- <script>
- var showUnsupported = window.location.hash.substr(1) != "short";
- function redraw() {
- showUnsupported = !$("#showshort").prop('checked');
- $('.unsupported')[showUnsupported?'show':'hide']();
- window.location.hash = showUnsupported?'':'short';
- }
- $(document).ready(function() {
- $("#showshort").attr('checked', !showUnsupported).change(redraw);
- redraw();
- })
- </script>
- </head>
-
- <body>
- <header id="topbar">
- <h1>Go Dashboard</h1>
- <div class="clear"></div>
- </header>
-
- <form action="." method="GET">
- <input type="hidden" name="repo" value="{{.Package.Path}}">
- <nav class="dashboards">
- {{if not (eq .Branch "")}}
- <label>
- <select name="branch" onchange="this.form.submit()">
- {{range $.Branches}}
- <option value="{{.}}"{{if eq $.Branch .}} selected{{end}}>{{.}}</option>
- {{end}}
- </select>
- </label>
- {{end}}
- <label>
- <input type=checkbox id="showshort">
- show only <a href="http://golang.org/wiki/PortingPolicy">first-class ports</a>
- </label>
- </nav>
- </form>
- {{with $.Package.Name}}<h2>{{.}}</h2>{{end}}
-
- <div class="page">
-
- {{if $.Commits}}
-
- <table class="build">
- <colgroup class="col-hash" {{if $.Package.Path}}span="2"{{end}}></colgroup>
- <colgroup class="col-user"></colgroup>
- <colgroup class="col-time"></colgroup>
- <colgroup class="col-desc"></colgroup>
- {{range $.Builders | builderSpans}}
- <colgroup class="col-result{{if .Unsupported}} unsupported{{end}}" span="{{.N}}"></colgroup>
- {{end}}
- <tr>
- <!-- extra row to make alternating colors use dark for first result -->
- </tr>
- <tr>
- {{if $.Package.Path}}
- <th colspan="2">revision</th>
- {{else}}
- <th> </th>
- {{end}}
- <th></th>
- <th></th>
- <th></th>
- {{range $.Builders | builderSpans}}
- <th {{if .Unsupported}}class="unsupported"{{end}} colspan="{{.N}}">{{.OS}}</th>
- {{end}}
- </tr>
-
- <tr>
- {{if $.Package.Path}}
- <th class="result arch">repo</th>
- <th class="result arch">{{$.Dashboard.Name}}</th>
- {{else}}
- <th> </th>
- {{end}}
- <th></th>
- <th></th>
- <th></th>
- {{range $.Builders}}
- <th class="result arch{{if (unsupported .)}} unsupported{{end}}{{if knownIssue .}} noise{{end}}" title="{{.}}">{{builderSubheading .}}</th>
- {{end}}
- </tr>
-
- <tr class="subheading2">
- <th {{if $.Package.Path}}colspan="2"{{end}}> </th>
- <th></th>
- <th></th>
- <th></th>
- {{range $.Builders}}
- <th class="result arch{{if (unsupported .)}} unsupported{{end}}{{if knownIssue .}} noise{{end}}" title="{{.}}">{{builderSubheading2 .}}</th>
- {{end}}
- </tr>
-
- {{range $c := $.Commits}}
- {{range $i, $h := $c.ResultGoHashes}}
- <tr class="commit">
- {{if $i}}
- <td> </td>
- {{if $h}}
- <td class="hash"><a href="https://go-review.googlesource.com/q/{{$h}}">{{shortHash $h}}</a></td>
- {{end}}
- <td> </td>
- <td> </td>
- <td> </td>
- {{else}}
- <td class="hash"><a href="https://go-review.googlesource.com/q/{{$c.Hash}}">{{shortHash $c.Hash}}</a></td>
- {{if $h}}
- <td class="hash"><a href="https://go-review.googlesource.com/q/{{$h}}">{{shortHash $h}}</a></td>
- {{end}}
- <td class="user" title="{{$c.User}}">{{shortUser $c.User}}</td>
- <td class="time">{{formatTime $c.Time}}</td>
- <td class="desc" title="{{$c.Desc}}">{{shortDesc $c.Desc}}</td>
- {{end}}
- {{range $builderName := $.Builders}}
- <td class="result{{if (unsupported .)}} unsupported{{end}}">
- {{if and (eq $.Repo "go") (isUntested $builderName "go" $.Branch "")}}•{{else}}
- {{with $c.Result $builderName $h}}
- {{if .BuildingURL}}
- <a href="{{.BuildingURL}}"><img src="https://golang.org/favicon.ico" height=16 width=16 border=0></a>
- {{else if .OK}}
- <span class="ok{{if knownIssue $builderName}} noise{{end}}">ok</span>
- {{else if knownIssue $builderName}}
- <a href="/log/{{.LogHash}}" class="noise" title="Builder {{$builderName}} has a known issue. See golang.org/issue/{{knownIssue $builderName}}.">fail</a>
- {{else}}
- <a href="/log/{{.LogHash}}" class="fail">fail</a>
- {{end}}
- {{else}}
-
- {{end}}
- {{end}}
- </td>
- {{end}}
- </tr>
- {{end}}
- {{end}}
- </table>
-
- {{with $.Pagination}}
- <div class="paginate">
- <nav>
- <a {{if .HasPrev}}href="?{{with $.Package.Path}}repo={{.}}&{{end}}page={{.Prev}}{{with $.Branch}}&branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>newer</a>
- <a {{if .Next}}href="?{{with $.Package.Path}}repo={{.}}&{{end}}page={{.Next}}{{with $.Branch}}&branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>older</a>
- <a {{if .HasPrev}}href=".{{with $.Branch}}?branch={{.}}{{end}}"{{else}}class="inactive"{{end}}>latest</a>
- </nav>
- </div>
- {{end}}
-
- {{else}}
- <p>No commits to display. Hm.</p>
- {{end}}
-
- {{range $.TagState}}
- {{$goHash := .Tag.Hash}}
- {{$goBranch := .Branch}}
- {{$builders := .Builders}}
- {{if .Packages}}
- <h2>
- golang.org/x repos at Go {{$goBranch}}
- <small>(<a href="https://go-review.googlesource.com/q/{{.Tag.Hash}}">{{shortHash .Tag.Hash}}</a>)</small>
- </h2>
-
- <table class="build">
- <colgroup class="col-package"></colgroup>
- <colgroup class="col-hash"></colgroup>
- <colgroup class="col-user"></colgroup>
- <colgroup class="col-time"></colgroup>
- <colgroup class="col-desc"></colgroup>
- {{range $builders | builderSpans}}
- <colgroup class="col-result{{if .Unsupported}} unsupported{{end}}" span="{{.N}}"></colgroup>
- {{end}}
- <tr>
- <!-- extra row to make alternating colors use dark for first result -->
- </tr>
- <tr>
- <th></th>
- <th></th>
- <th></th>
- <th></th>
- <th></th>
- {{range $builders | builderSpans}}
- <th {{if .Unsupported}}class="unsupported"{{end}} colspan="{{.N}}">{{.OS}}</th>
- {{end}}
- </tr>
- <tr>
- <th></th>
- <th></th>
- <th></th>
- <th></th>
- <th></th>
- {{range $builders}}
- <th class="result arch{{if (unsupported .)}} unsupported{{end}}" title="{{.}}">{{builderSubheading .}}</th>
- {{end}}
- </tr>
- <tr class="subheading2">
- <th> </th>
- <th></th>
- <th></th>
- <th></th>
- <th></th>
- {{range $builders}}
- <th class="result arch{{if (unsupported .)}} unsupported{{end}}" title="{{.}}">{{builderSubheading2 .}}</th>
- {{end}}
- </tr>
- {{range $pkg := .Packages}}
- <tr class="commit">
- <td><a title="{{.Package.Path}}" href="?repo={{.Package.Path}}">{{.Package.Name}}</a></td>
- <td class="hash">
- {{$h := $pkg.Commit.Hash}}
- <a href="https://go-review.googlesource.com/q/{{$h}}">{{shortHash $h}}</a>
- </td>
- {{with $pkg.Commit}}
- <td class="user" title="{{.User}}">{{shortUser .User}}</td>
- <td class="time">{{formatTime .Time}}</td>
- <td class="desc" title="{{.Desc}}">{{shortDesc .Desc}}</td>
- {{end}}
- {{range $builderName := $builders}}
- <td class="result{{if (unsupported .)}} unsupported{{end}}">
- {{if isUntested $builderName $pkg.Package.Name "master" $goBranch}}
- •
- {{else}}
- {{with $pkg.Commit.Result $builderName $goHash}}
- {{if .BuildingURL}}
- <a href="{{.BuildingURL}}"><img src="https://golang.org/favicon.ico" height=16 width=16 border=0></a>
- {{else if .OK}}
- <span class="ok">ok</span>
- {{else}}
- <a href="/log/{{.LogHash}}" class="fail">fail</a>
- {{end}}
- {{else}}
-
- {{end}}
- {{end}}
- </td>
- {{end}}
- </tr>
- {{end}}
- </table>
- {{end}}
- {{end}}
-
- </div>
- </body>
-</html>
diff --git a/app/appengine/ui_test.go b/app/appengine/ui_test.go
deleted file mode 100644
index 16f946f..0000000
--- a/app/appengine/ui_test.go
+++ /dev/null
@@ -1,541 +0,0 @@
-// Copyright 2019 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"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "golang.org/x/build/dashboard"
- "golang.org/x/build/maintner/maintnerd/apipb"
- "golang.org/x/build/types"
-)
-
-func TestUITemplateDataBuilder(t *testing.T) {
- // Thin the list of builders to make this test's data lighter
- // and require less maintenance keeping it in sync.
- origBuilders := dashboard.Builders
- defer func() { dashboard.Builders = origBuilders }()
- dashboard.Builders = map[string]*dashboard.BuildConfig{
- "linux-amd64": origBuilders["linux-amd64"],
- "linux-386": origBuilders["linux-386"],
- }
-
- tests := []struct {
- name string // test subname
- view dashboardView // one of htmlView{}, jsonView{}, or failuresView{}
- req *apipb.DashboardRequest // what we pretend we sent to maintner
- res *apipb.DashboardResponse // what we pretend we got back from maintner
- testCommitData map[string]*Commit // what we pretend we loaded from datastore
- activeBuilds []types.ActivePostSubmitBuild
- want *uiTemplateData // what we're hoping we generated for the view/template
- }{
- // Basic test.
- {
- name: "html,zero_value_req,no_commits",
- view: htmlView{},
- req: &apipb.DashboardRequest{},
- res: &apipb.DashboardResponse{
- Branches: []string{"release.foo", "release.bar", "dev.blah"},
- },
- want: &uiTemplateData{
- Dashboard: goDash,
- Repo: "go",
- Package: &Package{Name: "Go", Path: ""},
- Branches: []string{"release.foo", "release.bar", "dev.blah"},
- Builders: []string{"linux-386", "linux-amd64"},
- Pagination: &Pagination{},
- },
- },
-
- // Basic test + two commits: one that's in datastore and one that's not.
- {
- name: "html,zero_value_req,has_commit",
- view: htmlView{},
- req: &apipb.DashboardRequest{},
- // Have only one commit load from the datastore:
- testCommitData: map[string]*Commit{
- "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac": {
- PackagePath: "",
- Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- ResultData: []string{
- "openbsd-amd64|true||", // pretend openbsd-amd64 passed (and thus exists)
- },
- },
- },
- activeBuilds: []types.ActivePostSubmitBuild{
- {Builder: "linux-amd64", Commit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac", StatusURL: "http://fake-status"},
- },
- res: &apipb.DashboardResponse{
- Branches: []string{"release.foo", "release.bar", "dev.blah"},
- Commits: []*apipb.DashCommit{
- // This is the maintner commit response that is in the datastore:
- {
- Commit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- AuthorName: "Foo Bar",
- AuthorEmail: "foo@example.com",
- CommitTimeSec: 1257894001,
- Title: "runtime: fix all the bugs",
- Branch: "master",
- },
- // And another commit that's not in the datastore:
- {
- Commit: "ffffffffffffffffffffffffffffffffffffffff",
- AuthorName: "Fancy Fred",
- AuthorEmail: "f@eff.tld",
- CommitTimeSec: 1257894000,
- Title: "all: add effs",
- Branch: "master",
- },
- },
- CommitsTruncated: true, // pretend there's a page 2
- },
- want: &uiTemplateData{
- Dashboard: goDash,
- Repo: "go",
- Package: &Package{Name: "Go", Path: ""},
- Branches: []string{"release.foo", "release.bar", "dev.blah"},
- Builders: []string{"linux-386", "linux-amd64", "openbsd-amd64"},
- Pagination: &Pagination{
- Next: 1,
- },
- Commits: []*CommitInfo{
- {
- Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- User: "Foo Bar <foo@example.com>",
- Desc: "runtime: fix all the bugs",
- Time: time.Unix(1257894001, 0),
- ResultData: []string{
- "openbsd-amd64|true||",
- },
- Branch: "master",
- BuildingURLs: map[builderAndGoHash]string{{builder: "linux-amd64"}: "http://fake-status"},
- },
- {
- Hash: "ffffffffffffffffffffffffffffffffffffffff",
- User: "Fancy Fred <f@eff.tld>",
- Desc: "all: add effs",
- Time: time.Unix(1257894000, 0),
- Branch: "master",
- },
- },
- },
- },
-
- // Test that we generate the TagState (sections at
- // bottom with the x/foo repo state).
- {
- name: "html,zero_value_req,has_xrepos",
- view: htmlView{},
- req: &apipb.DashboardRequest{},
- testCommitData: map[string]*Commit{},
- res: &apipb.DashboardResponse{
- Branches: []string{"release.foo", "release.bar", "dev.blah"},
- Commits: []*apipb.DashCommit{
- {
- Commit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- AuthorName: "Foo Bar",
- AuthorEmail: "foo@example.com",
- CommitTimeSec: 1257894001,
- Title: "runtime: fix all the bugs",
- Branch: "master",
- },
- },
- Releases: []*apipb.GoRelease{
- {
- BranchName: "master",
- BranchCommit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- },
- {
- BranchName: "release-branch.go1.99",
- BranchCommit: "ffffffffffffffffffffffffffffffffffffffff",
- },
- },
- RepoHeads: []*apipb.DashRepoHead{
- {
- GerritProject: "go",
- Commit: &apipb.DashCommit{
- Commit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- AuthorName: "Foo Bar",
- AuthorEmail: "foo@example.com",
- CommitTimeSec: 1257894001,
- Title: "runtime: fix all the bugs",
- Branch: "master",
- },
- },
- {
- GerritProject: "net",
- Commit: &apipb.DashCommit{
- Commit: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
- AuthorName: "Ee Yore",
- AuthorEmail: "e@e.net",
- CommitTimeSec: 1257894001,
- Title: "all: fix networking",
- Branch: "master",
- },
- },
- {
- GerritProject: "sys",
- Commit: &apipb.DashCommit{
- Commit: "dddddddddddddddddddddddddddddddddddddddd",
- AuthorName: "Sys Tem",
- AuthorEmail: "sys@s.net",
- CommitTimeSec: 1257894001,
- Title: "sys: support more systems",
- Branch: "master",
- },
- },
- },
- },
- want: &uiTemplateData{
- Dashboard: goDash,
- Repo: "go",
- Package: &Package{Name: "Go", Path: ""},
- Branches: []string{"release.foo", "release.bar", "dev.blah"},
- Builders: []string{"linux-386", "linux-amd64"},
- Pagination: &Pagination{},
- Commits: []*CommitInfo{
- {
- Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- User: "Foo Bar <foo@example.com>",
- Desc: "runtime: fix all the bugs",
- Time: time.Unix(1257894001, 0),
- Branch: "master",
- },
- },
- TagState: []*TagState{
- {
- Name: "master",
- Tag: &CommitInfo{Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac"},
- Builders: []string{"linux-386", "linux-amd64"},
- Packages: []*PackageState{
- {
- Package: &Package{Name: "net", Path: "golang.org/x/net"},
- Commit: &CommitInfo{
- Hash: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
- PackagePath: "golang.org/x/net",
- User: "Ee Yore <e@e.net>",
- Desc: "all: fix networking",
- Time: time.Unix(1257894001, 0),
- Branch: "master",
- },
- },
- {
- Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
- Commit: &CommitInfo{
- Hash: "dddddddddddddddddddddddddddddddddddddddd",
- PackagePath: "golang.org/x/sys",
- User: "Sys Tem <sys@s.net>",
- Desc: "sys: support more systems",
- Time: time.Unix(1257894001, 0),
- Branch: "master",
- },
- },
- },
- },
- {
- Name: "release-branch.go1.99",
- Tag: &CommitInfo{Hash: "ffffffffffffffffffffffffffffffffffffffff"},
- Builders: []string{"linux-386", "linux-amd64"},
- Packages: []*PackageState{
- {
- Package: &Package{Name: "net", Path: "golang.org/x/net"},
- Commit: &CommitInfo{
- Hash: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
- PackagePath: "golang.org/x/net",
- User: "Ee Yore <e@e.net>",
- Desc: "all: fix networking",
- Time: time.Unix(1257894001, 0),
- Branch: "master",
- },
- },
- {
- Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
- Commit: &CommitInfo{
- Hash: "dddddddddddddddddddddddddddddddddddddddd",
- PackagePath: "golang.org/x/sys",
- User: "Sys Tem <sys@s.net>",
- Desc: "sys: support more systems",
- Time: time.Unix(1257894001, 0),
- Branch: "master",
- },
- },
- },
- },
- },
- },
- },
-
- // Test viewing a non-go repo.
- {
- name: "html,other_repo",
- view: htmlView{},
- req: &apipb.DashboardRequest{Repo: "golang.org/x/net"},
- testCommitData: map[string]*Commit{},
- res: &apipb.DashboardResponse{
- Branches: []string{"master", "dev.blah"},
- Commits: []*apipb.DashCommit{
- {
- Commit: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- AuthorName: "Foo Bar",
- AuthorEmail: "foo@example.com",
- CommitTimeSec: 1257894001,
- Title: "net: fix all the bugs",
- Branch: "master",
- GoCommitAtTime: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- GoCommitLatest: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
- },
- },
- },
- want: &uiTemplateData{
- Dashboard: goDash,
- Repo: "net",
- Package: &Package{Name: "net", Path: "golang.org/x/net"},
- Branches: []string{"master", "dev.blah"},
- Builders: []string{"linux-386", "linux-amd64"},
- Pagination: &Pagination{},
- Commits: []*CommitInfo{
- {
- PackagePath: "golang.org/x/net",
- Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- User: "Foo Bar <foo@example.com>",
- Desc: "net: fix all the bugs",
- Time: time.Unix(1257894001, 0),
- Branch: "master",
- ResultData: []string{
- "|false||aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- "|false||bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
- },
- },
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- tb := &uiTemplateDataBuilder{
- view: tt.view,
- req: tt.req,
- res: tt.res,
- activeBuilds: tt.activeBuilds,
- testCommitData: tt.testCommitData,
- }
- data, err := tb.buildTemplateData(context.Background())
- if err != nil {
- t.Fatal(err)
- }
- diff := cmp.Diff(tt.want, data, cmpopts.IgnoreUnexported(CommitInfo{}))
- if diff != "" {
- t.Errorf("mismatch want->got:\n%s", diff)
- }
- })
- }
-}
-
-func TestToBuildStatus(t *testing.T) {
- tests := []struct {
- name string
- data *uiTemplateData
- want types.BuildStatus
- }{
- {
- name: "go repo",
- data: &uiTemplateData{
- Dashboard: goDash,
- Repo: "go",
- Package: &Package{Name: "Go", Path: ""},
- Branches: []string{"release.foo", "release.bar", "dev.blah"},
- Builders: []string{"linux-386", "linux-amd64"},
- Pagination: &Pagination{},
- Commits: []*CommitInfo{
- {
- Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- User: "Foo Bar <foo@example.com>",
- Desc: "runtime: fix all the bugs",
- Time: time.Unix(1257894001, 0).UTC(),
- Branch: "master",
- },
- },
- TagState: []*TagState{
- {
- Name: "master",
- Tag: &CommitInfo{Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac"},
- Builders: []string{"linux-386", "linux-amd64"},
- Packages: []*PackageState{
- {
- Package: &Package{Name: "net", Path: "golang.org/x/net"},
- Commit: &CommitInfo{
- Hash: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
- PackagePath: "golang.org/x/net",
- User: "Ee Yore <e@e.net>",
- Desc: "all: fix networking",
- Time: time.Unix(1257894001, 0).UTC(),
- Branch: "master",
- },
- },
- {
- Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
- Commit: &CommitInfo{
- Hash: "dddddddddddddddddddddddddddddddddddddddd",
- PackagePath: "golang.org/x/sys",
- User: "Sys Tem <sys@s.net>",
- Desc: "sys: support more systems",
- Time: time.Unix(1257894001, 0).UTC(),
- Branch: "master",
- },
- },
- },
- },
- {
- Name: "release-branch.go1.99",
- Tag: &CommitInfo{Hash: "ffffffffffffffffffffffffffffffffffffffff"},
- Builders: []string{"linux-386", "linux-amd64"},
- Packages: []*PackageState{
- {
- Package: &Package{Name: "net", Path: "golang.org/x/net"},
- Commit: &CommitInfo{
- Hash: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
- PackagePath: "golang.org/x/net",
- User: "Ee Yore <e@e.net>",
- Desc: "all: fix networking",
- Time: time.Unix(1257894001, 0).UTC(),
- Branch: "master",
- },
- },
- {
- Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
- Commit: &CommitInfo{
- Hash: "dddddddddddddddddddddddddddddddddddddddd",
- PackagePath: "golang.org/x/sys",
- User: "Sys Tem <sys@s.net>",
- Desc: "sys: support more systems",
- Time: time.Unix(1257894001, 0).UTC(),
- Branch: "master",
- },
- },
- },
- },
- },
- },
- want: types.BuildStatus{
- Builders: []string{"linux-386", "linux-amd64"},
- Revisions: []types.BuildRevision{
- {
- Repo: "go",
- Revision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- Date: "2009-11-10T23:00:01Z",
- Branch: "master",
- Author: "Foo Bar <foo@example.com>",
- Desc: "runtime: fix all the bugs",
- Results: []string{"", ""},
- },
- {
- Repo: "net",
- Revision: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
- GoRevision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- Date: "2009-11-10T23:00:01Z",
- Branch: "master",
- GoBranch: "master",
- Author: "Ee Yore <e@e.net>",
- Desc: "all: fix networking",
- Results: []string{"", ""},
- },
- {
- Repo: "sys",
- Revision: "dddddddddddddddddddddddddddddddddddddddd",
- GoRevision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- Date: "2009-11-10T23:00:01Z",
- Branch: "master",
- GoBranch: "master",
- Author: "Sys Tem <sys@s.net>",
- Desc: "sys: support more systems",
- Results: []string{"", ""},
- },
- {
- Repo: "net",
- Revision: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
- GoRevision: "ffffffffffffffffffffffffffffffffffffffff",
- Date: "2009-11-10T23:00:01Z",
- Branch: "master",
- GoBranch: "release-branch.go1.99",
- Author: "Ee Yore <e@e.net>",
- Desc: "all: fix networking",
- Results: []string{"", ""},
- },
- {
- Repo: "sys",
- Revision: "dddddddddddddddddddddddddddddddddddddddd",
- GoRevision: "ffffffffffffffffffffffffffffffffffffffff",
- Date: "2009-11-10T23:00:01Z",
- Branch: "master",
- GoBranch: "release-branch.go1.99",
- Author: "Sys Tem <sys@s.net>",
- Desc: "sys: support more systems",
- Results: []string{"", ""},
- },
- },
- },
- },
- {
- name: "other repo",
- data: &uiTemplateData{
- Dashboard: goDash,
- Repo: "tools",
- Builders: []string{"linux", "windows"},
- Commits: []*CommitInfo{
- {
- PackagePath: "golang.org/x/tools",
- Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- User: "Foo Bar <foo@example.com>",
- Desc: "tools: fix all the bugs",
- Time: time.Unix(1257894001, 0).UTC(),
- Branch: "master",
- ResultData: []string{
- "linux|false|123|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- "windows|false|456|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
- },
- },
- },
- },
- want: types.BuildStatus{
- Builders: []string{"linux", "windows"},
- Revisions: []types.BuildRevision{
- {
- Repo: "tools",
- Revision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- GoRevision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
- Date: "2009-11-10T23:00:01Z",
- Branch: "master",
- Author: "Foo Bar <foo@example.com>",
- Desc: "tools: fix all the bugs",
- Results: []string{"", "https://build.golang.org/log/456"},
- },
- {
- Repo: "tools",
- Revision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
- GoRevision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- Date: "2009-11-10T23:00:01Z",
- Branch: "master",
- Author: "Foo Bar <foo@example.com>",
- Desc: "tools: fix all the bugs",
- Results: []string{"https://build.golang.org/log/123", ""},
- },
- },
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := toBuildStatus("build.golang.org", tt.data)
- if diff := cmp.Diff(tt.want, got); diff != "" {
- t.Errorf("buildStatus(...) mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
diff --git a/app/key/key.go b/app/key/key.go
deleted file mode 100644
index c903e24..0000000
--- a/app/key/key.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2011 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 key
-
-import (
- "context"
- "sync"
-
- "cloud.google.com/go/datastore"
-)
-
-var theKey struct {
- sync.RWMutex
- builderKey
-}
-
-type builderKey struct {
- Secret string
-}
-
-var dsKey = datastore.NameKey("BuilderKey", "root", nil)
-
-func Secret(ctx context.Context, c *datastore.Client) string {
- // check with rlock
- theKey.RLock()
- k := theKey.Secret
- theKey.RUnlock()
- if k != "" {
- return k
- }
-
- // prepare to fill; check with lock and keep lock
- theKey.Lock()
- defer theKey.Unlock()
- if theKey.Secret != "" {
- return theKey.Secret
- }
-
- // fill
- if err := c.Get(ctx, dsKey, &theKey.builderKey); err != nil {
- if err == datastore.ErrNoSuchEntity {
- // If the key is not stored in datastore, write it.
- // This only happens at the beginning of a new deployment.
- // The code is left here for SDK use and in case a fresh
- // deployment is ever needed. "gophers rule" is not the
- // real key.
- panic("lost key from datastore")
- theKey.Secret = "gophers rule"
- c.Put(ctx, dsKey, &theKey.builderKey)
- return theKey.Secret
- }
- panic("cannot load builder key: " + err.Error())
- }
-
- return theKey.Secret
-}