Documentation
¶
Overview ¶
Package portability writes and reads the full-site archive behind `hatamoto export` and `hatamoto import` (format described in docs/EXPORT.md).
Index ¶
Constants ¶
const ( FormatName = "hatamoto-export" FormatVersion = 1 )
FormatName and FormatVersion identify the archive. A reader refuses a newer version than it knows; the version only changes when the layout does.
const DefaultMaxImportBytes = 64 << 30
DefaultMaxImportBytes is the decompressed-size cap when none is set.
Variables ¶
var ErrNotEmpty = errors.New("database is not empty")
ErrNotEmpty is returned when the target database already has content.
Functions ¶
This section is empty.
Types ¶
type ExportOptions ¶
type ExportOptions struct {
// MediaDir is the upload directory the media rows' paths are relative to.
MediaDir string
// NoSubmissions leaves out form submissions (visitor data).
NoSubmissions bool
// OnMissing, when set, is called with the relative path of each referenced
// media file that is not on disk.
OnMissing func(rel string)
// Now is the archive's timestamp (tests); zero means time.Now.
Now time.Time
}
ExportOptions control Export.
type Header ¶
type Header struct {
Format string `json:"format"`
Version int `json:"version"`
CreatedAt time.Time `json:"created_at"`
SchemaVersion int64 `json:"schema_version"` // newest applied database migration
IncludeSubmission bool `json:"include_form_submissions"`
Tables []string `json:"tables"` // order the data files appear in
}
Header is the first entry of the archive.
type ImportOptions ¶
type ImportOptions struct {
// MediaDir is the upload directory media files are written to.
MediaDir string
// MaxBytes caps the decompressed archive (a gzip-bomb guard). Zero means
// DefaultMaxImportBytes.
MaxBytes int64
}
ImportOptions control Import.
type ImportResult ¶
type ImportResult struct {
Header Header
Rows map[string]int64
MediaFiles int
MediaBytes int64
Users int64
// Warnings lists media files the import skipped (per-file, not fatal).
Warnings []string
// People is how many person entities the post-import backfill made for an
// archive that predates people (zero for an archive that has them).
People int
// contains filtered or unexported fields
}
ImportResult says what was imported.
func Import ¶
func Import(ctx context.Context, pool *pgxpool.Pool, r io.Reader, opts ImportOptions) (*ImportResult, error)
Import reads an archive from r into an empty, migrated database, keeping ids and timestamps. It runs in one transaction: any error, including a failed manifest check, leaves the database as it was. Media files are staged in a temp directory under MediaDir and linked into place just before the commit; a failure removes only the files this import put there, never one that was already in MediaDir (an import refuses to overwrite). Users come back without passwords.
An archive from before people existed has authors and no person entities: once it is committed, person.Backfill makes the people (it is idempotent, so running it again after a failure is safe). No FormatVersion bump: the archive layout is unchanged, hatamoto_user_people is just one more table file, and archives without it import as before.
type Manifest ¶
type Manifest struct {
Rows map[string]int64 `json:"rows"`
MediaFiles int `json:"media_files"`
MediaBytes int64 `json:"media_bytes"`
// MediaMissing counts referenced files the export did not find on disk. It
// is informational; an import ignores it.
MediaMissing int `json:"media_missing,omitempty"`
}
Manifest is the last entry: what the archive must contain. An import checks it, so a truncated or tampered archive fails instead of importing half a site.
func Export ¶
func Export(ctx context.Context, pool *pgxpool.Pool, w io.Writer, opts ExportOptions) (*Manifest, error)
Export streams the site as a tar.gz to w. The database is read in one repeatable-read transaction, so the tables agree with each other; each table is spooled to a temp file (a tar entry needs its size first), so memory use does not grow with the site.