Refactor how data dir is initialized

This commit is contained in:
Brian Picciano 2022-05-07 13:17:18 -06:00
parent c99b37c5b3
commit a10a604018
8 changed files with 130 additions and 61 deletions

74
srv/src/cfg/data_dir.go Normal file
View File

@ -0,0 +1,74 @@
package cfg
import (
"context"
"fmt"
"os"
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
)
// DataDir manages the blog's data directory.
type DataDir struct {
Path string
deleteOnClose bool
}
// Init initializes the data directory, creating the directory named at path if
// it doesn't exist.
//
// If Path is not set, then a temporary directory will be created and its path
// set to the Path field. This directory will be removed when Close is called.
func (d *DataDir) Init() error {
if d.Path == "" {
d.deleteOnClose = true
var err error
if d.Path, err = os.MkdirTemp("", "mediocre-blog-data-*"); err != nil {
return fmt.Errorf("creating temporary directory: %w", err)
}
return nil
}
if err := os.MkdirAll(d.Path, 0700); err != nil {
return fmt.Errorf(
"creating directory (and parents) of %q: %w",
d.Path,
err,
)
}
return nil
}
// SetupCfg implement the cfg.Cfger interface.
func (d *DataDir) SetupCfg(cfg *Cfg) {
cfg.StringVar(&d.Path, "data-dir", "", "Directory to use for persistent storage. If unset a temp directory will be created, and will be deleted when the process exits.")
cfg.OnInit(func(ctx context.Context) error {
return d.Init()
})
}
// Annotate implements mctx.Annotator interface.
func (d *DataDir) Annotate(a mctx.Annotations) {
a["dataDirPath"] = d.Path
}
// Close cleans up any temporary state created by DataDir.
func (d *DataDir) Close() error {
if !d.deleteOnClose {
return nil
}
if err := os.RemoveAll(d.Path); err != nil {
return fmt.Errorf("removing temp dir %q: %w", d.Path, err)
}
return nil
}

View File

@ -0,0 +1,9 @@
package cfg
// this file contains functionality specific to the mediocre blog.
// NewBlogCfg returns a Cfg specifically configured for mediocre blog processes.
func NewBlogCfg(params Params) *Cfg {
params.EnvPrefix = "MEDIOCRE_BLOG"
return New(params)
}

View File

