restic: RunUnlock + RunStats (raw-data mode)

Add RunUnlock (delegates straight to runWithPump) and RunStats which
runs `restic stats --json --mode raw-data`, captures the single JSON
line from stdout into RepoStats, and returns an error if no JSON
arrives.  Tests cover arg plumbing for unlock, JSON parsing, and the
no-JSON error path.
This commit is contained in:
2026-05-03 22:22:19 +01:00
parent f3eaf511be
commit 49fd3f4441
2 changed files with 100 additions and 0 deletions
+47
View File
@@ -293,6 +293,53 @@ func runWithPump(cmd *exec.Cmd, handle LineHandler) error {
return nil
}
// RunUnlock executes `restic unlock`. Returns nil on a clean exit.
func (e Env) RunUnlock(ctx context.Context, handle LineHandler) error {
cmd := exec.CommandContext(ctx, e.Bin, "unlock")
cmd.Env = e.envSlice()
cmd.Dir = e.WorkDir
return runWithPump(cmd, handle)
}
// RepoStats mirrors `restic stats --json --mode raw-data` output.
type RepoStats struct {
TotalSize int64 `json:"total_size"`
TotalUncompressed int64 `json:"total_uncompressed_size"`
SnapshotsCount int64 `json:"snapshots_count"`
TotalFileCount int64 `json:"total_file_count"`
TotalBlobCount int64 `json:"total_blob_count"`
}
// RunStats executes `restic stats --json --mode raw-data` and parses
// the (single-line) JSON response. Tees raw output to handle so the
// caller can still log it. Returns an error if no JSON-shaped line
// arrived on stdout.
func (e Env) RunStats(ctx context.Context, handle LineHandler) (*RepoStats, error) {
cmd := exec.CommandContext(ctx, e.Bin, "stats", "--json", "--mode", "raw-data")
cmd.Env = e.envSlice()
cmd.Dir = e.WorkDir
var out *RepoStats
capture := func(stream, line string, ev any) {
if stream == "stdout" && strings.HasPrefix(line, "{") {
var s RepoStats
if json.Unmarshal([]byte(line), &s) == nil {
cp := s
out = &cp
}
}
if handle != nil {
handle(stream, line, ev)
}
}
if err := runWithPump(cmd, capture); err != nil {
return nil, err
}
if out == nil {
return nil, fmt.Errorf("restic stats: no JSON in output")
}
return out, nil
}
// CheckResult summarizes a `restic check` invocation. LockPresent is
// true if the stderr stream contained a stale-lock signal (caller is
// expected to surface this in the UI so the operator can run unlock).