corso/src/internal/m365/graph/collections.go
Keepers ce72acbcc1
auto-log recoverable errors with stack (#3598)
automatically log when we add a recoverable error or a skipped item to fault.  This log will include a stack trace of the call from the location of the logged recoverable.  Clues does not have a method for pulling a stack trace out of an error yet; that can be added at a future date.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🤖 Supportability/Tests

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
2023-06-13 22:18:18 +00:00

152 lines
3.6 KiB
Go

package graph
import (
"context"
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/m365/support"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path"
)
var _ data.BackupCollection = prefixCollection{}
// TODO: move this out of graph. /data would be a much better owner
// for a generic struct like this. However, support.StatusUpdater makes
// it difficult to extract from this package in a generic way.
type prefixCollection struct {
full path.Path
prev path.Path
su support.StatusUpdater
state data.CollectionState
}
func (c prefixCollection) Items(ctx context.Context, _ *fault.Bus) <-chan data.Stream {
res := make(chan data.Stream)
close(res)
s := support.CreateStatus(ctx, support.Backup, 0, support.CollectionMetrics{}, "")
c.su(s)
return res
}
func (c prefixCollection) FullPath() path.Path {
return c.full
}
func (c prefixCollection) PreviousPath() path.Path {
return c.prev
}
func (c prefixCollection) State() data.CollectionState {
return c.state
}
func (c prefixCollection) DoNotMergeItems() bool {
return false
}
// ---------------------------------------------------------------------------
// base collections
// ---------------------------------------------------------------------------
func BaseCollections(
ctx context.Context,
colls []data.BackupCollection,
tenant, rOwner string,
service path.ServiceType,
categories map[path.CategoryType]struct{},
su support.StatusUpdater,
errs *fault.Bus,
) ([]data.BackupCollection, error) {
var (
res = []data.BackupCollection{}
el = errs.Local()
lastErr error
collKeys = map[string]struct{}{}
)
// won't catch deleted collections, since they have no FullPath
for _, c := range colls {
if c.FullPath() != nil {
collKeys[c.FullPath().String()] = struct{}{}
}
}
for cat := range categories {
ictx := clues.Add(ctx, "base_service", service, "base_category", cat)
full, err := path.ServicePrefix(tenant, rOwner, service, cat)
if err != nil {
// Shouldn't happen.
err = clues.Wrap(err, "making path").WithClues(ictx)
el.AddRecoverable(ctx, err)
lastErr = err
continue
}
// only add this collection if it doesn't already exist in the set.
if _, ok := collKeys[full.String()]; !ok {
res = append(res, &prefixCollection{
prev: full,
full: full,
su: su,
state: data.StateOf(full, full),
})
}
}
return res, lastErr
}
// ---------------------------------------------------------------------------
// prefix migration
// ---------------------------------------------------------------------------
// Creates a new collection that only handles prefix pathing.
func NewPrefixCollection(
prev, full path.Path,
su support.StatusUpdater,
) (*prefixCollection, error) {
if prev != nil {
if len(prev.Item()) > 0 {
return nil, clues.New("prefix collection previous path contains an item")
}
if len(prev.Folders()) > 0 {
return nil, clues.New("prefix collection previous path contains folders")
}
}
if full != nil {
if len(full.Item()) > 0 {
return nil, clues.New("prefix collection full path contains an item")
}
if len(full.Folders()) > 0 {
return nil, clues.New("prefix collection full path contains folders")
}
}
pc := &prefixCollection{
prev: prev,
full: full,
su: su,
state: data.StateOf(prev, full),
}
if pc.state == data.DeletedState {
return nil, clues.New("collection attempted to delete prefix")
}
if pc.state == data.NewState {
return nil, clues.New("collection attempted to create a new prefix")
}
return pc, nil
}