Apply linter autofixes: internal/vaultik (refs #61)
This commit is contained in:
+195
-34
@@ -2,6 +2,7 @@ package vaultik
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -36,7 +37,8 @@ func (v *Vaultik) CreateSnapshot(opts *SnapshotCreateOptions) error {
|
||||
"index_path", v.Config.IndexPath,
|
||||
)
|
||||
|
||||
if err := v.EnsureStorageBinding(); err != nil {
|
||||
err := v.EnsureStorageBinding()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -72,12 +74,13 @@ func (v *Vaultik) CreateSnapshot(opts *SnapshotCreateOptions) error {
|
||||
}
|
||||
|
||||
if len(snapshotNames) == 0 {
|
||||
return fmt.Errorf("no snapshots configured")
|
||||
return errors.New("no snapshots configured")
|
||||
}
|
||||
|
||||
// Process each named snapshot
|
||||
for snapIdx, snapName := range snapshotNames {
|
||||
if err := v.createNamedSnapshot(opts, hostname, snapName, snapIdx+1, len(snapshotNames)); err != nil {
|
||||
err := v.createNamedSnapshot(opts, hostname, snapName, snapIdx+1, len(snapshotNames))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -88,7 +91,8 @@ func (v *Vaultik) CreateSnapshot(opts *SnapshotCreateOptions) error {
|
||||
}
|
||||
|
||||
if opts.Prune {
|
||||
if err := v.runPostBackupPrune(snapshotNames, opts.KeepNewerThan); err != nil {
|
||||
err := v.runPostBackupPrune(snapshotNames, opts.KeepNewerThan)
|
||||
if err != nil {
|
||||
return fmt.Errorf("post-backup prune: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -127,11 +131,13 @@ func (v *Vaultik) runPostBackupPrune(snapshotNames []string, keepNewerThan strin
|
||||
purgeOpts.KeepLatest = true
|
||||
}
|
||||
|
||||
if err := v.PurgeSnapshotsWithOptions(purgeOpts); err != nil {
|
||||
err := v.PurgeSnapshotsWithOptions(purgeOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("purging old snapshots: %w", err)
|
||||
}
|
||||
|
||||
if err := v.PruneBlobs(&PruneOptions{Force: true}); err != nil {
|
||||
err = v.PruneBlobs(&PruneOptions{Force: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("pruning orphaned blobs: %w", err)
|
||||
}
|
||||
|
||||
@@ -178,6 +184,7 @@ func (v *Vaultik) createNamedSnapshot(opts *SnapshotCreateOptions, hostname, sna
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating snapshot: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Beginning snapshot", "snapshot_id", snapshotID, "name", snapName)
|
||||
v.UI.Begin("Creating snapshot %s.", v.UI.Snapshot(snapshotID))
|
||||
|
||||
@@ -201,6 +208,7 @@ func (v *Vaultik) createNamedSnapshot(opts *SnapshotCreateOptions, hostname, sna
|
||||
"duration", time.Since(snapshotStartTime))
|
||||
|
||||
v.printSnapshotSummary(snapshotID, snapshotStartTime, stats)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -238,12 +246,14 @@ func (v *Vaultik) scanAllDirectories(scanner *snapshot.Scanner, resolvedDirs []s
|
||||
select {
|
||||
case <-v.ctx.Done():
|
||||
log.Info("Snapshot creation cancelled")
|
||||
|
||||
return nil, v.ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
log.Info("Scanning directory", "path", dir)
|
||||
v.UI.Begin("Enumerating snapshot source files in %s (%d of %d).", v.UI.Path(dir), i+1, len(resolvedDirs))
|
||||
|
||||
result, err := scanner.Scan(v.ctx, dir, snapshotID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan %s: %w", dir, err)
|
||||
@@ -297,15 +307,18 @@ func (v *Vaultik) finalizeSnapshotMetadata(snapshotID string, stats *snapshotSta
|
||||
UploadDurationMs: stats.uploadDuration.Milliseconds(),
|
||||
}
|
||||
|
||||
if err := v.SnapshotManager.UpdateSnapshotStatsExtended(v.ctx, snapshotID, extStats); err != nil {
|
||||
err := v.SnapshotManager.UpdateSnapshotStatsExtended(v.ctx, snapshotID, extStats)
|
||||
if err != nil {
|
||||
return fmt.Errorf("updating snapshot stats: %w", err)
|
||||
}
|
||||
|
||||
if err := v.SnapshotManager.CompleteSnapshot(v.ctx, snapshotID); err != nil {
|
||||
err = v.SnapshotManager.CompleteSnapshot(v.ctx, snapshotID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("completing snapshot: %w", err)
|
||||
}
|
||||
|
||||
if err := v.SnapshotManager.ExportSnapshotMetadata(v.ctx, v.Config.IndexPath, snapshotID); err != nil {
|
||||
err = v.SnapshotManager.ExportSnapshotMetadata(v.ctx, v.Config.IndexPath, snapshotID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("exporting snapshot metadata: %w", err)
|
||||
}
|
||||
|
||||
@@ -318,6 +331,7 @@ func (v *Vaultik) uploadSpeed(bytesUploaded int64, duration time.Duration) strin
|
||||
if bytesUploaded <= 0 || duration <= 0 {
|
||||
return v.UI.Speed(0)
|
||||
}
|
||||
|
||||
return v.UI.Speed(float64(bytesUploaded) / duration.Seconds())
|
||||
}
|
||||
|
||||
@@ -338,6 +352,7 @@ func (v *Vaultik) printSnapshotSummary(snapshotID string, startTime time.Time, s
|
||||
}
|
||||
|
||||
v.UI.Complete("Created snapshot %s.", v.UI.Snapshot(snapshotID))
|
||||
|
||||
filesMsg := fmt.Sprintf("Files: %s examined, %s backed up, %s unchanged",
|
||||
v.UI.Count(stats.totalFiles),
|
||||
v.UI.Count(totalFilesChanged),
|
||||
@@ -345,6 +360,7 @@ func (v *Vaultik) printSnapshotSummary(snapshotID string, startTime time.Time, s
|
||||
if stats.totalFilesDeleted > 0 {
|
||||
filesMsg += fmt.Sprintf(", %s deleted", v.UI.Count(stats.totalFilesDeleted))
|
||||
}
|
||||
|
||||
v.UI.Detail("%s.", filesMsg)
|
||||
|
||||
dataMsg := fmt.Sprintf("Data: %s total (%s backed up)",
|
||||
@@ -353,6 +369,7 @@ func (v *Vaultik) printSnapshotSummary(snapshotID string, startTime time.Time, s
|
||||
if stats.totalBytesDeleted > 0 {
|
||||
dataMsg += fmt.Sprintf(", %s deleted", v.UI.Size(stats.totalBytesDeleted))
|
||||
}
|
||||
|
||||
v.UI.Detail("%s.", dataMsg)
|
||||
|
||||
if stats.totalBlobsUploaded > 0 {
|
||||
@@ -366,6 +383,7 @@ func (v *Vaultik) printSnapshotSummary(snapshotID string, startTime time.Time, s
|
||||
v.UI.Duration(stats.uploadDuration),
|
||||
v.uploadSpeed(stats.totalBytesUploaded, stats.uploadDuration))
|
||||
}
|
||||
|
||||
v.UI.Detail("Snapshot create duration: %s.", v.UI.Duration(snapshotDuration))
|
||||
}
|
||||
|
||||
@@ -375,12 +393,14 @@ func (v *Vaultik) getSnapshotBlobSizes(snapshotID string) (compressed int64, unc
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
for _, hash := range blobHashes {
|
||||
if blob, err := v.Repositories.Blobs.GetByHash(v.ctx, hash); err == nil && blob != nil {
|
||||
compressed += blob.CompressedSize
|
||||
uncompressed += blob.UncompressedSize
|
||||
}
|
||||
}
|
||||
|
||||
return compressed, uncompressed
|
||||
}
|
||||
|
||||
@@ -418,6 +438,7 @@ func (v *Vaultik) ListSnapshots(jsonOutput bool) error {
|
||||
if ls.CompletedAt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
snapshots = append(snapshots, v.snapshotInfoFromLocal(ls))
|
||||
}
|
||||
|
||||
@@ -428,6 +449,7 @@ func (v *Vaultik) ListSnapshots(jsonOutput bool) error {
|
||||
if jsonOutput {
|
||||
encoder := json.NewEncoder(v.Stdout)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
return encoder.Encode(snapshots)
|
||||
}
|
||||
|
||||
@@ -442,6 +464,7 @@ func (v *Vaultik) ListSnapshots(jsonOutput bool) error {
|
||||
remoteKeys, err := v.listAllRemoteSnapshotKeys()
|
||||
if err != nil {
|
||||
v.UI.Warning("Could not list backup destination store: %v.", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -450,20 +473,25 @@ func (v *Vaultik) ListSnapshots(jsonOutput bool) error {
|
||||
if ls.CompletedAt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
localKeys[snapshot.RemoteSnapshotKey(ls.ID.String())] = ls.ID.String()
|
||||
}
|
||||
|
||||
remoteSet := make(map[string]bool, len(remoteKeys))
|
||||
for _, k := range remoteKeys {
|
||||
remoteSet[k] = true
|
||||
}
|
||||
|
||||
var localOnly []string
|
||||
|
||||
for key, humanID := range localKeys {
|
||||
if !remoteSet[key] {
|
||||
localOnly = append(localOnly, humanID)
|
||||
}
|
||||
}
|
||||
|
||||
var remoteOnlyCount int
|
||||
|
||||
for key := range remoteSet {
|
||||
if _, ok := localKeys[key]; !ok {
|
||||
remoteOnlyCount++
|
||||
@@ -472,11 +500,14 @@ func (v *Vaultik) ListSnapshots(jsonOutput bool) error {
|
||||
|
||||
if len(localOnly) > 0 {
|
||||
v.UI.Warning("%d local snapshot record(s) not found in backup destination store:", len(localOnly))
|
||||
|
||||
for _, id := range localOnly {
|
||||
v.UI.Info("%s", v.UI.Snapshot(id))
|
||||
}
|
||||
|
||||
v.UI.Info("Run 'vaultik snapshot cleanup' to remove stale local records.")
|
||||
}
|
||||
|
||||
if remoteOnlyCount > 0 {
|
||||
v.UI.Notice("NOTE: %d remote snapshot(s) found in backup destination store but not in local database.", remoteOnlyCount)
|
||||
}
|
||||
@@ -493,6 +524,7 @@ func (v *Vaultik) snapshotInfoFromLocal(ls *database.Snapshot) SnapshotInfo {
|
||||
totalSize, err := v.Repositories.Snapshots.GetSnapshotTotalCompressedSize(v.ctx, idStr)
|
||||
if err != nil {
|
||||
log.Warn("Failed to get total compressed size", "id", idStr, "error", err)
|
||||
|
||||
totalSize = ls.BlobSize
|
||||
}
|
||||
|
||||
@@ -523,19 +555,24 @@ func (v *Vaultik) printSnapshotTable(snapshots []SnapshotInfo) error {
|
||||
if _, err := fmt.Fprintln(w, "CONFIGURED SNAPSHOTS:"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(w, "NAME\tPATHS"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(w, "────\t─────"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range v.Config.SnapshotNames() {
|
||||
snap := v.Config.Snapshots[name]
|
||||
|
||||
paths := strings.Join(snap.Paths, ", ")
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\n", name, paths); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(w); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -543,9 +580,11 @@ func (v *Vaultik) printSnapshotTable(snapshots []SnapshotInfo) error {
|
||||
if _, err := fmt.Fprintln(w, "REMOTE SNAPSHOTS:"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(w, "SNAPSHOT ID\tTIMESTAMP\tCOMPRESSED SIZE\tUNCOMPRESSED SIZE\tNEW CHUNK SIZE"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(w, "───────────\t─────────\t───────────────\t─────────────────\t──────────────"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -554,10 +593,12 @@ func (v *Vaultik) printSnapshotTable(snapshots []SnapshotInfo) error {
|
||||
for _, snap := range snapshots {
|
||||
uncompressed := remoteOnlyCell
|
||||
newChunks := remoteOnlyCell
|
||||
|
||||
if snap.LocallyTracked {
|
||||
uncompressed = formatBytes(snap.UncompressedSize)
|
||||
newChunks = formatBytes(snap.NewChunkSize)
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
|
||||
snap.ID,
|
||||
snap.Timestamp.Format("2006-01-02 15:04:05"),
|
||||
@@ -611,11 +652,13 @@ func (v *Vaultik) PurgeSnapshotsWithOptions(opts *SnapshotPurgeOptions) error {
|
||||
if s.CompletedAt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(nameFilter) > 0 {
|
||||
if _, ok := nameFilter[parseSnapshotName(s.ID.String())]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
snapshots = append(snapshots, SnapshotInfo{
|
||||
ID: s.ID,
|
||||
Timestamp: s.StartedAt,
|
||||
@@ -634,12 +677,15 @@ func (v *Vaultik) PurgeSnapshotsWithOptions(opts *SnapshotPurgeOptions) error {
|
||||
// Keep the latest snapshot per snapshot name. Snapshots are sorted
|
||||
// newest-first, so the first occurrence of each name is kept.
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, snap := range snapshots {
|
||||
name := parseSnapshotName(snap.ID.String())
|
||||
if seen[name] {
|
||||
toDelete = append(toDelete, snap)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
seen[name] = true
|
||||
}
|
||||
} else if opts.OlderThan != "" {
|
||||
@@ -660,6 +706,7 @@ func (v *Vaultik) PurgeSnapshotsWithOptions(opts *SnapshotPurgeOptions) error {
|
||||
if !opts.Quiet {
|
||||
v.printlnStdout("No snapshots to delete")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -670,6 +717,7 @@ func (v *Vaultik) PurgeSnapshotsWithOptions(opts *SnapshotPurgeOptions) error {
|
||||
func (v *Vaultik) confirmAndExecutePurge(toDelete []SnapshotInfo, force, quiet bool) error {
|
||||
if !quiet {
|
||||
v.printfStdout("The following snapshots will be deleted:\n\n")
|
||||
|
||||
for _, snap := range toDelete {
|
||||
v.printfStdout(" %s (%s, %s)\n",
|
||||
snap.ID,
|
||||
@@ -681,14 +729,18 @@ func (v *Vaultik) confirmAndExecutePurge(toDelete []SnapshotInfo, force, quiet b
|
||||
// Confirm unless --force is used
|
||||
if !force {
|
||||
v.printfStdout("\nDelete %d snapshot(s)? [y/N] ", len(toDelete))
|
||||
|
||||
var confirm string
|
||||
if _, err := v.scanStdin(&confirm); err != nil {
|
||||
// Treat EOF or error as "no"
|
||||
v.printlnStdout("Cancelled")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.ToLower(confirm) != "y" {
|
||||
v.printlnStdout("Cancelled")
|
||||
|
||||
return nil
|
||||
}
|
||||
} else if !quiet {
|
||||
@@ -699,10 +751,14 @@ func (v *Vaultik) confirmAndExecutePurge(toDelete []SnapshotInfo, force, quiet b
|
||||
for _, snap := range toDelete {
|
||||
snapshotID := snap.ID.String()
|
||||
log.Info("Deleting snapshot", "id", snapshotID)
|
||||
if err := v.deleteSnapshotFromLocalDB(snapshotID); err != nil {
|
||||
|
||||
err := v.deleteSnapshotFromLocalDB(snapshotID)
|
||||
if err != nil {
|
||||
log.Error("Failed to delete from local database", "snapshot_id", snapshotID, "error", err)
|
||||
}
|
||||
if err := v.deleteRemoteSnapshotByKey(snapshot.RemoteSnapshotKey(snapshotID)); err != nil {
|
||||
|
||||
err = v.deleteRemoteSnapshotByKey(snapshot.RemoteSnapshotKey(snapshotID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting snapshot %s from remote: %w", snapshotID, err)
|
||||
}
|
||||
}
|
||||
@@ -711,7 +767,8 @@ func (v *Vaultik) confirmAndExecutePurge(toDelete []SnapshotInfo, force, quiet b
|
||||
// separate command after a purge. Guarded against nil for tests
|
||||
// that don't wire up a SnapshotManager.
|
||||
if v.SnapshotManager != nil {
|
||||
if err := v.SnapshotManager.CleanupOrphanedData(v.ctx); err != nil {
|
||||
err := v.SnapshotManager.CleanupOrphanedData(v.ctx)
|
||||
if err != nil {
|
||||
log.Warn("Failed to clean up orphaned local data after purge", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -730,6 +787,7 @@ func (v *Vaultik) VerifySnapshot(snapshotID string, deep bool) error {
|
||||
if deep {
|
||||
return v.RunDeepVerify(snapshotID, opts)
|
||||
}
|
||||
|
||||
return v.VerifySnapshotWithOptions(snapshotID, opts)
|
||||
}
|
||||
|
||||
@@ -740,6 +798,7 @@ func (v *Vaultik) VerifySnapshotWithOptions(snapshotID string, opts *VerifyOptio
|
||||
if opts.Deep {
|
||||
return v.RunDeepVerify(snapshotID, opts)
|
||||
}
|
||||
|
||||
result := &VerifyResult{
|
||||
SnapshotID: snapshotID,
|
||||
Mode: "shallow",
|
||||
@@ -754,8 +813,10 @@ func (v *Vaultik) VerifySnapshotWithOptions(snapshotID string, opts *VerifyOptio
|
||||
if opts.JSON {
|
||||
result.Status = "failed"
|
||||
result.ErrorMessage = fmt.Sprintf("downloading manifest: %v", err)
|
||||
|
||||
return v.outputVerifyJSON(result)
|
||||
}
|
||||
|
||||
return fmt.Errorf("downloading manifest: %w", err)
|
||||
}
|
||||
|
||||
@@ -766,11 +827,13 @@ func (v *Vaultik) VerifySnapshotWithOptions(snapshotID string, opts *VerifyOptio
|
||||
v.printfStdout("Snapshot information:\n")
|
||||
v.printfStdout(" Blob count: %d\n", manifest.BlobCount)
|
||||
v.printfStdout(" Total size: %s\n", humanize.Bytes(uint64(manifest.TotalCompressedSize)))
|
||||
|
||||
if manifest.Timestamp != "" {
|
||||
if t, err := time.Parse(time.RFC3339, manifest.Timestamp); err == nil {
|
||||
v.printfStdout(" Created: %s\n", t.Format("2006-01-02 15:04:05 MST"))
|
||||
}
|
||||
}
|
||||
|
||||
v.printlnStdout()
|
||||
|
||||
// Check each blob exists
|
||||
@@ -792,9 +855,11 @@ func (v *Vaultik) printVerifyHeader(snapshotID string, opts *VerifyOptions) {
|
||||
|
||||
if !opts.JSON {
|
||||
v.printfStdout("Verifying snapshot %s\n", snapshotID)
|
||||
|
||||
if !snapshotTime.IsZero() {
|
||||
v.printfStdout("Snapshot time: %s\n", snapshotTime.Format("2006-01-02 15:04:05 MST"))
|
||||
}
|
||||
|
||||
v.printlnStdout()
|
||||
}
|
||||
}
|
||||
@@ -810,12 +875,14 @@ func (v *Vaultik) verifyManifestBlobsExist(manifest *snapshot.Manifest, opts *Ve
|
||||
if !opts.JSON {
|
||||
v.printfStdout(" Missing: %s (%s)\n", blob.Hash, humanize.Bytes(uint64(blob.CompressedSize)))
|
||||
}
|
||||
|
||||
missing++
|
||||
missingSize += blob.CompressedSize
|
||||
} else {
|
||||
verified++
|
||||
}
|
||||
}
|
||||
|
||||
return verified, missing, missingSize
|
||||
}
|
||||
|
||||
@@ -828,22 +895,28 @@ func (v *Vaultik) formatVerifyResult(result *VerifyResult, manifest *snapshot.Ma
|
||||
} else {
|
||||
result.Status = "ok"
|
||||
}
|
||||
|
||||
return v.outputVerifyJSON(result)
|
||||
}
|
||||
|
||||
v.printfStdout("\nVerification complete:\n")
|
||||
v.printfStdout(" Verified: %d blobs (%s)\n", result.Verified,
|
||||
humanize.Bytes(uint64(manifest.TotalCompressedSize-result.MissingSize)))
|
||||
|
||||
if result.Missing > 0 {
|
||||
v.printfStdout(" Missing: %d blobs (%s)\n", result.Missing, humanize.Bytes(uint64(result.MissingSize)))
|
||||
} else {
|
||||
v.printfStdout(" Missing: 0 blobs\n")
|
||||
}
|
||||
|
||||
v.printfStdout(" Status: ")
|
||||
|
||||
if result.Missing > 0 {
|
||||
v.printfStdout("FAILED - %d blobs are missing\n", result.Missing)
|
||||
|
||||
return fmt.Errorf("%d blobs are missing", result.Missing)
|
||||
}
|
||||
|
||||
v.printfStdout("OK - All blobs verified\n")
|
||||
|
||||
return nil
|
||||
@@ -853,12 +926,16 @@ func (v *Vaultik) formatVerifyResult(result *VerifyResult, manifest *snapshot.Ma
|
||||
func (v *Vaultik) outputVerifyJSON(result *VerifyResult) error {
|
||||
encoder := json.NewEncoder(v.Stdout)
|
||||
encoder.SetIndent("", " ")
|
||||
if err := encoder.Encode(result); err != nil {
|
||||
|
||||
err := encoder.Encode(result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding JSON: %w", err)
|
||||
}
|
||||
|
||||
if result.Status == "failed" {
|
||||
return fmt.Errorf("verification failed: %s", result.ErrorMessage)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -871,10 +948,12 @@ func (v *Vaultik) CleanupLocalSnapshots() error {
|
||||
if err := v.EnsureStorageBinding(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteKeys, err := v.listAllRemoteSnapshotKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteSet := make(map[string]bool, len(remoteKeys))
|
||||
for _, k := range remoteKeys {
|
||||
remoteSet[k] = true
|
||||
@@ -886,14 +965,19 @@ func (v *Vaultik) CleanupLocalSnapshots() error {
|
||||
}
|
||||
|
||||
var removed int
|
||||
|
||||
for _, snap := range localSnapshots {
|
||||
id := snap.ID.String()
|
||||
if !remoteSet[snapshot.RemoteSnapshotKey(id)] {
|
||||
v.printfStdout("Removing stale local record: %s\n", id)
|
||||
if err := v.deleteSnapshotFromLocalDB(id); err != nil {
|
||||
|
||||
err := v.deleteSnapshotFromLocalDB(id)
|
||||
if err != nil {
|
||||
log.Error("Failed to delete local snapshot", "snapshot_id", id, "error", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
removed++
|
||||
}
|
||||
}
|
||||
@@ -903,6 +987,7 @@ func (v *Vaultik) CleanupLocalSnapshots() error {
|
||||
} else {
|
||||
v.printfStdout("Removed %d stale local snapshot record(s).\n", removed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -948,6 +1033,7 @@ func (v *Vaultik) syncWithRemote() error {
|
||||
if strings.HasPrefix(parts[1], ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
remoteSnapshots[parts[1]] = true
|
||||
}
|
||||
}
|
||||
@@ -962,11 +1048,14 @@ func (v *Vaultik) syncWithRemote() error {
|
||||
|
||||
// Remove local snapshots that don't exist remotely
|
||||
removedCount := 0
|
||||
|
||||
for _, snapshot := range localSnapshots {
|
||||
snapshotIDStr := snapshot.ID.String()
|
||||
if !remoteSnapshots[snapshotIDStr] {
|
||||
log.Info("Removing local snapshot not found in remote", "snapshot_id", snapshot.ID)
|
||||
if err := v.deleteSnapshotFromLocalDB(snapshotIDStr); err != nil {
|
||||
|
||||
err := v.deleteSnapshotFromLocalDB(snapshotIDStr)
|
||||
if err != nil {
|
||||
log.Error("Failed to delete local snapshot", "snapshot_id", snapshot.ID, "error", err)
|
||||
} else {
|
||||
removedCount++
|
||||
@@ -1015,22 +1104,28 @@ func (v *Vaultik) RemoveSnapshot(snapshotID string, opts *RemoveOptions) (*Remov
|
||||
SnapshotID: snapshotID,
|
||||
}
|
||||
|
||||
if err := v.EnsureStorageBinding(); err != nil {
|
||||
err := v.EnsureStorageBinding()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
if opts.DryRun {
|
||||
result.DryRun = true
|
||||
|
||||
if !opts.JSON {
|
||||
v.printfStdout("Would remove snapshot: %s\n", snapshotID)
|
||||
|
||||
if !opts.LocalOnly {
|
||||
v.printlnStdout("Would also remove snapshot metadata from remote storage (blobs untouched)")
|
||||
}
|
||||
|
||||
v.printlnStdout("[Dry run - no changes made]")
|
||||
}
|
||||
|
||||
if opts.JSON {
|
||||
return result, v.outputRemoveJSON(result)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -1040,32 +1135,41 @@ func (v *Vaultik) RemoveSnapshot(snapshotID string, opts *RemoveOptions) (*Remov
|
||||
} else {
|
||||
v.printfStdout("Remove snapshot '%s' from local database AND its metadata from remote storage? [y/N] ", snapshotID)
|
||||
}
|
||||
|
||||
var confirm string
|
||||
if _, err := v.scanStdin(&confirm); err != nil {
|
||||
v.printlnStdout("Cancelled")
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if strings.ToLower(confirm) != "y" {
|
||||
v.printlnStdout("Cancelled")
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Removing snapshot from local database", "snapshot_id", snapshotID)
|
||||
|
||||
if err := v.deleteSnapshotFromLocalDB(snapshotID); err != nil {
|
||||
err = v.deleteSnapshotFromLocalDB(snapshotID)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("removing from local database: %w", err)
|
||||
}
|
||||
|
||||
if !opts.LocalOnly {
|
||||
log.Info("Removing snapshot metadata from remote storage", "snapshot_id", snapshotID)
|
||||
|
||||
remoteKey := snapshot.RemoteSnapshotKey(snapshotID)
|
||||
if err := v.deleteRemoteSnapshotByKey(remoteKey); err != nil {
|
||||
|
||||
err := v.deleteRemoteSnapshotByKey(remoteKey)
|
||||
if err != nil {
|
||||
// Warn-and-proceed: the local-DB removal has already
|
||||
// happened, so let the user know the remote half didn't
|
||||
// finish and they can retry with `vaultik prune` once the
|
||||
// destination store is reachable.
|
||||
log.Warn("Could not remove snapshot metadata from remote storage", "error", err)
|
||||
|
||||
if v.UI != nil {
|
||||
v.UI.Warning("Could not remove snapshot metadata from remote: %v. Run '%s' once the remote is reachable to finish cleanup.", err, pruneCommandHint)
|
||||
}
|
||||
@@ -1075,7 +1179,8 @@ func (v *Vaultik) RemoveSnapshot(snapshotID string, opts *RemoveOptions) (*Remov
|
||||
}
|
||||
|
||||
if v.SnapshotManager != nil {
|
||||
if err := v.SnapshotManager.CleanupOrphanedData(v.ctx); err != nil {
|
||||
err := v.SnapshotManager.CleanupOrphanedData(v.ctx)
|
||||
if err != nil {
|
||||
log.Warn("Failed to clean up orphaned local data after removal", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -1085,6 +1190,7 @@ func (v *Vaultik) RemoveSnapshot(snapshotID string, opts *RemoveOptions) (*Remov
|
||||
}
|
||||
|
||||
v.printfStdout("Removed snapshot '%s' from local database\n", snapshotID)
|
||||
|
||||
if !opts.LocalOnly && result.RemoteRemoved {
|
||||
v.printlnStdout("Removed snapshot metadata from remote storage")
|
||||
v.printfStdout("\nNote: The removed snapshot's blobs remain on the remote. Run '%s' to delete any blobs no longer referenced by any remaining remote snapshot.\n", pruneCommandHint)
|
||||
@@ -1102,6 +1208,7 @@ func (v *Vaultik) RemoveAllSnapshots(opts *RemoveOptions) (*RemoveResult, error)
|
||||
if err := v.EnsureStorageBinding(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localSnaps, err := v.localSnapshotIDs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing local snapshots: %w", err)
|
||||
@@ -1124,7 +1231,9 @@ func (v *Vaultik) RemoveAllSnapshots(opts *RemoveOptions) (*RemoveResult, error)
|
||||
for _, id := range localSnaps {
|
||||
knownLocalKeys[snapshot.RemoteSnapshotKey(id)] = id
|
||||
}
|
||||
|
||||
var orphanRemoteKeys []string
|
||||
|
||||
for _, key := range remoteKeys {
|
||||
if _, known := knownLocalKeys[key]; !known {
|
||||
orphanRemoteKeys = append(orphanRemoteKeys, key)
|
||||
@@ -1135,6 +1244,7 @@ func (v *Vaultik) RemoveAllSnapshots(opts *RemoveOptions) (*RemoveResult, error)
|
||||
if !opts.JSON {
|
||||
v.printlnStdout("No snapshots found")
|
||||
}
|
||||
|
||||
return &RemoveResult{}, nil
|
||||
}
|
||||
|
||||
@@ -1152,15 +1262,19 @@ func (v *Vaultik) localSnapshotIDs() ([]string, error) {
|
||||
if v.Repositories == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
snaps, err := v.Repositories.Snapshots.ListRecent(v.ctx, 100000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids := make([]string, 0, len(snaps))
|
||||
for _, s := range snaps {
|
||||
ids = append(ids, s.ID.String())
|
||||
}
|
||||
|
||||
sort.Strings(ids)
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
@@ -1171,10 +1285,13 @@ func (v *Vaultik) localSnapshotIDs() ([]string, error) {
|
||||
// fatal.
|
||||
func (v *Vaultik) listAllRemoteSnapshotKeys() ([]string, error) {
|
||||
log.Info("Listing all remote snapshots")
|
||||
|
||||
objectCh := v.Storage.ListStream(v.ctx, "metadata/")
|
||||
|
||||
seen := make(map[string]bool)
|
||||
|
||||
var keys []string
|
||||
|
||||
for object := range objectCh {
|
||||
if object.Err != nil {
|
||||
return nil, fmt.Errorf("listing remote snapshots: %w", object.Err)
|
||||
@@ -1186,6 +1303,7 @@ func (v *Vaultik) listAllRemoteSnapshotKeys() ([]string, error) {
|
||||
if strings.HasPrefix(parts[1], ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasSuffix(object.Key, "/") || strings.Contains(object.Key, "/manifest.json.zst") {
|
||||
key := parts[1]
|
||||
if !seen[key] {
|
||||
@@ -1202,18 +1320,23 @@ func (v *Vaultik) listAllRemoteSnapshotKeys() ([]string, error) {
|
||||
// handleRemoveAllDryRun handles the dry-run mode for removing all snapshots
|
||||
func (v *Vaultik) handleRemoveAllDryRun(localSnaps, orphanRemoteKeys []string, opts *RemoveOptions) (*RemoveResult, error) {
|
||||
result := &RemoveResult{DryRun: true}
|
||||
|
||||
result.SnapshotsRemoved = append(result.SnapshotsRemoved, localSnaps...)
|
||||
if !opts.LocalOnly {
|
||||
result.SnapshotsRemoved = append(result.SnapshotsRemoved, orphanRemoteKeys...)
|
||||
}
|
||||
|
||||
if !opts.JSON {
|
||||
v.printfStdout("Would remove %d local snapshot(s):\n", len(localSnaps))
|
||||
|
||||
for _, id := range localSnaps {
|
||||
v.printfStdout(" %s\n", id)
|
||||
}
|
||||
|
||||
if !opts.LocalOnly {
|
||||
if len(orphanRemoteKeys) > 0 {
|
||||
v.printfStdout("Would also remove %d orphan remote snapshot key(s):\n", len(orphanRemoteKeys))
|
||||
|
||||
for _, key := range orphanRemoteKeys {
|
||||
v.printfStdout(" %s\n", key)
|
||||
}
|
||||
@@ -1221,11 +1344,14 @@ func (v *Vaultik) handleRemoveAllDryRun(localSnaps, orphanRemoteKeys []string, o
|
||||
v.printlnStdout("Would also remove snapshot metadata from remote storage (blobs untouched)")
|
||||
}
|
||||
}
|
||||
|
||||
v.printlnStdout("[Dry run - no changes made]")
|
||||
}
|
||||
|
||||
if opts.JSON {
|
||||
return result, v.outputRemoveJSON(result)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -1235,24 +1361,29 @@ func (v *Vaultik) handleRemoveAllDryRun(localSnaps, orphanRemoteKeys []string, o
|
||||
// hint is the next step.
|
||||
func (v *Vaultik) executeRemoveAll(localSnaps, orphanRemoteKeys []string, opts *RemoveOptions) (*RemoveResult, error) {
|
||||
if !opts.Force {
|
||||
return nil, fmt.Errorf("--all requires --force")
|
||||
return nil, errors.New("--all requires --force")
|
||||
}
|
||||
|
||||
log.Info("Removing all snapshots", "local_count", len(localSnaps), "orphan_remote_count", len(orphanRemoteKeys))
|
||||
|
||||
result := &RemoveResult{}
|
||||
remoteErrors := 0
|
||||
|
||||
for _, snapshotID := range localSnaps {
|
||||
log.Info("Removing snapshot", "snapshot_id", snapshotID)
|
||||
|
||||
if err := v.deleteSnapshotFromLocalDB(snapshotID); err != nil {
|
||||
err := v.deleteSnapshotFromLocalDB(snapshotID)
|
||||
if err != nil {
|
||||
log.Error("Failed to remove from local database", "snapshot_id", snapshotID, "error", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !opts.LocalOnly {
|
||||
if err := v.deleteRemoteSnapshotByKey(snapshot.RemoteSnapshotKey(snapshotID)); err != nil {
|
||||
err := v.deleteRemoteSnapshotByKey(snapshot.RemoteSnapshotKey(snapshotID))
|
||||
if err != nil {
|
||||
log.Warn("Failed to remove snapshot metadata from remote", "snapshot_id", snapshotID, "error", err)
|
||||
|
||||
remoteErrors++
|
||||
}
|
||||
}
|
||||
@@ -1263,11 +1394,16 @@ func (v *Vaultik) executeRemoveAll(localSnaps, orphanRemoteKeys []string, opts *
|
||||
if !opts.LocalOnly {
|
||||
for _, key := range orphanRemoteKeys {
|
||||
log.Info("Removing orphan remote snapshot", "remote_key", key)
|
||||
if err := v.deleteRemoteSnapshotByKey(key); err != nil {
|
||||
|
||||
err := v.deleteRemoteSnapshotByKey(key)
|
||||
if err != nil {
|
||||
log.Warn("Failed to remove orphan from remote", "remote_key", key, "error", err)
|
||||
|
||||
remoteErrors++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
result.SnapshotsRemoved = append(result.SnapshotsRemoved, key)
|
||||
}
|
||||
|
||||
@@ -1279,7 +1415,8 @@ func (v *Vaultik) executeRemoveAll(localSnaps, orphanRemoteKeys []string, opts *
|
||||
}
|
||||
|
||||
if v.SnapshotManager != nil {
|
||||
if err := v.SnapshotManager.CleanupOrphanedData(v.ctx); err != nil {
|
||||
err := v.SnapshotManager.CleanupOrphanedData(v.ctx)
|
||||
if err != nil {
|
||||
log.Warn("Failed to clean up orphaned local data after bulk removal", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -1289,6 +1426,7 @@ func (v *Vaultik) executeRemoveAll(localSnaps, orphanRemoteKeys []string, opts *
|
||||
}
|
||||
|
||||
v.printfStdout("Removed %d snapshot(s)\n", len(result.SnapshotsRemoved))
|
||||
|
||||
if !opts.LocalOnly && result.RemoteRemoved {
|
||||
v.printlnStdout("Removed snapshot metadata from remote storage")
|
||||
v.printfStdout("\nNote: Removed snapshots' blobs remain on the remote. Run '%s' to delete any blobs no longer referenced by any remaining remote snapshot.\n", pruneCommandHint)
|
||||
@@ -1304,16 +1442,23 @@ 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 {
|
||||
err := v.Repositories.Snapshots.DeleteSnapshotFiles(v.ctx, snapshotID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting snapshot files for %s: %w", snapshotID, err)
|
||||
}
|
||||
if err := v.Repositories.Snapshots.DeleteSnapshotBlobs(v.ctx, snapshotID); err != nil {
|
||||
|
||||
err = v.Repositories.Snapshots.DeleteSnapshotBlobs(v.ctx, snapshotID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting snapshot blobs for %s: %w", snapshotID, err)
|
||||
}
|
||||
if err := v.Repositories.Snapshots.DeleteSnapshotUploads(v.ctx, snapshotID); err != nil {
|
||||
|
||||
err = v.Repositories.Snapshots.DeleteSnapshotUploads(v.ctx, snapshotID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting snapshot uploads for %s: %w", snapshotID, err)
|
||||
}
|
||||
if err := v.Repositories.Snapshots.Delete(v.ctx, snapshotID); err != nil {
|
||||
|
||||
err = v.Repositories.Snapshots.Delete(v.ctx, snapshotID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting snapshot record %s: %w", snapshotID, err)
|
||||
}
|
||||
|
||||
@@ -1330,17 +1475,21 @@ func (v *Vaultik) deleteRemoteSnapshotByKey(remoteKey string) error {
|
||||
objectCh := v.Storage.ListStream(v.ctx, prefix)
|
||||
|
||||
var objectsToDelete []string
|
||||
|
||||
for object := range objectCh {
|
||||
if object.Err != nil {
|
||||
return fmt.Errorf("listing objects: %w", object.Err)
|
||||
}
|
||||
|
||||
objectsToDelete = append(objectsToDelete, object.Key)
|
||||
}
|
||||
|
||||
for _, key := range objectsToDelete {
|
||||
if err := v.Storage.Delete(v.ctx, key); err != nil {
|
||||
err := v.Storage.Delete(v.ctx, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("removing %s: %w", key, err)
|
||||
}
|
||||
|
||||
log.Debug("Deleted remote object", "key", key)
|
||||
}
|
||||
|
||||
@@ -1351,6 +1500,7 @@ func (v *Vaultik) deleteRemoteSnapshotByKey(remoteKey string) error {
|
||||
func (v *Vaultik) outputRemoveJSON(result *RemoveResult) error {
|
||||
encoder := json.NewEncoder(v.Stdout)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
return encoder.Encode(result)
|
||||
}
|
||||
|
||||
@@ -1384,16 +1534,23 @@ func (v *Vaultik) PruneDatabase() (*PruneResult, error) {
|
||||
snapshotIDStr := snapshot.ID.String()
|
||||
log.Info("Deleting incomplete snapshot", "snapshot_id", snapshot.ID)
|
||||
// Delete related records first
|
||||
if err := v.Repositories.Snapshots.DeleteSnapshotFiles(v.ctx, snapshotIDStr); err != nil {
|
||||
err := v.Repositories.Snapshots.DeleteSnapshotFiles(v.ctx, snapshotIDStr)
|
||||
if err != nil {
|
||||
log.Error("Failed to delete snapshot files", "snapshot_id", snapshot.ID, "error", err)
|
||||
}
|
||||
if err := v.Repositories.Snapshots.DeleteSnapshotBlobs(v.ctx, snapshotIDStr); err != nil {
|
||||
|
||||
err = v.Repositories.Snapshots.DeleteSnapshotBlobs(v.ctx, snapshotIDStr)
|
||||
if err != nil {
|
||||
log.Error("Failed to delete snapshot blobs", "snapshot_id", snapshot.ID, "error", err)
|
||||
}
|
||||
if err := v.Repositories.Snapshots.DeleteSnapshotUploads(v.ctx, snapshotIDStr); err != nil {
|
||||
|
||||
err = v.Repositories.Snapshots.DeleteSnapshotUploads(v.ctx, snapshotIDStr)
|
||||
if err != nil {
|
||||
log.Error("Failed to delete snapshot uploads", "snapshot_id", snapshot.ID, "error", err)
|
||||
}
|
||||
if err := v.Repositories.Snapshots.Delete(v.ctx, snapshotIDStr); err != nil {
|
||||
|
||||
err = v.Repositories.Snapshots.Delete(v.ctx, snapshotIDStr)
|
||||
if err != nil {
|
||||
log.Error("Failed to delete snapshot", "snapshot_id", snapshot.ID, "error", err)
|
||||
} else {
|
||||
result.SnapshotsDeleted++
|
||||
@@ -1427,6 +1584,7 @@ func (v *Vaultik) PruneDatabase() (*PruneResult, error) {
|
||||
)
|
||||
|
||||
snapshotCountAfter := snapshotCountBefore - result.SnapshotsDeleted
|
||||
|
||||
v.UI.Complete("Pruned local index database.")
|
||||
v.UI.Detail("Incomplete snapshots: %d removed (%d remain).", result.SnapshotsDeleted, snapshotCountAfter)
|
||||
v.UI.Detail("Orphaned files: %d removed (%d remain).", result.FilesDeleted, fileCountAfter)
|
||||
@@ -1451,10 +1609,13 @@ func (v *Vaultik) getTableCount(tableName string) (int64, error) {
|
||||
}
|
||||
|
||||
var count int64
|
||||
query := fmt.Sprintf("SELECT COUNT(*) FROM %s", tableName)
|
||||
|
||||
query := "SELECT COUNT(*) FROM " + tableName
|
||||
|
||||
err := v.DB.Conn().QueryRowContext(v.ctx, query).Scan(&count)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user