OneDrive Backup Operation (#738)
## Description Wires up the OneDrive collection logic to `operation.Backup` Includes an integration test that runs against the test domain Two bug fixes: - Skip the "root" item that is returned by the delta query - Fix incorrect usage of the `filepath.SplitList` function which does not split a path into components. Instead use `strings.Split`. This is ok because the paths returned here are not OS specific. Regardless - this logic will be refactored when we use the `path` pkg. ## Type of change Please check the type of change your PR introduces: - [x] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Test - [ ] 🐹 Trivial/Minor ## Issue(s) #548 ## Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
6cdc691c6f
commit
1246f51b22
@ -16,11 +16,13 @@ import (
|
||||
|
||||
"github.com/alcionai/corso/src/internal/connector/exchange"
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
"github.com/alcionai/corso/src/internal/connector/onedrive"
|
||||
"github.com/alcionai/corso/src/internal/connector/support"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/path"
|
||||
"github.com/alcionai/corso/src/pkg/account"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
"github.com/alcionai/corso/src/pkg/logger"
|
||||
"github.com/alcionai/corso/src/pkg/selectors"
|
||||
)
|
||||
|
||||
@ -418,3 +420,52 @@ func IsNonRecoverableError(e error) bool {
|
||||
var nonRecoverable support.NonRecoverableGCError
|
||||
return errors.As(e, &nonRecoverable)
|
||||
}
|
||||
|
||||
func (gc *GraphConnector) DataCollections(ctx context.Context, sels selectors.Selector) ([]data.Collection, error) {
|
||||
switch sels.Service {
|
||||
case selectors.ServiceExchange:
|
||||
return gc.ExchangeDataCollection(ctx, sels)
|
||||
case selectors.ServiceOneDrive:
|
||||
return gc.OneDriveDataCollections(ctx, sels)
|
||||
default:
|
||||
return nil, errors.Errorf("Service %s not supported", sels)
|
||||
}
|
||||
}
|
||||
|
||||
// OneDriveDataCollections returns a set of DataCollection which represents the OneDrive data
|
||||
// for the specified user
|
||||
func (gc *GraphConnector) OneDriveDataCollections(
|
||||
ctx context.Context,
|
||||
selector selectors.Selector,
|
||||
) ([]data.Collection, error) {
|
||||
odb, err := selector.ToOneDriveBackup()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "collecting onedrive data")
|
||||
}
|
||||
|
||||
collections := []data.Collection{}
|
||||
|
||||
scopes := odb.DiscreteScopes(gc.GetUsers())
|
||||
|
||||
var errs error
|
||||
|
||||
// for each scope that includes oneDrive items, get all
|
||||
for _, scope := range scopes {
|
||||
for _, user := range scope.Get(selectors.OneDriveUser) {
|
||||
logger.Ctx(ctx).With("user", user).Debug("Creating OneDrive collections")
|
||||
|
||||
odcs, err := onedrive.NewCollections(user, &gc.graphService, gc.UpdateStatus).Get(ctx)
|
||||
if err != nil {
|
||||
return nil, support.WrapAndAppend(user, err, errs)
|
||||
}
|
||||
|
||||
collections = append(collections, odcs...)
|
||||
}
|
||||
}
|
||||
|
||||
for range collections {
|
||||
gc.incrementAwaitingMessages()
|
||||
}
|
||||
|
||||
return collections, errs
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ package onedrive
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
"github.com/alcionai/corso/src/internal/connector/support"
|
||||
@ -77,7 +77,10 @@ func (oc *Collection) Items() <-chan data.Stream {
|
||||
}
|
||||
|
||||
func (oc *Collection) FullPath() []string {
|
||||
return filepath.SplitList(oc.folderPath)
|
||||
path := oc.folderPath
|
||||
// Remove leading `/` if any so that Split
|
||||
// doesn't return a ""
|
||||
return strings.Split(strings.TrimPrefix(path, "/"), "/")
|
||||
}
|
||||
|
||||
// Item represents a single item retrieved from OneDrive
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@ -60,7 +59,7 @@ func (suite *OneDriveCollectionSuite) TestOneDriveCollection() {
|
||||
folderPath := "dir1/dir2/dir3"
|
||||
coll := NewCollection(folderPath, "fakeDriveID", suite, suite.testStatusUpdater(&wg, &collStatus))
|
||||
require.NotNil(suite.T(), coll)
|
||||
assert.Equal(suite.T(), filepath.SplitList(folderPath), coll.FullPath())
|
||||
assert.Equal(suite.T(), []string{"dir1", "dir2", "dir3"}, coll.FullPath())
|
||||
|
||||
testItemID := "fakeItemID"
|
||||
testItemName := "itemName"
|
||||
|
||||
@ -59,8 +59,8 @@ func (c *Collections) Get(ctx context.Context) ([]data.Collection, error) {
|
||||
}
|
||||
|
||||
collections := make([]data.Collection, 0, len(c.collectionMap))
|
||||
for _, c := range c.collectionMap {
|
||||
collections = append(collections, c)
|
||||
for _, coll := range c.collectionMap {
|
||||
collections = append(collections, coll)
|
||||
}
|
||||
|
||||
return collections, nil
|
||||
@ -74,6 +74,10 @@ func (c *Collections) updateCollections(ctx context.Context, driveID string, ite
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item.GetRoot() != nil {
|
||||
// Skip the root item
|
||||
continue
|
||||
}
|
||||
if item.GetParentReference() == nil || item.GetParentReference().GetPath() == nil {
|
||||
return errors.Errorf("item does not have a parent reference. item name : %s", *item.GetName())
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
cs, err := gc.ExchangeDataCollection(ctx, op.Selectors)
|
||||
cs, err := gc.DataCollections(ctx, op.Selectors)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "retrieving service data")
|
||||
opStats.readErr = err
|
||||
|
||||
@ -207,3 +207,53 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *BackupOpIntegrationSuite) TestBackupOneDrive_Run() {
|
||||
t := suite.T()
|
||||
ctx := context.Background()
|
||||
|
||||
m365UserID := tester.M365UserID(t)
|
||||
acct := tester.NewM365Account(t)
|
||||
|
||||
// need to initialize the repository before we can test connecting to it.
|
||||
st := tester.NewPrefixedS3Storage(t)
|
||||
|
||||
k := kopia.NewConn(st)
|
||||
require.NoError(t, k.Initialize(ctx))
|
||||
|
||||
// kopiaRef comes with a count of 1 and Wrapper bumps it again so safe
|
||||
// to close here.
|
||||
defer k.Close(ctx)
|
||||
|
||||
kw, err := kopia.NewWrapper(k)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer kw.Close(ctx)
|
||||
|
||||
ms, err := kopia.NewModelStore(k)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer ms.Close(ctx)
|
||||
|
||||
sw := store.NewKopiaStore(ms)
|
||||
|
||||
sel := selectors.NewOneDriveBackup()
|
||||
sel.Include(sel.Users([]string{m365UserID}))
|
||||
|
||||
bo, err := NewBackupOperation(
|
||||
ctx,
|
||||
control.Options{},
|
||||
kw,
|
||||
sw,
|
||||
acct,
|
||||
sel.Selector)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, bo.Run(ctx))
|
||||
require.NotEmpty(t, bo.Results)
|
||||
require.NotEmpty(t, bo.Results.BackupID)
|
||||
assert.Equal(t, bo.Status, Completed)
|
||||
assert.Equal(t, bo.Results.ItemsRead, bo.Results.ItemsWritten)
|
||||
assert.NoError(t, bo.Results.ReadErrors)
|
||||
assert.NoError(t, bo.Results.WriteErrors)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user