corso/src/cmd/factory/impl/common.go
ryanfkeepers 6ed54fad9c first pass on ServiceResource tuple compliance
First pass for updating code outside of the path package
to comply with ServiceResource tuple slices.  Compliance
is still incomplete, so the build and tests
will still fail.  Changes
in this PR are focused on the easier-to-make changes, mostly
generating ServiceResources where there were
previously manual declarations of service and resource.
2023-08-10 17:55:34 -06:00

460 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"
"github.com/alcionai/corso/src/internal/m365/resource"
exchMock "github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
odStub "github.com/alcionai/corso/src/internal/m365/service/onedrive/stub"
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, protectedResource 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,
[]path.ServiceResource{{
Service: service,
ProtectedResource: protectedResource,
}},
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)
}