GC: Restore: Split restore data into separate file. (#857)
## Description Places `exchange.Restore` logic into single file <!-- Insert PR description--> ## Type of change - [x] 🐹 Trivial/Minor ## Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #<issue> ## Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
76a4f5531c
commit
f8a10b4de6
@ -32,10 +32,6 @@ var (
|
||||
const (
|
||||
collectionChannelBufferSize = 1000
|
||||
numberOfRetries = 4
|
||||
// RestorePropertyTag defined:
|
||||
// https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/pidtagmessageflags-canonical-property
|
||||
RestorePropertyTag = "Integer 0x0E07"
|
||||
RestoreCanonicalEnableValue = "4"
|
||||
)
|
||||
|
||||
// Collection implements the interface from data.Collection
|
||||
|
||||
29
src/internal/connector/exchange/exchange_vars.go
Normal file
29
src/internal/connector/exchange/exchange_vars.go
Normal file
@ -0,0 +1,29 @@
|
||||
package exchange
|
||||
|
||||
// exchange_vars.go is package level collection of interfaces and
|
||||
// constants that are used within the exchange.
|
||||
|
||||
// Legacy Value Tags and constants are used to override certain values within
|
||||
// M365 objects.
|
||||
// Master Property Value Document:
|
||||
// https://interoperability.blob.core.windows.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf
|
||||
const (
|
||||
// MailRestorePropertyTag inhibits exchange.Mail.Message from being "resent" through the server.
|
||||
// DEFINED: Section 2.791 PidTagMessageFlags
|
||||
MailRestorePropertyTag = "Integer 0x0E07"
|
||||
|
||||
// RestoreCanonicalEnableValue marks message as sent via RopSubmitMessage
|
||||
// Defined: https://interoperability.blob.core.windows.net/files/MS-OXCMSG/%5bMS-OXCMSG%5d.pdf
|
||||
// Section: 2.2.1.6 PidTagMessageFlags Property
|
||||
//nolint:lll
|
||||
// Additional Information: https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/pidtagmessageflags-canonical-property
|
||||
RestoreCanonicalEnableValue = "4"
|
||||
|
||||
// MailSendTimeOverrideProperty allows for send time to be updated.
|
||||
// Section: 2.635 PidTagClientSubmitTime
|
||||
MailSendDateTimeOverrideProperty = "SystemTime 0x0039"
|
||||
|
||||
// MailReceiveDateTimeOverrideProperty allows receive date time to be updated.
|
||||
// Section: 2.789 PidTagMessageDeliveryTime
|
||||
MailReceiveDateTimeOverriveProperty = "SystemTime 0x0E06"
|
||||
)
|
||||
@ -1,7 +1,6 @@
|
||||
package exchange
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -11,13 +10,9 @@ import (
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common"
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
"github.com/alcionai/corso/src/internal/connector/support"
|
||||
"github.com/alcionai/corso/src/internal/path"
|
||||
"github.com/alcionai/corso/src/pkg/account"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
"github.com/alcionai/corso/src/pkg/logger"
|
||||
"github.com/alcionai/corso/src/pkg/selectors"
|
||||
)
|
||||
|
||||
@ -374,221 +369,3 @@ func SetupExchangeCollectionVars(scope selectors.ExchangeScope) (
|
||||
|
||||
return nil, nil, nil, errors.New("exchange scope option not supported")
|
||||
}
|
||||
|
||||
// GetRestoreContainer utility function to create
|
||||
// an unique folder for the restore process
|
||||
// @param category: input from fullPath()[2]
|
||||
// that defines the application the folder is created in.
|
||||
func GetRestoreContainer(
|
||||
service graph.Service,
|
||||
user string,
|
||||
category path.CategoryType,
|
||||
) (string, error) {
|
||||
name := fmt.Sprintf("Corso_Restore_%s", common.FormatNow(common.SimpleDateTimeFormat))
|
||||
option := categoryToOptionIdentifier(category)
|
||||
|
||||
folderID, err := GetContainerID(service, name, user, option)
|
||||
if err == nil {
|
||||
return *folderID, nil
|
||||
}
|
||||
// Experienced error other than folder does not exist
|
||||
if !errors.Is(err, ErrFolderNotFound) {
|
||||
return "", support.WrapAndAppend(user, err, err)
|
||||
}
|
||||
|
||||
switch option {
|
||||
case messages:
|
||||
fold, err := CreateMailFolder(service, user, name)
|
||||
if err != nil {
|
||||
return "", support.WrapAndAppend(user, err, err)
|
||||
}
|
||||
|
||||
return *fold.GetId(), nil
|
||||
case contacts:
|
||||
fold, err := CreateContactFolder(service, user, name)
|
||||
if err != nil {
|
||||
return "", support.WrapAndAppend(user, err, err)
|
||||
}
|
||||
|
||||
return *fold.GetId(), nil
|
||||
case events:
|
||||
calendar, err := CreateCalendar(service, user, name)
|
||||
if err != nil {
|
||||
return "", support.WrapAndAppend(user, err, err)
|
||||
}
|
||||
|
||||
return *calendar.GetId(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("category: %s not supported for folder creation", option)
|
||||
}
|
||||
}
|
||||
|
||||
func RestoreExchangeObject(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
category path.CategoryType,
|
||||
policy control.CollisionPolicy,
|
||||
service graph.Service,
|
||||
destination, user string,
|
||||
) error {
|
||||
if policy != control.Copy {
|
||||
return fmt.Errorf("restore policy: %s not supported", policy)
|
||||
}
|
||||
|
||||
setting := categoryToOptionIdentifier(category)
|
||||
|
||||
switch setting {
|
||||
case messages:
|
||||
return RestoreMailMessage(ctx, bits, service, control.Copy, destination, user)
|
||||
case contacts:
|
||||
return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user)
|
||||
case events:
|
||||
return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user)
|
||||
default:
|
||||
return fmt.Errorf("type: %s not supported for exchange restore", category)
|
||||
}
|
||||
}
|
||||
|
||||
// RestoreExchangeContact restores a contact to the @bits byte
|
||||
// representation of M365 contact object.
|
||||
// @destination M365 ID representing a M365 Contact_Folder
|
||||
// Returns an error if the input bits do not parse into a models.Contactable object
|
||||
// or if an error is encountered sending data to the M365 account.
|
||||
// Post details: https://docs.microsoft.com/en-us/graph/api/user-post-contacts?view=graph-rest-1.0&tabs=go
|
||||
func RestoreExchangeContact(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Service,
|
||||
cp control.CollisionPolicy,
|
||||
destination, user string,
|
||||
) error {
|
||||
contact, err := support.CreateContactFromBytes(bits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := service.Client().UsersById(user).ContactFoldersById(destination).Contacts().Post(contact)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
return errors.New("msgraph contact post fail: REST response not received")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RestoreExchangeEvent(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Service,
|
||||
cp control.CollisionPolicy,
|
||||
destination, user string,
|
||||
) error {
|
||||
event, err := support.CreateEventFromBytes(bits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
return errors.New("msgraph event post fail: REST response not received")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreMailMessage utility function to place an exchange.Mail
|
||||
// message into the user's M365 Exchange account.
|
||||
// @param bits - byte array representation of exchange.Message from Corso backstore
|
||||
// @param service - connector to M365 graph
|
||||
// @param cp - collision policy that directs restore workflow
|
||||
// @param destination - M365 Folder ID. Verified and sent by higher function. `copy` policy can use directly
|
||||
func RestoreMailMessage(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Service,
|
||||
cp control.CollisionPolicy,
|
||||
destination,
|
||||
user string,
|
||||
) error {
|
||||
// Creates messageable object from original bytes
|
||||
originalMessage, err := support.CreateMessageFromBytes(bits)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "restore mail message rcvd: %v", bits)
|
||||
}
|
||||
// Sets fields from original message from storage
|
||||
clone := support.ToMessage(originalMessage)
|
||||
valueID := RestorePropertyTag
|
||||
enableValue := RestoreCanonicalEnableValue
|
||||
|
||||
// Set Extended Properties:
|
||||
// 1st: No transmission
|
||||
// 2nd: Send Date
|
||||
// 3rd: Recv Date
|
||||
sv1 := models.NewSingleValueLegacyExtendedProperty()
|
||||
sv1.SetId(&valueID)
|
||||
sv1.SetValue(&enableValue)
|
||||
|
||||
sv2 := models.NewSingleValueLegacyExtendedProperty()
|
||||
sendPropertyValue := common.FormatLegacyTime(*clone.GetSentDateTime())
|
||||
sendPropertyTag := "SystemTime 0x0039"
|
||||
sv2.SetId(&sendPropertyTag)
|
||||
sv2.SetValue(&sendPropertyValue)
|
||||
|
||||
sv3 := models.NewSingleValueLegacyExtendedProperty()
|
||||
recvPropertyValue := common.FormatLegacyTime(*clone.GetReceivedDateTime())
|
||||
recvPropertyTag := "SystemTime 0x0E06"
|
||||
sv3.SetId(&recvPropertyTag)
|
||||
sv3.SetValue(&recvPropertyValue)
|
||||
|
||||
svlep := []models.SingleValueLegacyExtendedPropertyable{sv1, sv2, sv3}
|
||||
clone.SetSingleValueExtendedProperties(svlep)
|
||||
|
||||
// Switch workflow based on collision policy
|
||||
switch cp {
|
||||
default:
|
||||
logger.Ctx(ctx).DPanicw("unrecognized restore policy; defaulting to copy",
|
||||
"policy", cp)
|
||||
fallthrough
|
||||
case control.Copy:
|
||||
return SendMailToBackStore(service, user, destination, clone)
|
||||
}
|
||||
}
|
||||
|
||||
// SendMailToBackStore function for transporting in-memory messageable item to M365 backstore
|
||||
// @param user string represents M365 ID of user within the tenant
|
||||
// @param destination represents M365 ID of a folder within the users's space
|
||||
// @param message is a models.Messageable interface from "github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
func SendMailToBackStore(service graph.Service, user, destination string, message models.Messageable) error {
|
||||
var (
|
||||
sentMessage models.Messageable
|
||||
err error
|
||||
)
|
||||
|
||||
for count := 0; count < numberOfRetries; count++ {
|
||||
sentMessage, err = service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(message)
|
||||
if err == nil && sentMessage != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return support.WrapAndAppend(": "+support.ConnectorStackErrorTrace(err), err, nil)
|
||||
}
|
||||
|
||||
if sentMessage == nil {
|
||||
return errors.New("message not Sent: blocked by server")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return support.WrapAndAppend(": "+support.ConnectorStackErrorTrace(err), err, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
232
src/internal/connector/exchange/service_restore.go
Normal file
232
src/internal/connector/exchange/service_restore.go
Normal file
@ -0,0 +1,232 @@
|
||||
package exchange
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common"
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
"github.com/alcionai/corso/src/internal/connector/support"
|
||||
"github.com/alcionai/corso/src/internal/path"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
"github.com/alcionai/corso/src/pkg/logger"
|
||||
)
|
||||
|
||||
// GetRestoreContainer utility function to create
|
||||
// an unique folder for the restore process
|
||||
// @param category: input from fullPath()[2]
|
||||
// that defines the application the folder is created in.
|
||||
func GetRestoreContainer(
|
||||
service graph.Service,
|
||||
user string,
|
||||
category path.CategoryType,
|
||||
) (string, error) {
|
||||
name := fmt.Sprintf("Corso_Restore_%s", common.FormatNow(common.SimpleDateTimeFormat))
|
||||
option := categoryToOptionIdentifier(category)
|
||||
|
||||
folderID, err := GetContainerID(service, name, user, option)
|
||||
if err == nil {
|
||||
return *folderID, nil
|
||||
}
|
||||
// Experienced error other than folder does not exist
|
||||
if !errors.Is(err, ErrFolderNotFound) {
|
||||
return "", support.WrapAndAppend(user, err, err)
|
||||
}
|
||||
|
||||
switch option {
|
||||
case messages:
|
||||
fold, err := CreateMailFolder(service, user, name)
|
||||
if err != nil {
|
||||
return "", support.WrapAndAppend(user, err, err)
|
||||
}
|
||||
|
||||
return *fold.GetId(), nil
|
||||
case contacts:
|
||||
fold, err := CreateContactFolder(service, user, name)
|
||||
if err != nil {
|
||||
return "", support.WrapAndAppend(user, err, err)
|
||||
}
|
||||
|
||||
return *fold.GetId(), nil
|
||||
case events:
|
||||
calendar, err := CreateCalendar(service, user, name)
|
||||
if err != nil {
|
||||
return "", support.WrapAndAppend(user, err, err)
|
||||
}
|
||||
|
||||
return *calendar.GetId(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("category: %s not supported for folder creation", option)
|
||||
}
|
||||
}
|
||||
|
||||
// RestoreExchangeObject directs restore pipeline towards restore function
|
||||
// based on the path.CategoryType. All input params are necessary to perform
|
||||
// the type-specific restore function.
|
||||
func RestoreExchangeObject(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
category path.CategoryType,
|
||||
policy control.CollisionPolicy,
|
||||
service graph.Service,
|
||||
destination, user string,
|
||||
) error {
|
||||
if policy != control.Copy {
|
||||
return fmt.Errorf("restore policy: %s not supported", policy)
|
||||
}
|
||||
|
||||
setting := categoryToOptionIdentifier(category)
|
||||
|
||||
switch setting {
|
||||
case messages:
|
||||
return RestoreMailMessage(ctx, bits, service, control.Copy, destination, user)
|
||||
case contacts:
|
||||
return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user)
|
||||
case events:
|
||||
return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user)
|
||||
default:
|
||||
return fmt.Errorf("type: %s not supported for exchange restore", category)
|
||||
}
|
||||
}
|
||||
|
||||
// RestoreExchangeContact restores a contact to the @bits byte
|
||||
// representation of M365 contact object.
|
||||
// @destination M365 ID representing a M365 Contact_Folder
|
||||
// Returns an error if the input bits do not parse into a models.Contactable object
|
||||
// or if an error is encountered sending data to the M365 account.
|
||||
// Post details: https://docs.microsoft.com/en-us/graph/api/user-post-contacts?view=graph-rest-1.0&tabs=go
|
||||
func RestoreExchangeContact(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Service,
|
||||
cp control.CollisionPolicy,
|
||||
destination, user string,
|
||||
) error {
|
||||
contact, err := support.CreateContactFromBytes(bits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := service.Client().UsersById(user).ContactFoldersById(destination).Contacts().Post(contact)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
return errors.New("msgraph contact post fail: REST response not received")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreExchangeEvent restores a contact to the @bits byte
|
||||
// representation of M365 event object.
|
||||
// @param destination is the M365 ID representing Calendar that will receive the event.
|
||||
// Returns an error if input byte array doesn't parse into models.Eventable object
|
||||
// or if an error occurs during sending data to M365 account.
|
||||
// Post details: https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-1.0&tabs=http
|
||||
func RestoreExchangeEvent(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Service,
|
||||
cp control.CollisionPolicy,
|
||||
destination, user string,
|
||||
) error {
|
||||
event, err := support.CreateEventFromBytes(bits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
return errors.New("msgraph event post fail: REST response not received")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreMailMessage utility function to place an exchange.Mail
|
||||
// message into the user's M365 Exchange account.
|
||||
// @param bits - byte array representation of exchange.Message from Corso backstore
|
||||
// @param service - connector to M365 graph
|
||||
// @param cp - collision policy that directs restore workflow
|
||||
// @param destination - M365 Folder ID. Verified and sent by higher function. `copy` policy can use directly
|
||||
func RestoreMailMessage(
|
||||
ctx context.Context,
|
||||
bits []byte,
|
||||
service graph.Service,
|
||||
cp control.CollisionPolicy,
|
||||
destination,
|
||||
user string,
|
||||
) error {
|
||||
// Creates messageable object from original bytes
|
||||
originalMessage, err := support.CreateMessageFromBytes(bits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Sets fields from original message from storage
|
||||
clone := support.ToMessage(originalMessage)
|
||||
valueID := MailRestorePropertyTag
|
||||
enableValue := RestoreCanonicalEnableValue
|
||||
|
||||
// Set Extended Properties:
|
||||
// 1st: No transmission
|
||||
// 2nd: Send Date
|
||||
// 3rd: Recv Date
|
||||
sv1 := models.NewSingleValueLegacyExtendedProperty()
|
||||
sv1.SetId(&valueID)
|
||||
sv1.SetValue(&enableValue)
|
||||
|
||||
sv2 := models.NewSingleValueLegacyExtendedProperty()
|
||||
sendPropertyValue := common.FormatLegacyTime(*clone.GetSentDateTime())
|
||||
sendPropertyTag := MailSendDateTimeOverrideProperty
|
||||
sv2.SetId(&sendPropertyTag)
|
||||
sv2.SetValue(&sendPropertyValue)
|
||||
|
||||
sv3 := models.NewSingleValueLegacyExtendedProperty()
|
||||
recvPropertyValue := common.FormatLegacyTime(*clone.GetReceivedDateTime())
|
||||
recvPropertyTag := MailReceiveDateTimeOverriveProperty
|
||||
sv3.SetId(&recvPropertyTag)
|
||||
sv3.SetValue(&recvPropertyValue)
|
||||
|
||||
svlep := []models.SingleValueLegacyExtendedPropertyable{sv1, sv2, sv3}
|
||||
clone.SetSingleValueExtendedProperties(svlep)
|
||||
|
||||
// Switch workflow based on collision policy
|
||||
switch cp {
|
||||
default:
|
||||
logger.Ctx(ctx).DPanicw("unrecognized restore policy; defaulting to copy",
|
||||
"policy", cp)
|
||||
fallthrough
|
||||
case control.Copy:
|
||||
return SendMailToBackStore(service, user, destination, clone)
|
||||
}
|
||||
}
|
||||
|
||||
// SendMailToBackStore function for transporting in-memory messageable item to M365 backstore
|
||||
// @param user string represents M365 ID of user within the tenant
|
||||
// @param destination represents M365 ID of a folder within the users's space
|
||||
// @param message is a models.Messageable interface from "github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
func SendMailToBackStore(service graph.Service, user, destination string, message models.Messageable) error {
|
||||
sentMessage, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(message)
|
||||
if err != nil {
|
||||
return support.WrapAndAppend(": "+support.ConnectorStackErrorTrace(err), err, nil)
|
||||
}
|
||||
|
||||
if sentMessage == nil {
|
||||
return errors.New("message not Sent: blocked by server")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return support.WrapAndAppend(": "+support.ConnectorStackErrorTrace(err), err, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user