Files
mesh-catalog/modules/nats/internal/busarchive/verifyfile.go
T
jschoubben 98ca97fc5b
mesh/merge-gate head 98ca97fc5b3f merged into trunk dcf6e86d0236 — pass: builds nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) fo…
mesh/repo-check head 98ca97fc5b3f merged into trunk dcf6e86d0236 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
mesh/check failed: ace: novox/mesh-catalog#442: checks/particulars/go.mod cannot be read to check its mesh-sdk pin: Gitea API /repos/novox/mesh-catalo…
Verify the bus's snapshot with a tool, sharing the snapshot program's archive code (issue 1842)
The upgrade and restore steps told a person to docker exec the snapshot program; no mesh tool
did it. nats_snapshot_verify reads the newest bus.tar of the snapshots directory only.
2026-10-22 14:24:25 +02:00

110 lines
3.2 KiB
Go

package busarchive
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
)
// StreamReport is one stream of a verified snapshot.
type StreamReport struct {
Name string `json:"name"`
LastSeq uint64 `json:"last_seq"`
}
// Report is the answer of VerifyFile: which file was held to its manifest, and whether it passed.
type Report struct {
File string `json:"file"`
Modified time.Time `json:"modified"`
Size int64 `json:"size"`
Taken *time.Time `json:"taken,omitempty"`
Streams []StreamReport `json:"streams"`
Skipped []Skipped `json:"skipped,omitempty"`
Passed bool `json:"passed"`
// Fault is the first thing wrong, empty when the snapshot passed.
Fault string `json:"fault,omitempty"`
}
// VerifyFile verifies one snapshot inside dir: the file called name, or the newest *.tar when name is
// empty. Nothing outside dir is read and nothing is written there; a snapshot that fails is an answer
// (Passed false, the first Fault), and only a directory without a snapshot is an error.
func VerifyFile(dir, name string) (*Report, error) {
path, info, err := pick(dir, name)
if err != nil {
return nil, err
}
r := &Report{File: filepath.Base(path), Modified: info.ModTime().UTC(), Size: info.Size(), Streams: []StreamReport{}}
f, err := os.Open(path)
if err != nil {
r.Fault = err.Error()
return r, nil
}
defer f.Close()
a, err := ReadArchive(f, "")
if err != nil {
r.Fault = err.Error()
return r, nil
}
defer a.Close()
taken := a.Manifest.Finished
r.Taken = &taken
r.Skipped = a.Manifest.Skipped
for _, s := range a.Manifest.Streams {
r.Streams = append(r.Streams, StreamReport{Name: s.Name, LastSeq: s.LastSeq})
}
if err := a.Verify(); err != nil {
r.Fault = err.Error()
return r, nil
}
r.Passed = true
return r, nil
}
// pick names the regular file to verify, and nothing outside dir: a name is one path component, and a
// symbolic link is not followed out of the directory.
func pick(dir, name string) (string, fs.FileInfo, error) {
if dir == "" {
return "", nil, errors.New("the snapshots directory is not set")
}
if name != "" {
if name != filepath.Base(name) || name == "." || name == ".." || strings.ContainsRune(name, 0) {
return "", nil, fmt.Errorf("%q is not a file name inside the snapshots directory", name)
}
p := filepath.Join(dir, name)
info, err := os.Lstat(p)
if err != nil {
return "", nil, fmt.Errorf("no snapshot %s in the snapshots directory: %w", name, err)
}
if !info.Mode().IsRegular() {
return "", nil, fmt.Errorf("%s is not a regular file", name)
}
return p, info, nil
}
entries, err := os.ReadDir(dir)
if err != nil {
return "", nil, fmt.Errorf("the snapshots directory does not read: %w", err)
}
var best string
var bestInfo fs.FileInfo
for _, e := range entries {
if !strings.HasSuffix(e.Name(), ".tar") {
continue
}
info, err := e.Info()
if err != nil || !info.Mode().IsRegular() {
continue
}
if bestInfo == nil || info.ModTime().After(bestInfo.ModTime()) {
best, bestInfo = e.Name(), info
}
}
if bestInfo == nil {
return "", nil, errors.New("the snapshots directory holds no .tar snapshot")
}
return filepath.Join(dir, best), bestInfo, nil
}