#### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🐛 Bugfix #### Test Plan - [x] 💚 E2E
458 lines
11 KiB
Go
458 lines
11 KiB
Go
package impl
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/alcionai/clues"
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/alcionai/corso/src/cli/print"
|
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
|
"github.com/alcionai/corso/src/internal/common/idname"
|
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
|
"github.com/alcionai/corso/src/internal/common/str"
|
|
"github.com/alcionai/corso/src/internal/data"
|
|
"github.com/alcionai/corso/src/internal/m365"
|
|
exchMock "github.com/alcionai/corso/src/internal/m365/exchange/mock"
|
|
odStub "github.com/alcionai/corso/src/internal/m365/onedrive/stub"
|
|
"github.com/alcionai/corso/src/internal/m365/resource"
|
|
m365Stub "github.com/alcionai/corso/src/internal/m365/stub"
|
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
|
"github.com/alcionai/corso/src/internal/tester"
|
|
"github.com/alcionai/corso/src/internal/version"
|
|
"github.com/alcionai/corso/src/pkg/account"
|
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
|
"github.com/alcionai/corso/src/pkg/control"
|
|
"github.com/alcionai/corso/src/pkg/count"
|
|
"github.com/alcionai/corso/src/pkg/credentials"
|
|
"github.com/alcionai/corso/src/pkg/fault"
|
|
"github.com/alcionai/corso/src/pkg/path"
|
|
"github.com/alcionai/corso/src/pkg/selectors"
|
|
)
|
|
|
|
var (
|
|
Count int
|
|
Destination string
|
|
Site string
|
|
Tenant string
|
|
User string
|
|
SecondaryUser string
|
|
)
|
|
|
|
// TODO: ErrGenerating = clues.New("not all items were successfully generated")
|
|
|
|
var ErrNotYetImplemented = clues.New("not yet implemented")
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// Restoration
|
|
// ------------------------------------------------------------------------------------------
|
|
|
|
type dataBuilderFunc func(id, now, subject, body string) []byte
|
|
|
|
func generateAndRestoreItems(
|
|
ctx context.Context,
|
|
ctrl *m365.Controller,
|
|
service path.ServiceType,
|
|
cat path.CategoryType,
|
|
sel selectors.Selector,
|
|
tenantID, userID, destFldr string,
|
|
howMany int,
|
|
dbf dataBuilderFunc,
|
|
opts control.Options,
|
|
errs *fault.Bus,
|
|
ctr *count.Bus,
|
|
) (*details.Details, error) {
|
|
items := make([]item, 0, howMany)
|
|
|
|
for i := 0; i < howMany; i++ {
|
|
var (
|
|
now = dttm.Now()
|
|
nowLegacy = dttm.FormatToLegacy(time.Now())
|
|
id = uuid.NewString()
|
|
subject = "automated " + now[:16] + " - " + id[:8]
|
|
body = "automated " + cat.String() + " generation for " + userID + " at " + now + " - " + id
|
|
)
|
|
|
|
items = append(items, item{
|
|
name: id,
|
|
data: dbf(id, nowLegacy, subject, body),
|
|
})
|
|
}
|
|
|
|
collections := []collection{{
|
|
PathElements: []string{destFldr},
|
|
category: cat,
|
|
items: items,
|
|
}}
|
|
|
|
restoreCfg := control.DefaultRestoreConfig(dttm.SafeForTesting)
|
|
restoreCfg.Location = destFldr
|
|
print.Infof(ctx, "Restoring to folder %s", restoreCfg.Location)
|
|
|
|
dataColls, err := buildCollections(
|
|
service,
|
|
tenantID, userID,
|
|
restoreCfg,
|
|
collections)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
print.Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination)
|
|
|
|
rcc := inject.RestoreConsumerConfig{
|
|
BackupVersion: version.Backup,
|
|
Options: opts,
|
|
ProtectedResource: sel,
|
|
RestoreConfig: restoreCfg,
|
|
Selector: sel,
|
|
}
|
|
|
|
return ctrl.ConsumeRestoreCollections(ctx, rcc, dataColls, errs, ctr)
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------
|
|
// Common Helpers
|
|
// ------------------------------------------------------------------------------------------
|
|
|
|
func getControllerAndVerifyResourceOwner(
|
|
ctx context.Context,
|
|
resourceCat resource.Category,
|
|
resourceOwner string,
|
|
pst path.ServiceType,
|
|
) (
|
|
*m365.Controller,
|
|
account.Account,
|
|
idname.Provider,
|
|
error,
|
|
) {
|
|
tid := str.First(Tenant, os.Getenv(account.AzureTenantID))
|
|
|
|
if len(Tenant) == 0 {
|
|
Tenant = tid
|
|
}
|
|
|
|
// get account info
|
|
m365Cfg := account.M365Config{
|
|
M365: credentials.GetM365(),
|
|
AzureTenantID: tid,
|
|
}
|
|
|
|
acct, err := account.NewAccount(account.ProviderM365, m365Cfg)
|
|
if err != nil {
|
|
return nil, account.Account{}, nil, clues.Wrap(err, "finding m365 account details")
|
|
}
|
|
|
|
ctrl, err := m365.NewController(ctx, acct, resourceCat, pst, control.Options{})
|
|
if err != nil {
|
|
return nil, account.Account{}, nil, clues.Wrap(err, "connecting to graph api")
|
|
}
|
|
|
|
id, _, err := ctrl.PopulateProtectedResourceIDAndName(ctx, resourceOwner, nil)
|
|
if err != nil {
|
|
return nil, account.Account{}, nil, clues.Wrap(err, "verifying user")
|
|
}
|
|
|
|
return ctrl, acct, ctrl.IDNameLookup.ProviderForID(id), nil
|
|
}
|
|
|
|
type item struct {
|
|
name string
|
|
data []byte
|
|
}
|
|
|
|
type collection struct {
|
|
// Elements (in order) for the path representing this collection. Should
|
|
// only contain elements after the prefix that corso uses for the path. For
|
|
// example, a collection for the Inbox folder in exchange mail would just be
|
|
// "Inbox".
|
|
PathElements []string
|
|
category path.CategoryType
|
|
items []item
|
|
}
|
|
|
|
func buildCollections(
|
|
service path.ServiceType,
|
|
tenant, user string,
|
|
restoreCfg control.RestoreConfig,
|
|
colls []collection,
|
|
) ([]data.RestoreCollection, error) {
|
|
collections := make([]data.RestoreCollection, 0, len(colls))
|
|
|
|
for _, c := range colls {
|
|
pth, err := path.Build(
|
|
tenant,
|
|
user,
|
|
service,
|
|
c.category,
|
|
false,
|
|
c.PathElements...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mc := exchMock.NewCollection(pth, pth, len(c.items))
|
|
|
|
for i := 0; i < len(c.items); i++ {
|
|
mc.Names[i] = c.items[i].name
|
|
mc.Data[i] = c.items[i].data
|
|
}
|
|
|
|
collections = append(collections, data.NoFetchRestoreCollection{Collection: mc})
|
|
}
|
|
|
|
return collections, nil
|
|
}
|
|
|
|
var (
|
|
folderAName = "folder-a"
|
|
folderBName = "b"
|
|
folderCName = "folder-c"
|
|
|
|
fileAData = []byte(strings.Repeat("a", 33))
|
|
fileBData = []byte(strings.Repeat("b", 65))
|
|
fileEData = []byte(strings.Repeat("e", 257))
|
|
|
|
// Cannot restore owner or empty permissions and so not testing them
|
|
writePerm = []string{"write"}
|
|
readPerm = []string{"read"}
|
|
)
|
|
|
|
func generateAndRestoreDriveItems(
|
|
ctrl *m365.Controller,
|
|
protectedResource idname.Provider,
|
|
secondaryUserID, secondaryUserName string,
|
|
acct account.Account,
|
|
service path.ServiceType,
|
|
cat path.CategoryType,
|
|
sel selectors.Selector,
|
|
tenantID, destFldr string,
|
|
intCount int,
|
|
errs *fault.Bus,
|
|
ctr *count.Bus,
|
|
) (
|
|
*details.Details,
|
|
error,
|
|
) {
|
|
ctx, flush := tester.NewContext(nil)
|
|
defer flush()
|
|
|
|
restoreCfg := control.DefaultRestoreConfig(dttm.SafeForTesting)
|
|
restoreCfg.Location = destFldr
|
|
print.Infof(ctx, "Restoring to folder %s", restoreCfg.Location)
|
|
|
|
var driveID string
|
|
|
|
switch service {
|
|
case path.SharePointService:
|
|
d, err := ctrl.AC.Stable.
|
|
Client().
|
|
Sites().
|
|
BySiteId(protectedResource.ID()).
|
|
Drive().
|
|
Get(ctx, nil)
|
|
if err != nil {
|
|
return nil, clues.Wrap(err, "getting site's default drive")
|
|
}
|
|
|
|
driveID = ptr.Val(d.GetId())
|
|
default:
|
|
d, err := ctrl.AC.Stable.Client().
|
|
Users().
|
|
ByUserId(protectedResource.ID()).
|
|
Drive().
|
|
Get(ctx, nil)
|
|
if err != nil {
|
|
return nil, clues.Wrap(err, "getting user's default drive")
|
|
}
|
|
|
|
driveID = ptr.Val(d.GetId())
|
|
}
|
|
|
|
var (
|
|
cols []odStub.ColInfo
|
|
|
|
rootPath = []string{"drives", driveID, "root:"}
|
|
folderAPath = []string{"drives", driveID, "root:", folderAName}
|
|
folderBPath = []string{"drives", driveID, "root:", folderBName}
|
|
folderCPath = []string{"drives", driveID, "root:", folderCName}
|
|
|
|
now = time.Now()
|
|
year, mnth, date = now.Date()
|
|
hour, min, sec = now.Clock()
|
|
currentTime = fmt.Sprintf("%d-%v-%d-%d-%d-%d", year, mnth, date, hour, min, sec)
|
|
)
|
|
|
|
for i := 0; i < intCount; i++ {
|
|
col := []odStub.ColInfo{
|
|
// basic folder and file creation
|
|
{
|
|
PathElements: rootPath,
|
|
Files: []odStub.ItemData{
|
|
{
|
|
Name: fmt.Sprintf("file-1st-count-%d-at-%s", i, currentTime),
|
|
Data: fileAData,
|
|
Meta: odStub.MetaData{
|
|
Perms: odStub.PermData{
|
|
User: secondaryUserName,
|
|
EntityID: secondaryUserID,
|
|
Roles: writePerm,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: fmt.Sprintf("file-2nd-count-%d-at-%s", i, currentTime),
|
|
Data: fileBData,
|
|
},
|
|
},
|
|
Folders: []odStub.ItemData{
|
|
{
|
|
Name: folderBName,
|
|
},
|
|
{
|
|
Name: folderAName,
|
|
Meta: odStub.MetaData{
|
|
Perms: odStub.PermData{
|
|
User: secondaryUserName,
|
|
EntityID: secondaryUserID,
|
|
Roles: readPerm,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: folderCName,
|
|
Meta: odStub.MetaData{
|
|
Perms: odStub.PermData{
|
|
User: secondaryUserName,
|
|
EntityID: secondaryUserID,
|
|
Roles: readPerm,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// a folder that has permissions with an item in the folder with
|
|
// the different permissions.
|
|
PathElements: folderAPath,
|
|
Files: []odStub.ItemData{
|
|
{
|
|
Name: fmt.Sprintf("file-count-%d-at-%s", i, currentTime),
|
|
Data: fileEData,
|
|
Meta: odStub.MetaData{
|
|
Perms: odStub.PermData{
|
|
User: secondaryUserName,
|
|
EntityID: secondaryUserID,
|
|
Roles: writePerm,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Meta: odStub.MetaData{
|
|
Perms: odStub.PermData{
|
|
User: secondaryUserName,
|
|
EntityID: secondaryUserID,
|
|
Roles: readPerm,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// a folder that has permissions with an item in the folder with
|
|
// no permissions.
|
|
PathElements: folderCPath,
|
|
Files: []odStub.ItemData{
|
|
{
|
|
Name: fmt.Sprintf("file-count-%d-at-%s", i, currentTime),
|
|
Data: fileAData,
|
|
},
|
|
},
|
|
Meta: odStub.MetaData{
|
|
Perms: odStub.PermData{
|
|
User: secondaryUserName,
|
|
EntityID: secondaryUserID,
|
|
Roles: readPerm,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
PathElements: folderBPath,
|
|
Files: []odStub.ItemData{
|
|
{
|
|
// restoring a file in a non-root folder that doesn't inherit
|
|
// permissions.
|
|
Name: fmt.Sprintf("file-count-%d-at-%s", i, currentTime),
|
|
Data: fileBData,
|
|
Meta: odStub.MetaData{
|
|
Perms: odStub.PermData{
|
|
User: secondaryUserName,
|
|
EntityID: secondaryUserID,
|
|
Roles: writePerm,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Folders: []odStub.ItemData{
|
|
{
|
|
Name: folderAName,
|
|
Meta: odStub.MetaData{
|
|
Perms: odStub.PermData{
|
|
User: secondaryUserName,
|
|
EntityID: secondaryUserID,
|
|
Roles: readPerm,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
cols = append(cols, col...)
|
|
}
|
|
|
|
input, err := odStub.DataForInfo(service, cols, version.Backup)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// collections := getCollections(
|
|
// service,
|
|
// tenantID,
|
|
// []string{resourceOwner},
|
|
// input,
|
|
// version.Backup)
|
|
|
|
opts := control.DefaultOptions()
|
|
restoreCfg.IncludePermissions = true
|
|
|
|
config := m365Stub.ConfigInfo{
|
|
Opts: opts,
|
|
Resource: resource.Users,
|
|
Service: service,
|
|
Tenant: tenantID,
|
|
ResourceOwners: []string{protectedResource.ID()},
|
|
RestoreCfg: restoreCfg,
|
|
}
|
|
|
|
_, _, collections, _, err := m365Stub.GetCollectionsAndExpected(
|
|
config,
|
|
input,
|
|
version.Backup)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rcc := inject.RestoreConsumerConfig{
|
|
BackupVersion: version.Backup,
|
|
Options: opts,
|
|
ProtectedResource: protectedResource,
|
|
RestoreConfig: restoreCfg,
|
|
Selector: sel,
|
|
}
|
|
|
|
return ctrl.ConsumeRestoreCollections(ctx, rcc, collections, errs, ctr)
|
|
}
|