Files
kopia/cli/command_index_inspect.go
Julio Lopez 7db061ee71 build(deps): Go 1.25 (#4987)
Upgrade to Go 1.25
Leverage `WaitGroup.Go` in Go 1.25
2025-11-17 16:42:12 -08:00

168 lines
4.2 KiB
Go

package cli
import (
"context"
"slices"
"sync"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"github.com/kopia/kopia/internal/gather"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/blob"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/content/indexblob"
)
type commandIndexInspect struct {
all bool
active bool
blobIDs []string
contentIDs []string
parallel int
out textOutput
}
func (c *commandIndexInspect) setup(svc appServices, parent commandParent) {
cmd := parent.Command("inspect", "Inspect index blob")
cmd.Flag("all", "Inspect all index blobs in the repository, including inactive").BoolVar(&c.all)
cmd.Flag("active", "Inspect all active index blobs").BoolVar(&c.active)
cmd.Flag("content-id", "Inspect all active index blobs").StringsVar(&c.contentIDs)
cmd.Flag("parallel", "Parallelism").Default("8").IntVar(&c.parallel)
cmd.Arg("blobs", "Names of index blobs to inspect").StringsVar(&c.blobIDs)
cmd.Action(svc.directRepositoryReadAction(c.run))
c.out.setup(svc)
}
func (c *commandIndexInspect) run(ctx context.Context, rep repo.DirectRepository) error {
output := make(chan indexBlobPlusContentInfo)
var wg sync.WaitGroup
wg.Go(func() {
c.dumpIndexBlobEntries(output)
})
err := c.runWithOutput(ctx, rep, output)
close(output)
wg.Wait()
return err
}
func (c *commandIndexInspect) runWithOutput(ctx context.Context, rep repo.DirectRepository, output chan indexBlobPlusContentInfo) error {
switch {
case c.all:
return c.inspectAllBlobs(ctx, rep, true, output)
case c.active:
return c.inspectAllBlobs(ctx, rep, false, output)
case len(c.blobIDs) > 0:
for _, indexBlobID := range c.blobIDs {
if err := c.inspectSingleIndexBlob(ctx, rep, blob.ID(indexBlobID), output); err != nil {
return err
}
}
default:
return errors.New("must pass either --all, --active or provide a list of blob IDs to inspect")
}
return nil
}
func (c *commandIndexInspect) inspectAllBlobs(ctx context.Context, rep repo.DirectRepository, includeInactive bool, output chan indexBlobPlusContentInfo) error {
indexes, err := rep.IndexBlobs(ctx, includeInactive)
if err != nil {
return errors.Wrap(err, "error listing index blobs")
}
indexesCh := make(chan indexblob.Metadata, len(indexes))
for _, bm := range indexes {
indexesCh <- bm
}
close(indexesCh)
var eg errgroup.Group
for range c.parallel {
eg.Go(func() error {
for bm := range indexesCh {
if err := c.inspectSingleIndexBlob(ctx, rep, bm.BlobID, output); err != nil {
return err
}
}
return nil
})
}
//nolint:wrapcheck
return eg.Wait()
}
func (c *commandIndexInspect) dumpIndexBlobEntries(entries chan indexBlobPlusContentInfo) {
for ent := range entries {
if !c.shouldInclude(ent.contentInfo) {
continue
}
ci := ent.contentInfo
bm := ent.indexBlob
state := "created"
if ci.Deleted {
state = "deleted"
}
c.out.printStdout("%v %v %v %v %v %v %v %v\n",
formatTimestampPrecise(bm.Timestamp), bm.BlobID,
ci.ContentID, state, formatTimestampPrecise(ci.Timestamp()), ci.PackBlobID, ci.PackOffset, ci.PackedLength)
}
}
func (c *commandIndexInspect) shouldInclude(ci content.Info) bool {
if len(c.contentIDs) == 0 {
return true
}
contentID := ci.ContentID.String()
return slices.Contains(c.contentIDs, contentID)
}
type indexBlobPlusContentInfo struct {
indexBlob blob.Metadata
contentInfo content.Info
}
func (c *commandIndexInspect) inspectSingleIndexBlob(ctx context.Context, rep repo.DirectRepository, blobID blob.ID, output chan indexBlobPlusContentInfo) error {
log(ctx).Debugf("Inspecting blob %v...", blobID)
bm, err := rep.BlobReader().GetMetadata(ctx, blobID)
if err != nil {
return errors.Wrapf(err, "unable to get metadata for %v", blobID)
}
var data gather.WriteBuffer
defer data.Close()
if err = rep.BlobReader().GetBlob(ctx, blobID, 0, -1, &data); err != nil {
return errors.Wrapf(err, "unable to get data for %v", blobID)
}
entries, err := content.ParseIndexBlob(blobID, data.Bytes(), rep.ContentReader().ContentFormat())
if err != nil {
return errors.Wrapf(err, "unable to recover index from %v", blobID)
}
for _, ent := range entries {
output <- indexBlobPlusContentInfo{bm, ent}
}
return nil
}