Folder Granularity scope Added to Collections Retrieval (#482)

Backup function `DataExchangeCollections` supports the ability to backup a single file using the `selectors` package. Support files and documentation updated accordingly
This commit is contained in:
Danny 2022-08-05 11:20:36 -04:00 committed by GitHub
parent 73942fe952
commit 97113aa80b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 186 additions and 16 deletions

View File

@ -25,4 +25,4 @@ corso [flags]
* [corso repo](corso_repo.md) - Manage your repositories
* [corso restore](corso_restore.md) - Restore your service data
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -30,4 +30,4 @@ corso backup [flags]
* [corso backup details](corso_backup_details.md) - Shows the details of a backup for a service
* [corso backup list](corso_backup_list.md) - List the history of backups for a service
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -24,4 +24,4 @@ corso backup create [flags]
* [corso backup](corso_backup.md) - Backup your service data
* [corso backup create exchange](corso_backup_create_exchange.md) - Backup M365 Exchange service data
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -26,4 +26,4 @@ corso backup create exchange [flags]
* [corso backup create](corso_backup_create.md) - Backup an M365 Service
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -24,4 +24,4 @@ corso backup details [flags]
* [corso backup](corso_backup.md) - Backup your service data
* [corso backup details exchange](corso_backup_details_exchange.md) - Shows the details of a M365 Exchange service backup
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -31,4 +31,4 @@ corso backup details exchange [flags]
* [corso backup details](corso_backup_details.md) - Shows the details of a backup for a service
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -24,4 +24,4 @@ corso backup list [flags]
* [corso backup](corso_backup.md) - Backup your service data
* [corso backup list exchange](corso_backup_list_exchange.md) - List the history of M365 Exchange service backups
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -23,4 +23,4 @@ corso backup list exchange [flags]
* [corso backup list](corso_backup_list.md) - List the history of backups for a service
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -29,4 +29,4 @@ corso repo [flags]
* [corso repo connect](corso_repo_connect.md) - Connect to a repository.
* [corso repo init](corso_repo_init.md) - Initialize a repository.
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -28,4 +28,4 @@ corso repo connect [flags]
* [corso repo](corso_repo.md) - Manage your repositories
* [corso repo connect s3](corso_repo_connect_s3.md) - Connect to a S3 repository
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -31,4 +31,4 @@ corso repo connect s3 [flags]
* [corso repo connect](corso_repo_connect.md) - Connect to a repository.
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -28,4 +28,4 @@ corso repo init [flags]
* [corso repo](corso_repo.md) - Manage your repositories
* [corso repo init s3](corso_repo_init_s3.md) - Initialize a S3 repository
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -31,4 +31,4 @@ corso repo init s3 [flags]
* [corso repo init](corso_repo_init.md) - Initialize a repository.
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -28,4 +28,4 @@ corso restore [flags]
* [corso](corso.md) - Protect your Microsoft 365 data.
* [corso restore exchange](corso_restore_exchange.md) - Restore M365 Exchange service data
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -31,4 +31,4 @@ corso restore exchange [flags]
* [corso restore](corso_restore.md) - Restore your service data
###### Auto generated by spf13/cobra on 4-Aug-2022
###### Auto generated by spf13/cobra on 5-Aug-2022

View File

@ -182,11 +182,18 @@ func SetupExchangeCollectionVars(scope selectors.ExchangeScope) (
GraphIterateFunc,
) {
if scope.IncludesCategory(selectors.ExchangeMail) {
folders := scope.Get(selectors.ExchangeMailFolder)
if folders[0] == selectors.AnyTgt {
return models.CreateMessageCollectionResponseFromDiscriminatorValue,
GetAllMessagesForUser,
IterateSelectAllMessagesForCollections
}
return models.CreateMessageCollectionResponseFromDiscriminatorValue,
GetAllMessagesForUser,
IterateAndFilterMessagesForCollections
}
return nil, nil, nil
}

View File

@ -1,7 +1,10 @@
package exchange
import (
"fmt"
absser "github.com/microsoft/kiota-abstractions-go/serialization"
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
"github.com/microsoftgraph/msgraph-sdk-go/models"
msfolder "github.com/microsoftgraph/msgraph-sdk-go/users/item/mailfolders"
msmessage "github.com/microsoftgraph/msgraph-sdk-go/users/item/messages"
@ -35,12 +38,27 @@ type GraphQuery func(graph.Service, []string) (absser.Parsable, error)
func GetAllMessagesForUser(gs graph.Service, identities []string) (absser.Parsable, error) {
selecting := []string{"id", "parentFolderId"}
options, err := optionsForMessages(selecting)
if err != nil {
return nil, err
}
return gs.Client().UsersById(identities[0]).Messages().GetWithRequestConfigurationAndResponseHandler(options, nil)
}
// GetAllFolderDisplayNamesForUser is a GraphQuery function for getting FolderId and display
// names for Mail Folder. All other information for the MailFolder object is omitted.
func GetAllFolderNamesForUser(gs graph.Service, identities []string) (absser.Parsable, error) {
options, err := optionsForMailFolders([]string{"id", "displayName"})
if err != nil {
return nil, err
}
return gs.Client().UsersById(identities[0]).MailFolders().GetWithRequestConfigurationAndResponseHandler(options, nil)
}
// GraphIterateFuncs are iterate functions to be used with the M365 iterators (e.g. msgraphgocore.NewPageIterator)
// @returns a callback func that works with msgraphgocore.PageIterator.Iterate function
type GraphIterateFunc func(
@ -98,6 +116,123 @@ func IterateSelectAllMessagesForCollections(
}
}
func IterateAndFilterMessagesForCollections(
tenant string,
scope selectors.ExchangeScope,
errs error,
failFast bool,
credentials account.M365Config,
collections map[string]*Collection,
statusCh chan<- *support.ConnectorOperationStatus,
) func(any) bool {
var isFilterSet bool
return func(messageItem any) bool {
user := scope.Get(selectors.ExchangeUser)[0]
if !isFilterSet {
err := CollectMailFolders(
scope,
tenant,
user,
collections,
credentials,
failFast,
statusCh,
)
if err != nil {
errs = support.WrapAndAppend(user, err, errs)
return false
}
isFilterSet = true
}
message, ok := messageItem.(models.Messageable)
if !ok {
errs = support.WrapAndAppend(user, errors.New("message iteration failure"), errs)
return true
}
// Saving only messages for the created directories
directory := *message.GetParentFolderId()
if _, ok = collections[directory]; !ok {
return true
}
collections[directory].AddJob(*message.GetId())
return true
}
}
func CollectMailFolders(
scope selectors.ExchangeScope,
tenant string,
user string,
collections map[string]*Collection,
credentials account.M365Config,
failFast bool,
statusCh chan<- *support.ConnectorOperationStatus,
) error {
queryService, err := createService(credentials, failFast)
if err != nil {
return errors.New("unable to create a mail folder query service for " + user)
}
query, err := GetAllFolderNamesForUser(queryService, []string{user})
if err != nil {
return fmt.Errorf(
"unable to query mail folder for %s: details: %s",
user,
support.ConnectorStackErrorTrace(err),
)
}
// Iterator required to ensure all potential folders are inspected
// when the breadth of the folder space is large
pageIterator, err := msgraphgocore.NewPageIterator(
query,
&queryService.adapter,
models.CreateMailFolderCollectionResponseFromDiscriminatorValue)
if err != nil {
return errors.Wrap(err, "unable to create iterator during mail folder query service")
}
var service graph.Service
callbackFunc := func(pageItem any) bool {
folder, ok := pageItem.(models.MailFolderable)
if !ok {
err = support.WrapAndAppend(user, errors.New("unable to transform folderable item"), err)
return true
}
if !scope.Contains(selectors.ExchangeMailFolder, *folder.GetDisplayName()) {
return true
}
directory := *folder.GetId()
service, err = createService(credentials, failFast)
if err != nil {
err = support.WrapAndAppend(
*folder.GetDisplayName(),
errors.New("unable to create service a folder query service for "+user),
err,
)
return true
}
temp := NewCollection(
user,
[]string{tenant, user, mailCategory, directory},
messages,
service,
statusCh,
)
collections[directory] = &temp
return true
}
iterateFailure := pageIterator.Iterate(callbackFunc)
if iterateFailure != nil {
err = support.WrapAndAppend(user+" iterate failure", iterateFailure, err)
}
return err
}
//---------------------------------------------------
// exchange.Query Option Section
//------------------------------------------------

View File

@ -139,6 +139,34 @@ func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_restoreMessages(
assert.NoError(suite.T(), err)
}
// TestGraphConnector_SingleMailFolderCollectionQuery verifies that single folder support
// enabled createCollections
func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_SingleMailFolderCollectionQuery() {
t := suite.T()
sel := selectors.NewExchangeBackup()
sel.Include(sel.MailFolders([]string{suite.user}, []string{"Inbox"}))
scopes := sel.Scopes()
for _, scope := range scopes {
collections, err := suite.connector.createCollections(context.Background(), scope)
require.NoError(t, err)
suite.Equal(len(collections), 1)
for _, edc := range collections {
streamChannel := edc.Items()
// Verify that each message can be restored
for stream := range streamChannel {
buf := &bytes.Buffer{}
read, err := buf.ReadFrom(stream.ToReader())
suite.NoError(err)
suite.NotZero(read)
message, err := support.CreateMessageFromBytes(buf.Bytes())
suite.NotNil(message)
suite.NoError(err)
}
}
}
}
///------------------------------------------------------------
// Exchange Functions
//-------------------------------------------------------