Add snapshot files to list a snapshot's files without restoring it (closes #435)
check / check (push) Failing after 22m48s
check / check (push) Failing after 22m48s
`vaultik snapshot files <snapshot-id> [path] [--json]` downloads and decrypts the snapshot database, as restore does, and prints mode, size, modification time and path for every file at or under the path. A path that matches nothing is an error. It needs the private key. The mode is in Go's file-mode notation, which the README spells out. The table quotes a path that holds a non-printable character. Named `files` because `ls` is already the alias of `snapshot list`. The missing-key error no longer says "for restore", since this command and deep verify return it too. The README opening now states the private-key rule first. Model: opus-5-5
This commit is contained in:
1 parent
5ca0d46932
commit
88f2ea6f06
14 files changed
+552
-82
No files matched your search
@@ -0,0 +1,115 @@
|
||||
package vaultik
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/vaultik/internal/database"
|
||||
"sneak.berlin/go/vaultik/internal/ui"
|
||||
)
|
||||
|
||||
// ListedFile is one entry printed by `snapshot files`. Mode is the Go
|
||||
// file mode string, which shows the entry type and the permission bits
|
||||
// (for example -rw-r--r-- or drwxr-xr-x).
|
||||
type ListedFile struct {
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
Mode string `json:"mode"`
|
||||
MTime time.Time `json:"mtime"`
|
||||
}
|
||||
|
||||
// ListSnapshotFiles prints the path, size, mode and modification time of
|
||||
// every file the snapshot holds at or under path, or of every file when
|
||||
// path is empty. The file list is only in the snapshot's encrypted
|
||||
// database, so this downloads and decrypts it as restore does, and needs
|
||||
// the private key.
|
||||
func (v *Vaultik) ListSnapshotFiles(snapshotID, path string, jsonOutput bool) error {
|
||||
identities, err := v.restoreIdentities()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteKey, err := v.resolveSnapshotRemoteKey(snapshotID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, tempDir, err := v.downloadSnapshotDB(snapshotID, remoteKey, identities)
|
||||
if errors.Is(err, errWrongPrivateKey) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("downloading snapshot database: %w", err)
|
||||
}
|
||||
|
||||
defer v.closeSnapshotDB(db, tempDir)
|
||||
|
||||
repos := database.NewRepositories(db)
|
||||
|
||||
var files []*database.File
|
||||
|
||||
if path == "" {
|
||||
files, err = repos.Files.ListAll(v.ctx)
|
||||
} else {
|
||||
files, err = repos.Files.ListUnderPath(v.ctx, filepath.Clean(path))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing files: %w", err)
|
||||
}
|
||||
|
||||
if path != "" && len(files) == 0 {
|
||||
return fmt.Errorf("%w: %s", errPathsNotInSnapshot, path)
|
||||
}
|
||||
|
||||
entries := make([]ListedFile, 0, len(files))
|
||||
|
||||
for _, file := range files {
|
||||
entries = append(entries, ListedFile{
|
||||
Path: file.Path.String(),
|
||||
Size: file.Size,
|
||||
Mode: os.FileMode(file.Mode).String(),
|
||||
MTime: file.MTime,
|
||||
})
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
encoder := json.NewEncoder(v.Stdout)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
return encoder.Encode(entries)
|
||||
}
|
||||
|
||||
if v.UI.Quiet() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return v.printListedFileTable(entries)
|
||||
}
|
||||
|
||||
// printListedFileTable writes the entries as a plain table to stdout.
|
||||
func (v *Vaultik) printListedFileTable(entries []ListedFile) error {
|
||||
w := tabwriter.NewWriter(v.Stdout, 0, 0, tabPadding, ' ', 0)
|
||||
|
||||
_, err := fmt.Fprintln(w, "MODE\tSIZE\tMODIFIED\tPATH")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
_, err = fmt.Fprintf(w, "%s\t%d\t%s\t%s\n",
|
||||
entry.Mode, entry.Size,
|
||||
entry.MTime.Format(time.RFC3339), ui.QuoteUnprintable(entry.Path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
Reference in new issue
Block a user