From a10a604018d0cb07babfe218d9fb2e00e1c8ae3b Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sat, 7 May 2022 13:17:18 -0600 Subject: [PATCH] Refactor how data dir is initialized --- srv/src/cfg/data_dir.go | 74 +++++++++++++++++++++++++++++ srv/src/cfg/mediocre_blog.go | 9 ++++ srv/src/cmd/mailinglist-cli/main.go | 16 +++---- srv/src/cmd/mediocre-blog/main.go | 16 +++---- srv/src/mailinglist/store.go | 14 ++++-- srv/src/mailinglist/store_test.go | 23 ++++----- srv/src/post/store.go | 11 +++-- srv/src/post/store_test.go | 22 +++------ 8 files changed, 127 insertions(+), 58 deletions(-) create mode 100644 srv/src/cfg/data_dir.go create mode 100644 srv/src/cfg/mediocre_blog.go diff --git a/srv/src/cfg/data_dir.go b/srv/src/cfg/data_dir.go new file mode 100644 index 0000000..649bc15 --- /dev/null +++ b/srv/src/cfg/data_dir.go @@ -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 +} diff --git a/srv/src/cfg/mediocre_blog.go b/srv/src/cfg/mediocre_blog.go new file mode 100644 index 0000000..98fe08c --- /dev/null +++ b/srv/src/cfg/mediocre_blog.go @@ -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) +} diff --git a/srv/src/cmd/mailinglist-cli/main.go b/srv/src/cmd/mailinglist-cli/main.go index c3207df..c090f41 100644 --- a/srv/src/cmd/mailinglist-cli/main.go +++ b/srv/src/cmd/mailinglist-cli/main.go @@ -4,9 +4,9 @@ import ( "context" "errors" "io" - "path" "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/mediocre-go-lib/v2/mctx" "github.com/mediocregopher/mediocre-go-lib/v2/mlog" @@ -17,11 +17,12 @@ func main() { ctx := context.Background() - cfg := cfg.New(cfg.Params{ - EnvPrefix: "MEDIOCRE_BLOG", - }) + cfg := cfgpkg.NewBlogCfg(cfg.Params{}) - 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 mailerParams.SetupCfg(cfg) @@ -54,10 +55,7 @@ func main() { mailer = mailinglist.NewMailer(mailerParams) } - mailingListDBFile := path.Join(*dataDir, "mailinglist.sqlite3") - ctx = mctx.Annotate(ctx, "mailingListDBFile", mailingListDBFile) - - mlStore, err := mailinglist.NewStore(mailingListDBFile) + mlStore, err := mailinglist.NewStore(dataDir) if err != nil { logger.Fatal(ctx, "initializing mailing list storage", err) } diff --git a/srv/src/cmd/mediocre-blog/main.go b/srv/src/cmd/mediocre-blog/main.go index 4cf3024..58a43e9 100644 --- a/srv/src/cmd/mediocre-blog/main.go +++ b/srv/src/cmd/mediocre-blog/main.go @@ -4,12 +4,12 @@ import ( "context" "os" "os/signal" - "path" "syscall" "time" "github.com/mediocregopher/blog.mediocregopher.com/srv/api" "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/mailinglist" "github.com/mediocregopher/blog.mediocregopher.com/srv/pow" @@ -23,11 +23,12 @@ func main() { ctx := context.Background() - cfg := cfg.New(cfg.Params{ - EnvPrefix: "MEDIOCRE_BLOG", - }) + cfg := cfg.NewBlogCfg(cfg.Params{}) - 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 powMgrParams.SetupCfg(cfg) @@ -91,10 +92,7 @@ func main() { mailer = mailinglist.NewMailer(mailerParams) } - mailingListDBFile := path.Join(*dataDir, "mailinglist.sqlite3") - ctx = mctx.Annotate(ctx, "mailingListDBFile", mailingListDBFile) - - mlStore, err := mailinglist.NewStore(mailingListDBFile) + mlStore, err := mailinglist.NewStore(dataDir) if err != nil { logger.Fatal(ctx, "initializing mailing list storage", err) } diff --git a/srv/src/mailinglist/store.go b/srv/src/mailinglist/store.go index a7a210f..49e7617 100644 --- a/srv/src/mailinglist/store.go +++ b/srv/src/mailinglist/store.go @@ -7,10 +7,12 @@ import ( "errors" "fmt" "io" + "path" "strings" "time" _ "github.com/mattn/go-sqlite3" + "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" migrate "github.com/rubenv/sql-migrate" ) @@ -83,13 +85,15 @@ type store struct { db *sql.DB } -// NewStore initializes a new Store using a sqlite3 database at the given file -// path. -func NewStore(dbFile string) (Store, error) { +// NewStore initializes a new Store using a sqlite3 database in the given +// DataDir. +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 { - 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} diff --git a/srv/src/mailinglist/store_test.go b/srv/src/mailinglist/store_test.go index 25eb150..9093d90 100644 --- a/srv/src/mailinglist/store_test.go +++ b/srv/src/mailinglist/store_test.go @@ -2,31 +2,24 @@ package mailinglist import ( "io" - "io/ioutil" - "os" "testing" "time" + "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" "github.com/stretchr/testify/assert" ) func TestStore(t *testing.T) { - tmpFile, err := ioutil.TempFile(os.TempDir(), "mediocre-blog-mailinglist-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) + var dataDir cfg.DataDir - t.Cleanup(func() { - if err := os.Remove(tmpFilePath); err != nil { - panic(err) - } - }) + if err := dataDir.Init(); err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { dataDir.Close() }) - store, err := NewStore(tmpFilePath) + store, err := NewStore(dataDir) assert.NoError(t, err) t.Cleanup(func() { diff --git a/srv/src/post/store.go b/srv/src/post/store.go index 3f044e2..6bdccc2 100644 --- a/srv/src/post/store.go +++ b/srv/src/post/store.go @@ -9,6 +9,7 @@ import ( "time" _ "github.com/mattn/go-sqlite3" // we need dis + "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" 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 // unless otherwise noted. type StoreParams struct { - - // Path to the file the database will be stored at. - DBFilePath string + DataDir cfg.DataDir } type store struct { @@ -113,9 +112,11 @@ type store struct { // path. 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 { - 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} diff --git a/srv/src/post/store_test.go b/srv/src/post/store_test.go index 9b0ee45..1b4fb30 100644 --- a/srv/src/post/store_test.go +++ b/srv/src/post/store_test.go @@ -1,13 +1,12 @@ package post import ( - "io/ioutil" - "os" "sort" "strconv" "testing" "time" + "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" "github.com/stretchr/testify/assert" "github.com/tilinna/clock" ) @@ -29,25 +28,18 @@ type storeTestHarness struct { func newStoreTestHarness(t *testing.T) storeTestHarness { - clock := clock.NewMock(time.Now().UTC().Truncate(1 * time.Hour)) + var dataDir cfg.DataDir - tmpFile, err := ioutil.TempFile(os.TempDir(), "mediocre-blog-post-store-test-") - if err != nil { - 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() { - if err := os.Remove(tmpFilePath); err != nil { - panic(err) - } - }) + clock := clock.NewMock(time.Now().UTC().Truncate(1 * time.Hour)) store, err := NewStore(StoreParams{ - DBFilePath: tmpFilePath, + DataDir: dataDir, }) assert.NoError(t, err)