Bind restore to committed remote snapshots
This commit is contained in:
@@ -2,6 +2,8 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
@@ -108,6 +110,9 @@ func executeRestoreDownloadAction(
|
||||
return fmt.Errorf("download to temp file: %w", err)
|
||||
}
|
||||
defer func() { _ = temporary.Cleanup() }()
|
||||
if err := verifyRestoredObject(ctx, store, action, temporary); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if action.LocalRelativePath == config.PathManifestFile {
|
||||
file, err := temporary.Open()
|
||||
@@ -131,6 +136,56 @@ func executeRestoreDownloadAction(
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyRestoredObject(ctx context.Context, store storage.ObjectStore, action RestoreAction, temporary *fileops.DownloadedTempFile) error {
|
||||
if strings.TrimSpace(action.SHA256) == "" && strings.TrimSpace(action.Generation) == "" {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(action.SHA256) == "" || strings.TrimSpace(action.Generation) == "" {
|
||||
return fmt.Errorf("committed object identity for %q is incomplete", action.RemoteKey)
|
||||
}
|
||||
file, err := temporary.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("open downloaded object for verification: %w", err)
|
||||
}
|
||||
digest := sha256.New()
|
||||
count, copyErr := io.Copy(digest, file)
|
||||
closeErr := file.Close()
|
||||
if copyErr != nil {
|
||||
return fmt.Errorf("checksum downloaded object: %w", copyErr)
|
||||
}
|
||||
if closeErr != nil {
|
||||
return fmt.Errorf("close downloaded object: %w", closeErr)
|
||||
}
|
||||
if count != action.Size {
|
||||
return fmt.Errorf("committed object size mismatch for %q: got %d, want %d", action.RemoteKey, count, action.Size)
|
||||
}
|
||||
if got := hex.EncodeToString(digest.Sum(nil)); got != action.SHA256 {
|
||||
return fmt.Errorf("committed object checksum mismatch for %q: got %s, want %s", action.RemoteKey, got, action.SHA256)
|
||||
}
|
||||
objects, err := store.List(ctx, action.RemoteKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read committed object identity for %q: %w", action.RemoteKey, err)
|
||||
}
|
||||
var found *storage.ObjectInfo
|
||||
for _, object := range objects {
|
||||
if normalizeRemoteKey(object.Key) != normalizeRemoteKey(action.RemoteKey) {
|
||||
continue
|
||||
}
|
||||
if found != nil {
|
||||
return fmt.Errorf("committed object %q is ambiguous", action.RemoteKey)
|
||||
}
|
||||
copy := object
|
||||
found = ©
|
||||
}
|
||||
if found == nil {
|
||||
return fmt.Errorf("committed object %q is missing", action.RemoteKey)
|
||||
}
|
||||
if found.Size != action.Size || strings.TrimSpace(found.ETag) != action.Generation {
|
||||
return fmt.Errorf("committed object generation mismatch for %q", action.RemoteKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func executeRestoreAudioAction(
|
||||
ctx context.Context,
|
||||
cfg *config.Config,
|
||||
@@ -190,6 +245,9 @@ func validateRestoredManifest(ctx context.Context, cfg *config.Config, current *
|
||||
if expected := strings.TrimSpace(current.Campaign); expected != "" && manifestCampaign != expected {
|
||||
return fmt.Errorf("manifest campaign %q does not match discovered campaign %q", manifestCampaign, expected)
|
||||
}
|
||||
if expected := strings.TrimSpace(current.RunID); expected != "" && strings.TrimSpace(m.RunID) != expected {
|
||||
return fmt.Errorf("manifest run_id %q does not match discovered run_id %q", strings.TrimSpace(m.RunID), expected)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user