blob: 950ed26e56f8930f6a3b5ca819375a6eacfe26a6 [file] [log] [blame]
// scrapexchange is the web admin and API server for the scrap exchange service.
//
// See http://go/scrap-exchange for more details.
package main
import (
"context"
"flag"
"html/template"
"net/http"
"path/filepath"
"cloud.google.com/go/storage"
"github.com/gorilla/mux"
"github.com/unrolled/secure"
"go.skia.org/infra/go/allowed"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/baseapp"
"go.skia.org/infra/go/gcs/gcsclient"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/login"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/scrap/go/api"
"go.skia.org/infra/scrap/go/scrap"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
)
// flags
var (
authGroup = flag.String("auth_group", "google/skia-staff@google.com", "The chrome infra auth group to use for restricting access.")
internalPort = flag.String("internal_port", ":9000", "HTTP internal service address (e.g., ':9000') for unauthenticated in-cluster requests.")
bucket = flag.String("bucket", "", "The Google Cloud Storage bucket that scraps are stored in.")
)
// server is the state of the server.
type server struct {
apiEndpoints *api.Api
templates *template.Template
}
// New implements baseapp.Constructor.
func New() (baseapp.App, error) {
if *bucket == "" {
return nil, skerr.Fmt("--bucket is a required flag.")
}
ctx := context.Background()
ts, err := google.DefaultTokenSource(ctx, storage.ScopeFullControl, auth.ScopeUserinfoEmail)
if err != nil {
return nil, skerr.Wrap(err)
}
var admin allowed.Allow
if !*baseapp.Local {
client := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
admin, err = allowed.NewAllowedFromChromeInfraAuth(client, *authGroup)
if err != nil {
return nil, skerr.Wrap(err)
}
} else {
admin = allowed.NewAllowedFromList([]string{"fred@example.org", "barney@example.org", "wilma@example.org"})
}
login.SimpleInitWithAllow(*baseapp.Port, *baseapp.Local, admin, nil, nil)
client := httputils.DefaultClientConfig().WithTokenSource(ts).WithoutRetries().Client()
storageClient, err := storage.NewClient(ctx, option.WithHTTPClient(client))
if err != nil {
return nil, skerr.Wrap(err)
}
gcsClient := gcsclient.New(storageClient, *bucket)
scrapExchange, err := scrap.New(gcsClient)
if err != nil {
return nil, skerr.Wrapf(err, "Failed to create ScrapExchange.")
}
apiEndpoints := api.New(scrapExchange)
srv := &server{
apiEndpoints: apiEndpoints,
}
srv.loadTemplates()
srv.startInternalServer()
return srv, nil
}
func (srv *server) loadTemplates() {
srv.templates = template.Must(template.New("").Delims("{%", "%}").ParseFiles(
filepath.Join(*baseapp.ResourcesDir, "index.html"),
))
}
func (srv *server) mainHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
if *baseapp.Local {
srv.loadTemplates()
}
if err := srv.templates.ExecuteTemplate(w, "index.html", map[string]string{
// Look in //scrap/pages/BUILD.bazel for where the nonce templates are injected.
"Nonce": secure.CSPNonce(r.Context()),
}); err != nil {
sklog.Errorf("Failed to expand template: %s", err)
}
}
// user returns the currently logged in user, or a placeholder if running locally.
func (srv *server) user(r *http.Request) string {
user := "barney@example.org"
if !*baseapp.Local {
user = login.LoggedInAs(r)
}
return user
}
// AddHandlers implements baseapp.App.
func (srv *server) AddHandlers(r *mux.Router) {
var h http.HandlerFunc = srv.mainHandler
if !*baseapp.Local {
h = login.RestrictAdminFn(h)
}
r.HandleFunc("/", h)
r.HandleFunc("/loginstatus/", login.StatusHandler).Methods("GET")
srv.apiEndpoints.AddHandlers(r, api.DoNotAddProtectedEndpoints)
}
// AddMiddleware implements baseapp.App.
func (srv *server) AddMiddleware() []mux.MiddlewareFunc {
return []mux.MiddlewareFunc{}
}
func (srv *server) startInternalServer() {
// Internal endpoints that are only accessible from within the cluster.
internal := mux.NewRouter()
srv.apiEndpoints.AddHandlers(internal, api.AddProtectedEndpoints)
go func() {
sklog.Fatal(http.ListenAndServe(*internalPort, internal))
}()
}
func main() {
baseapp.Serve(New, []string{"scrap.skia.org"})
}