blob: f855b5826186aa4d4617eb3110d04626730afa5e [file] [log] [blame]
package main
import (
"context"
"flag"
"fmt"
"html/template"
"net/http"
"path/filepath"
"cloud.google.com/go/datastore"
"github.com/gorilla/mux"
"google.golang.org/api/option"
"go.skia.org/infra/go/allowed"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/baseapp"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/login"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
)
// Flags
var (
modifyGroup = flag.String("modify_group", "project-skia-committers", "The chrome infra auth group to use for who is allowed to change tree status.")
adminGroup = flag.String("admin_group", "google/skia-staff@google.com", "The chrome infra auth group to use for who is allowed to update rotations.")
chromeInfraAuthJWT = flag.String("chrome_infra_auth_jwt", "/var/secrets/skia-public-auth/key.json", "The JWT key for the service account that has access to chrome infra auth.")
namespace = flag.String("namespace", "tree-status-staging", "The Cloud Datastore namespace.")
project = flag.String("project", "skia-public", "The Google Cloud project name.")
)
var (
// dsClient is the Cloud Datastore client to access tree statuses and rotations.
dsClient *datastore.Client
)
// Server is the state of the server.
type Server struct {
templates *template.Template
modify allowed.Allow // Who is allowed to modify tree status.
admin allowed.Allow // Who is allowed to modify rotations.
}
// See baseapp.Constructor.
func New() (baseapp.App, error) {
ctx := context.Background()
ts, err := auth.NewDefaultTokenSource(*baseapp.Local, "https://www.googleapis.com/auth/datastore")
if err != nil {
return nil, skerr.Wrapf(err, "Problem setting up default token source")
}
dsClient, err = datastore.NewClient(context.Background(), *project, option.WithTokenSource(ts))
if err != nil {
return nil, skerr.Wrapf(err, "Failed to initialize Cloud Datastore for tree status")
}
// Start watching for statuses with autorollers specified.
if err := AutorollersInit(ctx, ts); err != nil {
return nil, skerr.Wrapf(err, "Could not init autorollers")
}
// Load the last status and whether autorollers need to be watched.
s, err := GetLatestStatus()
if err != nil {
return nil, skerr.Wrapf(err, "Could not find latest status")
}
if s.Rollers != "" {
sklog.Infof("Last status has rollers that need to be watched: %s", s.Rollers)
StartWatchingAutorollers(s.Rollers)
}
var modify allowed.Allow
var admin allowed.Allow
if !*baseapp.Local {
ts, err := auth.NewJWTServiceAccountTokenSource("", *chromeInfraAuthJWT, auth.SCOPE_USERINFO_EMAIL)
if err != nil {
return nil, err
}
client := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
modify, err = allowed.NewAllowedFromChromeInfraAuth(client, *modifyGroup)
if err != nil {
return nil, err
}
admin, err = allowed.NewAllowedFromChromeInfraAuth(client, *adminGroup)
if err != nil {
return nil, err
}
} else {
modify = allowed.NewAllowedFromList([]string{"barney@example.org"})
admin = allowed.NewAllowedFromList([]string{"barney@example.org"})
}
login.SimpleInitWithAllow(*baseapp.Port, *baseapp.Local, admin, modify, nil /* Everyone is allowed to access */)
srv := &Server{
modify: modify,
admin: admin,
}
srv.loadTemplates()
liveness := metrics2.NewLiveness("alive", map[string]string{})
fmt.Println(liveness)
return srv, nil
}
func (srv *Server) loadTemplates() {
blah := *baseapp.ResourcesDir
srv.templates = template.Must(template.New("").Delims("{%", "%}").ParseFiles(
filepath.Join(blah, "index.html"),
filepath.Join(blah, "rotations.html"),
))
}
// 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
}
// See baseapp.App.
func (srv *Server) AddHandlers(r *mux.Router) {
// For login/logout.
r.HandleFunc(login.DEFAULT_OAUTH2_CALLBACK, login.OAuth2CallbackHandler)
r.HandleFunc("/logout/", login.LogoutHandler)
r.HandleFunc("/loginstatus/", login.StatusHandler)
// All endpoints that require authentication should be added to this router. The
// rest of endpoints are left unauthenticated because they are accessed from various
// places like: Skia infra apps, Gerrit plugin, Chrome extensions, presubmits, etc.
appRouter := mux.NewRouter()
// For tree status.
appRouter.HandleFunc("/", srv.treeStateHandler).Methods("GET")
appRouter.HandleFunc("/_/add_tree_status", srv.addStatusHandler).Methods("POST")
appRouter.HandleFunc("/_/get_autorollers", srv.autorollersHandler).Methods("POST")
appRouter.HandleFunc("/_/recent_statuses", srv.recentStatusesHandler).Methods("POST")
r.HandleFunc("/current", httputils.CorsHandler(srv.bannerStatusHandler)).Methods("GET")
// For rotations.
appRouter.HandleFunc("/sheriff", srv.sheriffHandler).Methods("GET")
appRouter.HandleFunc("/robocop", srv.robocopHandler).Methods("GET")
appRouter.HandleFunc("/wrangler", srv.wranglerHandler).Methods("GET")
appRouter.HandleFunc("/trooper", srv.trooperHandler).Methods("GET")
appRouter.HandleFunc("/update_sheriff_rotations", srv.updateSheriffRotationsHandler).Methods("GET")
appRouter.HandleFunc("/update_robocop_rotations", srv.updateRobocopRotationsHandler).Methods("GET")
appRouter.HandleFunc("/update_wrangler_rotations", srv.updateWranglerRotationsHandler).Methods("GET")
appRouter.HandleFunc("/update_trooper_rotations", srv.updateTrooperRotationsHandler).Methods("GET")
appRouter.HandleFunc("/_/get_rotations", srv.autorollersHandler).Methods("POST")
r.HandleFunc("/current-sheriff", httputils.CorsHandler(srv.currentSheriffHandler)).Methods("GET")
r.HandleFunc("/current-robocop", httputils.CorsHandler(srv.currentRobocopHandler)).Methods("GET")
r.HandleFunc("/current-wrangler", httputils.CorsHandler(srv.currentWranglerHandler)).Methods("GET")
r.HandleFunc("/current-trooper", httputils.CorsHandler(srv.currentTrooperHandler)).Methods("GET")
r.HandleFunc("/next-sheriff", httputils.CorsHandler(srv.nextSheriffHandler)).Methods("GET")
r.HandleFunc("/next-robocop", httputils.CorsHandler(srv.nextRobocopHandler)).Methods("GET")
r.HandleFunc("/next-wrangler", httputils.CorsHandler(srv.nextWranglerHandler)).Methods("GET")
r.HandleFunc("/next-trooper", httputils.CorsHandler(srv.nextTrooperHandler)).Methods("GET")
// Use the appRouter as a handler and wrap it into middleware that enforces authentication.
appHandler := http.Handler(appRouter)
if !*baseapp.Local {
appHandler = login.ForceAuth(appRouter, login.DEFAULT_REDIRECT_URL)
}
r.PathPrefix("/").Handler(appHandler)
}
// See baseapp.App.
func (srv *Server) AddMiddleware() []mux.MiddlewareFunc {
return []mux.MiddlewareFunc{}
}
func main() {
baseapp.Serve(New, []string{"tree-status.skia.org"})
}