| // This program serves content that is mostly static and needs to be highly |
| // available. The content comes from highly available backend services like |
| // GCS. It needs to be deployed in a redundant way to ensure high uptime. |
| // It is read-only; it does not create new baselines or update expectations. |
| package main |
| |
| import ( |
| "context" |
| "flag" |
| "net/http" |
| "os" |
| "path/filepath" |
| |
| "go.skia.org/infra/golden/go/clstore/fs_clstore" |
| |
| "github.com/gorilla/mux" |
| "go.skia.org/infra/go/auth" |
| "go.skia.org/infra/go/common" |
| "go.skia.org/infra/go/firestore" |
| "go.skia.org/infra/go/httputils" |
| "go.skia.org/infra/go/skiaversion" |
| "go.skia.org/infra/go/sklog" |
| "go.skia.org/infra/golden/go/baseline/simple_baseliner" |
| "go.skia.org/infra/golden/go/expstorage/fs_expstore" |
| "go.skia.org/infra/golden/go/shared" |
| "go.skia.org/infra/golden/go/storage" |
| "go.skia.org/infra/golden/go/web" |
| gstorage "google.golang.org/api/storage/v1" |
| ) |
| |
| func main() { |
| // Command line flags. |
| var ( |
| fsNamespace = flag.String("fs_namespace", "", "Typically the instance id. e.g. 'flutter', 'skia', etc") |
| fsProjectID = flag.String("fs_project_id", "skia-firestore", "The project with the firestore instance. Datastore and Firestore can't be in the same project.") |
| hashesGSPath = flag.String("hashes_gs_path", "", "GS path, where the known hashes file should be stored. This should match the same flag in skiacorrectness which writes the hashes. Format: <bucket>/<path>.") |
| local = flag.Bool("local", false, "if running local (not in production)") |
| primaryCRS = flag.String("primary_crs", "gerrit", "Primary CodeReviewSystem (e.g. 'gerrit', 'github'") |
| port = flag.String("port", ":9000", "HTTP service address (e.g., ':9000')") |
| promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')") |
| ) |
| |
| // Parse the options. So we can configure logging. |
| flag.Parse() |
| |
| if *fsNamespace == "" { |
| sklog.Fatalf("--fs_namespace must be set") |
| } |
| |
| firestore.EnsureNotEmulator() |
| |
| // Set up the logging options. |
| logOpts := []common.Opt{ |
| common.PrometheusOpt(promPort), |
| } |
| |
| _, appName := filepath.Split(os.Args[0]) |
| common.InitWithMust(appName, logOpts...) |
| skiaversion.MustLogVersion() |
| // Auth note: the underlying firestore.NewClient looks at the |
| // GOOGLE_APPLICATION_CREDENTIALS env variable, so we don't need to supply |
| // a token source. |
| fsClient, err := firestore.NewClient(context.Background(), *fsProjectID, "gold", *fsNamespace, nil) |
| if err != nil { |
| sklog.Fatalf("Unable to configure Firestore: %s", err) |
| } |
| |
| expStore, err := fs_expstore.New(context.Background(), fsClient, nil, fs_expstore.ReadOnly) |
| if err != nil { |
| sklog.Fatalf("Unable to initialize fs_expstore: %s", err) |
| } |
| |
| // Initialize the Baseliner instance from the values set above. |
| baseliner := simple_baseliner.New(expStore) |
| |
| gsClientOpt := storage.GCSClientOptions{ |
| HashesGSPath: *hashesGSPath, |
| } |
| |
| tokenSource, err := auth.NewDefaultTokenSource(*local, gstorage.CloudPlatformScope) |
| if err != nil { |
| sklog.Fatalf("Could not create token source: %s", err) |
| } |
| |
| client := httputils.DefaultClientConfig().WithTokenSource(tokenSource).Client() |
| |
| gsClient, err := storage.NewGCSClient(client, gsClientOpt) |
| if err != nil { |
| sklog.Fatalf("Unable to create GCSClient: %s", err) |
| } |
| |
| // Baseline doesn't need to access this, just needs a way to indicate which CRS we are on. |
| emptyCLStore := fs_clstore.New(nil, *primaryCRS) |
| |
| // We only need to fill in the HandlersConfig struct with the following subset, since the baseline |
| // server only supplies a subset of the functionality. |
| handlers, err := web.NewHandlers(web.HandlersConfig{ |
| GCSClient: gsClient, |
| Baseliner: baseliner, |
| ChangeListStore: emptyCLStore, |
| }, web.BaselineSubset) |
| if err != nil { |
| sklog.Fatalf("Failed to initialize web handlers: %s", err) |
| } |
| |
| // Set up a router for all the application endpoints which are part of the Gold API. |
| appRouter := mux.NewRouter() |
| |
| // Serve the known hashes from GCS. |
| appRouter.HandleFunc(shared.KnownHashesRoute, handlers.TextKnownHashesProxy).Methods("GET") |
| |
| // Serve the expectations for the master branch and for CLs in progress. |
| appRouter.HandleFunc(shared.ExpectationsRoute, handlers.BaselineHandler).Methods("GET") |
| appRouter.HandleFunc(shared.ExpectationsIssueRoute, handlers.BaselineHandler).Methods("GET") |
| |
| // Only log and compress the app routes, but not the health check. |
| router := mux.NewRouter() |
| router.HandleFunc("/healthz", httputils.ReadyHandleFunc) |
| router.PathPrefix("/").Handler(httputils.LoggingGzipRequestResponse(appRouter)) |
| |
| // Start the server |
| sklog.Infof("Serving on http://127.0.0.1" + *port) |
| sklog.Fatal(http.ListenAndServe(*port, router)) |
| } |