cmd/frontend: add -local flag

Create -local and -gopath_mode flags to enable using a local datasource
and loading local modules to memory.

Updates golang/go#40371
Fixes golang/go#40159

Change-Id: I36941adde9c6b186d95b5792051854ac3d1a2ac8
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/260779
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Trust: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/cmd/frontend/main.go b/cmd/frontend/main.go
index 89468e4..8d30fa7 100644
--- a/cmd/frontend/main.go
+++ b/cmd/frontend/main.go
@@ -10,6 +10,7 @@
 	"flag"
 	"net/http"
 	"os"
+	"path/filepath"
 	"time"
 
 	"cloud.google.com/go/profiler"
@@ -23,6 +24,7 @@
 	"golang.org/x/pkgsite/internal/dcensus"
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/frontend"
+	"golang.org/x/pkgsite/internal/localdatasource"
 	"golang.org/x/pkgsite/internal/log"
 	"golang.org/x/pkgsite/internal/middleware"
 	"golang.org/x/pkgsite/internal/postgres"
@@ -43,6 +45,8 @@
 		"for direct proxy mode and frontend fetches")
 	directProxy = flag.Bool("direct_proxy", false, "if set to true, uses the module proxy referred to by this URL "+
 		"as a direct backend, bypassing the database")
+	localPaths         = flag.String("local", "", "run locally, accepts a GOPATH-like collection of local paths for modules to load to memory")
+	gopathMode         = flag.Bool("gopath_mode", false, "assume that local modules' paths are relative to GOPATH/src, used only with -local")
 	bypassLicenseCheck = flag.Bool("bypass_license_check", false, "display all information, even for non-redistributable paths")
 )
 
@@ -66,52 +70,76 @@
 		dsg        func(context.Context) internal.DataSource
 		fetchQueue queue.Queue
 	)
-	proxyClient, err := proxy.New(*proxyURL)
-	if err != nil {
-		log.Fatal(ctx, err)
-	}
 	if *bypassLicenseCheck {
 		log.Info(ctx, "BYPASSING LICENSE CHECKING: DISPLAYING NON-REDISTRIBUTABLE INFORMATION")
 	}
+
 	expg := cmdconfig.ExperimentGetter(ctx, cfg)
-	if *directProxy {
-		var pds *proxydatasource.DataSource
-		if *bypassLicenseCheck {
-			pds = proxydatasource.NewBypassingLicenseCheck(proxyClient)
+	if *localPaths != "" {
+		lds := localdatasource.New()
+		paths := filepath.SplitList(*localPaths)
+		if *gopathMode {
+			for _, path := range paths {
+				err := lds.LoadFromGOPATH(ctx, path)
+				if err != nil {
+					log.Error(ctx, err)
+				}
+			}
 		} else {
-			pds = proxydatasource.New(proxyClient)
+			for _, path := range paths {
+				err := lds.Load(ctx, path)
+				if err != nil {
+					log.Error(ctx, err)
+				}
+			}
 		}
-		dsg = func(context.Context) internal.DataSource { return pds }
+		dsg = func(context.Context) internal.DataSource { return lds }
 	} else {
-		// Wrap the postgres driver with OpenCensus instrumentation.
-		ocDriver, err := ocsql.Register("postgres", ocsql.WithAllTraceOptions())
-		if err != nil {
-			log.Fatalf(ctx, "unable to register the ocsql driver: %v\n", err)
-		}
-		ddb, err := openDB(ctx, cfg, ocDriver)
+		proxyClient, err := proxy.New(*proxyURL)
 		if err != nil {
 			log.Fatal(ctx, err)
 		}
-		var db *postgres.DB
-		if *bypassLicenseCheck {
-			db = postgres.NewBypassingLicenseCheck(ddb)
+
+		if *directProxy {
+			var pds *proxydatasource.DataSource
+			if *bypassLicenseCheck {
+				pds = proxydatasource.NewBypassingLicenseCheck(proxyClient)
+			} else {
+				pds = proxydatasource.New(proxyClient)
+			}
+			dsg = func(context.Context) internal.DataSource { return pds }
 		} else {
-			db = postgres.New(ddb)
-		}
-		defer db.Close()
-		dsg = func(context.Context) internal.DataSource { return db }
-		sourceClient := source.NewClient(config.SourceTimeout)
-		// The closure passed to queue.New is only used for testing and local
-		// execution, not in production. So it's okay that it doesn't use a
-		// per-request connection.
-		fetchQueue, err = queue.New(ctx, cfg, queueName, *workers, expg,
-			func(ctx context.Context, modulePath, version string) (int, error) {
-				return frontend.FetchAndUpdateState(ctx, modulePath, version, proxyClient, sourceClient, db)
-			})
-		if err != nil {
-			log.Fatalf(ctx, "queue.New: %v", err)
+			// Wrap the postgres driver with OpenCensus instrumentation.
+			ocDriver, err := ocsql.Register("postgres", ocsql.WithAllTraceOptions())
+			if err != nil {
+				log.Fatalf(ctx, "unable to register the ocsql driver: %v\n", err)
+			}
+			ddb, err := openDB(ctx, cfg, ocDriver)
+			if err != nil {
+				log.Fatal(ctx, err)
+			}
+			var db *postgres.DB
+			if *bypassLicenseCheck {
+				db = postgres.NewBypassingLicenseCheck(ddb)
+			} else {
+				db = postgres.New(ddb)
+			}
+			defer db.Close()
+			dsg = func(context.Context) internal.DataSource { return db }
+			sourceClient := source.NewClient(config.SourceTimeout)
+			// The closure passed to queue.New is only used for testing and local
+			// execution, not in production. So it's okay that it doesn't use a
+			// per-request connection.
+			fetchQueue, err = queue.New(ctx, cfg, queueName, *workers, expg,
+				func(ctx context.Context, modulePath, version string) (int, error) {
+					return frontend.FetchAndUpdateState(ctx, modulePath, version, proxyClient, sourceClient, db)
+				})
+			if err != nil {
+				log.Fatalf(ctx, "queue.New: %v", err)
+			}
 		}
 	}
+
 	var haClient *redis.Client
 	if cfg.RedisHAHost != "" {
 		haClient = redis.NewClient(&redis.Options{