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?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #4254

#### Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
ashmrtn 2023-11-15 18:48:54 -08:00 committed by GitHub
parent dbdd3f236c
commit 51f44c2988
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 195 additions and 29 deletions

View File

@ -21,13 +21,13 @@ func (ctrl *Controller) NewServiceHandler(
switch service { switch service {
case path.OneDriveService: case path.OneDriveService:
return onedrive.NewOneDriveHandler(opts), nil return onedrive.NewOneDriveHandler(opts, ctrl.AC, ctrl.resourceHandler), nil
case path.SharePointService: case path.SharePointService:
return sharepoint.NewSharePointHandler(opts), nil return sharepoint.NewSharePointHandler(opts, ctrl.AC, ctrl.resourceHandler), nil
case path.GroupsService: case path.GroupsService:
return groups.NewGroupsHandler(opts), nil return groups.NewGroupsHandler(opts, ctrl.AC, ctrl.resourceHandler), nil
} }
return nil, clues.New("unrecognized service"). return nil, clues.New("unrecognized service").

View File

@ -1,5 +1,9 @@
package resource package resource
import "github.com/alcionai/clues"
var ErrNoResourceLookup = clues.New("missing resource lookup client")
type Category string type Category string
const ( const (

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/m365/collection/drive" "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/collection/groups"
"github.com/alcionai/corso/src/internal/m365/resource"
"github.com/alcionai/corso/src/internal/operations/inject" "github.com/alcionai/corso/src/internal/operations/inject"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control" "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/fault"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path" "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( func NewGroupsHandler(
opts control.Options, opts control.Options,
) *baseGroupsHandler { apiClient api.Client,
return &baseGroupsHandler{ resourceGetter idname.GetResourceIDAndNamer,
opts: opts, ) *groupsHandler {
backupDriveIDNames: idname.NewCache(nil), return &groupsHandler{
backupSiteIDWebURL: idname.NewCache(nil), 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 { type baseGroupsHandler struct {
opts control.Options opts control.Options
@ -134,3 +148,39 @@ func (h *baseGroupsHandler) ProduceExportCollections(
return ec, el.Failure() 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()
}

View File

@ -21,6 +21,7 @@ import (
"github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/export"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
type ExportUnitSuite struct { type ExportUnitSuite struct {
@ -96,7 +97,7 @@ func (suite *ExportUnitSuite) TestExportRestoreCollections_messages() {
stats := data.ExportStats{} stats := data.ExportStats{}
ecs, err := NewGroupsHandler(control.DefaultOptions()). ecs, err := NewGroupsHandler(control.DefaultOptions(), api.Client{}, nil).
ProduceExportCollections( ProduceExportCollections(
ctx, ctx,
int(version.Backup), 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) handler.CacheItemInfo(dii)
stats := data.ExportStats{} stats := data.ExportStats{}

View File

@ -5,35 +5,58 @@ import (
"github.com/alcionai/clues" "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/data"
"github.com/alcionai/corso/src/internal/m365/collection/drive" "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/internal/operations/inject"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/export"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "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( func NewOneDriveHandler(
opts control.Options, opts control.Options,
) *baseOnedriveHandler { apiClient api.Client,
return &baseOnedriveHandler{ resourceGetter idname.GetResourceIDAndNamer,
opts: opts, ) *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 // ProduceExportCollections will create the export collections for the
// given restore collections. // given restore collections.
func (h *baseOnedriveHandler) ProduceExportCollections( func (h *baseOneDriveHandler) ProduceExportCollections(
ctx context.Context, ctx context.Context,
backupVersion int, backupVersion int,
exportCfg control.ExportConfig, exportCfg control.ExportConfig,
@ -65,3 +88,39 @@ func (h *baseOnedriveHandler) ProduceExportCollections(
return ec, el.Failure() 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()
}

View File

@ -20,6 +20,7 @@ import (
"github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/export"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
type ExportUnitSuite struct { type ExportUnitSuite struct {
@ -341,7 +342,7 @@ func (suite *ExportUnitSuite) TestExportRestoreCollections() {
stats := data.ExportStats{} stats := data.ExportStats{}
ecs, err := NewOneDriveHandler(control.DefaultOptions()). ecs, err := NewOneDriveHandler(control.DefaultOptions(), api.Client{}, nil).
ProduceExportCollections( ProduceExportCollections(
ctx, ctx,
int(version.Backup), int(version.Backup),

View File

@ -8,6 +8,7 @@ import (
"github.com/alcionai/corso/src/internal/common/idname" "github.com/alcionai/corso/src/internal/common/idname"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/m365/collection/drive" "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/internal/operations/inject"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/control" "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/fault"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path" "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( func NewSharePointHandler(
opts control.Options, opts control.Options,
) *baseSharepointHandler { apiClient api.Client,
return &baseSharepointHandler{ resourceGetter idname.GetResourceIDAndNamer,
opts: opts, ) *sharepointHandler {
backupDriveIDNames: idname.NewCache(nil), 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 opts control.Options
backupDriveIDNames idname.CacheBuilder 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. // Old versions would store SharePoint data as OneDrive.
switch { switch {
case v.SharePoint != nil: case v.SharePoint != nil:
@ -46,7 +60,7 @@ func (h *baseSharepointHandler) CacheItemInfo(v details.ItemInfo) {
// ProduceExportCollections will create the export collections for the // ProduceExportCollections will create the export collections for the
// given restore collections. // given restore collections.
func (h *baseSharepointHandler) ProduceExportCollections( func (h *baseSharePointHandler) ProduceExportCollections(
ctx context.Context, ctx context.Context,
backupVersion int, backupVersion int,
exportCfg control.ExportConfig, exportCfg control.ExportConfig,
@ -88,3 +102,39 @@ func (h *baseSharepointHandler) ProduceExportCollections(
return ec, el.Failure() 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()
}

View File

@ -20,6 +20,7 @@ import (
"github.com/alcionai/corso/src/pkg/export" "github.com/alcionai/corso/src/pkg/export"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
type ExportUnitSuite struct { 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) handler.CacheItemInfo(test.itemInfo)
stats := data.ExportStats{} stats := data.ExportStats{}