add clues/fault to exchange restore (#2491)
## Does this PR need a docs update or release note? - [ ] ⛔ No ## Type of change - [x] 🧹 Tech Debt/Cleanup ## Issue(s) * #1970 ## Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
31c9195a12
commit
570ce85656
@ -53,6 +53,7 @@ func generateAndRestoreItems(
|
|||||||
howMany int,
|
howMany int,
|
||||||
dbf dataBuilderFunc,
|
dbf dataBuilderFunc,
|
||||||
opts control.Options,
|
opts control.Options,
|
||||||
|
errs *fault.Errors,
|
||||||
) (*details.Details, error) {
|
) (*details.Details, error) {
|
||||||
items := make([]item, 0, howMany)
|
items := make([]item, 0, howMany)
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ func generateAndRestoreItems(
|
|||||||
|
|
||||||
Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination)
|
Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination)
|
||||||
|
|
||||||
return gc.RestoreDataCollections(ctx, backup.Version, acct, sel, dest, opts, dataColls)
|
return gc.RestoreDataCollections(ctx, backup.Version, acct, sel, dest, opts, dataColls, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
package impl
|
package impl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/alcionai/clues"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
. "github.com/alcionai/corso/src/cli/print"
|
. "github.com/alcionai/corso/src/cli/print"
|
||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/connector/mockconnector"
|
"github.com/alcionai/corso/src/internal/connector/mockconnector"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
@ -42,6 +45,7 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error {
|
|||||||
ctx = cmd.Context()
|
ctx = cmd.Context()
|
||||||
service = path.ExchangeService
|
service = path.ExchangeService
|
||||||
category = path.EmailCategory
|
category = path.EmailCategory
|
||||||
|
errs = fault.New(false)
|
||||||
)
|
)
|
||||||
|
|
||||||
if utils.HasNoFlagsAndShownHelp(cmd) {
|
if utils.HasNoFlagsAndShownHelp(cmd) {
|
||||||
@ -69,11 +73,16 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error {
|
|||||||
now, now, now, now)
|
now, now, now, now)
|
||||||
},
|
},
|
||||||
control.Options{},
|
control.Options{},
|
||||||
)
|
errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log := logger.Ctx(ctx)
|
||||||
|
for _, e := range errs.Errs() {
|
||||||
|
log.Errorw(e.Error(), clues.InErr(err).Slice()...)
|
||||||
|
}
|
||||||
|
|
||||||
deets.PrintEntries(ctx)
|
deets.PrintEntries(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -84,6 +93,7 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error
|
|||||||
ctx = cmd.Context()
|
ctx = cmd.Context()
|
||||||
service = path.ExchangeService
|
service = path.ExchangeService
|
||||||
category = path.EventsCategory
|
category = path.EventsCategory
|
||||||
|
errs = fault.New(false)
|
||||||
)
|
)
|
||||||
|
|
||||||
if utils.HasNoFlagsAndShownHelp(cmd) {
|
if utils.HasNoFlagsAndShownHelp(cmd) {
|
||||||
@ -110,11 +120,16 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error
|
|||||||
now, now, false)
|
now, now, false)
|
||||||
},
|
},
|
||||||
control.Options{},
|
control.Options{},
|
||||||
)
|
errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log := logger.Ctx(ctx)
|
||||||
|
for _, e := range errs.Errs() {
|
||||||
|
log.Errorw(e.Error(), clues.InErr(err).Slice()...)
|
||||||
|
}
|
||||||
|
|
||||||
deets.PrintEntries(ctx)
|
deets.PrintEntries(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -125,6 +140,7 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error {
|
|||||||
ctx = cmd.Context()
|
ctx = cmd.Context()
|
||||||
service = path.ExchangeService
|
service = path.ExchangeService
|
||||||
category = path.ContactsCategory
|
category = path.ContactsCategory
|
||||||
|
errs = fault.New(false)
|
||||||
)
|
)
|
||||||
|
|
||||||
if utils.HasNoFlagsAndShownHelp(cmd) {
|
if utils.HasNoFlagsAndShownHelp(cmd) {
|
||||||
@ -156,11 +172,16 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
control.Options{},
|
control.Options{},
|
||||||
)
|
errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log := logger.Ctx(ctx)
|
||||||
|
for _, e := range errs.Errs() {
|
||||||
|
log.Errorw(e.Error(), clues.InErr(err).Slice()...)
|
||||||
|
}
|
||||||
|
|
||||||
deets.PrintEntries(ctx)
|
deets.PrintEntries(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -243,6 +243,7 @@ func (gc *GraphConnector) RestoreDataCollections(
|
|||||||
dest control.RestoreDestination,
|
dest control.RestoreDestination,
|
||||||
opts control.Options,
|
opts control.Options,
|
||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
|
errs *fault.Errors,
|
||||||
) (*details.Details, error) {
|
) (*details.Details, error) {
|
||||||
ctx, end := D.Span(ctx, "connector:restore")
|
ctx, end := D.Span(ctx, "connector:restore")
|
||||||
defer end()
|
defer end()
|
||||||
@ -260,7 +261,7 @@ func (gc *GraphConnector) RestoreDataCollections(
|
|||||||
|
|
||||||
switch selector.Service {
|
switch selector.Service {
|
||||||
case selectors.ServiceExchange:
|
case selectors.ServiceExchange:
|
||||||
status, err = exchange.RestoreExchangeDataCollections(ctx, creds, gc.Service, dest, dcs, deets)
|
status, err = exchange.RestoreExchangeDataCollections(ctx, creds, gc.Service, dest, dcs, deets, errs)
|
||||||
case selectors.ServiceOneDrive:
|
case selectors.ServiceOneDrive:
|
||||||
status, err = onedrive.RestoreCollections(ctx, backupVersion, gc.Service, dest, opts, dcs, deets)
|
status, err = onedrive.RestoreCollections(ctx, backupVersion, gc.Service, dest, opts, dcs, deets)
|
||||||
case selectors.ServiceSharePoint:
|
case selectors.ServiceSharePoint:
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/connector/uploadsession"
|
"github.com/alcionai/corso/src/internal/connector/uploadsession"
|
||||||
@ -46,46 +47,42 @@ func uploadAttachment(
|
|||||||
uploader attachmentUploadable,
|
uploader attachmentUploadable,
|
||||||
attachment models.Attachmentable,
|
attachment models.Attachmentable,
|
||||||
) error {
|
) error {
|
||||||
logger.Ctx(ctx).Debugf("uploading attachment with size %d", *attachment.GetSize())
|
attachmentType := attachmentType(attachment)
|
||||||
|
|
||||||
|
ctx = clues.AddAll(
|
||||||
|
ctx,
|
||||||
|
"attachment_size", ptr.Val(attachment.GetSize()),
|
||||||
|
"attachment_id", ptr.Val(attachment.GetId()),
|
||||||
|
"attachment_name", ptr.Val(attachment.GetName()), // TODO: pii
|
||||||
|
"attachment_type", attachmentType,
|
||||||
|
"internal_item_type", getItemAttachmentItemType(attachment),
|
||||||
|
"uploader_item_id", uploader.getItemID())
|
||||||
|
|
||||||
|
logger.Ctx(ctx).Debugw("uploading attachment")
|
||||||
|
|
||||||
var (
|
|
||||||
attachmentType = attachmentType(attachment)
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
// Reference attachments that are inline() do not need to be recreated. The contents are part of the body.
|
// Reference attachments that are inline() do not need to be recreated. The contents are part of the body.
|
||||||
if attachmentType == models.REFERENCE_ATTACHMENTTYPE &&
|
if attachmentType == models.REFERENCE_ATTACHMENTTYPE && ptr.Val(attachment.GetIsInline()) {
|
||||||
attachment.GetIsInline() != nil && *attachment.GetIsInline() {
|
logger.Ctx(ctx).Debug("skip uploading inline reference attachment: ", ptr.Val(attachment.GetName()))
|
||||||
logger.Ctx(ctx).Debugf("skip uploading inline reference attachment: ", *attachment.GetName())
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// item Attachments to be skipped until the completion of Issue #2353
|
// item Attachments to be skipped until the completion of Issue #2353
|
||||||
if attachmentType == models.ITEM_ATTACHMENTTYPE {
|
if attachmentType == models.ITEM_ATTACHMENTTYPE {
|
||||||
prev := attachment
|
a, err := support.ToItemAttachment(attachment)
|
||||||
|
|
||||||
attachment, err = support.ToItemAttachment(attachment)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
name := ptr.Val(prev.GetName())
|
logger.Ctx(ctx).
|
||||||
msg := "item attachment restore not supported for this type. skipping upload."
|
With("err", err).
|
||||||
|
Infow("item attachment restore not supported for this type. skipping upload.", clues.InErr(err).Slice()...)
|
||||||
// TODO: (rkeepers) Update to support PII protection
|
|
||||||
logger.Ctx(ctx).Infow(msg,
|
|
||||||
"err", err,
|
|
||||||
"attachment_name", name,
|
|
||||||
"attachment_type", attachmentType,
|
|
||||||
"internal_item_type", getItemAttachmentItemType(prev),
|
|
||||||
"attachment_id", ptr.Val(prev.GetId()),
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachment = a
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Item/Reference attachments *or* file attachments < 3MB, use the attachments endpoint
|
// For Item/Reference attachments *or* file attachments < 3MB, use the attachments endpoint
|
||||||
if attachmentType != models.FILE_ATTACHMENTTYPE || *attachment.GetSize() < largeAttachmentSize {
|
if attachmentType != models.FILE_ATTACHMENTTYPE || ptr.Val(attachment.GetSize()) < largeAttachmentSize {
|
||||||
err := uploader.uploadSmallAttachment(ctx, attachment)
|
return uploader.uploadSmallAttachment(ctx, attachment)
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadLargeAttachment(ctx, uploader, attachment)
|
return uploadLargeAttachment(ctx, uploader, attachment)
|
||||||
@ -93,7 +90,9 @@ func uploadAttachment(
|
|||||||
|
|
||||||
// uploadLargeAttachment will upload the specified attachment by creating an upload session and
|
// uploadLargeAttachment will upload the specified attachment by creating an upload session and
|
||||||
// doing a chunked upload
|
// doing a chunked upload
|
||||||
func uploadLargeAttachment(ctx context.Context, uploader attachmentUploadable,
|
func uploadLargeAttachment(
|
||||||
|
ctx context.Context,
|
||||||
|
uploader attachmentUploadable,
|
||||||
attachment models.Attachmentable,
|
attachment models.Attachmentable,
|
||||||
) error {
|
) error {
|
||||||
ab := attachmentBytes(attachment)
|
ab := attachmentBytes(attachment)
|
||||||
|
|||||||
@ -3,12 +3,11 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
msusers "github.com/microsoftgraph/msgraph-sdk-go/users"
|
msusers "github.com/microsoftgraph/msgraph-sdk-go/users"
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// attachementUploadable represents structs that are able to upload small attachments directly to an item or use an
|
// attachementUploadable represents structs that are able to upload small attachments directly to an item or use an
|
||||||
@ -45,7 +44,7 @@ func (mau *mailAttachmentUploader) uploadSmallAttachment(ctx context.Context, at
|
|||||||
Attachments().
|
Attachments().
|
||||||
Post(ctx, attach, nil)
|
Post(ctx, attach, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -69,12 +68,7 @@ func (mau *mailAttachmentUploader) uploadSession(
|
|||||||
CreateUploadSession().
|
CreateUploadSession().
|
||||||
Post(ctx, session, nil)
|
Post(ctx, session, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(
|
return nil, clues.Wrap(err, "uploading mail attachment").WithClues(ctx).WithAll(graph.ErrData(err)...)
|
||||||
err,
|
|
||||||
"failed to create attachment upload session for item %s. details: %s",
|
|
||||||
mau.itemID,
|
|
||||||
support.ConnectorStackErrorTrace(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
@ -100,7 +94,7 @@ func (eau *eventAttachmentUploader) uploadSmallAttachment(ctx context.Context, a
|
|||||||
Attachments().
|
Attachments().
|
||||||
Post(ctx, attach, nil)
|
Post(ctx, attach, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -122,11 +116,7 @@ func (eau *eventAttachmentUploader) uploadSession(
|
|||||||
CreateUploadSession().
|
CreateUploadSession().
|
||||||
Post(ctx, session, nil)
|
Post(ctx, session, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(
|
return nil, clues.Wrap(err, "uploading event attachment").WithClues(ctx).WithAll(graph.ErrData(err)...)
|
||||||
err,
|
|
||||||
"failed to create attachment upload session for event item %s. details: %s",
|
|
||||||
eau.itemID, support.ConnectorStackErrorTrace(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -119,7 +120,8 @@ func (suite *ExchangeRestoreSuite) TestRestoreEvent() {
|
|||||||
suite.gs,
|
suite.gs,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
calendarID,
|
calendarID,
|
||||||
userID)
|
userID,
|
||||||
|
fault.New(true))
|
||||||
assert.NoError(t, err, support.ConnectorStackErrorTrace(err))
|
assert.NoError(t, err, support.ConnectorStackErrorTrace(err))
|
||||||
assert.NotNil(t, info, "event item info")
|
assert.NotNil(t, info, "event item info")
|
||||||
}
|
}
|
||||||
@ -346,7 +348,7 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
|
|||||||
service,
|
service,
|
||||||
destination,
|
destination,
|
||||||
userID,
|
userID,
|
||||||
)
|
fault.New(true))
|
||||||
assert.NoError(t, err, support.ConnectorStackErrorTrace(err))
|
assert.NoError(t, err, support.ConnectorStackErrorTrace(err))
|
||||||
assert.NotNil(t, info, "item info was not populated")
|
assert.NotNil(t, info, "item info was not populated")
|
||||||
assert.NotNil(t, deleters)
|
assert.NotNil(t, deleters)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"runtime/trace"
|
"runtime/trace"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
@ -35,20 +37,21 @@ func RestoreExchangeObject(
|
|||||||
policy control.CollisionPolicy,
|
policy control.CollisionPolicy,
|
||||||
service graph.Servicer,
|
service graph.Servicer,
|
||||||
destination, user string,
|
destination, user string,
|
||||||
|
errs *fault.Errors,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
if policy != control.Copy {
|
if policy != control.Copy {
|
||||||
return nil, fmt.Errorf("restore policy: %s not supported for RestoreExchangeObject", policy)
|
return nil, clues.Wrap(clues.New(policy.String()), "policy not supported for Exchange restore").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
case path.EmailCategory:
|
case path.EmailCategory:
|
||||||
return RestoreMailMessage(ctx, bits, service, control.Copy, destination, user)
|
return RestoreMailMessage(ctx, bits, service, control.Copy, destination, user, errs)
|
||||||
case path.ContactsCategory:
|
case path.ContactsCategory:
|
||||||
return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user)
|
return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user)
|
||||||
case path.EventsCategory:
|
case path.EventsCategory:
|
||||||
return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user)
|
return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user, errs)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("type: %s not supported for RestoreExchangeObject", category)
|
return nil, clues.Wrap(clues.New(category.String()), "not supported for Exchange restore")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,22 +70,18 @@ func RestoreExchangeContact(
|
|||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
contact, err := support.CreateContactFromBytes(bits)
|
contact, err := support.CreateContactFromBytes(bits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "creating contact from bytes: RestoreExchangeContact")
|
return nil, clues.Wrap(err, "creating contact from bytes").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx = clues.Add(ctx, "item_id", ptr.Val(contact.GetId()))
|
||||||
|
|
||||||
response, err := service.Client().UsersById(user).ContactFoldersById(destination).Contacts().Post(ctx, contact, nil)
|
response, err := service.Client().UsersById(user).ContactFoldersById(destination).Contacts().Post(ctx, contact, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
name := ptr.Val(contact.GetGivenName())
|
return nil, clues.Wrap(err, "uploading Contact").WithClues(ctx).WithAll(graph.ErrData(err)...)
|
||||||
|
|
||||||
return nil, errors.Wrap(
|
|
||||||
err,
|
|
||||||
"uploading Contact during RestoreExchangeContact: "+name+" "+
|
|
||||||
support.ConnectorStackErrorTrace(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if response == nil {
|
if response == nil {
|
||||||
return nil, errors.New("msgraph contact post fail: REST response not received")
|
return nil, clues.New("nil response from post").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
info := api.ContactInfo(contact)
|
info := api.ContactInfo(contact)
|
||||||
@ -103,17 +102,18 @@ func RestoreExchangeEvent(
|
|||||||
service graph.Servicer,
|
service graph.Servicer,
|
||||||
cp control.CollisionPolicy,
|
cp control.CollisionPolicy,
|
||||||
destination, user string,
|
destination, user string,
|
||||||
|
errs *fault.Errors,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
event, err := support.CreateEventFromBytes(bits)
|
event, err := support.CreateEventFromBytes(bits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "creating event from bytes: RestoreExchangeEvent")
|
return nil, clues.Wrap(err, "creating event from bytes").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
transformedEvent := support.ToEventSimplified(event)
|
ctx = clues.Add(ctx, "item_id", ptr.Val(event.GetId()))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
attached []models.Attachmentable
|
transformedEvent = support.ToEventSimplified(event)
|
||||||
errs error
|
attached []models.Attachmentable
|
||||||
)
|
)
|
||||||
|
|
||||||
if *event.GetHasAttachments() {
|
if *event.GetHasAttachments() {
|
||||||
@ -124,15 +124,11 @@ func RestoreExchangeEvent(
|
|||||||
|
|
||||||
response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(ctx, transformedEvent, nil)
|
response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(ctx, transformedEvent, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err,
|
return nil, clues.Wrap(err, "uploading event").WithClues(ctx).WithAll(graph.ErrData(err)...)
|
||||||
fmt.Sprintf(
|
|
||||||
"uploading event during RestoreExchangeEvent: %s",
|
|
||||||
support.ConnectorStackErrorTrace(err)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if response == nil {
|
if response == nil {
|
||||||
return nil, errors.New("msgraph event post fail: REST response not received")
|
return nil, clues.New("nil response from post").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
uploader := &eventAttachmentUploader{
|
uploader := &eventAttachmentUploader{
|
||||||
@ -143,25 +139,19 @@ func RestoreExchangeEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, attach := range attached {
|
for _, attach := range attached {
|
||||||
if err := uploadAttachment(ctx, uploader, attach); err != nil {
|
if errs.Err() != nil {
|
||||||
errs = support.WrapAndAppend(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"uploading attachment for message %s: %s",
|
|
||||||
ptr.Val(transformedEvent.GetId()),
|
|
||||||
support.ConnectorStackErrorTrace(err),
|
|
||||||
),
|
|
||||||
err,
|
|
||||||
errs,
|
|
||||||
)
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := uploadAttachment(ctx, uploader, attach); err != nil {
|
||||||
|
errs.Add(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info := api.EventInfo(event)
|
info := api.EventInfo(event)
|
||||||
info.Size = int64(len(bits))
|
info.Size = int64(len(bits))
|
||||||
|
|
||||||
return info, errs
|
return info, errs.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreMailMessage utility function to place an exchange.Mail
|
// RestoreMailMessage utility function to place an exchange.Mail
|
||||||
@ -176,16 +166,21 @@ func RestoreMailMessage(
|
|||||||
service graph.Servicer,
|
service graph.Servicer,
|
||||||
cp control.CollisionPolicy,
|
cp control.CollisionPolicy,
|
||||||
destination, user string,
|
destination, user string,
|
||||||
|
errs *fault.Errors,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
// Creates messageable object from original bytes
|
// Creates messageable object from original bytes
|
||||||
originalMessage, err := support.CreateMessageFromBytes(bits)
|
originalMessage, err := support.CreateMessageFromBytes(bits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "creating email from bytes: RestoreMailMessage")
|
return nil, clues.Wrap(err, "creating mail from bytes").WithClues(ctx)
|
||||||
}
|
}
|
||||||
// Sets fields from original message from storage
|
|
||||||
clone := support.ToMessage(originalMessage)
|
ctx = clues.Add(ctx, "item_id", ptr.Val(originalMessage.GetId()))
|
||||||
valueID := MailRestorePropertyTag
|
|
||||||
enableValue := RestoreCanonicalEnableValue
|
var (
|
||||||
|
clone = support.ToMessage(originalMessage)
|
||||||
|
valueID = MailRestorePropertyTag
|
||||||
|
enableValue = RestoreCanonicalEnableValue
|
||||||
|
)
|
||||||
|
|
||||||
// Set Extended Properties:
|
// Set Extended Properties:
|
||||||
// 1st: No transmission
|
// 1st: No transmission
|
||||||
@ -219,17 +214,8 @@ func RestoreMailMessage(
|
|||||||
|
|
||||||
clone.SetSingleValueExtendedProperties(svlep)
|
clone.SetSingleValueExtendedProperties(svlep)
|
||||||
|
|
||||||
// Switch workflow based on collision policy
|
if err := SendMailToBackStore(ctx, service, user, destination, clone, errs); err != nil {
|
||||||
switch cp {
|
return nil, err
|
||||||
default:
|
|
||||||
logger.Ctx(ctx).DPanicw("restoreMailMessage received unrecognized restore policy; defaulting to copy",
|
|
||||||
"policy", cp)
|
|
||||||
fallthrough
|
|
||||||
case control.Copy:
|
|
||||||
err := SendMailToBackStore(ctx, service, user, destination, clone)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info := api.MailInfo(clone)
|
info := api.MailInfo(clone)
|
||||||
@ -253,28 +239,23 @@ func SendMailToBackStore(
|
|||||||
service graph.Servicer,
|
service graph.Servicer,
|
||||||
user, destination string,
|
user, destination string,
|
||||||
message models.Messageable,
|
message models.Messageable,
|
||||||
|
errs *fault.Errors,
|
||||||
) error {
|
) error {
|
||||||
var (
|
attached := message.GetAttachments()
|
||||||
attached []models.Attachmentable
|
|
||||||
errs error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
||||||
attached = message.GetAttachments()
|
|
||||||
message.SetAttachments([]models.Attachmentable{})
|
message.SetAttachments([]models.Attachmentable{})
|
||||||
|
|
||||||
sentMessage, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(ctx, message, nil)
|
response, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(ctx, message, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err,
|
return clues.Wrap(err, "restoring mail").WithClues(ctx).WithAll(graph.ErrData(err)...)
|
||||||
user+": failure sendMailAPI: Dest: "+destination+" Details: "+support.ConnectorStackErrorTrace(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sentMessage == nil {
|
if response == nil {
|
||||||
return errors.New("message not Sent: blocked by server")
|
return clues.New("nil response from post").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
id := *sentMessage.GetId()
|
id := ptr.Val(response.GetId())
|
||||||
|
|
||||||
uploader := &mailAttachmentUploader{
|
uploader := &mailAttachmentUploader{
|
||||||
userID: user,
|
userID: user,
|
||||||
@ -284,29 +265,28 @@ func SendMailToBackStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, attachment := range attached {
|
for _, attachment := range attached {
|
||||||
|
if errs.Err() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if err := uploadAttachment(ctx, uploader, attachment); err != nil {
|
if err := uploadAttachment(ctx, uploader, attachment); err != nil {
|
||||||
if ptr.Val(attachment.GetOdataType()) == "#microsoft.graph.itemAttachment" {
|
if ptr.Val(attachment.GetOdataType()) == "#microsoft.graph.itemAttachment" {
|
||||||
name := ptr.Val(attachment.GetName())
|
name := ptr.Val(attachment.GetName())
|
||||||
|
|
||||||
logger.Ctx(ctx).Infow(
|
logger.Ctx(ctx).
|
||||||
"item attachment upload not successful. content not accepted by M365 server",
|
With("err", err, "attachment_name", name).
|
||||||
"Attachment Name", name)
|
Infow("mail upload failed", clues.InErr(err).Slice()...)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
errs = support.WrapAndAppend(
|
errs.Add(errors.Wrap(err, "uploading mail attachment"))
|
||||||
fmt.Sprintf("uploading attachment for message %s: %s",
|
|
||||||
id, support.ConnectorStackErrorTrace(err)),
|
|
||||||
err,
|
|
||||||
errs,
|
|
||||||
)
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreExchangeDataCollections restores M365 objects in data.RestoreCollection to MSFT
|
// RestoreExchangeDataCollections restores M365 objects in data.RestoreCollection to MSFT
|
||||||
@ -319,22 +299,25 @@ func RestoreExchangeDataCollections(
|
|||||||
dest control.RestoreDestination,
|
dest control.RestoreDestination,
|
||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
deets *details.Builder,
|
deets *details.Builder,
|
||||||
|
errs *fault.Errors,
|
||||||
) (*support.ConnectorOperationStatus, error) {
|
) (*support.ConnectorOperationStatus, error) {
|
||||||
var (
|
var (
|
||||||
// map of caches... but not yet...
|
|
||||||
directoryCaches = make(map[string]map[path.CategoryType]graph.ContainerResolver)
|
directoryCaches = make(map[string]map[path.CategoryType]graph.ContainerResolver)
|
||||||
metrics support.CollectionMetrics
|
metrics support.CollectionMetrics
|
||||||
errs error
|
userID string
|
||||||
// TODO policy to be updated from external source after completion of refactoring
|
// TODO policy to be updated from external source after completion of refactoring
|
||||||
policy = control.Copy
|
policy = control.Copy
|
||||||
)
|
)
|
||||||
|
|
||||||
errUpdater := func(id string, err error) {
|
if len(dcs) > 0 {
|
||||||
errs = support.WrapAndAppend(id, err, errs)
|
userID = dcs[0].FullPath().ResourceOwner()
|
||||||
|
ctx = clues.Add(ctx, "resource_owner", userID) // TODO: pii
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dc := range dcs {
|
for _, dc := range dcs {
|
||||||
userID := dc.FullPath().ResourceOwner()
|
if errs.Err() != nil {
|
||||||
|
return nil, errs.Err()
|
||||||
|
}
|
||||||
|
|
||||||
userCaches := directoryCaches[userID]
|
userCaches := directoryCaches[userID]
|
||||||
if userCaches == nil {
|
if userCaches == nil {
|
||||||
@ -349,11 +332,11 @@ func RestoreExchangeDataCollections(
|
|||||||
dest.ContainerName,
|
dest.ContainerName,
|
||||||
userCaches)
|
userCaches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = support.WrapAndAppend(dc.FullPath().ShortRef(), err, errs)
|
errs.Add(clues.Wrap(err, "creating destination").WithClues(ctx))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
temp, canceled := restoreCollection(ctx, gs, dc, containerID, policy, deets, errUpdater)
|
temp, canceled := restoreCollection(ctx, gs, dc, containerID, policy, deets, errs)
|
||||||
|
|
||||||
metrics.Combine(temp)
|
metrics.Combine(temp)
|
||||||
|
|
||||||
@ -362,14 +345,15 @@ func RestoreExchangeDataCollections(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
status := support.CreateStatus(ctx,
|
status := support.CreateStatus(
|
||||||
|
ctx,
|
||||||
support.Restore,
|
support.Restore,
|
||||||
len(dcs),
|
len(dcs),
|
||||||
metrics,
|
metrics,
|
||||||
errs,
|
errs.Err(),
|
||||||
dest.ContainerName)
|
dest.ContainerName)
|
||||||
|
|
||||||
return status, errs
|
return status, errs.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// restoreCollection handles restoration of an individual collection.
|
// restoreCollection handles restoration of an individual collection.
|
||||||
@ -380,7 +364,7 @@ func restoreCollection(
|
|||||||
folderID string,
|
folderID string,
|
||||||
policy control.CollisionPolicy,
|
policy control.CollisionPolicy,
|
||||||
deets *details.Builder,
|
deets *details.Builder,
|
||||||
errUpdater func(string, error),
|
errs *fault.Errors,
|
||||||
) (support.CollectionMetrics, bool) {
|
) (support.CollectionMetrics, bool) {
|
||||||
ctx, end := D.Span(ctx, "gc:exchange:restoreCollection", D.Label("path", dc.FullPath()))
|
ctx, end := D.Span(ctx, "gc:exchange:restoreCollection", D.Label("path", dc.FullPath()))
|
||||||
defer end()
|
defer end()
|
||||||
@ -394,6 +378,12 @@ func restoreCollection(
|
|||||||
user = directory.ResourceOwner()
|
user = directory.ResourceOwner()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ctx = clues.AddAll(
|
||||||
|
ctx,
|
||||||
|
"full_path", directory,
|
||||||
|
"service", service,
|
||||||
|
"category", category)
|
||||||
|
|
||||||
colProgress, closer := observe.CollectionProgress(
|
colProgress, closer := observe.CollectionProgress(
|
||||||
ctx,
|
ctx,
|
||||||
category.String(),
|
category.String(),
|
||||||
@ -405,34 +395,39 @@ func restoreCollection(
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
errUpdater("context cancelled", ctx.Err())
|
errs.Add(clues.Wrap(ctx.Err(), "context cancelled").WithClues(ctx))
|
||||||
return metrics, true
|
return metrics, true
|
||||||
|
|
||||||
case itemData, ok := <-items:
|
case itemData, ok := <-items:
|
||||||
if !ok {
|
if !ok || errs.Err() != nil {
|
||||||
return metrics, false
|
return metrics, false
|
||||||
}
|
}
|
||||||
metrics.Objects++
|
|
||||||
|
|
||||||
trace.Log(ctx, "gc:exchange:restoreCollection:item", itemData.UUID())
|
ictx := clues.Add(ctx, "item_id", itemData.UUID())
|
||||||
|
trace.Log(ictx, "gc:exchange:restoreCollection:item", itemData.UUID())
|
||||||
|
metrics.Objects++
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
_, err := buf.ReadFrom(itemData.ToReader())
|
_, err := buf.ReadFrom(itemData.ToReader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errUpdater(itemData.UUID()+": byteReadError during RestoreDataCollection", err)
|
errs.Add(clues.Wrap(err, "reading item bytes").WithClues(ictx))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
byteArray := buf.Bytes()
|
byteArray := buf.Bytes()
|
||||||
|
|
||||||
info, err := RestoreExchangeObject(ctx, byteArray, category, policy, gs, folderID, user)
|
info, err := RestoreExchangeObject(
|
||||||
|
ictx,
|
||||||
|
byteArray,
|
||||||
|
category,
|
||||||
|
policy,
|
||||||
|
gs,
|
||||||
|
folderID,
|
||||||
|
user,
|
||||||
|
errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// More information to be here
|
errs.Add(err)
|
||||||
errUpdater(
|
|
||||||
itemData.UUID()+": failed to upload RestoreExchangeObject: "+service.String()+"-"+category.String(),
|
|
||||||
err)
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,7 +436,7 @@ func restoreCollection(
|
|||||||
|
|
||||||
itemPath, err := dc.FullPath().Append(itemData.UUID(), true)
|
itemPath, err := dc.FullPath().Append(itemData.UUID(), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Ctx(ctx).DPanicw("transforming item to full path", "error", err)
|
errs.Add(clues.Wrap(err, "building full path with item").WithClues(ctx))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,7 +482,7 @@ func CreateContainerDestination(
|
|||||||
// TODO(rkeepers): pass the api client into this func, rather than generating one.
|
// TODO(rkeepers): pass the api client into this func, rather than generating one.
|
||||||
ac, err := api.NewClient(creds)
|
ac, err := api.NewClient(creds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", clues.Stack(err).WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
@ -569,7 +564,7 @@ func CreateContainerDestination(
|
|||||||
newCache)
|
newCache)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("category: %s not support for exchange cache", category)
|
return "", clues.Wrap(fmt.Errorf("%T", category), "not support for exchange cache").WithClues(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,6 +586,8 @@ func establishMailRestoreLocation(
|
|||||||
folderID := rootFolderAlias
|
folderID := rootFolderAlias
|
||||||
pb := path.Builder{}
|
pb := path.Builder{}
|
||||||
|
|
||||||
|
ctx = clues.Add(ctx, "is_new_cache", isNewCache)
|
||||||
|
|
||||||
for _, folder := range folders {
|
for _, folder := range folders {
|
||||||
pb = *pb.Append(folder)
|
pb = *pb.Append(folder)
|
||||||
|
|
||||||
@ -647,6 +644,8 @@ func establishContactsRestoreLocation(
|
|||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx = clues.Add(ctx, "is_new_cache", isNewCache)
|
||||||
|
|
||||||
temp, err := ac.Contacts().CreateContactFolder(ctx, user, folders[0])
|
temp, err := ac.Contacts().CreateContactFolder(ctx, user, folders[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
||||||
@ -681,6 +680,8 @@ func establishEventsRestoreLocation(
|
|||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx = clues.Add(ctx, "is_new_cache", isNewCache)
|
||||||
|
|
||||||
temp, err := ac.Events().CreateCalendar(ctx, user, folders[0])
|
temp, err := ac.Events().CreateCalendar(ctx, user, folders[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
||||||
|
|||||||
@ -250,7 +250,7 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreFailsBadService() {
|
|||||||
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
|
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
)
|
fault.New(true))
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.NotNil(t, deets)
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() {
|
|||||||
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
|
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
|
||||||
},
|
},
|
||||||
test.col,
|
test.col,
|
||||||
)
|
fault.New(true))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, deets)
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
@ -422,7 +422,7 @@ func runRestoreBackupTest(
|
|||||||
dest,
|
dest,
|
||||||
opts,
|
opts,
|
||||||
collections,
|
collections,
|
||||||
)
|
fault.New(true))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, deets)
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
@ -544,7 +544,7 @@ func runRestoreBackupTestVersion0(
|
|||||||
dest,
|
dest,
|
||||||
opts,
|
opts,
|
||||||
collections,
|
collections,
|
||||||
)
|
fault.New(true))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, deets)
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
@ -1515,7 +1515,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames
|
|||||||
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
|
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
|
||||||
},
|
},
|
||||||
collections,
|
collections,
|
||||||
)
|
fault.New(true))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, deets)
|
require.NotNil(t, deets)
|
||||||
|
|
||||||
|
|||||||
@ -63,6 +63,7 @@ func CreateStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasErrors := err != nil
|
hasErrors := err != nil
|
||||||
|
// TODO(keeprs): remove
|
||||||
numErr := GetNumberOfErrors(err)
|
numErr := GetNumberOfErrors(err)
|
||||||
|
|
||||||
status := ConnectorOperationStatus{
|
status := ConnectorOperationStatus{
|
||||||
|
|||||||
@ -347,7 +347,8 @@ func generateContainerOfItems(
|
|||||||
sel,
|
sel,
|
||||||
dest,
|
dest,
|
||||||
control.Options{RestorePermissions: true},
|
control.Options{RestorePermissions: true},
|
||||||
dataColls)
|
dataColls,
|
||||||
|
fault.New(true))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return deets
|
return deets
|
||||||
|
|||||||
@ -258,7 +258,8 @@ func (op *RestoreOperation) do(
|
|||||||
op.Selectors,
|
op.Selectors,
|
||||||
op.Destination,
|
op.Destination,
|
||||||
op.Options,
|
op.Options,
|
||||||
dcs)
|
dcs,
|
||||||
|
op.Errors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "restoring collections")
|
return nil, errors.Wrap(err, "restoring collections")
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user