godev: create config struct and document local development process

Change-Id: I370490ec93934be34d9df29b6f3819fca4a93ed6
Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/499916
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Peter Weinberger <pjw@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
diff --git a/godev/cmd/telemetrygodev/README.md b/godev/cmd/telemetrygodev/README.md
index 78dad44..b792bac 100644
--- a/godev/cmd/telemetrygodev/README.md
+++ b/godev/cmd/telemetrygodev/README.md
@@ -4,7 +4,21 @@
 
 For local development, simply build and run. It serves on localhost:8080.
 
-    go run .
+    go run ./cmd/telemetrygodev
+
+By default, the server will use the filesystem for storage object I/O. Run the
+cloud storage emulator and use the -gcs flag to use the Cloud Storage API.
+
+    ./devtools/localstorage.sh
+    go run ./cmd/telemetrygodev --gcs
+
+### Environment Variables
+
+| Name                               | Default        | Description                                               |
+| ---------------------------------- | -------------- | --------------------------------------------------------- |
+| GO_TELEMETRY_PROJECT_ID            | go-telemetry   | GCP project ID                                            |
+| GO_TELEMETRY_STORAGE_EMULATOR_HOST | localhost:8081 | Host for the Cloud Storage emulator                       |
+| GO_TELEMETRY_LOCAL_STORAGE         | .localstorage  | Directory for storage emulator I/O or file system storage |
 
 ## Testing
 
diff --git a/godev/cmd/telemetrygodev/config.go b/godev/cmd/telemetrygodev/config.go
new file mode 100644
index 0000000..b92929c
--- /dev/null
+++ b/godev/cmd/telemetrygodev/config.go
@@ -0,0 +1,63 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"os"
+)
+
+type config struct {
+	// Port is the port your HTTP server should listen on.
+	Port string
+
+	// ProjectID is a GCP project ID.
+	ProjectID string
+
+	// StorageEmulatorHost is a network address for a Cloud Storage emulator.
+	StorageEmulatorHost string
+
+	// LocalStorage is a directory for storage I/O used when the using the filesystem
+	// or storage emulator modes.
+	LocalStorage string
+
+	// UploadBucket is the storage bucket for report uploads.
+	UploadBucket string
+
+	// UseGCS is true if the server should use the Cloud Storage API for reading and
+	// writing storage objects.
+	UseGCS bool
+
+	// DevMode is true if the server should read content files from the filesystem.
+	// If false, content files are read from the embed.FS in ../content.go.
+	DevMode bool
+}
+
+var (
+	devMode = flag.Bool("dev", false, "load static content and templates from the filesystem")
+	useGCS  = flag.Bool("gcs", false, "use Cloud Storage for reading and writing storage objects")
+)
+
+// newConfig returns a new config. Getting the config should follow a call to flag.Parse.
+func newConfig() *config {
+	// K_SERVICE is a Cloud Run environment variable.
+	service := env("K_SERVICE", "local-telemetry")
+	return &config{
+		Port:                env("PORT", "8080"),
+		ProjectID:           env("GO_TELEMETRY_PROJECT_ID", "go-telemetry"),
+		StorageEmulatorHost: env("GO_TELEMETRY_STORAGE_EMULATOR_HOST", "localhost:8081"),
+		LocalStorage:        env("GO_TELEMETRY_LOCAL_STORAGE", ".localstorage"),
+		UploadBucket:        service + "-uploads",
+		UseGCS:              *useGCS,
+		DevMode:             *devMode,
+	}
+}
+
+func env(key, fallback string) string {
+	if value, ok := os.LookupEnv(key); ok {
+		return value
+	}
+	return fallback
+}
diff --git a/godev/cmd/telemetrygodev/config_test.go b/godev/cmd/telemetrygodev/config_test.go
new file mode 100644
index 0000000..b26c7b7
--- /dev/null
+++ b/godev/cmd/telemetrygodev/config_test.go
@@ -0,0 +1,23 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestConfig(t *testing.T) {
+	want := &config{
+		Port:                "8080",
+		ProjectID:           "go-telemetry",
+		StorageEmulatorHost: "localhost:8081",
+		LocalStorage:        ".localstorage",
+		UploadBucket:        "local-telemetry-uploads",
+	}
+	if got := newConfig(); !reflect.DeepEqual(got, want) {
+		t.Errorf("Config() = %v, want %v", got, want)
+	}
+}
diff --git a/godev/cmd/telemetrygodev/main.go b/godev/cmd/telemetrygodev/main.go
index 684dfbf..7afad18 100644
--- a/godev/cmd/telemetrygodev/main.go
+++ b/godev/cmd/telemetrygodev/main.go
@@ -19,17 +19,13 @@
 	"golang.org/x/telemetry/godev/internal/unionfs"
 )
 
-var (
-	addr = flag.String("addr", ":8080", "server listens on the given TCP network address")
-	dev  = flag.Bool("dev", false, "load static content and templates from the filesystem")
-)
-
 func main() {
 	flag.Parse()
-	s := content.Server(fsys(*dev))
+	cfg := newConfig()
+	s := content.Server(fsys(cfg.DevMode))
 	mw := middleware.Default
-	fmt.Printf("server listening at http://%s\n", *addr)
-	log.Fatal(http.ListenAndServe(*addr, mw(s)))
+	fmt.Printf("server listening at http://localhost:%s\n", cfg.Port)
+	log.Fatal(http.ListenAndServe(":"+cfg.Port, mw(s)))
 }
 
 func fsys(fromOS bool) fs.FS {
diff --git a/godev/devtools/localstorage.sh b/godev/devtools/localstorage.sh
index fb21cc1..376faf8 100755
--- a/godev/devtools/localstorage.sh
+++ b/godev/devtools/localstorage.sh
@@ -32,7 +32,7 @@
 
 version=v0.0.0-20230523204811-eccb7d2267b0
 port=8081
-dir=.localstorage
+dir="${GO_TELEMETRY_LOCAL_STORAGE:-.localstorage}"
 if [ ! -z "$1" ]; then
   dir="$1"
 fi