add cli flag for to-resource
adds a cli flag for restoring to a different resource (mailbox, user, site) from the one stored in the backup.
This commit is contained in:
parent
4580c8f6c0
commit
effecdd290
@ -7,8 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased] (beta)
|
||||
|
||||
### Added
|
||||
- Restore commands now accept an optional resource override with the `--to-resource` flag. This allows restores to recreate backup data witthin different mailboxes, sites, and users.
|
||||
|
||||
### Fixed
|
||||
- SharePoint document libraries deleted after the last backup can now be restored.
|
||||
- Restore requires the protected resource to have access to the service being restored.
|
||||
|
||||
## [v0.11.0] (beta) - 2023-07-18
|
||||
|
||||
|
||||
@ -9,11 +9,13 @@ import (
|
||||
const (
|
||||
CollisionsFN = "collisions"
|
||||
DestinationFN = "destination"
|
||||
ToResourceFN = "to-resource"
|
||||
)
|
||||
|
||||
var (
|
||||
CollisionsFV string
|
||||
DestinationFV string
|
||||
ToResourceFV string
|
||||
)
|
||||
|
||||
// AddRestoreConfigFlags adds the restore config flag set.
|
||||
@ -25,5 +27,8 @@ func AddRestoreConfigFlags(cmd *cobra.Command) {
|
||||
"Sets the behavior for existing item collisions: "+string(control.Skip)+", "+string(control.Copy)+", or "+string(control.Replace))
|
||||
fs.StringVar(
|
||||
&DestinationFV, DestinationFN, "",
|
||||
"Overrides the destination where items get restored; '/' places items into their original location")
|
||||
"Overrides the folder where items get restored; '/' places items into their original location")
|
||||
fs.StringVar(
|
||||
&ToResourceFV, ToResourceFN, "",
|
||||
"Overrides the protected resource (mailbox, site, etc) where data gets restored")
|
||||
}
|
||||
|
||||
@ -84,6 +84,7 @@ func (suite *ExchangeUnitSuite) TestAddExchangeCommands() {
|
||||
|
||||
"--" + flags.CollisionsFN, testdata.Collisions,
|
||||
"--" + flags.DestinationFN, testdata.Destination,
|
||||
"--" + flags.ToResourceFN, testdata.ToResource,
|
||||
|
||||
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
||||
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
||||
@ -125,6 +126,7 @@ func (suite *ExchangeUnitSuite) TestAddExchangeCommands() {
|
||||
|
||||
assert.Equal(t, testdata.Collisions, opts.RestoreCfg.Collisions)
|
||||
assert.Equal(t, testdata.Destination, opts.RestoreCfg.Destination)
|
||||
assert.Equal(t, testdata.ToResource, opts.RestoreCfg.ProtectedResource)
|
||||
|
||||
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
||||
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
||||
|
||||
@ -70,6 +70,7 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() {
|
||||
|
||||
"--" + flags.CollisionsFN, testdata.Collisions,
|
||||
"--" + flags.DestinationFN, testdata.Destination,
|
||||
"--" + flags.ToResourceFN, testdata.ToResource,
|
||||
|
||||
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
||||
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
||||
@ -80,6 +81,9 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() {
|
||||
"--" + flags.AzureClientSecretFN, testdata.AzureClientSecret,
|
||||
|
||||
"--" + flags.CorsoPassphraseFN, testdata.CorsoPassphrase,
|
||||
|
||||
// bool flags
|
||||
"--" + flags.RestorePermissionsFN,
|
||||
})
|
||||
|
||||
cmd.SetOut(new(bytes.Buffer)) // drop output
|
||||
@ -99,6 +103,7 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() {
|
||||
|
||||
assert.Equal(t, testdata.Collisions, opts.RestoreCfg.Collisions)
|
||||
assert.Equal(t, testdata.Destination, opts.RestoreCfg.Destination)
|
||||
assert.Equal(t, testdata.ToResource, opts.RestoreCfg.ProtectedResource)
|
||||
|
||||
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
||||
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
||||
@ -109,6 +114,7 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() {
|
||||
assert.Equal(t, testdata.AzureClientSecret, flags.AzureClientSecretFV)
|
||||
|
||||
assert.Equal(t, testdata.CorsoPassphrase, flags.CorsoPassphraseFV)
|
||||
assert.True(t, flags.RestorePermissionsFV)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +75,7 @@ func (suite *SharePointUnitSuite) TestAddSharePointCommands() {
|
||||
|
||||
"--" + flags.CollisionsFN, testdata.Collisions,
|
||||
"--" + flags.DestinationFN, testdata.Destination,
|
||||
"--" + flags.ToResourceFN, testdata.ToResource,
|
||||
|
||||
"--" + flags.AWSAccessKeyFN, testdata.AWSAccessKeyID,
|
||||
"--" + flags.AWSSecretAccessKeyFN, testdata.AWSSecretAccessKey,
|
||||
@ -85,6 +86,9 @@ func (suite *SharePointUnitSuite) TestAddSharePointCommands() {
|
||||
"--" + flags.AzureClientSecretFN, testdata.AzureClientSecret,
|
||||
|
||||
"--" + flags.CorsoPassphraseFN, testdata.CorsoPassphrase,
|
||||
|
||||
// bool flags
|
||||
"--" + flags.RestorePermissionsFN,
|
||||
})
|
||||
|
||||
cmd.SetOut(new(bytes.Buffer)) // drop output
|
||||
@ -111,6 +115,7 @@ func (suite *SharePointUnitSuite) TestAddSharePointCommands() {
|
||||
|
||||
assert.Equal(t, testdata.Collisions, opts.RestoreCfg.Collisions)
|
||||
assert.Equal(t, testdata.Destination, opts.RestoreCfg.Destination)
|
||||
assert.Equal(t, testdata.ToResource, opts.RestoreCfg.ProtectedResource)
|
||||
|
||||
assert.Equal(t, testdata.AWSAccessKeyID, flags.AWSAccessKeyFV)
|
||||
assert.Equal(t, testdata.AWSSecretAccessKey, flags.AWSSecretAccessKeyFV)
|
||||
@ -121,6 +126,9 @@ func (suite *SharePointUnitSuite) TestAddSharePointCommands() {
|
||||
assert.Equal(t, testdata.AzureClientSecret, flags.AzureClientSecretFV)
|
||||
|
||||
assert.Equal(t, testdata.CorsoPassphrase, flags.CorsoPassphraseFV)
|
||||
|
||||
// bool flags
|
||||
assert.True(t, flags.RestorePermissionsFV)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ type RestoreCfgOpts struct {
|
||||
// to the default folder name. Defaults to
|
||||
// dttm.HumanReadable.
|
||||
DTTMFormat dttm.TimeFormat
|
||||
ProtectedResource string
|
||||
RestorePermissions bool
|
||||
|
||||
Populated flags.PopulatedFlags
|
||||
@ -29,6 +30,7 @@ func makeRestoreCfgOpts(cmd *cobra.Command) RestoreCfgOpts {
|
||||
Collisions: flags.CollisionsFV,
|
||||
Destination: flags.DestinationFV,
|
||||
DTTMFormat: dttm.HumanReadable,
|
||||
ProtectedResource: flags.ToResourceFV,
|
||||
RestorePermissions: flags.RestorePermissionsFV,
|
||||
|
||||
// populated contains the list of flags that appear in the
|
||||
@ -69,6 +71,7 @@ func MakeRestoreConfig(
|
||||
restoreCfg.Location = opts.Destination
|
||||
}
|
||||
|
||||
restoreCfg.ProtectedResource = opts.ProtectedResource
|
||||
restoreCfg.IncludePermissions = opts.RestorePermissions
|
||||
|
||||
Infof(ctx, "Restoring to folder %s", restoreCfg.Location)
|
||||
|
||||
1
src/cli/utils/testdata/flags.go
vendored
1
src/cli/utils/testdata/flags.go
vendored
@ -46,6 +46,7 @@ var (
|
||||
|
||||
Collisions = "collisions"
|
||||
Destination = "destination"
|
||||
ToResource = "toResource"
|
||||
RestorePermissions = true
|
||||
|
||||
AzureClientID = "testAzureClientId"
|
||||
|
||||
@ -51,16 +51,15 @@ func ConsumeRestoreCollections(
|
||||
ctr *count.Bus,
|
||||
) (*support.ControllerOperationStatus, error) {
|
||||
var (
|
||||
restoreMetrics support.CollectionMetrics
|
||||
el = errs.Local()
|
||||
caches = NewRestoreCaches(backupDriveIDNames)
|
||||
protectedResourceID = rcc.ProtectedResource.ID()
|
||||
fallbackDriveName = "" // onedrive cannot create drives
|
||||
restoreMetrics support.CollectionMetrics
|
||||
el = errs.Local()
|
||||
caches = NewRestoreCaches(backupDriveIDNames)
|
||||
fallbackDriveName = "" // onedrive cannot create drives
|
||||
)
|
||||
|
||||
ctx = clues.Add(ctx, "backup_version", rcc.BackupVersion)
|
||||
|
||||
err := caches.Populate(ctx, rh, protectedResourceID)
|
||||
err := caches.Populate(ctx, rh, rcc.ProtectedResource.ID())
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "initializing restore caches")
|
||||
}
|
||||
@ -132,15 +131,14 @@ func RestoreCollection(
|
||||
ctr *count.Bus,
|
||||
) (support.CollectionMetrics, error) {
|
||||
var (
|
||||
metrics = support.CollectionMetrics{}
|
||||
directory = dc.FullPath()
|
||||
protectedResourceID = directory.ResourceOwner()
|
||||
el = errs.Local()
|
||||
metricsObjects int64
|
||||
metricsBytes int64
|
||||
metricsSuccess int64
|
||||
wg sync.WaitGroup
|
||||
complete bool
|
||||
metrics = support.CollectionMetrics{}
|
||||
directory = dc.FullPath()
|
||||
el = errs.Local()
|
||||
metricsObjects int64
|
||||
metricsBytes int64
|
||||
metricsSuccess int64
|
||||
wg sync.WaitGroup
|
||||
complete bool
|
||||
)
|
||||
|
||||
ctx, end := diagnostics.Span(ctx, "gc:drive:restoreCollection", diagnostics.Label("path", directory))
|
||||
@ -156,7 +154,7 @@ func RestoreCollection(
|
||||
rh,
|
||||
caches,
|
||||
drivePath,
|
||||
protectedResourceID,
|
||||
rcc.ProtectedResource.ID(),
|
||||
fallbackDriveName)
|
||||
if err != nil {
|
||||
return metrics, clues.Wrap(err, "ensuring drive exists")
|
||||
|
||||
@ -41,14 +41,13 @@ func ConsumeRestoreCollections(
|
||||
ctr *count.Bus,
|
||||
) (*support.ControllerOperationStatus, error) {
|
||||
var (
|
||||
lrh = libraryRestoreHandler{ac}
|
||||
protectedResourceID = dcs[0].FullPath().ResourceOwner()
|
||||
restoreMetrics support.CollectionMetrics
|
||||
caches = onedrive.NewRestoreCaches(backupDriveIDNames)
|
||||
el = errs.Local()
|
||||
lrh = libraryRestoreHandler{ac}
|
||||
restoreMetrics support.CollectionMetrics
|
||||
caches = onedrive.NewRestoreCaches(backupDriveIDNames)
|
||||
el = errs.Local()
|
||||
)
|
||||
|
||||
err := caches.Populate(ctx, lrh, protectedResourceID)
|
||||
err := caches.Populate(ctx, lrh, rcc.ProtectedResource.ID())
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "initializing restore caches")
|
||||
}
|
||||
@ -69,7 +68,7 @@ func ConsumeRestoreCollections(
|
||||
metrics support.CollectionMetrics
|
||||
ictx = clues.Add(ctx,
|
||||
"category", category,
|
||||
"restore_location", rcc.RestoreConfig.Location,
|
||||
"restore_location", clues.Hide(rcc.RestoreConfig.Location),
|
||||
"resource_owner", clues.Hide(dc.FullPath().ResourceOwner()),
|
||||
"full_path", dc.FullPath())
|
||||
)
|
||||
|
||||
@ -108,3 +108,18 @@ the copy of`reports.txt` is named `reports 1.txt`.
|
||||
Collisions will entirely replace the current version of the item with the backup
|
||||
version. If multiple existing items collide with the backup item, only one of the
|
||||
existing items is replaced.
|
||||
|
||||
## To resource
|
||||
|
||||
The `--to-resource` flag lets you select which resource will receive the restored data.
|
||||
A resource can be a mailbox, user, or sharepoint site.
|
||||
|
||||
<CodeBlock language="bash">{
|
||||
`corso restore onedrive --backup abcd --to-resource adelev@alcion.ai`
|
||||
}</CodeBlock>
|
||||
|
||||
### Limitations
|
||||
|
||||
* The resource must exist. Corso will not create new mailboxes, users, or sites.
|
||||
* The resource must have access to the service being restored. No restore will be
|
||||
performed for an unlicensed resource.
|
||||
Loading…
x
Reference in New Issue
Block a user