diff --git a/cmd/litestream/restore.go b/cmd/litestream/restore.go index e432481..9aa3fdc 100644 --- a/cmd/litestream/restore.go +++ b/cmd/litestream/restore.go @@ -111,8 +111,16 @@ func (c *RestoreCommand) Run(ctx context.Context, args []string) (err error) { // Build replica from either a URL or config. r, err := c.loadReplica(ctx, config, pathOrURL) - if err != nil { - return err + if err == litestream.ErrNoGeneration { + // Return an error if no replicas can be loaded to restore from. + // If optional flag set, return success. Useful for automated recovery. + if c.ifReplicaExists { + fmt.Fprintln(c.stdout, "no replicas have generations to restore from, skipping") + return nil + } + return fmt.Errorf("no replicas have generations to restore from") + } else if err != nil { + return fmt.Errorf("cannot determine latest replica: %w", err) } // Determine latest generation if one is not specified. @@ -218,11 +226,7 @@ func (c *RestoreCommand) loadReplicaFromConfig(ctx context.Context, config Confi } // Determine latest replica to restore from. - r, err := litestream.LatestReplica(ctx, db.Replicas) - if err != nil { - return nil, fmt.Errorf("cannot determine latest replica: %w", err) - } - return r, nil + return litestream.LatestReplica(ctx, db.Replicas) } // Usage prints the help screen to STDOUT. diff --git a/cmd/litestream/restore_test.go b/cmd/litestream/restore_test.go index 6744963..7e41baa 100644 --- a/cmd/litestream/restore_test.go +++ b/cmd/litestream/restore_test.go @@ -138,6 +138,19 @@ func TestRestoreCommand(t *testing.T) { } }) + t.Run("IfReplicaExists/Multiple", func(t *testing.T) { + testDir := filepath.Join("testdata", "restore", "if-replica-exists-flag-multiple") + defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)() + + m, _, stdout, _ := newMain() + err := m.Run(context.Background(), []string{"restore", "-config", filepath.Join(testDir, "litestream.yml"), "-if-replica-exists", filepath.Join(testDir, "db")}) + if err != nil { + t.Fatal(err) + } else if got, want := stdout.String(), string(testingutil.ReadFile(t, filepath.Join(testDir, "stdout"))); got != want { + t.Fatalf("stdout=%q, want %q", got, want) + } + }) + t.Run("ErrNoBackups", func(t *testing.T) { testDir := filepath.Join("testdata", "restore", "no-backups") defer testingutil.Setenv(t, "LITESTREAM_TESTDIR", testDir)() diff --git a/cmd/litestream/testdata/restore/if-replica-exists-flag-multiple/litestream.yml b/cmd/litestream/testdata/restore/if-replica-exists-flag-multiple/litestream.yml new file mode 100644 index 0000000..8696dbe --- /dev/null +++ b/cmd/litestream/testdata/restore/if-replica-exists-flag-multiple/litestream.yml @@ -0,0 +1,5 @@ +dbs: + - path: $LITESTREAM_TESTDIR/db + replicas: + - path: $LITESTREAM_TESTDIR/replica0 + - path: $LITESTREAM_TESTDIR/replica1 diff --git a/cmd/litestream/testdata/restore/if-replica-exists-flag-multiple/stdout b/cmd/litestream/testdata/restore/if-replica-exists-flag-multiple/stdout new file mode 100644 index 0000000..a452281 --- /dev/null +++ b/cmd/litestream/testdata/restore/if-replica-exists-flag-multiple/stdout @@ -0,0 +1 @@ +no replicas have generations to restore from, skipping