96 lines
3.8 KiB
Go
96 lines
3.8 KiB
Go
// Command wallet-outbox-archive-restore validates one downloaded COS batch and,
|
|
// only after _SUCCESS and every checksum pass, writes it to isolation tables.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"hyapp/services/wallet-service/internal/outboxarchiverestore"
|
|
)
|
|
|
|
const defaultDSNEnv = "WALLET_OUTBOX_ARCHIVE_RESTORE_DSN"
|
|
|
|
func main() {
|
|
dataPath := flag.String("data", "", "local data.ndjson.gz downloaded from the private archive bucket")
|
|
manifestPath := flag.String("manifest", "", "local manifest.json from the same batch directory")
|
|
successPath := flag.String("success", "", "local _SUCCESS from the same batch directory")
|
|
restore := flag.Bool("restore", false, "persist the verified batch into fixed isolation tables")
|
|
dsnEnv := flag.String("dsn-env", defaultDSNEnv, "environment variable containing the restore-target MySQL DSN")
|
|
expectedDatabase := flag.String("expected-database", "", "exact restore-target database returned by SELECT DATABASE()")
|
|
rehearsalID := flag.String("rehearsal-id", "", "stable 1..64 character restore rehearsal key")
|
|
confirmation := flag.String("confirm", "", "restore confirmation RESTORE:<rehearsal-id>:<batch-id>")
|
|
timeout := flag.Duration("timeout", 10*time.Minute, "whole verification/restore timeout, at most 30m")
|
|
flag.Parse()
|
|
|
|
if strings.TrimSpace(*dataPath) == "" || strings.TrimSpace(*manifestPath) == "" || strings.TrimSpace(*successPath) == "" {
|
|
fatal(errors.New("--data, --manifest, and --success are required"))
|
|
}
|
|
if *timeout <= 0 || *timeout > 30*time.Minute {
|
|
fatal(errors.New("--timeout must be within (0,30m]"))
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
|
defer cancel()
|
|
|
|
verification, err := outboxarchiverestore.VerifyLocalFiles(*dataPath, *manifestPath, *successPath, outboxarchiverestore.DefaultLimits())
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
if !*restore {
|
|
printJSON(map[string]any{
|
|
"state": "FILES_VERIFIED", "batch_id": verification.Manifest.BatchID,
|
|
"app_code": verification.Manifest.AppCode, "row_count": verification.Manifest.RowCount,
|
|
"data_object_key": verification.Manifest.DataObjectKey,
|
|
"first_cursor": verification.Manifest.FirstCursor, "last_cursor": verification.Manifest.LastCursor,
|
|
"ndjson_sha256": verification.Manifest.NDJSONSHA256, "gzip_sha256": verification.Manifest.GzipSHA256,
|
|
"data_crc64": verification.Manifest.DataCRC64,
|
|
"manifest_sha256": verification.ManifestSHA256, "manifest_crc64": verification.ManifestCRC64,
|
|
"success_sha256": verification.SuccessSHA256, "success_crc64": verification.SuccessCRC64,
|
|
"available_delta_total": verification.Manifest.AvailableDeltaTotal,
|
|
"frozen_delta_total": verification.Manifest.FrozenDeltaTotal, "no_database_writes": true,
|
|
})
|
|
return
|
|
}
|
|
|
|
rehearsal := strings.TrimSpace(*rehearsalID)
|
|
expectedConfirmation := "RESTORE:" + rehearsal + ":" + verification.Manifest.BatchID
|
|
if rehearsal == "" || strings.TrimSpace(*confirmation) != expectedConfirmation {
|
|
fatal(fmt.Errorf("--restore requires --rehearsal-id and --confirm=%s", expectedConfirmation))
|
|
}
|
|
envName := strings.TrimSpace(*dsnEnv)
|
|
if envName == "" {
|
|
fatal(errors.New("--dsn-env cannot be empty"))
|
|
}
|
|
dsn := strings.TrimSpace(os.Getenv(envName))
|
|
if dsn == "" {
|
|
fatal(fmt.Errorf("restore DSN environment variable %s is empty", envName))
|
|
}
|
|
result, err := outboxarchiverestore.Restore(ctx, dsn, *expectedDatabase, rehearsal, verification)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
printJSON(struct {
|
|
outboxarchiverestore.RestoreResult
|
|
DSNEnv string `json:"dsn_env"`
|
|
}{RestoreResult: result, DSNEnv: envName})
|
|
}
|
|
|
|
func printJSON(value any) {
|
|
body, err := json.Marshal(value)
|
|
if err != nil {
|
|
fatal(err)
|
|
}
|
|
fmt.Println(string(body))
|
|
}
|
|
|
|
func fatal(err error) {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|