GC: Backup: Contacts: Retrieve IDs from all Contact Folders (#830)
## Description Adds the ability to retrieve M365 IDs of contacts not in the default folder. ## Type of change <!--- Please check the type of change your PR introduces: ---> - [x] 🌻 Feature ## Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * closes #804 * closes #767<issue> * closes #669 ## Test Plan - [x] ⚡ Unit test
This commit is contained in:
parent
6d5540cdf6
commit
87014a132b
@ -5,7 +5,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -305,117 +304,11 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestParseCalendarIDFromEvent verifies that parse function
|
|
||||||
// works on the current accepted reference format of
|
|
||||||
// additional data["calendar@odata.associationLink"]
|
|
||||||
func (suite *ExchangeServiceSuite) TestParseCalendarIDFromEvent() {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
checkError assert.ErrorAssertionFunc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Empty string",
|
|
||||||
input: "",
|
|
||||||
checkError: assert.Error,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid string",
|
|
||||||
input: "https://github.com/whyNot/calendarNot Used",
|
|
||||||
checkError: assert.Error,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Missing calendarID not found",
|
|
||||||
input: "https://graph.microsoft.com/v1.0/users" +
|
|
||||||
"('invalid@onmicrosoft.com')/calendars(" +
|
|
||||||
"'')/$ref",
|
|
||||||
checkError: assert.Error,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Valid string",
|
|
||||||
input: "https://graph.microsoft.com/v1.0/users" +
|
|
||||||
"('valid@onmicrosoft.com')/calendars(" +
|
|
||||||
"'AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAA" +
|
|
||||||
"DCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAEGAADSEBNbUIB9RL6ePDeF3FIYAAAZkDq1AAA=')/$ref",
|
|
||||||
checkError: assert.NoError,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
_, err := parseCalendarIDFromEvent(test.input)
|
|
||||||
test.checkError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRetrievalFunctions ensures that utility functions used
|
|
||||||
// to transform work within the current version of GraphAPI.
|
|
||||||
func (suite *ExchangeServiceSuite) TestRetrievalFunctions() {
|
|
||||||
var (
|
|
||||||
userID = tester.M365UserID(suite.T())
|
|
||||||
objectID string
|
|
||||||
)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
query GraphQuery
|
|
||||||
retrieveFunc GraphRetrievalFunc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test Retrieve Message Function",
|
|
||||||
query: GetAllMessagesForUser,
|
|
||||||
retrieveFunc: RetrieveMessageDataForUser,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test Retrieve Contact Function",
|
|
||||||
query: GetAllContactsForUser,
|
|
||||||
retrieveFunc: RetrieveContactDataForUser,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test Retrieve Event Function",
|
|
||||||
query: GetAllEventsForUser,
|
|
||||||
retrieveFunc: RetrieveEventDataForUser,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
output, err := test.query(suite.es, userID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
switch v := output.(type) {
|
|
||||||
case *models.MessageCollectionResponse:
|
|
||||||
transform := output.(models.MessageCollectionResponseable)
|
|
||||||
response := transform.GetValue()
|
|
||||||
require.Greater(t, len(response), 0)
|
|
||||||
|
|
||||||
objectID = *response[0].GetId()
|
|
||||||
case *models.ContactCollectionResponse:
|
|
||||||
transform := output.(models.ContactCollectionResponseable)
|
|
||||||
response := transform.GetValue()
|
|
||||||
require.Greater(t, len(response), 0)
|
|
||||||
|
|
||||||
objectID = *response[0].GetId()
|
|
||||||
case *models.EventCollectionResponse:
|
|
||||||
transform := output.(models.EventCollectionResponseable)
|
|
||||||
response := transform.GetValue()
|
|
||||||
require.Greater(t, len(response), 0)
|
|
||||||
|
|
||||||
objectID = *response[0].GetId()
|
|
||||||
default:
|
|
||||||
t.Logf("What is this type: %T\n", v)
|
|
||||||
}
|
|
||||||
require.NotEmpty(t, objectID)
|
|
||||||
retrieved, err := test.retrieveFunc(suite.es, userID, objectID)
|
|
||||||
assert.NoError(t, err, support.ConnectorStackErrorTrace(err))
|
|
||||||
assert.NotNil(t, retrieved)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestGetMailFolderID verifies the ability to retrieve folder ID of folders
|
// TestGetMailFolderID verifies the ability to retrieve folder ID of folders
|
||||||
// at the top level of the file tree
|
// at the top level of the file tree
|
||||||
func (suite *ExchangeServiceSuite) TestGetContainerID() {
|
func (suite *ExchangeServiceSuite) TestGetContainerID() {
|
||||||
userID := tester.M365UserID(suite.T())
|
userID := tester.M365UserID(suite.T())
|
||||||
|
ctx := context.Background()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
containerName string
|
containerName string
|
||||||
@ -464,6 +357,7 @@ func (suite *ExchangeServiceSuite) TestGetContainerID() {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
_, err := GetContainerID(
|
_, err := GetContainerID(
|
||||||
|
ctx,
|
||||||
suite.es,
|
suite.es,
|
||||||
test.containerName,
|
test.containerName,
|
||||||
userID,
|
userID,
|
||||||
@ -549,6 +443,7 @@ func (suite *ExchangeServiceSuite) TestRestoreEvent() {
|
|||||||
// TestGetRestoreContainer checks the ability to Create a "container" for the
|
// TestGetRestoreContainer checks the ability to Create a "container" for the
|
||||||
// GraphConnector's Restore Workflow based on OptionIdentifier.
|
// GraphConnector's Restore Workflow based on OptionIdentifier.
|
||||||
func (suite *ExchangeServiceSuite) TestGetRestoreContainer() {
|
func (suite *ExchangeServiceSuite) TestGetRestoreContainer() {
|
||||||
|
ctx := context.Background()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
option path.CategoryType
|
option path.CategoryType
|
||||||
@ -591,7 +486,7 @@ func (suite *ExchangeServiceSuite) TestGetRestoreContainer() {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
containerID, err := GetRestoreContainer(suite.es, userID, test.option)
|
containerID, err := GetRestoreContainer(ctx, suite.es, userID, test.option)
|
||||||
require.True(t, test.checkError(t, err, support.ConnectorStackErrorTrace(err)))
|
require.True(t, test.checkError(t, err, support.ConnectorStackErrorTrace(err)))
|
||||||
|
|
||||||
if test.cleanupFunc != nil {
|
if test.cleanupFunc != nil {
|
||||||
|
|||||||
@ -27,3 +27,17 @@ const (
|
|||||||
// Section: 2.789 PidTagMessageDeliveryTime
|
// Section: 2.789 PidTagMessageDeliveryTime
|
||||||
MailReceiveDateTimeOverriveProperty = "SystemTime 0x0E06"
|
MailReceiveDateTimeOverriveProperty = "SystemTime 0x0E06"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// descendable represents objects that implement msgraph-sdk-go/models.entityable
|
||||||
|
// and have the concept of a "parent folder".
|
||||||
|
type descendable interface {
|
||||||
|
GetId() *string
|
||||||
|
GetParentFolderId() *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// displayable represents objects that implement msgraph-sdk-fo/models.entityable
|
||||||
|
// and have the concept of a display name.
|
||||||
|
type displayable interface {
|
||||||
|
GetId() *string
|
||||||
|
GetDisplayName() *string
|
||||||
|
}
|
||||||
|
|||||||
@ -130,6 +130,12 @@ func (suite *ExchangeIteratorSuite) TestIterativeFunctions() {
|
|||||||
iterativeFunction: IterateSelectAllDescendablesForCollections,
|
iterativeFunction: IterateSelectAllDescendablesForCollections,
|
||||||
scope: contactScope,
|
scope: contactScope,
|
||||||
transformer: models.CreateContactFromDiscriminatorValue,
|
transformer: models.CreateContactFromDiscriminatorValue,
|
||||||
|
}, {
|
||||||
|
name: "Contact Folder Traversal",
|
||||||
|
queryFunction: GetAllContactFolderNamesForUser,
|
||||||
|
iterativeFunction: IterateSelectAllContactsForCollections,
|
||||||
|
scope: contactScope,
|
||||||
|
transformer: models.CreateContactFolderCollectionResponseFromDiscriminatorValue,
|
||||||
}, {
|
}, {
|
||||||
name: "Events Iterative Check",
|
name: "Events Iterative Check",
|
||||||
queryFunction: GetAllCalendarNamesForUser,
|
queryFunction: GetAllCalendarNamesForUser,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
msuser "github.com/microsoftgraph/msgraph-sdk-go/users"
|
msuser "github.com/microsoftgraph/msgraph-sdk-go/users"
|
||||||
mscalendars "github.com/microsoftgraph/msgraph-sdk-go/users/item/calendars"
|
mscalendars "github.com/microsoftgraph/msgraph-sdk-go/users/item/calendars"
|
||||||
mscontactfolder "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders"
|
mscontactfolder "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders"
|
||||||
|
mscontactfolderitem "github.com/microsoftgraph/msgraph-sdk-go/users/item/contactfolders/item/contacts"
|
||||||
mscontacts "github.com/microsoftgraph/msgraph-sdk-go/users/item/contacts"
|
mscontacts "github.com/microsoftgraph/msgraph-sdk-go/users/item/contacts"
|
||||||
msevents "github.com/microsoftgraph/msgraph-sdk-go/users/item/events"
|
msevents "github.com/microsoftgraph/msgraph-sdk-go/users/item/events"
|
||||||
msfolder "github.com/microsoftgraph/msgraph-sdk-go/users/item/mailfolders"
|
msfolder "github.com/microsoftgraph/msgraph-sdk-go/users/item/mailfolders"
|
||||||
@ -242,6 +243,26 @@ func optionsForMailFoldersItem(
|
|||||||
return options, nil
|
return options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optionsForContactFoldersItem is the same as optionsForContacts.
|
||||||
|
// TODO: Remove after Issue #828; requires updating msgraph to v0.34
|
||||||
|
func optionsForContactFoldersItem(
|
||||||
|
moreOps []string,
|
||||||
|
) (*mscontactfolderitem.ContactsRequestBuilderGetRequestConfiguration, error) {
|
||||||
|
selecting, err := buildOptions(moreOps, contacts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
requestParameters := &mscontactfolderitem.ContactsRequestBuilderGetQueryParameters{
|
||||||
|
Select: selecting,
|
||||||
|
}
|
||||||
|
options := &mscontactfolderitem.ContactsRequestBuilderGetRequestConfiguration{
|
||||||
|
QueryParameters: requestParameters,
|
||||||
|
}
|
||||||
|
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
|
|
||||||
// optionsForEvents ensures valid option inputs for exchange.Events
|
// optionsForEvents ensures valid option inputs for exchange.Events
|
||||||
// @return is first call in Events().GetWithRequestConfigurationAndResponseHandler(options, handler)
|
// @return is first call in Events().GetWithRequestConfigurationAndResponseHandler(options, handler)
|
||||||
func optionsForEvents(moreOps []string) (*msevents.EventsRequestBuilderGetRequestConfiguration, error) {
|
func optionsForEvents(moreOps []string) (*msevents.EventsRequestBuilderGetRequestConfiguration, error) {
|
||||||
@ -325,7 +346,7 @@ func buildOptions(options []string, optID optionIdentifier) ([]string, error) {
|
|||||||
for _, entry := range options {
|
for _, entry := range options {
|
||||||
_, ok := allowedOptions[entry]
|
_, ok := allowedOptions[entry]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unsupported option: %v", entry)
|
return nil, fmt.Errorf("unsupported element passed to buildOptions: %v", entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
returnedOptions = append(returnedOptions, entry)
|
returnedOptions = append(returnedOptions, entry)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package exchange
|
package exchange
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -100,7 +101,8 @@ func DeleteCalendar(gs graph.Service, user, calendarID string) error {
|
|||||||
// If successful, returns the created folder object.
|
// If successful, returns the created folder object.
|
||||||
func CreateContactFolder(gs graph.Service, user, folderName string) (models.ContactFolderable, error) {
|
func CreateContactFolder(gs graph.Service, user, folderName string) (models.ContactFolderable, error) {
|
||||||
requestBody := models.NewContactFolder()
|
requestBody := models.NewContactFolder()
|
||||||
requestBody.SetDisplayName(&folderName)
|
temp := folderName
|
||||||
|
requestBody.SetDisplayName(&temp)
|
||||||
|
|
||||||
return gs.Client().UsersById(user).ContactFolders().Post(requestBody)
|
return gs.Client().UsersById(user).ContactFolders().Post(requestBody)
|
||||||
}
|
}
|
||||||
@ -244,13 +246,22 @@ func GetAllContactFolders(gs graph.Service, user, nameContains string) ([]models
|
|||||||
// @param containerName is the target's name, user-readable and case sensitive
|
// @param containerName is the target's name, user-readable and case sensitive
|
||||||
// @param category switches query and iteration to support multiple exchange applications
|
// @param category switches query and iteration to support multiple exchange applications
|
||||||
// @returns a *string if the folder exists. If the folder does not exist returns nil, error-> folder not found
|
// @returns a *string if the folder exists. If the folder does not exist returns nil, error-> folder not found
|
||||||
func GetContainerID(service graph.Service, containerName, user string, category optionIdentifier) (*string, error) {
|
func GetContainerID(
|
||||||
|
ctx context.Context,
|
||||||
|
service graph.Service,
|
||||||
|
containerName,
|
||||||
|
user string,
|
||||||
|
category optionIdentifier,
|
||||||
|
) (*string, error) {
|
||||||
var (
|
var (
|
||||||
errs error
|
errs error
|
||||||
targetID *string
|
targetID *string
|
||||||
query GraphQuery
|
query GraphQuery
|
||||||
transform absser.ParsableFactory
|
transform absser.ParsableFactory
|
||||||
isCalendar bool
|
isCalendar bool
|
||||||
|
errUpdater = func(id string, err error) {
|
||||||
|
errs = support.WrapAndAppend(id, err, errs)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
switch category {
|
switch category {
|
||||||
@ -291,7 +302,7 @@ func GetContainerID(service graph.Service, containerName, user string, category
|
|||||||
containerName,
|
containerName,
|
||||||
service.Adapter().GetBaseUrl(),
|
service.Adapter().GetBaseUrl(),
|
||||||
isCalendar,
|
isCalendar,
|
||||||
errs,
|
errUpdater,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := pageIterator.Iterate(callbackFunc); err != nil {
|
if err := pageIterator.Iterate(callbackFunc); err != nil {
|
||||||
@ -305,32 +316,6 @@ func GetContainerID(service graph.Service, containerName, user string, category
|
|||||||
return targetID, errs
|
return targetID, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseCalendarIDFromEvent returns the M365 ID for a calendar
|
|
||||||
// @param reference: string from additionalData map of an event
|
|
||||||
// References should follow the form `https://... calendars('ID')/$ref`
|
|
||||||
// If the reference does not follow form an error is returned
|
|
||||||
func parseCalendarIDFromEvent(reference string) (string, error) {
|
|
||||||
stringArray := strings.Split(reference, "calendars('")
|
|
||||||
if len(stringArray) < 2 {
|
|
||||||
return "", errors.New("calendarID not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
temp := stringArray[1]
|
|
||||||
stringArray = strings.Split(temp, "')/$ref")
|
|
||||||
|
|
||||||
if len(stringArray) < 2 {
|
|
||||||
return "", errors.New("calendarID not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
calendarID := stringArray[0]
|
|
||||||
|
|
||||||
if len(calendarID) == 0 {
|
|
||||||
return "", errors.New("calendarID empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return calendarID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetupExchangeCollectionVars is a helper function returns a sets
|
// SetupExchangeCollectionVars is a helper function returns a sets
|
||||||
// Exchange.Type specific functions based on scope
|
// Exchange.Type specific functions based on scope
|
||||||
func SetupExchangeCollectionVars(scope selectors.ExchangeScope) (
|
func SetupExchangeCollectionVars(scope selectors.ExchangeScope) (
|
||||||
@ -361,9 +346,9 @@ func SetupExchangeCollectionVars(scope selectors.ExchangeScope) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if scope.IncludesCategory(selectors.ExchangeContact) {
|
if scope.IncludesCategory(selectors.ExchangeContact) {
|
||||||
return models.CreateContactFromDiscriminatorValue,
|
return models.CreateContactFolderCollectionResponseFromDiscriminatorValue,
|
||||||
GetAllContactsForUser,
|
GetAllContactFolderNamesForUser,
|
||||||
IterateSelectAllDescendablesForCollections,
|
IterateSelectAllContactsForCollections,
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
@ -14,20 +15,6 @@ import (
|
|||||||
|
|
||||||
var errNilResolver = errors.New("nil resolver")
|
var errNilResolver = errors.New("nil resolver")
|
||||||
|
|
||||||
// descendable represents objects that implement msgraph-sdk-go/models.entityable
|
|
||||||
// and have the concept of a "parent folder".
|
|
||||||
type descendable interface {
|
|
||||||
GetId() *string
|
|
||||||
GetParentFolderId() *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// displayable represents objects that implement msgraph-sdk-fo/models.entityable
|
|
||||||
// and have the concept of a display name.
|
|
||||||
type displayable interface {
|
|
||||||
GetId() *string
|
|
||||||
GetDisplayName() *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphIterateFuncs are iterate functions to be used with the M365 iterators (e.g. msgraphgocore.NewPageIterator)
|
// 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
|
// @returns a callback func that works with msgraphgocore.PageIterator.Iterate function
|
||||||
type GraphIterateFunc func(
|
type GraphIterateFunc func(
|
||||||
@ -420,6 +407,128 @@ func IterateFilterFolderDirectoriesForCollections(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IterateSelectAllContactsForCollections(
|
||||||
|
ctx context.Context,
|
||||||
|
qp graph.QueryParams,
|
||||||
|
errUpdater func(string, error),
|
||||||
|
collections map[string]*Collection,
|
||||||
|
statusUpdater support.StatusUpdater,
|
||||||
|
) func(any) bool {
|
||||||
|
var isPrimarySet bool
|
||||||
|
|
||||||
|
return func(folderItem any) bool {
|
||||||
|
folder, ok := folderItem.(models.ContactFolderable)
|
||||||
|
if !ok {
|
||||||
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
errors.New("casting folderItem to models.ContactFolderable"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isPrimarySet && folder.GetParentFolderId() != nil {
|
||||||
|
service, err := createService(qp.Credentials, qp.FailFast)
|
||||||
|
if err != nil {
|
||||||
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
errors.Wrap(err, "unable to create service during IterateSelectAllContactsForCollections"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
contactIDS, err := ReturnContactIDsFromDirectory(service, qp.User, *folder.GetParentFolderId())
|
||||||
|
if err != nil {
|
||||||
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
dirPath, err := path.Builder{}.Append(*folder.GetParentFolderId()).ToDataLayerExchangePathForCategory(
|
||||||
|
qp.Credentials.TenantID,
|
||||||
|
qp.User,
|
||||||
|
path.ContactsCategory,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
edc := NewCollection(
|
||||||
|
qp.User,
|
||||||
|
dirPath,
|
||||||
|
contacts,
|
||||||
|
service,
|
||||||
|
statusUpdater,
|
||||||
|
)
|
||||||
|
edc.jobs = append(edc.jobs, contactIDS...)
|
||||||
|
collections["Contacts"] = &edc
|
||||||
|
isPrimarySet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := createService(qp.Credentials, qp.FailFast)
|
||||||
|
if err != nil {
|
||||||
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
folderID := *folder.GetId()
|
||||||
|
|
||||||
|
listOfIDs, err := ReturnContactIDsFromDirectory(service, qp.User, folderID)
|
||||||
|
if err != nil {
|
||||||
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if folder.GetDisplayName() == nil ||
|
||||||
|
listOfIDs == nil {
|
||||||
|
return true // Invalid state TODO: How should this be named
|
||||||
|
}
|
||||||
|
|
||||||
|
dirPath, err := path.Builder{}.Append(*folder.GetId()).ToDataLayerExchangePathForCategory(
|
||||||
|
qp.Credentials.TenantID,
|
||||||
|
qp.User,
|
||||||
|
path.ContactsCategory,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
errUpdater(
|
||||||
|
qp.User,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
edc := NewCollection(
|
||||||
|
qp.User,
|
||||||
|
dirPath,
|
||||||
|
contacts,
|
||||||
|
service,
|
||||||
|
statusUpdater,
|
||||||
|
)
|
||||||
|
edc.jobs = append(edc.jobs, listOfIDs...)
|
||||||
|
collections[*folder.GetId()] = &edc
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// iterateFindContainerID is a utility function that supports finding
|
// iterateFindContainerID is a utility function that supports finding
|
||||||
// M365 folders objects that matches the folderName. Iterator callback function
|
// M365 folders objects that matches the folderName. Iterator callback function
|
||||||
// will work on folderCollection responses whose objects implement
|
// will work on folderCollection responses whose objects implement
|
||||||
@ -431,7 +540,7 @@ func iterateFindContainerID(
|
|||||||
containerID **string,
|
containerID **string,
|
||||||
containerName, errorIdentifier string,
|
containerName, errorIdentifier string,
|
||||||
isCalendar bool,
|
isCalendar bool,
|
||||||
errs error,
|
errUpdater func(string, error),
|
||||||
) func(any) bool {
|
) func(any) bool {
|
||||||
return func(entry any) bool {
|
return func(entry any) bool {
|
||||||
if isCalendar {
|
if isCalendar {
|
||||||
@ -446,10 +555,9 @@ func iterateFindContainerID(
|
|||||||
|
|
||||||
folder, ok := entry.(displayable)
|
folder, ok := entry.(displayable)
|
||||||
if !ok {
|
if !ok {
|
||||||
errs = support.WrapAndAppend(
|
errUpdater(
|
||||||
errorIdentifier,
|
errorIdentifier,
|
||||||
errors.New("struct does not implement displayable"),
|
errors.New("struct does not implement displayable"),
|
||||||
errs,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -473,3 +581,55 @@ func iterateFindContainerID(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IDistFunc collection of helper functions which return a list of strings
|
||||||
|
// from a response.
|
||||||
|
type IDListFunc func(gs graph.Service, user, m365ID string) ([]string, error)
|
||||||
|
|
||||||
|
// ReturnContactIDsFromDirectory function that returns a list of all the m365IDs of the contacts
|
||||||
|
// of the targeted directory
|
||||||
|
func ReturnContactIDsFromDirectory(gs graph.Service, user, directoryID string) ([]string, error) {
|
||||||
|
options, err := optionsForContactFoldersItem([]string{"parentFolderId"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stringArray := []string{}
|
||||||
|
|
||||||
|
response, err := gs.Client().
|
||||||
|
UsersById(user).
|
||||||
|
ContactFoldersById(directoryID).
|
||||||
|
Contacts().
|
||||||
|
GetWithRequestConfigurationAndResponseHandler(options, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pageIterator, err := msgraphgocore.NewPageIterator(
|
||||||
|
response,
|
||||||
|
gs.Adapter(),
|
||||||
|
models.CreateContactCollectionResponseFromDiscriminatorValue,
|
||||||
|
)
|
||||||
|
|
||||||
|
callbackFunc := func(pageItem any) bool {
|
||||||
|
entry, ok := pageItem.(models.Contactable)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("casting pageItem to models.Contactable")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
stringArray = append(stringArray, *entry.GetId())
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if iterateErr := pageIterator.Iterate(callbackFunc); iterateErr != nil {
|
||||||
|
return nil, iterateErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringArray, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -67,7 +67,7 @@ func GetAllCalendarNamesForUser(gs graph.Service, user string) (absser.Parsable,
|
|||||||
// and display names for contacts. All other information is omitted.
|
// and display names for contacts. All other information is omitted.
|
||||||
// Does not return the primary Contact Folder
|
// Does not return the primary Contact Folder
|
||||||
func GetAllContactFolderNamesForUser(gs graph.Service, user string) (absser.Parsable, error) {
|
func GetAllContactFolderNamesForUser(gs graph.Service, user string) (absser.Parsable, error) {
|
||||||
options, err := optionsForContactFolders([]string{"displayName"})
|
options, err := optionsForContactFolders([]string{"displayName", "parentFolderId"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
// @param category: input from fullPath()[2]
|
// @param category: input from fullPath()[2]
|
||||||
// that defines the application the folder is created in.
|
// that defines the application the folder is created in.
|
||||||
func GetRestoreContainer(
|
func GetRestoreContainer(
|
||||||
|
ctx context.Context,
|
||||||
service graph.Service,
|
service graph.Service,
|
||||||
user string,
|
user string,
|
||||||
category path.CategoryType,
|
category path.CategoryType,
|
||||||
@ -27,39 +28,39 @@ func GetRestoreContainer(
|
|||||||
name := fmt.Sprintf("Corso_Restore_%s", common.FormatNow(common.SimpleDateTimeFormat))
|
name := fmt.Sprintf("Corso_Restore_%s", common.FormatNow(common.SimpleDateTimeFormat))
|
||||||
option := categoryToOptionIdentifier(category)
|
option := categoryToOptionIdentifier(category)
|
||||||
|
|
||||||
folderID, err := GetContainerID(service, name, user, option)
|
folderID, err := GetContainerID(ctx, service, name, user, option)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return *folderID, nil
|
return *folderID, nil
|
||||||
}
|
}
|
||||||
// Experienced error other than folder does not exist
|
// Experienced error other than folder does not exist
|
||||||
if !errors.Is(err, ErrFolderNotFound) {
|
if !errors.Is(err, ErrFolderNotFound) {
|
||||||
return "", support.WrapAndAppend(user, err, err)
|
return "", support.WrapAndAppend(user+": lookup failue during GetContainerID", err, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch option {
|
switch option {
|
||||||
case messages:
|
case messages:
|
||||||
fold, err := CreateMailFolder(service, user, name)
|
fold, err := CreateMailFolder(service, user, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", support.WrapAndAppend(user, err, err)
|
return "", support.WrapAndAppend(fmt.Sprintf("creating folder %s for user %s", name, user), err, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return *fold.GetId(), nil
|
return *fold.GetId(), nil
|
||||||
case contacts:
|
case contacts:
|
||||||
fold, err := CreateContactFolder(service, user, name)
|
fold, err := CreateContactFolder(service, user, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", support.WrapAndAppend(user, err, err)
|
return "", support.WrapAndAppend(user+"failure during CreateContactFolder during restore Contact", err, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return *fold.GetId(), nil
|
return *fold.GetId(), nil
|
||||||
case events:
|
case events:
|
||||||
calendar, err := CreateCalendar(service, user, name)
|
calendar, err := CreateCalendar(service, user, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", support.WrapAndAppend(user, err, err)
|
return "", support.WrapAndAppend(user+"failure during CreateCalendar during restore Event", err, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return *calendar.GetId(), nil
|
return *calendar.GetId(), nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("category: %s not supported for folder creation", option)
|
return "", fmt.Errorf("category: %s not supported for folder creation: GetRestoreContainer", option)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ func RestoreExchangeObject(
|
|||||||
destination, user string,
|
destination, user string,
|
||||||
) error {
|
) error {
|
||||||
if policy != control.Copy {
|
if policy != control.Copy {
|
||||||
return fmt.Errorf("restore policy: %s not supported", policy)
|
return fmt.Errorf("restore policy: %s not supported for RestoreExchangeObject", policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
setting := categoryToOptionIdentifier(category)
|
setting := categoryToOptionIdentifier(category)
|
||||||
@ -88,7 +89,7 @@ func RestoreExchangeObject(
|
|||||||
case events:
|
case events:
|
||||||
return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user)
|
return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("type: %s not supported for exchange restore", category)
|
return fmt.Errorf("type: %s not supported for RestoreExchangeObject", category)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,12 +108,16 @@ func RestoreExchangeContact(
|
|||||||
) error {
|
) error {
|
||||||
contact, err := support.CreateContactFromBytes(bits)
|
contact, err := support.CreateContactFromBytes(bits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(err, "failure to create contact from bytes: RestoreExchangeContact")
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := service.Client().UsersById(user).ContactFoldersById(destination).Contacts().Post(contact)
|
response, err := service.Client().UsersById(user).ContactFoldersById(destination).Contacts().Post(contact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
return errors.Wrap(
|
||||||
|
err,
|
||||||
|
"failure to create Contact during RestoreExchangeContact: "+
|
||||||
|
support.ConnectorStackErrorTrace(err),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response == nil {
|
if response == nil {
|
||||||
@ -142,7 +147,11 @@ func RestoreExchangeEvent(
|
|||||||
|
|
||||||
response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(event)
|
response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, support.ConnectorStackErrorTrace(err))
|
return errors.Wrap(err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"failure to event creation failure during RestoreExchangeEvent: %s",
|
||||||
|
support.ConnectorStackErrorTrace(err)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response == nil {
|
if response == nil {
|
||||||
@ -202,7 +211,7 @@ func RestoreMailMessage(
|
|||||||
// Switch workflow based on collision policy
|
// Switch workflow based on collision policy
|
||||||
switch cp {
|
switch cp {
|
||||||
default:
|
default:
|
||||||
logger.Ctx(ctx).DPanicw("unrecognized restore policy; defaulting to copy",
|
logger.Ctx(ctx).DPanicw("restoreMailMessage received unrecognized restore policy; defaulting to copy",
|
||||||
"policy", cp)
|
"policy", cp)
|
||||||
fallthrough
|
fallthrough
|
||||||
case control.Copy:
|
case control.Copy:
|
||||||
@ -217,16 +226,14 @@ func RestoreMailMessage(
|
|||||||
func SendMailToBackStore(service graph.Service, user, destination string, message models.Messageable) error {
|
func SendMailToBackStore(service graph.Service, user, destination string, message models.Messageable) error {
|
||||||
sentMessage, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(message)
|
sentMessage, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return support.WrapAndAppend(": "+support.ConnectorStackErrorTrace(err), err, nil)
|
return errors.Wrap(err,
|
||||||
|
*message.GetId()+": failure sendMailAPI: "+support.ConnectorStackErrorTrace(err),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sentMessage == nil {
|
if sentMessage == nil {
|
||||||
return errors.New("message not Sent: blocked by server")
|
return errors.New("message not Sent: blocked by server")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return support.WrapAndAppend(": "+support.ConnectorStackErrorTrace(err), err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -279,10 +279,11 @@ func (gc *GraphConnector) RestoreDataCollections(
|
|||||||
|
|
||||||
switch service {
|
switch service {
|
||||||
case path.ExchangeService:
|
case path.ExchangeService:
|
||||||
folderID, errs = exchange.GetRestoreContainer(&gc.graphService, user, category)
|
folderID, errs = exchange.GetRestoreContainer(ctx, &gc.graphService, user, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
|
fmt.Println("RestoreContainer Failed")
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,7 +295,7 @@ func (gc *GraphConnector) RestoreDataCollections(
|
|||||||
case itemData, ok := <-items:
|
case itemData, ok := <-items:
|
||||||
if !ok {
|
if !ok {
|
||||||
exit = true
|
exit = true
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
attempts++
|
attempts++
|
||||||
|
|
||||||
@ -302,7 +303,12 @@ func (gc *GraphConnector) RestoreDataCollections(
|
|||||||
|
|
||||||
_, err := buf.ReadFrom(itemData.ToReader())
|
_, err := buf.ReadFrom(itemData.ToReader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = support.WrapAndAppend(itemData.UUID(), err, errs)
|
errs = support.WrapAndAppend(
|
||||||
|
itemData.UUID()+": byteReadError during RestoreDataCollection",
|
||||||
|
err,
|
||||||
|
errs,
|
||||||
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +318,13 @@ func (gc *GraphConnector) RestoreDataCollections(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = support.WrapAndAppend(itemData.UUID(), err, errs)
|
// More information to be here
|
||||||
|
errs = support.WrapAndAppend(
|
||||||
|
itemData.UUID()+": failed to upload RestoreExchangeObject: "+service.String()+"-"+category.String(),
|
||||||
|
err,
|
||||||
|
errs,
|
||||||
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
successes++
|
successes++
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user