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