Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions cmd/litestream/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2062,10 +2062,20 @@ func (c *ReplicaConfig) ReplicaType() string {

// DefaultConfigPath returns the default config path.
func DefaultConfigPath() string {
path, _ := resolveConfigPath("")
return path
}

// resolveConfigPath returns the config path to read for flagPath and reports
// whether it was explicitly selected by the flag or by LITESTREAM_CONFIG.
func resolveConfigPath(flagPath string) (path string, explicit bool) {
if flagPath != "" {
return flagPath, true
}
if v := os.Getenv("LITESTREAM_CONFIG"); v != "" {
return v
return v, true
}
return defaultConfigPath
return defaultConfigPath, false
}

func registerConfigFlag(fs *flag.FlagSet) (configPath *string, noExpandEnv *bool) {
Expand Down
33 changes: 17 additions & 16 deletions cmd/litestream/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"io/fs"
Expand Down Expand Up @@ -43,24 +44,24 @@ func (c *ResetCommand) Run(ctx context.Context, args []string) (err error) {
}
}

// Load configuration to find the database (if config exists)
// A missing config file is tolerated only when the path is the implicit default.
resolvedConfigPath, explicitConfig := resolveConfigPath(*configPath)

var dbConfig *DBConfig
if *configPath != "" {
config, configErr := ReadConfigFile(*configPath, !*noExpandEnv)
if configErr != nil {
return fmt.Errorf("cannot read config: %w", configErr)
}
config, configErr := ReadConfigFile(resolvedConfigPath, !*noExpandEnv)
if configErr != nil && (explicitConfig || !errors.Is(configErr, ErrConfigFileNotFound)) {
return fmt.Errorf("cannot read config: %w", configErr)
}

// Find database config
for _, dbc := range config.DBs {
expandedPath := dbc.Path
if !filepath.IsAbs(expandedPath) {
expandedPath, _ = filepath.Abs(expandedPath)
}
if expandedPath == dbPath {
dbConfig = dbc
break
}
// Find database config
for _, dbc := range config.DBs {
expandedPath := dbc.Path
if !filepath.IsAbs(expandedPath) {
expandedPath, _ = filepath.Abs(expandedPath)
}
if expandedPath == dbPath {
dbConfig = dbc
break
}
}

Expand Down
95 changes: 95 additions & 0 deletions cmd/litestream/reset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"errors"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -53,6 +54,100 @@ func TestResetCommand_Run(t *testing.T) {
}
}

func TestResetCommand_RunWithConfigEnv(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "db.sqlite")
if err := os.WriteFile(dbPath, []byte(""), 0600); err != nil {
t.Fatal(err)
}

// Put the Litestream state at a non-default meta path that is only
// discoverable through the config, so the test fails if reset ignores
// LITESTREAM_CONFIG and falls back to the default path.
metaPath := filepath.Join(t.TempDir(), "meta")
db := litestream.NewDB(dbPath)
db.SetMetaPath(metaPath)
ltxDir := filepath.Join(db.LTXDir(), "0")
if err := os.MkdirAll(ltxDir, 0700); err != nil {
t.Fatal(err)
}
ltxPath := filepath.Join(ltxDir, "0000000000000001-0000000000000001.ltx")
if err := os.WriteFile(ltxPath, []byte("ltx"), 0600); err != nil {
t.Fatal(err)
}

replicaPath := filepath.Join(t.TempDir(), "replica")
configPath := filepath.Join(t.TempDir(), "litestream.yml")
config := "dbs:\n" +
" - path: " + dbPath + "\n" +
" meta-path: " + metaPath + "\n" +
" replicas:\n" +
" - url: file://" + replicaPath + "\n"
if err := os.WriteFile(configPath, []byte(config), 0600); err != nil {
t.Fatal(err)
}
t.Setenv("LITESTREAM_CONFIG", configPath)

output := captureLTXCommandStdout(t, func() {
cmd := &ResetCommand{}
if err := cmd.Run(context.Background(), []string{dbPath}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})

if !strings.Contains(output, "Reset complete.") {
t.Fatalf("expected reset completion output:\n%s", output)
}
if _, err := os.Stat(ltxPath); !os.IsNotExist(err) {
t.Fatalf("expected LTX file at the config meta path to be removed, stat err=%v", err)
}
}

func TestResetCommand_RunWithMissingConfigFlag(t *testing.T) {
dbPath, ltxPath := createResetCommandTestData(t)
t.Setenv("LITESTREAM_CONFIG", "")
configPath := filepath.Join(t.TempDir(), "missing.yml")

cmd := &ResetCommand{}
if err := cmd.Run(context.Background(), []string{"-config", configPath, dbPath}); !errors.Is(err, ErrConfigFileNotFound) {
t.Fatalf("expected ErrConfigFileNotFound, got %v", err)
}
if _, err := os.Stat(ltxPath); err != nil {
t.Fatalf("expected LTX file to be kept: %v", err)
}
}

func TestResetCommand_RunWithMissingConfigEnv(t *testing.T) {
dbPath, ltxPath := createResetCommandTestData(t)
t.Setenv("LITESTREAM_CONFIG", filepath.Join(t.TempDir(), "missing.yml"))

cmd := &ResetCommand{}
if err := cmd.Run(context.Background(), []string{dbPath}); !errors.Is(err, ErrConfigFileNotFound) {
t.Fatalf("expected ErrConfigFileNotFound, got %v", err)
}
if _, err := os.Stat(ltxPath); err != nil {
t.Fatalf("expected LTX file to be kept: %v", err)
}
}

func TestResetCommand_RunWithMissingDefaultConfig(t *testing.T) {
dbPath, ltxPath := createResetCommandTestData(t)
t.Setenv("LITESTREAM_CONFIG", "")

output := captureLTXCommandStdout(t, func() {
cmd := &ResetCommand{}
if err := cmd.Run(context.Background(), []string{dbPath}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})

if !strings.Contains(output, "Reset complete.") {
t.Fatalf("expected reset completion output:\n%s", output)
}
if _, err := os.Stat(ltxPath); !os.IsNotExist(err) {
t.Fatalf("expected LTX file to be removed, stat err=%v", err)
}
}

func createResetCommandTestData(t *testing.T) (string, string) {
t.Helper()

Expand Down
Loading