diff --git a/internal/vaultik/restore.go b/internal/vaultik/restore.go index d136f59..afe58b7 100644 --- a/internal/vaultik/restore.go +++ b/internal/vaultik/restore.go @@ -122,6 +122,8 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { if err := v.restoreFile(v.ctx, repos, file, opts.TargetDir, identity, chunkToBlobMap, blobCache, result); err != nil { log.Error("Failed to restore file", "path", file.Path, "error", err) + result.FilesFailed++ + result.FailedFiles = append(result.FailedFiles, file.Path.String()) // Continue with other files continue } @@ -151,6 +153,13 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { result.Duration.Round(time.Second), ) + if result.FilesFailed > 0 { + _, _ = fmt.Fprintf(v.Stdout, "\nWARNING: %d file(s) failed to restore:\n", result.FilesFailed) + for _, path := range result.FailedFiles { + _, _ = fmt.Fprintf(v.Stdout, " - %s\n", path) + } + } + // Run verification if requested if opts.Verify { if err := v.verifyRestoredFiles(v.ctx, repos, files, opts.TargetDir, result); err != nil { @@ -171,6 +180,10 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { ) } + if result.FilesFailed > 0 { + return fmt.Errorf("%d file(s) failed to restore", result.FilesFailed) + } + return nil } diff --git a/internal/vaultik/snapshot.go b/internal/vaultik/snapshot.go index 2960389..e0d93b2 100644 --- a/internal/vaultik/snapshot.go +++ b/internal/vaultik/snapshot.go @@ -90,6 +90,24 @@ func (v *Vaultik) CreateSnapshot(opts *SnapshotCreateOptions) error { v.printfStdout("\nAll %d snapshots completed in %s\n", len(snapshotNames), time.Since(overallStartTime).Round(time.Second)) } + // Prune old snapshots and unreferenced blobs if --prune was specified + if opts.Prune { + log.Info("Pruning enabled - deleting old snapshots and unreferenced blobs") + v.printlnStdout("\nPruning old snapshots (keeping latest)...") + + if err := v.PurgeSnapshots(true, "", true); err != nil { + return fmt.Errorf("prune: purging old snapshots: %w", err) + } + + v.printlnStdout("Pruning unreferenced blobs...") + + if err := v.PruneBlobs(&PruneOptions{Force: true}); err != nil { + return fmt.Errorf("prune: removing unreferenced blobs: %w", err) + } + + log.Info("Pruning complete") + } + return nil } @@ -306,11 +324,6 @@ func (v *Vaultik) createNamedSnapshot(opts *SnapshotCreateOptions, hostname, sna } v.printfStdout("Duration: %s\n", formatDuration(snapshotDuration)) - if opts.Prune { - log.Info("Pruning enabled - will delete old snapshots after snapshot") - // TODO: Implement pruning - } - return nil } @@ -1004,16 +1017,16 @@ func (v *Vaultik) deleteSnapshotFromLocalDB(snapshotID string) error { // Delete related records first to avoid foreign key constraints if err := v.Repositories.Snapshots.DeleteSnapshotFiles(v.ctx, snapshotID); err != nil { - log.Error("Failed to delete snapshot files", "snapshot_id", snapshotID, "error", err) + return fmt.Errorf("deleting snapshot files for %s: %w", snapshotID, err) } if err := v.Repositories.Snapshots.DeleteSnapshotBlobs(v.ctx, snapshotID); err != nil { - log.Error("Failed to delete snapshot blobs", "snapshot_id", snapshotID, "error", err) + return fmt.Errorf("deleting snapshot blobs for %s: %w", snapshotID, err) } if err := v.Repositories.Snapshots.DeleteSnapshotUploads(v.ctx, snapshotID); err != nil { - log.Error("Failed to delete snapshot uploads", "snapshot_id", snapshotID, "error", err) + return fmt.Errorf("deleting snapshot uploads for %s: %w", snapshotID, err) } if err := v.Repositories.Snapshots.Delete(v.ctx, snapshotID); err != nil { - log.Error("Failed to delete snapshot record", "snapshot_id", snapshotID, "error", err) + return fmt.Errorf("deleting snapshot record %s: %w", snapshotID, err) } return nil diff --git a/internal/vaultik/snapshot_prune_test.go b/internal/vaultik/snapshot_prune_test.go new file mode 100644 index 0000000..dbff412 --- /dev/null +++ b/internal/vaultik/snapshot_prune_test.go @@ -0,0 +1,23 @@ +package vaultik + +import ( + "testing" +) + +// TestSnapshotCreateOptions_PruneFlag verifies the Prune field exists on +// SnapshotCreateOptions and can be set. +func TestSnapshotCreateOptions_PruneFlag(t *testing.T) { + opts := &SnapshotCreateOptions{ + Prune: true, + } + if !opts.Prune { + t.Error("Expected Prune to be true") + } + + opts2 := &SnapshotCreateOptions{ + Prune: false, + } + if opts2.Prune { + t.Error("Expected Prune to be false") + } +}