@ -4,9 +4,9 @@ import (
"context" "context"
"errors" "errors"
"io" "io"
"path"
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
cfgpkg "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
"github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist" "github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist"
"github.com/mediocregopher/mediocre-go-lib/v2/mctx" "github.com/mediocregopher/mediocre-go-lib/v2/mctx"
"github.com/mediocregopher/mediocre-go-lib/v2/mlog" "github.com/mediocregopher/mediocre-go-lib/v2/mlog"
@ -17,11 +17,12 @@ func main() {
ctx := context.Background() ctx := context.Background()
cfg := cfg.New(cfg.Params{ cfg := cfgpkg.NewBlogCfg(cfg.Params{})
EnvPrefix: "MEDIOCRE_BLOG",
})
dataDir := cfg.String("data-dir", ".", "Directory to use for long term storage") var dataDir cfgpkg.DataDir
dataDir.SetupCfg(cfg)
defer dataDir.Close()
ctx = mctx.WithAnnotator(ctx, &dataDir)
var mailerParams mailinglist.MailerParams var mailerParams mailinglist.MailerParams
mailerParams.SetupCfg(cfg) mailerParams.SetupCfg(cfg)
@ -54,10 +55,7 @@ func main() {
mailer = mailinglist.NewMailer(mailerParams) mailer = mailinglist.NewMailer(mailerParams)
} }
mailingListDBFile := path.Join(*dataDir, "mailinglist.sqlite3") mlStore, err := mailinglist.NewStore(dataDir)
ctx = mctx.Annotate(ctx, "mailingListDBFile", mailingListDBFile)
mlStore, err := mailinglist.NewStore(mailingListDBFile)
if err != nil { if err != nil {
logger.Fatal(ctx, "initializing mailing list storage", err) logger.Fatal(ctx, "initializing mailing list storage", err)
} }

View File

@ -4,12 +4,12 @@ import (
"context" "context"
"os" "os"
"os/signal" "os/signal"
"path"
"syscall" "syscall"
"time" "time"
"github.com/mediocregopher/blog.mediocregopher.com/srv/api" "github.com/mediocregopher/blog.mediocregopher.com/srv/api"
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
cfgpkg "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
"github.com/mediocregopher/blog.mediocregopher.com/srv/chat" "github.com/mediocregopher/blog.mediocregopher.com/srv/chat"
"github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist" "github.com/mediocregopher/blog.mediocregopher.com/srv/mailinglist"
"github.com/mediocregopher/blog.mediocregopher.com/srv/pow" "github.com/mediocregopher/blog.mediocregopher.com/srv/pow"
@ -23,11 +23,12 @@ func main() {
ctx := context.Background() ctx := context.Background()
cfg := cfg.New(cfg.Params{ cfg := cfg.NewBlogCfg(cfg.Params{})
EnvPrefix: "MEDIOCRE_BLOG",
})
dataDir := cfg.String("data-dir", ".", "Directory to use for long term storage") var dataDir cfgpkg.DataDir
dataDir.SetupCfg(cfg)
defer dataDir.Close()
ctx = mctx.WithAnnotator(ctx, &dataDir)
var powMgrParams pow.ManagerParams var powMgrParams pow.ManagerParams
powMgrParams.SetupCfg(cfg) powMgrParams.SetupCfg(cfg)
@ -91,10 +92,7 @@ func main() {
mailer = mailinglist.NewMailer(mailerParams) mailer = mailinglist.NewMailer(mailerParams)
} }
mailingListDBFile := path.Join(*dataDir, "mailinglist.sqlite3") mlStore, err := mailinglist.NewStore(dataDir)
ctx = mctx.Annotate(ctx, "mailingListDBFile", mailingListDBFile)
mlStore, err := mailinglist.NewStore(mailingListDBFile)
if err != nil { if err != nil {
logger.Fatal(ctx, "initializing mailing list storage", err) logger.Fatal(ctx, "initializing mailing list storage", err)
} }

View File

@ -7,10 +7,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"path"
"strings" "strings"
"time" "time"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
migrate "github.com/rubenv/sql-migrate" migrate "github.com/rubenv/sql-migrate"
) )
@ -83,13 +85,15 @@ type store struct {
db *sql.DB db *sql.DB
} }
// NewStore initializes a new Store using a sqlite3 database at the given file // NewStore initializes a new Store using a sqlite3 database in the given
// path. // DataDir.
func NewStore(dbFile string) (Store, error) { func NewStore(dataDir cfg.DataDir) (Store, error) {
db, err := sql.Open("sqlite3", dbFile) path := path.Join(dataDir.Path, "mailinglist.sqlite3")
db, err := sql.Open("sqlite3", path)
if err != nil { if err != nil {
return nil, fmt.Errorf("opening sqlite file: %w", err) return nil, fmt.Errorf("opening sqlite file at %q: %w", path, err)
} }
migrations := &migrate.MemoryMigrationSource{Migrations: migrations} migrations := &migrate.MemoryMigrationSource{Migrations: migrations}

View File

@ -2,31 +2,24 @@ package mailinglist
import ( import (
"io" "io"
"io/ioutil"
"os"
"testing" "testing"
"time" "time"
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestStore(t *testing.T) { func TestStore(t *testing.T) {
tmpFile, err := ioutil.TempFile(os.TempDir(), "mediocre-blog-mailinglist-store-test-")
if err != nil { var dataDir cfg.DataDir
t.Fatal("Cannot create temporary file", err)
if err := dataDir.Init(); err != nil {
t.Fatal(err)
} }
tmpFilePath := tmpFile.Name()
tmpFile.Close()
t.Logf("using temporary sqlite file at %q", tmpFilePath) t.Cleanup(func() { dataDir.Close() })
t.Cleanup(func() { store, err := NewStore(dataDir)
if err := os.Remove(tmpFilePath); err != nil {
panic(err)
}
})
store, err := NewStore(tmpFilePath)
assert.NoError(t, err) assert.NoError(t, err)
t.Cleanup(func() { t.Cleanup(func() {

View File

@ -9,6 +9,7 @@ import (
"time" "time"
_ "github.com/mattn/go-sqlite3" // we need dis _ "github.com/mattn/go-sqlite3" // we need dis
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
migrate "github.com/rubenv/sql-migrate" migrate "github.com/rubenv/sql-migrate"
) )
@ -99,9 +100,7 @@ var migrations = []*migrate.Migration{
// Params are parameters used to initialize a new Store. All fields are required // Params are parameters used to initialize a new Store. All fields are required
// unless otherwise noted. // unless otherwise noted.
type StoreParams struct { type StoreParams struct {
DataDir cfg.DataDir
// Path to the file the database will be stored at.
DBFilePath string
} }
type store struct { type store struct {
@ -113,9 +112,11 @@ type store struct {
// path. // path.
func NewStore(params StoreParams) (Store, error) { func NewStore(params StoreParams) (Store, error) {
db, err := sql.Open("sqlite3", params.DBFilePath) path := path.Join(params.DataDir.Path, "post.sqlite3")
db, err := sql.Open("sqlite3", path)
if err != nil { if err != nil {
return nil, fmt.Errorf("opening sqlite file: %w", err) return nil, fmt.Errorf("opening sqlite file at %q: %w", path, err)
} }
migrations := &migrate.MemoryMigrationSource{Migrations: migrations} migrations := &migrate.MemoryMigrationSource{Migrations: migrations}

View File

@ -1,13 +1,12 @@
package post package post
import ( import (
"io/ioutil"
"os"
"sort" "sort"
"strconv" "strconv"
"testing" "testing"
"time" "time"
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tilinna/clock" "github.com/tilinna/clock"
) )
@ -29,25 +28,18 @@ type storeTestHarness struct {
func newStoreTestHarness(t *testing.T) storeTestHarness { func newStoreTestHarness(t *testing.T) storeTestHarness {
var dataDir cfg.DataDir
if err := dataDir.Init(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { dataDir.Close() })
clock := clock.NewMock(time.Now().UTC().Truncate(1 * time.Hour)) clock := clock.NewMock(time.Now().UTC().Truncate(1 * time.Hour))
tmpFile, err := ioutil.TempFile(os.TempDir(), "mediocre-blog-post-store-test-")
if err != nil {
t.Fatal("Cannot create temporary file", err)
}
tmpFilePath := tmpFile.Name()
tmpFile.Close()
t.Logf("using temporary sqlite file at %q", tmpFilePath)
t.Cleanup(func() {
if err := os.Remove(tmpFilePath); err != nil {
panic(err)
}
})
store, err := NewStore(StoreParams{ store, err := NewStore(StoreParams{
DBFilePath: tmpFilePath, DataDir: dataDir,
}) })
assert.NoError(t, err) assert.NoError(t, err)