look up restore resource if specified
If the restore configuration specifies a protected resource as a restore target, use that as the destination for the restore. First step is to ensure the provided target can be retrieved and identified.
This commit is contained in:
parent
1272066d50
commit
3a02e3269b
@ -144,7 +144,7 @@ func getControllerAndVerifyResourceOwner(
|
|||||||
return nil, account.Account{}, nil, clues.Wrap(err, "connecting to graph api")
|
return nil, account.Account{}, nil, clues.Wrap(err, "connecting to graph api")
|
||||||
}
|
}
|
||||||
|
|
||||||
id, _, err := ctrl.PopulateOwnerIDAndNamesFrom(ctx, resourceOwner, nil)
|
id, _, err := ctrl.PopulateProtectedResourceIDAndName(ctx, resourceOwner, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, account.Account{}, nil, clues.Wrap(err, "verifying user")
|
return nil, account.Account{}, nil, clues.Wrap(err, "verifying user")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,10 @@ type is struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewProvider(id, name string) *is {
|
||||||
|
return &is{id, name}
|
||||||
|
}
|
||||||
|
|
||||||
func (is is) ID() string { return is.id }
|
func (is is) ID() string { return is.id }
|
||||||
func (is is) Name() string { return is.name }
|
func (is is) Name() string { return is.name }
|
||||||
|
|
||||||
|
|||||||
@ -367,7 +367,7 @@ func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Libraries() {
|
|||||||
siteIDs = []string{siteID}
|
siteIDs = []string{siteID}
|
||||||
)
|
)
|
||||||
|
|
||||||
id, name, err := ctrl.PopulateOwnerIDAndNamesFrom(ctx, siteID, nil)
|
id, name, err := ctrl.PopulateProtectedResourceIDAndName(ctx, siteID, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
sel := selectors.NewSharePointBackup(siteIDs)
|
sel := selectors.NewSharePointBackup(siteIDs)
|
||||||
@ -414,7 +414,7 @@ func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Lists() {
|
|||||||
siteIDs = []string{siteID}
|
siteIDs = []string{siteID}
|
||||||
)
|
)
|
||||||
|
|
||||||
id, name, err := ctrl.PopulateOwnerIDAndNamesFrom(ctx, siteID, nil)
|
id, name, err := ctrl.PopulateProtectedResourceIDAndName(ctx, siteID, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
sel := selectors.NewSharePointBackup(siteIDs)
|
sel := selectors.NewSharePointBackup(siteIDs)
|
||||||
|
|||||||
@ -248,7 +248,7 @@ func (r resourceClient) getOwnerIDAndNameFrom(
|
|||||||
return id, name, nil
|
return id, name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PopulateOwnerIDAndNamesFrom takes the provided owner identifier and produces
|
// PopulateProtectedResourceIDAndName takes the provided owner identifier and produces
|
||||||
// the owner's name and ID from that value. Returns an error if the owner is
|
// the owner's name and ID from that value. Returns an error if the owner is
|
||||||
// not recognized by the current tenant.
|
// not recognized by the current tenant.
|
||||||
//
|
//
|
||||||
@ -256,7 +256,7 @@ func (r resourceClient) getOwnerIDAndNameFrom(
|
|||||||
// the tenant before reaching this step. In that case, the data gets handed
|
// the tenant before reaching this step. In that case, the data gets handed
|
||||||
// down for this func to consume instead of performing further queries. The
|
// down for this func to consume instead of performing further queries. The
|
||||||
// data gets stored inside the controller instance for later re-use.
|
// data gets stored inside the controller instance for later re-use.
|
||||||
func (ctrl *Controller) PopulateOwnerIDAndNamesFrom(
|
func (ctrl *Controller) PopulateProtectedResourceIDAndName(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
owner string, // input value, can be either id or name
|
owner string, // input value, can be either id or name
|
||||||
ins idname.Cacher,
|
ins idname.Cacher,
|
||||||
|
|||||||
@ -222,7 +222,7 @@ func (suite *ControllerUnitSuite) TestPopulateOwnerIDAndNamesFrom() {
|
|||||||
|
|
||||||
ctrl := &Controller{ownerLookup: test.rc}
|
ctrl := &Controller{ownerLookup: test.rc}
|
||||||
|
|
||||||
rID, rName, err := ctrl.PopulateOwnerIDAndNamesFrom(ctx, test.owner, test.ins)
|
rID, rName, err := ctrl.PopulateProtectedResourceIDAndName(ctx, test.owner, test.ins)
|
||||||
test.expectErr(t, err, clues.ToCore(err))
|
test.expectErr(t, err, clues.ToCore(err))
|
||||||
assert.Equal(t, test.expectID, rID, "id")
|
assert.Equal(t, test.expectID, rID, "id")
|
||||||
assert.Equal(t, test.expectName, rName, "name")
|
assert.Equal(t, test.expectName, rName, "name")
|
||||||
@ -1295,7 +1295,7 @@ func (suite *ControllerIntegrationSuite) TestBackup_CreatesPrefixCollections() {
|
|||||||
start = time.Now()
|
start = time.Now()
|
||||||
)
|
)
|
||||||
|
|
||||||
id, name, err := backupCtrl.PopulateOwnerIDAndNamesFrom(ctx, backupSel.DiscreteOwner, nil)
|
id, name, err := backupCtrl.PopulateProtectedResourceIDAndName(ctx, backupSel.DiscreteOwner, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
backupSel.SetDiscreteOwnerIDName(id, name)
|
backupSel.SetDiscreteOwnerIDName(id, name)
|
||||||
|
|||||||
@ -26,6 +26,10 @@ type Controller struct {
|
|||||||
Err error
|
Err error
|
||||||
|
|
||||||
Stats data.CollectionStats
|
Stats data.CollectionStats
|
||||||
|
|
||||||
|
ProtectedResourceID string
|
||||||
|
ProtectedResourceName string
|
||||||
|
ProtectedResourceErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctrl Controller) ProduceBackupCollections(
|
func (ctrl Controller) ProduceBackupCollections(
|
||||||
@ -71,3 +75,13 @@ func (ctrl Controller) ConsumeRestoreCollections(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctrl Controller) CacheItemInfo(dii details.ItemInfo) {}
|
func (ctrl Controller) CacheItemInfo(dii details.ItemInfo) {}
|
||||||
|
|
||||||
|
func (ctrl Controller) PopulateProtectedResourceIDAndName(
|
||||||
|
ctx context.Context,
|
||||||
|
protectedResource string, // input value, can be either id or name
|
||||||
|
ins idname.Cacher,
|
||||||
|
) (string, string, error) {
|
||||||
|
return ctrl.ProtectedResourceID,
|
||||||
|
ctrl.ProtectedResourceName,
|
||||||
|
ctrl.ProtectedResourceErr
|
||||||
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ func ControllerWithSelector(
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
id, name, err := ctrl.PopulateOwnerIDAndNamesFrom(ctx, sel.DiscreteOwner, ins)
|
id, name, err := ctrl.PopulateProtectedResourceIDAndName(ctx, sel.DiscreteOwner, ins)
|
||||||
if !assert.NoError(t, err, clues.ToCore(err)) {
|
if !assert.NoError(t, err, clues.ToCore(err)) {
|
||||||
if onFail != nil {
|
if onFail != nil {
|
||||||
onFail()
|
onFail()
|
||||||
|
|||||||
@ -48,6 +48,7 @@ type (
|
|||||||
Wait() *data.CollectionStats
|
Wait() *data.CollectionStats
|
||||||
|
|
||||||
CacheItemInfoer
|
CacheItemInfoer
|
||||||
|
PopulateProtectedResourceIDAndNamer
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheItemInfoer interface {
|
CacheItemInfoer interface {
|
||||||
@ -59,6 +60,25 @@ type (
|
|||||||
CacheItemInfo(v details.ItemInfo)
|
CacheItemInfo(v details.ItemInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PopulateProtectedResourceIDAndNamer interface {
|
||||||
|
// PopulateProtectedResourceIDAndName takes the provided owner identifier and produces
|
||||||
|
// the owner's name and ID from that value. Returns an error if the owner is
|
||||||
|
// not recognized by the current tenant.
|
||||||
|
//
|
||||||
|
// The id-name swapper should be optional. Some processes will look up all owners in
|
||||||
|
// the tenant before reaching this step. In that case, the data gets handed
|
||||||
|
// down for this func to consume instead of performing further queries. The
|
||||||
|
// data gets stored inside the controller instance for later re-use.
|
||||||
|
PopulateProtectedResourceIDAndName(
|
||||||
|
ctx context.Context,
|
||||||
|
owner string, // input value, can be either id or name
|
||||||
|
ins idname.Cacher,
|
||||||
|
) (
|
||||||
|
id, name string,
|
||||||
|
err error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
RepoMaintenancer interface {
|
RepoMaintenancer interface {
|
||||||
RepoMaintenance(ctx context.Context, opts repository.Maintenance) error
|
RepoMaintenance(ctx context.Context, opts repository.Maintenance) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/crash"
|
"github.com/alcionai/corso/src/internal/common/crash"
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
|
"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/diagnostics"
|
"github.com/alcionai/corso/src/internal/diagnostics"
|
||||||
"github.com/alcionai/corso/src/internal/events"
|
"github.com/alcionai/corso/src/internal/events"
|
||||||
@ -217,7 +218,19 @@ func (op *RestoreOperation) do(
|
|||||||
return nil, clues.Wrap(err, "getting backup and details")
|
return nil, clues.Wrap(err, "getting backup and details")
|
||||||
}
|
}
|
||||||
|
|
||||||
observe.Message(ctx, "Restoring", observe.Bullet, clues.Hide(bup.Selector.DiscreteOwner))
|
restoreProtectedResource, err := chooseRestoreResource(ctx, op.rc, op.RestoreCfg, bup.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.Wrap(err, "getting destination protected resource")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = clues.Add(
|
||||||
|
ctx,
|
||||||
|
"backup_protected_resource_id", bup.Selector.ID(),
|
||||||
|
"backup_protected_resource_name", clues.Hide(bup.Selector.Name()),
|
||||||
|
"restore_protected_resource_id", restoreProtectedResource.ID(),
|
||||||
|
"restore_protected_resource_name", clues.Hide(restoreProtectedResource.Name()))
|
||||||
|
|
||||||
|
observe.Message(ctx, "Restoring", observe.Bullet, clues.Hide(restoreProtectedResource.Name()))
|
||||||
|
|
||||||
paths, err := formatDetailsForRestoration(
|
paths, err := formatDetailsForRestoration(
|
||||||
ctx,
|
ctx,
|
||||||
@ -232,8 +245,6 @@ func (op *RestoreOperation) do(
|
|||||||
|
|
||||||
ctx = clues.Add(
|
ctx = clues.Add(
|
||||||
ctx,
|
ctx,
|
||||||
"resource_owner_id", bup.Selector.ID(),
|
|
||||||
"resource_owner_name", clues.Hide(bup.Selector.Name()),
|
|
||||||
"details_entries", len(deets.Entries),
|
"details_entries", len(deets.Entries),
|
||||||
"details_paths", len(paths),
|
"details_paths", len(paths),
|
||||||
"backup_snapshot_id", bup.SnapshotID,
|
"backup_snapshot_id", bup.SnapshotID,
|
||||||
@ -321,6 +332,24 @@ func (op *RestoreOperation) persistResults(
|
|||||||
return op.Errors.Failure()
|
return op.Errors.Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func chooseRestoreResource(
|
||||||
|
ctx context.Context,
|
||||||
|
pprian inject.PopulateProtectedResourceIDAndNamer,
|
||||||
|
restoreCfg control.RestoreConfig,
|
||||||
|
orig idname.Provider,
|
||||||
|
) (idname.Provider, error) {
|
||||||
|
if len(restoreCfg.ProtectedResource) == 0 {
|
||||||
|
return orig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id, name, err := pprian.PopulateProtectedResourceIDAndName(
|
||||||
|
ctx,
|
||||||
|
restoreCfg.ProtectedResource,
|
||||||
|
nil)
|
||||||
|
|
||||||
|
return idname.NewProvider(id, name), clues.Stack(err).OrNil()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Restorer funcs
|
// Restorer funcs
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
inMock "github.com/alcionai/corso/src/internal/common/idname/mock"
|
inMock "github.com/alcionai/corso/src/internal/common/idname/mock"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/events"
|
"github.com/alcionai/corso/src/internal/events"
|
||||||
@ -40,15 +42,15 @@ import (
|
|||||||
// unit
|
// unit
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type RestoreOpSuite struct {
|
type RestoreOpUnitSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRestoreOpSuite(t *testing.T) {
|
func TestRestoreOpUnitSuite(t *testing.T) {
|
||||||
suite.Run(t, &RestoreOpSuite{Suite: tester.NewUnitSuite(t)})
|
suite.Run(t, &RestoreOpUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
func (suite *RestoreOpUnitSuite) TestRestoreOperation_PersistResults() {
|
||||||
var (
|
var (
|
||||||
kw = &kopia.Wrapper{}
|
kw = &kopia.Wrapper{}
|
||||||
sw = &store.Wrapper{}
|
sw = &store.Wrapper{}
|
||||||
@ -138,6 +140,75 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreOpUnitSuite) TestChooseRestoreResource() {
|
||||||
|
var (
|
||||||
|
id = "id"
|
||||||
|
name = "name"
|
||||||
|
cfgWithPR = control.DefaultRestoreConfig(dttm.HumanReadable)
|
||||||
|
)
|
||||||
|
|
||||||
|
cfgWithPR.ProtectedResource = "cfgid"
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
cfg control.RestoreConfig
|
||||||
|
ctrl *mock.Controller
|
||||||
|
orig idname.Provider
|
||||||
|
expectErr assert.ErrorAssertionFunc
|
||||||
|
expectProvider assert.ValueAssertionFunc
|
||||||
|
expectID string
|
||||||
|
expectName string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "use original",
|
||||||
|
cfg: control.DefaultRestoreConfig(dttm.HumanReadable),
|
||||||
|
ctrl: &mock.Controller{
|
||||||
|
ProtectedResourceID: id,
|
||||||
|
ProtectedResourceName: name,
|
||||||
|
},
|
||||||
|
orig: idname.NewProvider("oid", "oname"),
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
expectID: "oid",
|
||||||
|
expectName: "oname",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "look up resource with iface",
|
||||||
|
cfg: cfgWithPR,
|
||||||
|
ctrl: &mock.Controller{
|
||||||
|
ProtectedResourceID: id,
|
||||||
|
ProtectedResourceName: name,
|
||||||
|
},
|
||||||
|
orig: idname.NewProvider("oid", "oname"),
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
expectID: id,
|
||||||
|
expectName: name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error looking up protected resource",
|
||||||
|
cfg: cfgWithPR,
|
||||||
|
ctrl: &mock.Controller{
|
||||||
|
ProtectedResourceErr: assert.AnError,
|
||||||
|
},
|
||||||
|
orig: idname.NewProvider("oid", "oname"),
|
||||||
|
expectErr: assert.Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
result, err := chooseRestoreResource(ctx, test.ctrl, test.cfg, test.orig)
|
||||||
|
test.expectErr(t, err, clues.ToCore(err))
|
||||||
|
require.NotNil(t, result)
|
||||||
|
assert.Equal(t, test.expectID, result.ID())
|
||||||
|
assert.Equal(t, test.expectName, result.Name())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// integration
|
// integration
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -550,7 +550,7 @@ func ControllerWithSelector(
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
id, name, err := ctrl.PopulateOwnerIDAndNamesFrom(ctx, sel.DiscreteOwner, ins)
|
id, name, err := ctrl.PopulateProtectedResourceIDAndName(ctx, sel.DiscreteOwner, ins)
|
||||||
if !assert.NoError(t, err, clues.ToCore(err)) {
|
if !assert.NoError(t, err, clues.ToCore(err)) {
|
||||||
if onFail != nil {
|
if onFail != nil {
|
||||||
onFail(t, ctx)
|
onFail(t, ctx)
|
||||||
|
|||||||
@ -329,7 +329,7 @@ func (r repository) NewBackupWithLookup(
|
|||||||
return operations.BackupOperation{}, clues.Wrap(err, "connecting to m365")
|
return operations.BackupOperation{}, clues.Wrap(err, "connecting to m365")
|
||||||
}
|
}
|
||||||
|
|
||||||
ownerID, ownerName, err := ctrl.PopulateOwnerIDAndNamesFrom(ctx, sel.DiscreteOwner, ins)
|
ownerID, ownerName, err := ctrl.PopulateProtectedResourceIDAndName(ctx, sel.DiscreteOwner, ins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return operations.BackupOperation{}, clues.Wrap(err, "resolving resource owner details")
|
return operations.BackupOperation{}, clues.Wrap(err, "resolving resource owner details")
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user