From 51f44c2988c55526e87a4cba9c2b6eee1b4ad7db Mon Sep 17 00:00:00 2001 From: ashmrtn <3891298+ashmrtn@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:48:54 -0800 Subject: [PATCH] Add functions used for restore to existing service-level handlers (#4687) This continues the push towards having service-level handlers that know how to perform different operations. It adds the helper functions that will be used during restore operations to the existing handler code This logic is not currently used nor does this PR change the restore call path --- #### 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 - [ ] :bug: Bugfix - [ ] :world_map: Documentation - [ ] :robot: Supportability/Tests - [ ] :computer: CI/Deployment - [x] :broom: Tech Debt/Cleanup #### Issue(s) * #4254 #### Test Plan - [ ] :muscle: Manual - [x] :zap: Unit test - [ ] :green_heart: E2E --- src/internal/m365/export.go | 6 +- src/internal/m365/resource/resource.go | 4 + src/internal/m365/service/groups/export.go | 62 +++++++++++++-- .../m365/service/groups/export_test.go | 5 +- src/internal/m365/service/onedrive/export.go | 75 +++++++++++++++++-- .../m365/service/onedrive/export_test.go | 3 +- .../m365/service/sharepoint/export.go | 66 ++++++++++++++-- .../m365/service/sharepoint/export_test.go | 3 +- 8 files changed, 195 insertions(+), 29 deletions(-) diff --git a/src/internal/m365/export.go b/src/internal/m365/export.go index 2c21d5363..7ec8de2da 100644 --- a/src/internal/m365/export.go +++ b/src/internal/m365/export.go @@ -21,13 +21,13 @@ func (ctrl *Controller) NewServiceHandler( switch service { case path.OneDriveService: - return onedrive.NewOneDriveHandler(opts), nil + return onedrive.NewOneDriveHandler(opts, ctrl.AC, ctrl.resourceHandler), nil case path.SharePointService: - return sharepoint.NewSharePointHandler(opts), nil + return sharepoint.NewSharePointHandler(opts, ctrl.AC, ctrl.resourceHandler), nil case path.GroupsService: - return groups.NewGroupsHandler(opts), nil + return groups.NewGroupsHandler(opts, ctrl.AC, ctrl.resourceHandler), nil } return nil, clues.New("unrecognized service"). diff --git a/src/internal/m365/resource/resource.go b/src/internal/m365/resource/resource.go index 6aca21924..57d1fef02 100644 --- a/src/internal/m365/resource/resource.go +++ b/src/internal/m365/resource/resource.go @@ -1,5 +1,9 @@ package resource +import "github.com/alcionai/clues" + +var ErrNoResourceLookup = clues.New("missing resource lookup client") + type Category string const ( diff --git a/src/internal/m365/service/groups/export.go b/src/internal/m365/service/groups/export.go index 163acfe1a..5bdc8c0a1 100644 --- a/src/internal/m365/service/groups/export.go +++ b/src/internal/m365/service/groups/export.go @@ -10,6 +10,7 @@ import ( "github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/m365/collection/drive" "github.com/alcionai/corso/src/internal/m365/collection/groups" + "github.com/alcionai/corso/src/internal/m365/resource" "github.com/alcionai/corso/src/internal/operations/inject" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/control" @@ -17,20 +18,33 @@ import ( "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/services/m365/api" ) -var _ inject.ServiceHandler = &baseGroupsHandler{} +var _ inject.ServiceHandler = &groupsHandler{} func NewGroupsHandler( opts control.Options, -) *baseGroupsHandler { - return &baseGroupsHandler{ - opts: opts, - backupDriveIDNames: idname.NewCache(nil), - backupSiteIDWebURL: idname.NewCache(nil), + apiClient api.Client, + resourceGetter idname.GetResourceIDAndNamer, +) *groupsHandler { + return &groupsHandler{ + baseGroupsHandler: baseGroupsHandler{ + opts: opts, + backupDriveIDNames: idname.NewCache(nil), + backupSiteIDWebURL: idname.NewCache(nil), + }, + apiClient: apiClient, + resourceGetter: resourceGetter, } } +// ========================================================================== // +// baseGroupsHandler +// ========================================================================== // + +// baseGroupsHandler contains logic for tracking data and doing operations +// (e.x. export) that don't require contact with external M356 services. type baseGroupsHandler struct { opts control.Options @@ -134,3 +148,39 @@ func (h *baseGroupsHandler) ProduceExportCollections( return ec, el.Failure() } + +// ========================================================================== // +// groupsHandler +// ========================================================================== // + +// groupsHandler contains logic for handling data and performing operations +// (e.x. restore) regardless of whether they require contact with external M365 +// services or not. +type groupsHandler struct { + baseGroupsHandler + apiClient api.Client + resourceGetter idname.GetResourceIDAndNamer +} + +func (h *groupsHandler) IsServiceEnabled( + ctx context.Context, + resourceID string, +) (bool, error) { + // TODO(ashmrtn): Move free function implementation to this function. + res, err := IsServiceEnabled(ctx, h.apiClient.Groups(), resourceID) + return res, clues.Stack(err).OrNil() +} + +func (h *groupsHandler) PopulateProtectedResourceIDAndName( + ctx context.Context, + resourceID string, // Can be either ID or name. + ins idname.Cacher, +) (idname.Provider, error) { + if h.resourceGetter == nil { + return nil, clues.StackWC(ctx, resource.ErrNoResourceLookup) + } + + pr, err := h.resourceGetter.GetResourceIDAndNameFrom(ctx, resourceID, ins) + + return pr, clues.Wrap(err, "identifying resource owner").OrNil() +} diff --git a/src/internal/m365/service/groups/export_test.go b/src/internal/m365/service/groups/export_test.go index d2f1de8e2..a5a5aaa24 100644 --- a/src/internal/m365/service/groups/export_test.go +++ b/src/internal/m365/service/groups/export_test.go @@ -21,6 +21,7 @@ import ( "github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" + "github.com/alcionai/corso/src/pkg/services/m365/api" ) type ExportUnitSuite struct { @@ -96,7 +97,7 @@ func (suite *ExportUnitSuite) TestExportRestoreCollections_messages() { stats := data.ExportStats{} - ecs, err := NewGroupsHandler(control.DefaultOptions()). + ecs, err := NewGroupsHandler(control.DefaultOptions(), api.Client{}, nil). ProduceExportCollections( ctx, int(version.Backup), @@ -196,7 +197,7 @@ func (suite *ExportUnitSuite) TestExportRestoreCollections_libraries() { }, } - handler := NewGroupsHandler(control.DefaultOptions()) + handler := NewGroupsHandler(control.DefaultOptions(), api.Client{}, nil) handler.CacheItemInfo(dii) stats := data.ExportStats{} diff --git a/src/internal/m365/service/onedrive/export.go b/src/internal/m365/service/onedrive/export.go index 784e0e731..304e6b4a0 100644 --- a/src/internal/m365/service/onedrive/export.go +++ b/src/internal/m365/service/onedrive/export.go @@ -5,35 +5,58 @@ import ( "github.com/alcionai/clues" + "github.com/alcionai/corso/src/internal/common/idname" "github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/m365/collection/drive" + "github.com/alcionai/corso/src/internal/m365/resource" "github.com/alcionai/corso/src/internal/operations/inject" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" + "github.com/alcionai/corso/src/pkg/services/m365/api" ) -var _ inject.ServiceHandler = &baseOnedriveHandler{} +var _ inject.ServiceHandler = &onedriveHandler{} func NewOneDriveHandler( opts control.Options, -) *baseOnedriveHandler { - return &baseOnedriveHandler{ - opts: opts, + apiClient api.Client, + resourceGetter idname.GetResourceIDAndNamer, +) *onedriveHandler { + return &onedriveHandler{ + baseOneDriveHandler: baseOneDriveHandler{ + opts: opts, + backupDriveIDNames: idname.NewCache(nil), + }, + apiClient: apiClient, + resourceGetter: resourceGetter, } } -type baseOnedriveHandler struct { - opts control.Options +// ========================================================================== // +// baseOneDriveHandler +// ========================================================================== // + +// baseOneDriveHandler contains logic for tracking data and doing operations +// (e.x. export) that don't require contact with external M356 services. +type baseOneDriveHandler struct { + opts control.Options + backupDriveIDNames idname.CacheBuilder } -func (h *baseOnedriveHandler) CacheItemInfo(v details.ItemInfo) {} +func (h *baseOneDriveHandler) CacheItemInfo(v details.ItemInfo) { + if v.OneDrive == nil { + return + } + + h.backupDriveIDNames.Add(v.OneDrive.DriveID, v.OneDrive.DriveName) +} // ProduceExportCollections will create the export collections for the // given restore collections. -func (h *baseOnedriveHandler) ProduceExportCollections( +func (h *baseOneDriveHandler) ProduceExportCollections( ctx context.Context, backupVersion int, exportCfg control.ExportConfig, @@ -65,3 +88,39 @@ func (h *baseOnedriveHandler) ProduceExportCollections( return ec, el.Failure() } + +// ========================================================================== // +// onedriveHandler +// ========================================================================== // + +// onedriveHandler contains logic for handling data and performing operations +// (e.x. restore) regardless of whether they require contact with external M365 +// services or not. +type onedriveHandler struct { + baseOneDriveHandler + apiClient api.Client + resourceGetter idname.GetResourceIDAndNamer +} + +func (h *onedriveHandler) IsServiceEnabled( + ctx context.Context, + resourceID string, +) (bool, error) { + // TODO(ashmrtn): Move free function implementation to this function. + res, err := IsServiceEnabled(ctx, h.apiClient.Users(), resourceID) + return res, clues.Stack(err).OrNil() +} + +func (h *onedriveHandler) PopulateProtectedResourceIDAndName( + ctx context.Context, + resourceID string, // Can be either ID or name. + ins idname.Cacher, +) (idname.Provider, error) { + if h.resourceGetter == nil { + return nil, clues.StackWC(ctx, resource.ErrNoResourceLookup) + } + + pr, err := h.resourceGetter.GetResourceIDAndNameFrom(ctx, resourceID, ins) + + return pr, clues.Wrap(err, "identifying resource owner").OrNil() +} diff --git a/src/internal/m365/service/onedrive/export_test.go b/src/internal/m365/service/onedrive/export_test.go index bf2cd5cfc..daaa0eaf0 100644 --- a/src/internal/m365/service/onedrive/export_test.go +++ b/src/internal/m365/service/onedrive/export_test.go @@ -20,6 +20,7 @@ import ( "github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" + "github.com/alcionai/corso/src/pkg/services/m365/api" ) type ExportUnitSuite struct { @@ -341,7 +342,7 @@ func (suite *ExportUnitSuite) TestExportRestoreCollections() { stats := data.ExportStats{} - ecs, err := NewOneDriveHandler(control.DefaultOptions()). + ecs, err := NewOneDriveHandler(control.DefaultOptions(), api.Client{}, nil). ProduceExportCollections( ctx, int(version.Backup), diff --git a/src/internal/m365/service/sharepoint/export.go b/src/internal/m365/service/sharepoint/export.go index 99276d80b..bc5ff4009 100644 --- a/src/internal/m365/service/sharepoint/export.go +++ b/src/internal/m365/service/sharepoint/export.go @@ -8,6 +8,7 @@ import ( "github.com/alcionai/corso/src/internal/common/idname" "github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/m365/collection/drive" + "github.com/alcionai/corso/src/internal/m365/resource" "github.com/alcionai/corso/src/internal/operations/inject" "github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/control" @@ -15,25 +16,38 @@ import ( "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/services/m365/api" ) -var _ inject.ServiceHandler = &baseSharepointHandler{} +var _ inject.ServiceHandler = &sharepointHandler{} func NewSharePointHandler( opts control.Options, -) *baseSharepointHandler { - return &baseSharepointHandler{ - opts: opts, - backupDriveIDNames: idname.NewCache(nil), + apiClient api.Client, + resourceGetter idname.GetResourceIDAndNamer, +) *sharepointHandler { + return &sharepointHandler{ + baseSharePointHandler: baseSharePointHandler{ + opts: opts, + backupDriveIDNames: idname.NewCache(nil), + }, + apiClient: apiClient, + resourceGetter: resourceGetter, } } -type baseSharepointHandler struct { +// ========================================================================== // +// baseSharePointHandler +// ========================================================================== // + +// baseSharePointHandler contains logic for tracking data and doing operations +// (e.x. export) that don't require contact with external M356 services. +type baseSharePointHandler struct { opts control.Options backupDriveIDNames idname.CacheBuilder } -func (h *baseSharepointHandler) CacheItemInfo(v details.ItemInfo) { +func (h *baseSharePointHandler) CacheItemInfo(v details.ItemInfo) { // Old versions would store SharePoint data as OneDrive. switch { case v.SharePoint != nil: @@ -46,7 +60,7 @@ func (h *baseSharepointHandler) CacheItemInfo(v details.ItemInfo) { // ProduceExportCollections will create the export collections for the // given restore collections. -func (h *baseSharepointHandler) ProduceExportCollections( +func (h *baseSharePointHandler) ProduceExportCollections( ctx context.Context, backupVersion int, exportCfg control.ExportConfig, @@ -88,3 +102,39 @@ func (h *baseSharepointHandler) ProduceExportCollections( return ec, el.Failure() } + +// ========================================================================== // +// sharepointHandler +// ========================================================================== // + +// sharepointHandler contains logic for handling data and performing operations +// (e.x. restore) regardless of whether they require contact with external M365 +// services or not. +type sharepointHandler struct { + baseSharePointHandler + apiClient api.Client + resourceGetter idname.GetResourceIDAndNamer +} + +func (h *sharepointHandler) IsServiceEnabled( + ctx context.Context, + resourceID string, +) (bool, error) { + // TODO(ashmrtn): Move free function implementation to this function. + res, err := IsServiceEnabled(ctx, h.apiClient.Sites(), resourceID) + return res, clues.Stack(err).OrNil() +} + +func (h *sharepointHandler) PopulateProtectedResourceIDAndName( + ctx context.Context, + resourceID string, // Can be either ID or name. + ins idname.Cacher, +) (idname.Provider, error) { + if h.resourceGetter == nil { + return nil, clues.StackWC(ctx, resource.ErrNoResourceLookup) + } + + pr, err := h.resourceGetter.GetResourceIDAndNameFrom(ctx, resourceID, ins) + + return pr, clues.Wrap(err, "identifying resource owner").OrNil() +} diff --git a/src/internal/m365/service/sharepoint/export_test.go b/src/internal/m365/service/sharepoint/export_test.go index 4b2ea2521..a33655a51 100644 --- a/src/internal/m365/service/sharepoint/export_test.go +++ b/src/internal/m365/service/sharepoint/export_test.go @@ -20,6 +20,7 @@ import ( "github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" + "github.com/alcionai/corso/src/pkg/services/m365/api" ) type ExportUnitSuite struct { @@ -125,7 +126,7 @@ func (suite *ExportUnitSuite) TestExportRestoreCollections() { }, } - handler := NewSharePointHandler(control.DefaultOptions()) + handler := NewSharePointHandler(control.DefaultOptions(), api.Client{}, nil) handler.CacheItemInfo(test.itemInfo) stats := data.ExportStats{}