initial commit
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
This commit is contained in:
commit
951dd8d26b
175 changed files with 50041 additions and 0 deletions
62
internal/store/backup.go
Normal file
62
internal/store/backup.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/urpi/go-bananas/internal/domain"
|
||||
)
|
||||
|
||||
// Snapshot writes a consistent copy of the database to dest.
|
||||
//
|
||||
// It uses SQLite's VACUUM INTO rather than copying the file. Copying a database
|
||||
// while a write is in flight yields a torn copy: the header can say one thing and
|
||||
// the pages say another, and the result may open successfully while being
|
||||
// corrupt. VACUUM INTO reads through SQLite's own backup machinery, so the result
|
||||
// is consistent even while the service is serving traffic, and it does not block
|
||||
// writers for the length of the copy.
|
||||
//
|
||||
// Two further properties come from the statement itself rather than from code
|
||||
// here: it refuses to overwrite an existing file, so a stale snapshot can never be
|
||||
// mistaken for a fresh one, and it writes to a temporary name and renames on
|
||||
// success, so a snapshot that fails halfway is not left looking complete.
|
||||
//
|
||||
// The connection must not be read-only: VACUUM INTO needs to read the schema and
|
||||
// pages through a normal connection even though it only ever writes to the new
|
||||
// file.
|
||||
func (s *Store) Snapshot(ctx context.Context, dest string) error {
|
||||
trimmed := strings.TrimSpace(dest)
|
||||
if trimmed == "" {
|
||||
return fmt.Errorf("%w: snapshot destination must not be empty", domain.ErrInvalid)
|
||||
}
|
||||
if dir := filepath.Dir(trimmed); dir != "" && dir != "." {
|
||||
// Created by the process rather than by SQLite, which has no mkdir. The
|
||||
// snapshot directory normally already exists (the Helm chart's subPath mount
|
||||
// creates it), so this is a convenience for a manual invocation.
|
||||
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||
return fmt.Errorf("create snapshot directory %s: %w", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
// VACUUM INTO takes an SQL string literal rather than a bound parameter on some
|
||||
// SQLite builds, so the path is escaped into one here. Doubling the quote is
|
||||
// the documented escape, and rejecting an embedded NUL keeps the literal
|
||||
// well-formed regardless.
|
||||
if strings.ContainsRune(trimmed, 0) {
|
||||
return fmt.Errorf("%w: snapshot destination contains a NUL byte", domain.ErrInvalid)
|
||||
}
|
||||
literal := "'" + strings.ReplaceAll(trimmed, "'", "''") + "'"
|
||||
|
||||
//nolint:gosec // G202: VACUUM INTO takes an SQL string literal rather than a
|
||||
// bound parameter on some SQLite builds. The literal is escaped above by doubling
|
||||
// any single quote, which is the documented escape, and NUL bytes are rejected so
|
||||
// the literal cannot be broken out of. TestSnapshotEscapesQuotesInThePath covers
|
||||
// a path containing a quote.
|
||||
if _, err := s.db.ExecContext(ctx, "VACUUM INTO "+literal); err != nil {
|
||||
return fmt.Errorf("write snapshot to %s: %w", trimmed, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue