1
0
Fork 0
mirror of https://github.com/imjasonh/terraform-playground synced 2026-07-08 07:44:57 +00:00
terraform-playground/litestream/main.go
Jason Hall 0983014992 clean up logging
Signed-off-by: Jason Hall <jason@chainguard.dev>
2026-02-05 21:55:20 -05:00

178 lines
4.7 KiB
Go

package main
import (
"context"
"database/sql"
"flag"
"fmt"
"html/template"
"net/http"
"os"
"time"
"cloud.google.com/go/compute/metadata"
"github.com/benbjohnson/litestream"
_ "github.com/benbjohnson/litestream/gs" // register "gs" replica client factory
"github.com/chainguard-dev/clog"
_ "github.com/chainguard-dev/clog/gcp/init"
_ "github.com/mattn/go-sqlite3"
"github.com/psanford/sqlite3vfs"
)
var dbfile = flag.String("file", "db.sqlite", "path to database file")
func main() {
flag.Parse()
dbfile := *dbfile
dsn := dbfile
log := clog.FromContext(context.Background())
if metadata.OnGCE() {
replicaURL := os.Getenv("LITESTREAM_REPLICA_URL")
if replicaURL == "" {
clog.Fatal("LITESTREAM_REPLICA_URL environment variable is required on GCE")
}
client, err := litestream.NewReplicaClientFromURL(replicaURL)
if err != nil {
clog.Fatalf("failed to create replica client: %v", err)
}
if err := client.Init(context.Background()); err != nil {
clog.Fatalf("failed to init replica client: %v", err)
}
vfs := litestream.NewVFS(client, &log.Logger)
vfs.PollInterval = 1 * time.Second
vfs.WriteEnabled = true
vfs.WriteSyncInterval = 1 * time.Second
if bufPath := os.Getenv("LITESTREAM_BUFFER_PATH"); bufPath != "" {
vfs.WriteBufferPath = bufPath
}
clog.Infof("registering litestream VFS...")
if err := sqlite3vfs.RegisterVFS("litestream", vfs); err != nil {
clog.Fatalf("failed to register VFS: %v", err)
}
clog.Infof("litestream VFS registered")
dsn = "file:db?vfs=litestream"
}
db, err := sql.Open("sqlite3", dsn)
if err != nil {
clog.Fatalf("failed to open database: %v", err)
}
defer db.Close()
if _, err := db.Exec("create table if not exists test3 (time integer primary key)"); err != nil {
clog.Fatalf("failed to create table: %v", err)
}
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "no favicon", http.StatusNotFound) })
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
data, err := getData(db, true)
if err != nil {
clog.Fatalf("failed to get data: %v", err)
}
if err := page.Execute(w, data); err != nil {
clog.Fatalf("failed to execute template: %v", err)
}
})
http.HandleFunc("/click", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
data, err := getData(db, false)
if err != nil {
clog.Fatalf("failed to get data: %v", err)
}
if err := div.Execute(w, data); err != nil {
clog.Fatalf("failed to execute template: %v", err)
}
})
clog.Fatal("ListenAndServe", "err", http.ListenAndServe(":8080", nil))
}
func getData(db *sql.DB, getVersion bool) (data, error) {
var version string
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 (cast(unixepoch('now','subsec') * 1000 as integer))"); err != nil {
return data{}, fmt.Errorf("failed to insert row: %w", err)
}
rows, err := db.Query("select count(*) from test3")
if err != nil {
return data{}, fmt.Errorf("failed to query rows: %w", err)
}
defer rows.Close()
for rows.Next() {
var count int
if err := rows.Scan(&count); err != nil {
return data{}, fmt.Errorf("failed to scan row: %w", err)
}
return data{Version: version, Count: count}, nil
}
return data{}, fmt.Errorf("failed to get count")
}
type data struct {
Version string
Count int
}
var (
page = template.Must(template.New("").Parse(`<!DOCTYPE html>
<html>
<head>
<title>litestream</title>
<script src="https://unpkg.com/htmx.org@2.0.0" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw" crossorigin="anonymous"></script>
<style>
body {
font-family: Arial, sans-serif;
}
@keyframes highlight {
0% { background: yellow; }
100% { background: none; }
}
.highlight {
animation: highlight 1s;
}
div#data {
width: 200px;
}
</style>
</head>
<body>
<h1>✨ litestream ✨</h1>
<p>sqlite version: {{.Version}}</p>
<div id="data" hx-swap="outerHTML">
<p>count: {{.Count}}</p>
<button hx-post="/click" hx-trigger="click" hx-target="#data">Click to increment</button>
</div>
</body>
</html>
`))
div = template.Must(template.New("").Parse(`
<div id="data" hx-swap="outerHTML">
<p class="highlight">count: {{.Count}}</p>
<button hx-post="/click" hx-trigger="click" hx-target="#data">Click to increment</button>
</div>
`))
)