Complete migration to GAE flex environment

+ Remove reliance on GAE-specific API calls
+ Adds instructions to do local dev using the datastore emulator
+ Small golint fixes

Update golang/go#21645

Change-Id: Icc7679fab4c166a3d8fef3043414d6069865661d
Reviewed-on: https://go-review.googlesource.com/84815
Reviewed-by: Chris Broadfoot <cbro@golang.org>
diff --git a/README.md b/README.md
index 4cb0d12..2e43bde 100644
--- a/README.md
+++ b/README.md
@@ -14,11 +14,21 @@
 docker build -t frontend frontend/
 ```
 
+### Dev Setup
+
+```
+gcloud components install cloud-datastore-emulator
+```
+
 ### Running
 
 ```
+# run the datastore emulator
+gcloud --project golang-org beta emulators datastore start
+# set env vars
+$(gcloud beta emulators datastore env-init)
 # run the frontend
-docker run -d --rm -p 8080:8080 frontend
+cd frontend && go install && frontend
 ```
 
 Now visit localhost:8080 to ensure it worked.
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 0b81a86..1594a89 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -6,42 +6,126 @@
 
 # BEGIN deps (run `make update-deps` to update)
 
+# Repo cloud.google.com/go at 3051b91 (2017-12-06)
+ENV REV=3051b919da3b8d62bc3a57ab4b353ca1c72402d5
+RUN go get -d cloud.google.com/go/compute/metadata `#and 6 other pkgs` &&\
+    (cd /go/src/cloud.google.com/go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
 # Repo github.com/golang/protobuf at 1e59b77 (2017-11-13)
 ENV REV=1e59b77b52bf8e4b449a57e6f79f21226d571845
-RUN go get -d github.com/golang/protobuf/proto &&\
+RUN go get -d github.com/golang/protobuf/proto `#and 8 other pkgs` &&\
     (cd /go/src/github.com/golang/protobuf && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
 
+# Repo github.com/googleapis/gax-go at 317e000 (2017-09-15)
+ENV REV=317e0006254c44a0ac427cc52a0e083ff0b9622f
+RUN go get -d github.com/googleapis/gax-go &&\
+    (cd /go/src/github.com/googleapis/gax-go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
 # Repo golang.org/x/net at faacc1b (2017-12-07)
 ENV REV=faacc1b5e36e3ff02cbec9661c69ac63dd5a83ad
-RUN go get -d golang.org/x/net/context &&\
+RUN go get -d golang.org/x/net/context `#and 8 other pkgs` &&\
     (cd /go/src/golang.org/x/net && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
 
+# Repo golang.org/x/oauth2 at 6a2004c (2017-12-06)
+ENV REV=6a2004c8907a86949d71c664c81574897a4e55a6
+RUN go get -d golang.org/x/oauth2 `#and 5 other pkgs` &&\
+    (cd /go/src/golang.org/x/oauth2 && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/text at be25de4 (2017-12-07)
+ENV REV=be25de41fadfae372d6470bda81ca6beb55ef551
+RUN go get -d golang.org/x/text/secure/bidirule `#and 4 other pkgs` &&\
+    (cd /go/src/golang.org/x/text && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
 # Repo golang.org/x/tools at 5d8e38b (2017-12-10)
 ENV REV=5d8e38b9550d35d893ff8276756b4118ec1bf360
 RUN go get -d golang.org/x/tools/go/ast/astutil `#and 3 other pkgs` &&\
     (cd /go/src/golang.org/x/tools && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
 
-# Repo google.golang.org/appengine at 24e4144 (2017-09-21)
-ENV REV=24e4144ec923c2374f6b06610c0df16a9222c3d9
-RUN go get -d google.golang.org/appengine `#and 10 other pkgs` &&\
-    (cd /go/src/google.golang.org/appengine && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+# Repo google.golang.org/api at 9a048ca (2017-12-07)
+ENV REV=9a048cac3675aa589c62a35d7d42b25451dd15f1
+RUN go get -d google.golang.org/api/googleapi `#and 6 other pkgs` &&\
+    (cd /go/src/google.golang.org/api && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo google.golang.org/genproto at 7f0da29 (2017-11-23)
+ENV REV=7f0da29060c682909f650ad8ed4e515bd74fa12a
+RUN go get -d google.golang.org/genproto/googleapis/api/annotations `#and 4 other pkgs` &&\
+    (cd /go/src/google.golang.org/genproto && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo google.golang.org/grpc at b8191e5 (2017-12-06)
+ENV REV=b8191e57b23de650278db4d23bf596219e5f3665
+RUN go get -d google.golang.org/grpc `#and 24 other pkgs` &&\
+    (cd /go/src/google.golang.org/grpc && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
 
 # Optimization to speed up iterative development, not necessary for correctness:
-RUN go install github.com/golang/protobuf/proto \
+RUN go install cloud.google.com/go/compute/metadata \
+	cloud.google.com/go/datastore \
+	cloud.google.com/go/internal \
+	cloud.google.com/go/internal/atomiccache \
+	cloud.google.com/go/internal/fields \
+	cloud.google.com/go/internal/version \
+	github.com/golang/protobuf/proto \
+	github.com/golang/protobuf/protoc-gen-go/descriptor \
+	github.com/golang/protobuf/ptypes \
+	github.com/golang/protobuf/ptypes/any \
+	github.com/golang/protobuf/ptypes/duration \
+	github.com/golang/protobuf/ptypes/struct \
+	github.com/golang/protobuf/ptypes/timestamp \
+	github.com/golang/protobuf/ptypes/wrappers \
+	github.com/googleapis/gax-go \
 	golang.org/x/net/context \
+	golang.org/x/net/context/ctxhttp \
+	golang.org/x/net/http2 \
+	golang.org/x/net/http2/hpack \
+	golang.org/x/net/idna \
+	golang.org/x/net/internal/timeseries \
+	golang.org/x/net/lex/httplex \
+	golang.org/x/net/trace \
+	golang.org/x/oauth2 \
+	golang.org/x/oauth2/google \
+	golang.org/x/oauth2/internal \
+	golang.org/x/oauth2/jws \
+	golang.org/x/oauth2/jwt \
+	golang.org/x/text/secure/bidirule \
+	golang.org/x/text/transform \
+	golang.org/x/text/unicode/bidi \
+	golang.org/x/text/unicode/norm \
 	golang.org/x/tools/go/ast/astutil \
 	golang.org/x/tools/godoc/static \
 	golang.org/x/tools/imports \
-	google.golang.org/appengine \
-	google.golang.org/appengine/datastore \
-	google.golang.org/appengine/internal \
-	google.golang.org/appengine/internal/app_identity \
-	google.golang.org/appengine/internal/base \
-	google.golang.org/appengine/internal/datastore \
-	google.golang.org/appengine/internal/log \
-	google.golang.org/appengine/internal/modules \
-	google.golang.org/appengine/internal/remote_api \
-	google.golang.org/appengine/log
+	google.golang.org/api/googleapi \
+	google.golang.org/api/googleapi/internal/uritemplates \
+	google.golang.org/api/internal \
+	google.golang.org/api/iterator \
+	google.golang.org/api/option \
+	google.golang.org/api/transport/grpc \
+	google.golang.org/genproto/googleapis/api/annotations \
+	google.golang.org/genproto/googleapis/datastore/v1 \
+	google.golang.org/genproto/googleapis/rpc/status \
+	google.golang.org/genproto/googleapis/type/latlng \
+	google.golang.org/grpc \
+	google.golang.org/grpc/balancer \
+	google.golang.org/grpc/balancer/base \
+	google.golang.org/grpc/balancer/roundrobin \
+	google.golang.org/grpc/codes \
+	google.golang.org/grpc/connectivity \
+	google.golang.org/grpc/credentials \
+	google.golang.org/grpc/credentials/oauth \
+	google.golang.org/grpc/encoding \
+	google.golang.org/grpc/grpclb/grpc_lb_v1/messages \
+	google.golang.org/grpc/grpclog \
+	google.golang.org/grpc/internal \
+	google.golang.org/grpc/keepalive \
+	google.golang.org/grpc/metadata \
+	google.golang.org/grpc/naming \
+	google.golang.org/grpc/peer \
+	google.golang.org/grpc/resolver \
+	google.golang.org/grpc/resolver/dns \
+	google.golang.org/grpc/resolver/manual \
+	google.golang.org/grpc/resolver/passthrough \
+	google.golang.org/grpc/stats \
+	google.golang.org/grpc/status \
+	google.golang.org/grpc/tap \
+	google.golang.org/grpc/transport
 # END deps
 
 # Add and compile frontend daemon
diff --git a/frontend/edit.go b/frontend/edit.go
index 8312f4f..f24fcb7 100644
--- a/frontend/edit.go
+++ b/frontend/edit.go
@@ -8,11 +8,10 @@
 	"fmt"
 	"html/template"
 	"net/http"
+	"os"
 	"strings"
 
-	"google.golang.org/appengine"
-	"google.golang.org/appengine/datastore"
-	"google.golang.org/appengine/log"
+	"cloud.google.com/go/datastore"
 )
 
 const hostname = "play.golang.org"
@@ -20,7 +19,7 @@
 var editTemplate = template.Must(template.ParseFiles("edit.html"))
 
 type editData struct {
-	Snippet *Snippet
+	Snippet *snippet
 	Share   bool
 }
 
@@ -31,25 +30,25 @@
 		return
 	}
 
-	snip := &Snippet{Body: []byte(hello)}
+	snip := &snippet{Body: []byte(hello)}
 	if strings.HasPrefix(r.URL.Path, "/p/") {
 		if !allowShare(r) {
 			w.WriteHeader(http.StatusUnavailableForLegalReasons)
 			w.Write([]byte(`<h1>Unavailable For Legal Reasons</h1><p>Viewing and/or sharing code snippets is not available in your country for legal reasons. This message might also appear if your country is misdetected. If you believe this is an error, please <a href="https://golang.org/issue">file an issue</a>.</p>`))
 			return
 		}
-		c := appengine.NewContext(r)
+		ctx := r.Context()
 		id := r.URL.Path[3:]
 		serveText := false
 		if strings.HasSuffix(id, ".go") {
 			id = id[:len(id)-3]
 			serveText = true
 		}
-		key := datastore.NewKey(c, "Snippet", id, 0, nil)
-		err := datastore.Get(c, key, snip)
+		key := datastore.NameKey("Snippet", id, nil)
+		err := datastoreClient.Get(ctx, key, snip)
 		if err != nil {
 			if err != datastore.ErrNoSuchEntity {
-				log.Errorf(c, "loading Snippet: %v", err)
+				fmt.Fprintf(os.Stderr, "loading Snippet: %v", err)
 			}
 			http.Error(w, "Snippet not found", http.StatusNotFound)
 			return
diff --git a/frontend/frontend.go b/frontend/frontend.go
index fc97f08..2d831e3 100644
--- a/frontend/frontend.go
+++ b/frontend/frontend.go
@@ -5,14 +5,30 @@
 package main
 
 import (
+	"context"
+	"flag"
+	"fmt"
 	"io"
+	"log"
 	"net/http"
+	"os"
 
+	"cloud.google.com/go/compute/metadata"
+	"cloud.google.com/go/datastore"
 	"golang.org/x/tools/godoc/static"
-	"google.golang.org/appengine"
 )
 
+var datastoreClient *datastore.Client
+
 func main() {
+	flag.Parse()
+
+	var err error
+	datastoreClient, err = datastore.NewClient(context.Background(), projectID())
+	if err != nil {
+		log.Fatal(err)
+	}
+
 	http.Handle("/", hstsHandler(edit))
 	http.Handle("/compile", hstsHandler(compile))
 	http.Handle("/fmt", hstsHandler(fmtHandler))
@@ -23,7 +39,27 @@
 	http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
 		http.ServeFile(w, r, "./static/favicon.ico")
 	})
-	appengine.Main()
+	http.HandleFunc("/_ah/health", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, "ok")
+	})
+	port := os.Getenv("PORT")
+	if port == "" {
+		port = "8080"
+	}
+	log.Printf("Listening on :%v ...", port)
+	http.ListenAndServe(":"+port, nil)
+}
+
+func projectID() string {
+	id := os.Getenv("DATASTORE_PROJECT_ID")
+	if id != "" {
+		return id
+	}
+	id, err := metadata.ProjectID()
+	if err != nil {
+		log.Fatalf("Could not determine the project ID (%v); If running locally, ensure DATASTORE_PROJECT_ID is set.", err)
+	}
+	return id
 }
 
 func play(w http.ResponseWriter, r *http.Request) {
diff --git a/frontend/share.go b/frontend/share.go
index fb0c9c0..60c9a78 100644
--- a/frontend/share.go
+++ b/frontend/share.go
@@ -13,20 +13,18 @@
 	"net/http"
 	"os"
 
-	"google.golang.org/appengine"
-	"google.golang.org/appengine/datastore"
-	"google.golang.org/appengine/log"
+	"cloud.google.com/go/datastore"
 )
 
 const salt = "[replace this with something unique]"
 
 const maxSnippetSize = 64 * 1024
 
-type Snippet struct {
+type snippet struct {
 	Body []byte
 }
 
-func (s *Snippet) Id() string {
+func (s *snippet) ID() string {
 	h := sha1.New()
 	io.WriteString(h, salt)
 	h.Write(s.Body)
@@ -52,13 +50,13 @@
 		http.Error(w, http.StatusText(status), status)
 		return
 	}
-	c := appengine.NewContext(r)
+	ctx := r.Context()
 
 	var body bytes.Buffer
 	_, err := io.Copy(&body, io.LimitReader(r.Body, maxSnippetSize+1))
 	r.Body.Close()
 	if err != nil {
-		log.Errorf(c, "reading Body: %v", err)
+		fmt.Fprintf(os.Stderr, "reading Body: %v", err)
 		http.Error(w, "Server Error", http.StatusInternalServerError)
 		return
 	}
@@ -67,13 +65,13 @@
 		return
 	}
 
-	snip := &Snippet{Body: body.Bytes()}
-	id := snip.Id()
-	key := datastore.NewKey(c, "Snippet", id, 0, nil)
-	_, err = datastore.Put(c, key, snip)
+	snip := &snippet{Body: body.Bytes()}
+	id := snip.ID()
+	key := datastore.NameKey("Snippet", id, nil)
+	_, err = datastoreClient.Put(ctx, key, snip)
 	if err != nil {
-		log.Errorf(c, "putting Snippet: %v", err)
-		http.Error(w, "Server Error", http.StatusInternalServerError)
+		fmt.Fprintf(os.Stderr, "putting Snippet: %v", err)
+		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 		return
 	}