cmd/relui: load development state on boot
When relui starts, it will look in dev-data-directory for data
persisted from the last boot. This enables local development to continue
when testing changes manually, building on CL 246298.
For golang/go#40279
Change-Id: I02f8b6e1178f82425cafcd2a0544327ba84e028e
Reviewed-on: https://go-review.googlesource.com/c/build/+/250917
Run-TryBot: Alexander Rakoczy <alex@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/relui/main.go b/cmd/relui/main.go
index 21c52db..4ba50cb 100644
--- a/cmd/relui/main.go
+++ b/cmd/relui/main.go
@@ -23,7 +23,11 @@
func main() {
flag.Parse()
- s := &server{store: newFileStore(*devDataDir), configs: loadWorkflowConfig("./workflows")}
+ fs := newFileStore(*devDataDir)
+ if err := fs.load(); err != nil {
+ log.Fatalf("Error loading state from %q: %v", *devDataDir, err)
+ }
+ s := &server{store: fs, configs: loadWorkflowConfig("./workflows")}
http.Handle("/workflows/create", http.HandlerFunc(s.createWorkflowHandler))
http.Handle("/workflows/new", http.HandlerFunc(s.newWorkflowHandler))
http.Handle("/", fileServerHandler(relativeFile("./static"), http.HandlerFunc(s.homeHandler)))
@@ -31,7 +35,6 @@
if port == "" {
port = "8080"
}
-
log.Printf("Listening on :" + port)
log.Fatal(http.ListenAndServe(":"+port, http.DefaultServeMux))
}
diff --git a/cmd/relui/store.go b/cmd/relui/store.go
index a861058..acb2a4c 100644
--- a/cmd/relui/store.go
+++ b/cmd/relui/store.go
@@ -42,6 +42,7 @@
ls *reluipb.LocalStorage
// persistDir is a path to a directory for saving application data in textproto format.
+ // Set persistDir to an empty string to disable saving and loading from the filesystem.
persistDir string
}
@@ -83,3 +84,21 @@
}
return nil
}
+
+// load reads fileStore state from persistDir/fileStoreName.
+func (f *fileStore) load() error {
+ if f.persistDir == "" {
+ return nil
+ }
+ path := filepath.Join(f.persistDir, fileStoreName)
+ b, err := ioutil.ReadFile(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return fmt.Errorf("ioutil.ReadFile(%q) = _, %v", path, err)
+ }
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ return proto.UnmarshalText(string(b), f.ls)
+}
diff --git a/cmd/relui/store_test.go b/cmd/relui/store_test.go
index 79843db..db5fcd3 100644
--- a/cmd/relui/store_test.go
+++ b/cmd/relui/store_test.go
@@ -16,9 +16,9 @@
)
func TestFileStorePersist(t *testing.T) {
- dir, err := ioutil.TempDir("", "memory-store-test")
+ dir, err := ioutil.TempDir("", "fileStore-test")
if err != nil {
- t.Fatalf("ioutil.TempDir(%q, %q) = _, %v", "", "memory-store-test", err)
+ t.Fatalf("ioutil.TempDir(%q, %q) = _, %v", "", "fileStore-test", err)
}
defer os.RemoveAll(dir)
want := &reluipb.LocalStorage{
@@ -50,3 +50,95 @@
t.Errorf("reluipb.LocalStorage mismatch (-want, +got):\n%s", diff)
}
}
+
+func TestFileStoreLoad(t *testing.T) {
+ dir, err := ioutil.TempDir("", "fileStore-test")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir(%q, %q) = _, %v", "", "fileStore-test", err)
+ }
+ defer os.RemoveAll(dir)
+ if err := os.MkdirAll(filepath.Join(dir, "relui"), 0755); err != nil {
+ t.Errorf("os.MkDirAll(%q, %v) = %w", filepath.Join(dir, "relui"), 0755, err)
+ }
+ want := &reluipb.LocalStorage{
+ Workflows: []*reluipb.Workflow{
+ {
+ Name: "Load Test",
+ BuildableTasks: []*reluipb.BuildableTask{{Name: "Load Test Task"}},
+ },
+ },
+ }
+ data := []byte(proto.MarshalTextString(want))
+ dst := filepath.Join(dir, "relui", fileStoreName)
+ if err := ioutil.WriteFile(dst, data, 0644); err != nil {
+ t.Fatalf("ioutil.WriteFile(%q, _, %v) = %v", dst, 0644, err)
+ }
+
+ fs := newFileStore(filepath.Join(dir, "relui"))
+ if err := fs.load(); err != nil {
+ t.Errorf("reluipb.load() = %v, wanted no error", err)
+ }
+
+ if diff := cmp.Diff(want, fs.localStorage()); diff != "" {
+ t.Errorf("reluipb.LocalStorage mismatch (-want, +got):\n%s", diff)
+ }
+}
+
+func TestFileStoreLoadErrors(t *testing.T) {
+ empty, err := ioutil.TempDir("", "fileStoreLoad")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir(%q, %q) = %v, wanted no error", "", "fileStoreLoad", err)
+ }
+ defer os.RemoveAll(empty)
+
+ collision, err := ioutil.TempDir("", "fileStoreLoad")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir(%q, %q) = %v, wanted no error", "", "fileStoreLoad", err)
+ }
+ defer os.RemoveAll(collision)
+ // We want to trigger an error when trying to read the file, so make a directory with the same name.
+ if err := os.MkdirAll(filepath.Join(collision, fileStoreName), 0755); err != nil {
+ t.Errorf("os.MkDirAll(%q, %v) = %w", filepath.Join(collision, fileStoreName), 0755, err)
+ }
+
+ corrupt, err := ioutil.TempDir("", "fileStoreLoad")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir(%q, %q) = %v, wanted no error", "", "fileStoreLoad", err)
+ }
+ defer os.RemoveAll(corrupt)
+ if err := ioutil.WriteFile(filepath.Join(corrupt, fileStoreName), []byte("oh no"), 0644); err != nil {
+ t.Fatalf("ioutil.WriteFile(%q, %q, %v) = %v, wanted no error", filepath.Join(corrupt, fileStoreName), "oh no", 0644, err)
+ }
+
+ cases := []struct {
+ desc string
+ dir string
+ wantErr bool
+ }{
+ {
+ desc: "no persistDir configured",
+ },
+ {
+ desc: "no file in persistDir",
+ dir: empty,
+ },
+ {
+ desc: "other error reading file",
+ dir: collision,
+ wantErr: true,
+ },
+ {
+ desc: "corrupt data in persistDir",
+ dir: corrupt,
+ wantErr: true,
+ },
+ }
+ for _, c := range cases {
+ t.Run(c.desc, func(t *testing.T) {
+ f := newFileStore(c.dir)
+ if err := f.load(); (err != nil) != c.wantErr {
+ t.Errorf("f.load() = %v, wantErr = %t", err, c.wantErr)
+ }
+ })
+ }
+}