From 715e436dd92b8aa819d2e5fce81381f103c4cd69 Mon Sep 17 00:00:00 2001 From: ashmrtn Date: Fri, 16 Dec 2022 16:06:44 -0800 Subject: [PATCH] Do not return an error if folder was deleted (#1849) ## Description When backing up Exchange, don't return an error if the folder/calendar we're trying to fetch item IDs for has been deleted. Error codes pulled with graph explorer API Manually tested with an Exchange mail backup ## Does this PR need a docs update or release note? - [ ] :white_check_mark: Yes, it's included - [ ] :clock1: Yes, but in a later PR - [x] :no_entry: No ## Type of change - [ ] :sunflower: Feature - [x] :bug: Bugfix - [ ] :world_map: Documentation - [ ] :robot: Test - [ ] :computer: CI/Deployment - [ ] :hamster: Trivial/Minor ## Issue(s) * closes #1846 ## Test Plan - [x] :muscle: Manual - [ ] :zap: Unit test - [ ] :green_heart: E2E --- .../connector/exchange/service_iterators.go | 80 ++++++++++++++++--- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/src/internal/connector/exchange/service_iterators.go b/src/internal/connector/exchange/service_iterators.go index f71e8f731..085f8f20b 100644 --- a/src/internal/connector/exchange/service_iterators.go +++ b/src/internal/connector/exchange/service_iterators.go @@ -7,6 +7,7 @@ import ( multierror "github.com/hashicorp/go-multierror" "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" msuser "github.com/microsoftgraph/msgraph-sdk-go/users" "github.com/pkg/errors" @@ -19,6 +20,24 @@ import ( "github.com/alcionai/corso/src/pkg/selectors" ) +const ( + errEmailFolderNotFound = "ErrorSyncFolderNotFound" + errItemNotFound = "ErrorItemNotFound" +) + +var errContainerDeleted = errors.New("container deleted") + +func hasErrorCode(err error, code string) bool { + var oDataError *odataerrors.ODataError + if !errors.As(err, &oDataError) { + return false + } + + return oDataError.GetError() != nil && + oDataError.GetError().GetCode() != nil && + *oDataError.GetError().GetCode() == code +} + // FilterContainersAndFillCollections is a utility function // that places the M365 object ids belonging to specific directories // into a Collection. Messages outside of those directories are omitted. @@ -75,6 +94,30 @@ func FilterContainersAndFillCollections( continue } + fetchFunc, err := getFetchIDFunc(qp.Category) + if err != nil { + errs = support.WrapAndAppend(qp.ResourceOwner, err, errs) + continue + } + + var deleted bool + + jobs, delta, err := fetchFunc(ctx, service, qp.ResourceOwner, cID, dps.deltas[cID]) + if err != nil && !errors.Is(err, errContainerDeleted) { + deleted = true + errs = support.WrapAndAppend(qp.ResourceOwner, err, errs) + } + + if len(delta) > 0 { + deltaURLs[cID] = delta + } + + // Delay creating the new container so we can handle setting the current + // path correctly if the folder was deleted. + if deleted { + dirPath = nil + } + edc := NewCollection( qp.ResourceOwner, dirPath, @@ -86,23 +129,12 @@ func FilterContainersAndFillCollections( ) collections[cID] = &edc - fetchFunc, err := getFetchIDFunc(qp.Category) - if err != nil { - errs = support.WrapAndAppend(qp.ResourceOwner, err, errs) + if deleted { continue } - jobs, delta, err := fetchFunc(ctx, edc.service, qp.ResourceOwner, cID, dps.deltas[cID]) - if err != nil { - errs = support.WrapAndAppend(qp.ResourceOwner, err, errs) - } - edc.jobs = append(edc.jobs, jobs...) - if len(delta) > 0 { - deltaURLs[cID] = delta - } - // add the current path for the container ID to be used in the next backup // as the "previous path", for reference in case of a rename or relocation. currPaths[cID] = dirPath.Folder() @@ -226,6 +258,14 @@ func FetchEventIDsFromCalendar( for { resp, err := builder.Get(ctx, options) if err != nil { + if hasErrorCode(err, errItemNotFound) { + // The folder was deleted between the time we populated the container + // cache and when we tried to fetch data for it. All we can do is + // return no jobs because we've only pulled basic info about each + // item. + return nil, "", errors.WithStack(errContainerDeleted) + } + return nil, "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) } @@ -288,6 +328,14 @@ func FetchContactIDsFromDirectory( for { resp, err := builder.Get(ctx, options) if err != nil { + if hasErrorCode(err, errItemNotFound) { + // The folder was deleted between the time we populated the container + // cache and when we tried to fetch data for it. All we can do is + // return no jobs because we've only pulled basic info about each + // item. + return nil, "", errors.WithStack(errContainerDeleted) + } + return nil, deltaURL, errors.Wrap(err, support.ConnectorStackErrorTrace(err)) } @@ -354,6 +402,14 @@ func FetchMessageIDsFromDirectory( for { resp, err := builder.Get(ctx, options) if err != nil { + if hasErrorCode(err, errEmailFolderNotFound) { + // The folder was deleted between the time we populated the container + // cache and when we tried to fetch data for it. All we can do is + // return no jobs because we've only pulled basic info about each + // item. + return nil, "", errors.WithStack(errContainerDeleted) + } + return nil, deltaURL, errors.Wrap(err, support.ConnectorStackErrorTrace(err)) }