diff --git a/litestream/README.md b/litestream/README.md index 1cab475..440797d 100644 --- a/litestream/README.md +++ b/litestream/README.md @@ -50,3 +50,7 @@ These costs are shown in monthly units, but are billed per 100 milliseconds of a Since it's just SQLite, this also supports standard SQL operations and semantics. It can be trivially tested locally without access to a cloud instance, or moved to other Litestream-compatible backends like S3. There are also potentially tenancy benefits to using a separate database for each user, which can be easily achieved with multiple SQLite instances. + +### Running locally + +Since the database is just SQLite, it's very easy to run this locally. Simply `go run ./` and the service will create `db.sqlite` in the current directory. You can browse to http://localhost:8080/ to see the service running, and interact with the database using standard tools. diff --git a/litestream/go.mod b/litestream/go.mod index e7556ad..0028f80 100644 --- a/litestream/go.mod +++ b/litestream/go.mod @@ -3,6 +3,7 @@ module github.com/imjasonh/terraform-playground/litestream go 1.21.5 require ( + cloud.google.com/go/compute/metadata v0.3.0 github.com/chainguard-dev/clog v1.3.1 github.com/glebarez/go-sqlite v1.22.0 ) diff --git a/litestream/go.sum b/litestream/go.sum index 3591dc4..c0467ab 100644 --- a/litestream/go.sum +++ b/litestream/go.sum @@ -1,3 +1,5 @@ +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/chainguard-dev/clog v1.3.1 h1:CDNCty5WKQhJzoOPubk0GdXt+bPQyargmfClqebrpaQ= github.com/chainguard-dev/clog v1.3.1/go.mod h1:cV516KZWqYc/phZsCNwF36u/KMGS+Gj5Uqeb8Hlp95Y= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= diff --git a/litestream/main.go b/litestream/main.go index c48ef58..6d0a0ae 100644 --- a/litestream/main.go +++ b/litestream/main.go @@ -2,7 +2,6 @@ package main import ( "database/sql" - "errors" "flag" "fmt" "html/template" @@ -12,44 +11,51 @@ import ( "os/exec" "time" + "cloud.google.com/go/compute/metadata" "github.com/chainguard-dev/clog" _ "github.com/chainguard-dev/clog/gcp/init" _ "github.com/glebarez/go-sqlite" ) -var dbfile = flag.String("file", "/data/db.sqlite", "path to database file") +var dbfile = flag.String("file", "db.sqlite", "path to database file") func main() { flag.Parse() - if _, err := os.Stat(*dbfile); errors.Is(err, os.ErrNotExist) { - if os.Getenv("BUCKET") != "" { - // Before serving requests, restore the database from the latest replica. - start := time.Now() - cmd := exec.Command("litestream", "restore", - "-o", *dbfile, - fmt.Sprintf("gcs://%s/litestream", os.Getenv("BUCKET"))) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - clog.Fatalf("failed to restore database: %v", err) - } - clog.Infof("restoring database took %s", time.Since(start)) - } else { - clog.Infof("creating database file: %s", *dbfile) - if _, err := os.Create(*dbfile); err != nil { + dbfile := *dbfile + + if metadata.OnGCE() { + if os.Getenv("BUCKET") == "" { + clog.Fatal("BUCKET environment variable is required on GCE") + } + dbfile = "/data/" + dbfile + // Before serving requests, restore the database from the latest replica. + start := time.Now() + cmd := exec.Command("litestream", "restore", + "-o", dbfile, + fmt.Sprintf("gcs://%s/litestream", os.Getenv("BUCKET"))) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + clog.Fatalf("failed to restore database: %v", err) + } + clog.Infof("restoring database took %s", time.Since(start)) + } else { + if _, err := os.Stat(dbfile); os.IsNotExist(err) { + clog.Infof("creating database file: %s", dbfile) + if _, err := os.Create(dbfile); err != nil { clog.Fatalf("failed to create database file: %v", err) } } } - fi, err := os.Stat(*dbfile) + fi, err := os.Stat(dbfile) if err != nil { clog.Fatalf("failed to stat database: %v", err) } clog.Infof("database size: %d bytes", fi.Size()) - db, err := sql.Open("sqlite", *dbfile) + db, err := sql.Open("sqlite", dbfile) if err != nil { clog.Fatalf("failed to open database: %v", err) } @@ -65,7 +71,7 @@ func main() { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } - data, err := getData(db) + data, err := getData(db, true) if err != nil { clog.Fatalf("failed to get data: %v", err) } @@ -78,7 +84,7 @@ func main() { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } - data, err := getData(db) + data, err := getData(db, false) if err != nil { clog.Fatalf("failed to get data: %v", err) } @@ -89,11 +95,13 @@ func main() { log.Fatal(http.ListenAndServe(":8080", nil)) } -func getData(db *sql.DB) (data, error) { - row := db.QueryRow("select sqlite_version()") +func getData(db *sql.DB, getVersion bool) (data, error) { var version string - if err := row.Scan(&version); err != nil { - return data{}, fmt.Errorf("failed to query database version: %w", err) + if getVersion { + row := db.QueryRow("select sqlite_version()") + if err := row.Scan(&version); err != nil { + return data{}, fmt.Errorf("failed to query database version: %w", err) + } } if _, err := db.Exec("insert into test3 (time) values (unixepoch('now','subsec'))"); err != nil { @@ -128,8 +136,8 @@ var (
sqlite version: {{.Version}}
sqlite version: {{.Version}}
count: {{.Count}}
sqlite version: {{.Version}}
count: {{.Count}}