devapp: further preparation for running on GKE

+ Remove superfluous check for PORT env var since only the flag
  will be used.
+ Remove HTML being written to the page before escaping step,
  causing jank.
+ Grab the GitHub token from GCE metadata if it’s available.

Updates golang/go#20691

Change-Id: I10fbc163ee91907ef0b843c823f40fd87a62f476
Reviewed-on: https://go-review.googlesource.com/46210
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/devapp/devapp.go b/devapp/devapp.go
index f55d784..552506e 100644
--- a/devapp/devapp.go
+++ b/devapp/devapp.go
@@ -141,7 +141,6 @@
 		if cls {
 			data.PrintCLs(&output)
 		} else {
-			fmt.Fprintf(&output, fmt.Sprintf(`<a href="/stats/release?cycle=%d">Go 1.%d Issue Stats Dashboard</a>`, data.GoReleaseCycle, data.GoReleaseCycle))
 			data.PrintIssues(&output)
 		}
 		var html bytes.Buffer
diff --git a/devapp/devappserver/Dockerfile.0 b/devapp/devappserver/Dockerfile.0
index e857b72..fce55a2 100644
--- a/devapp/devappserver/Dockerfile.0
+++ b/devapp/devappserver/Dockerfile.0
@@ -28,6 +28,9 @@
 RUN go get -d golang.org/x/sync/errgroup && \
     cd /go/src/golang.org/x/sync && git reset --hard f52d1811a62927559de87708c8913c1650ce4f26
 
+RUN go get -d cloud.google.com/go/compute/metadata && \
+    cd /go/src/cloud.google.com/go && git reset --hard 23179f286bc31e07fba2eddaa540fb999b1b1fd9
+
 # Optimization to speed COPY+go install steps later. This go install
 # isn't required for correctness.
 RUN go install github.com/aclements/go-gg/generic/slice \
@@ -39,7 +42,8 @@
     github.com/kylelemons/godebug/pretty \
     golang.org/x/net/context \
     golang.org/x/oauth2 \
-    golang.org/x/sync/errgroup
+    golang.org/x/sync/errgroup \
+    cloud.google.com/go/compute/metadata
 
 COPY . /go/src/golang.org/x/build/
 
diff --git a/devapp/devappserver/main.go b/devapp/devappserver/main.go
index e623bcf..cd9657c 100644
--- a/devapp/devappserver/main.go
+++ b/devapp/devappserver/main.go
@@ -7,10 +7,9 @@
 //
 // Usage:
 //
-//	devappserver --port=8081
+//	devappserver --http=:8081
 //
-// By default devapp listens on port 8081. You can also configure the port by
-// setting the PORT environment variable (but not both).
+// By default devappserver listens on port 80.
 //
 // For the moment, Github issues and Gerrit CL's are stored in memory
 // in the running process. To trigger an initial download, visit
@@ -32,35 +31,21 @@
 
 func init() {
 	flag.Usage = func() {
-		os.Stderr.WriteString(`usage: devapp [-port=port]
+		os.Stderr.WriteString(`usage: devappserver [-http=addr]
 
-Devapp generates the dashboard that powers dev.golang.org.
+devappserver generates the dashboard that powers dev.golang.org.
 	`)
 	}
 }
 
-const defaultPort = "8081"
-
 func main() {
-	portFlag := flag.String("port", "", "Port to listen on")
+	httpAddr := flag.String("http", ":80", "HTTP service address (e.g., ':8080')")
 	flag.Parse()
-	if *portFlag != "" && os.Getenv("PORT") != "" {
-		os.Stderr.WriteString("cannot set both $PORT and --port flags\n")
-		os.Exit(2)
-	}
-	var port string
-	if p := os.Getenv("PORT"); p != "" {
-		port = p
-	} else if *portFlag != "" {
-		port = *portFlag
-	} else {
-		port = defaultPort
-	}
-	ln, err := net.Listen("tcp", ":"+port)
+	ln, err := net.Listen("tcp", *httpAddr)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error listening on %s: %v\n", port, err)
+		fmt.Fprintf(os.Stderr, "Error listening on %s: %v\n", *httpAddr, err)
 		os.Exit(2)
 	}
-	fmt.Fprintf(os.Stderr, "Listening on port %s\n", port)
+	fmt.Fprintf(os.Stderr, "Serving at %s\n", ln.Addr().String())
 	log.Fatal(http.Serve(ln, nil))
 }
diff --git a/devapp/noappengine.go b/devapp/noappengine.go
index 759b12d..6b42161 100644
--- a/devapp/noappengine.go
+++ b/devapp/noappengine.go
@@ -8,7 +8,6 @@
 package devapp
 
 import (
-	"errors"
 	"flag"
 	"fmt"
 	"io/ioutil"
@@ -20,6 +19,7 @@
 	"sync"
 	"time"
 
+	"cloud.google.com/go/compute/metadata"
 	"golang.org/x/net/context"
 )
 
@@ -130,11 +130,13 @@
 	return nil
 }
 
-var githubToken string
-var githubOnceErr error
-var githubOnce sync.Once
+var (
+	githubToken   string
+	githubOnceErr error
+	githubOnce    sync.Once
+)
 
-func getToken(ctx context.Context) (string, error) {
+func getTokenFromFile(ctx context.Context) (string, error) {
 	githubOnce.Do(func() {
 		const short = ".github-issue-token"
 		filename := filepath.Clean(os.Getenv("HOME") + "/" + short)
@@ -145,14 +147,16 @@
 		}
 		data, err := ioutil.ReadFile(filename)
 		if err != nil {
-			msg := fmt.Sprintln("reading token: ", err, "\n\n"+
-				"Please create a personal access token at https://github.com/settings/tokens/new\n"+
-				"and write it to ", shortFilename, " to use this program.\n"+
-				"The token only needs the repo scope, or private_repo if you want to\n"+
-				"view or edit issues for private repositories.\n"+
-				"The benefit of using a personal access token over using your GitHub\n"+
-				"password directly is that you can limit its use and revoke it at any time.\n\n")
-			githubOnceErr = errors.New(msg)
+			const fmtStr = `reading token: %v
+
+Please create a personal access token at https://github.com/settings/tokens/new
+and write it to %s or store it in GCE metadata using the
+key 'maintner-github-token' to use this program.
+The token only needs the repo scope, or private_repo if you want to view or edit
+issues for private repositories. The benefit of using a personal access token
+over using your GitHub password directly is that you can limit its use and revoke
+it at any time.`
+			githubOnceErr = fmt.Errorf(fmtStr, err, shortFilename)
 			return
 		}
 		fi, err := os.Stat(filename)
@@ -169,6 +173,17 @@
 	return githubToken, githubOnceErr
 }
 
+func getToken(ctx context.Context) (string, error) {
+	if metadata.OnGCE() {
+		// Re-use maintner-github-token until this is migrated to using the maintner API.
+		token, err := metadata.ProjectAttributeValue("maintner-github-token")
+		if len(token) > 0 && err == nil {
+			return token, nil
+		}
+	}
+	return getTokenFromFile(ctx)
+}
+
 func getContext(r *http.Request) context.Context {
 	return r.Context()
 }