fix restore to dest with leading slash (#3713)
restoring to "--destination /foo" errors, since drive folders aren't allowed to contain leading slashes in their name. This PR oversteps the correction a little to remove the leading slash prefix from all destination declarations. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🐛 Bugfix #### Issue(s) * #3562 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
597a51c6bc
commit
f58cc90958
@ -90,7 +90,7 @@ func Err(ctx context.Context, s ...any) {
|
||||
// if s is nil, prints nothing.
|
||||
// Prepends the message with "Error: "
|
||||
func Errf(ctx context.Context, tmpl string, s ...any) {
|
||||
outf(getRootCmd(ctx).ErrOrStderr(), "Error: "+tmpl, s...)
|
||||
outf(getRootCmd(ctx).ErrOrStderr(), "\nError: \n\t"+tmpl+"\n", s...)
|
||||
}
|
||||
|
||||
// Out prints the params to cobra's output writer (stdOut by default)
|
||||
|
||||
@ -1,15 +1,8 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||
"github.com/alcionai/corso/src/pkg/extensions"
|
||||
"github.com/alcionai/corso/src/pkg/logger"
|
||||
)
|
||||
|
||||
// Options holds the optional configurations for a process
|
||||
@ -54,87 +47,6 @@ func Defaults() Options {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Restore Configuration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
defaultRestoreLocation = "Corso_Restore_"
|
||||
)
|
||||
|
||||
// CollisionPolicy describes how the datalayer behaves in case of a collision.
|
||||
type CollisionPolicy string
|
||||
|
||||
const (
|
||||
Unknown CollisionPolicy = ""
|
||||
Skip CollisionPolicy = "skip"
|
||||
Copy CollisionPolicy = "copy"
|
||||
Replace CollisionPolicy = "replace"
|
||||
)
|
||||
|
||||
func ValidCollisionPolicies() map[CollisionPolicy]struct{} {
|
||||
return map[CollisionPolicy]struct{}{
|
||||
Skip: {},
|
||||
Copy: {},
|
||||
Replace: {},
|
||||
}
|
||||
}
|
||||
|
||||
const RootLocation = "/"
|
||||
|
||||
// RestoreConfig contains
|
||||
type RestoreConfig struct {
|
||||
// Defines the per-item collision handling policy.
|
||||
// Defaults to Skip.
|
||||
OnCollision CollisionPolicy
|
||||
|
||||
// ProtectedResource specifies which resource the data will be restored to.
|
||||
// If empty, restores to the same resource that was backed up.
|
||||
// Defaults to empty.
|
||||
ProtectedResource string
|
||||
|
||||
// Location specifies the container into which the data will be restored.
|
||||
// Only accepts container names, does not accept IDs.
|
||||
// If empty or "/", data will get restored in place, beginning at the root.
|
||||
// Defaults to "Corso_Restore_<current_dttm>"
|
||||
Location string
|
||||
|
||||
// Drive specifies the drive into which the data will be restored.
|
||||
// If empty, data is restored to the same drive that was backed up.
|
||||
// Defaults to empty.
|
||||
Drive string
|
||||
}
|
||||
|
||||
func DefaultRestoreConfig(timeFormat dttm.TimeFormat) RestoreConfig {
|
||||
return RestoreConfig{
|
||||
OnCollision: Skip,
|
||||
Location: defaultRestoreLocation + dttm.FormatNow(timeFormat),
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureRestoreConfigDefaults sets all non-supported values in the config
|
||||
// struct to the default value.
|
||||
func EnsureRestoreConfigDefaults(
|
||||
ctx context.Context,
|
||||
rc RestoreConfig,
|
||||
) RestoreConfig {
|
||||
if !slices.Contains([]CollisionPolicy{Skip, Copy, Replace}, rc.OnCollision) {
|
||||
logger.Ctx(ctx).
|
||||
With(
|
||||
"bad_collision_policy", rc.OnCollision,
|
||||
"default_collision_policy", Skip).
|
||||
Info("setting collision policy to default")
|
||||
|
||||
rc.OnCollision = Skip
|
||||
}
|
||||
|
||||
if strings.TrimSpace(rc.Location) == RootLocation {
|
||||
rc.Location = ""
|
||||
}
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Feature Flags and Toggles
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
87
src/pkg/control/restore.go
Normal file
87
src/pkg/control/restore.go
Normal file
@ -0,0 +1,87 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||
"github.com/alcionai/corso/src/pkg/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRestoreLocation = "Corso_Restore_"
|
||||
)
|
||||
|
||||
// CollisionPolicy describes how the datalayer behaves in case of a collision.
|
||||
type CollisionPolicy string
|
||||
|
||||
const (
|
||||
Unknown CollisionPolicy = ""
|
||||
Skip CollisionPolicy = "skip"
|
||||
Copy CollisionPolicy = "copy"
|
||||
Replace CollisionPolicy = "replace"
|
||||
)
|
||||
|
||||
func ValidCollisionPolicies() map[CollisionPolicy]struct{} {
|
||||
return map[CollisionPolicy]struct{}{
|
||||
Skip: {},
|
||||
Copy: {},
|
||||
Replace: {},
|
||||
}
|
||||
}
|
||||
|
||||
const RootLocation = "/"
|
||||
|
||||
// RestoreConfig contains
|
||||
type RestoreConfig struct {
|
||||
// Defines the per-item collision handling policy.
|
||||
// Defaults to Skip.
|
||||
OnCollision CollisionPolicy
|
||||
|
||||
// ProtectedResource specifies which resource the data will be restored to.
|
||||
// If empty, restores to the same resource that was backed up.
|
||||
// Defaults to empty.
|
||||
ProtectedResource string
|
||||
|
||||
// Location specifies the container into which the data will be restored.
|
||||
// Only accepts container names, does not accept IDs.
|
||||
// If empty or "/", data will get restored in place, beginning at the root.
|
||||
// Defaults to "Corso_Restore_<current_dttm>"
|
||||
Location string
|
||||
|
||||
// Drive specifies the drive into which the data will be restored.
|
||||
// If empty, data is restored to the same drive that was backed up.
|
||||
// Defaults to empty.
|
||||
Drive string
|
||||
}
|
||||
|
||||
func DefaultRestoreConfig(timeFormat dttm.TimeFormat) RestoreConfig {
|
||||
return RestoreConfig{
|
||||
OnCollision: Skip,
|
||||
Location: defaultRestoreLocation + dttm.FormatNow(timeFormat),
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureRestoreConfigDefaults sets all non-supported values in the config
|
||||
// struct to the default value.
|
||||
func EnsureRestoreConfigDefaults(
|
||||
ctx context.Context,
|
||||
rc RestoreConfig,
|
||||
) RestoreConfig {
|
||||
if !slices.Contains(maps.Keys(ValidCollisionPolicies()), rc.OnCollision) {
|
||||
logger.Ctx(ctx).
|
||||
With(
|
||||
"bad_collision_policy", rc.OnCollision,
|
||||
"default_collision_policy", Skip).
|
||||
Info("setting collision policy to default")
|
||||
|
||||
rc.OnCollision = Skip
|
||||
}
|
||||
|
||||
rc.Location = strings.TrimPrefix(strings.TrimSpace(rc.Location), "/")
|
||||
|
||||
return rc
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package test
|
||||
package control_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -10,15 +10,15 @@ import (
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
)
|
||||
|
||||
type OptionsUnitSuite struct {
|
||||
type RestoreUnitSuite struct {
|
||||
tester.Suite
|
||||
}
|
||||
|
||||
func TestOptionsUnitSuite(t *testing.T) {
|
||||
suite.Run(t, &OptionsUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||
func TestRestoreUnitSuite(t *testing.T) {
|
||||
suite.Run(t, &RestoreUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||
}
|
||||
|
||||
func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
||||
func (suite *RestoreUnitSuite) TestEnsureRestoreConfigDefaults() {
|
||||
table := []struct {
|
||||
name string
|
||||
input control.RestoreConfig
|
||||
@ -69,6 +69,21 @@ func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
||||
Drive: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "populated with slash prefix, then modified",
|
||||
input: control.RestoreConfig{
|
||||
OnCollision: control.CollisionPolicy("batman"),
|
||||
ProtectedResource: "",
|
||||
Location: "/smarfs",
|
||||
Drive: "",
|
||||
},
|
||||
expect: control.RestoreConfig{
|
||||
OnCollision: control.Skip,
|
||||
ProtectedResource: "",
|
||||
Location: "smarfs",
|
||||
Drive: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
Loading…
x
Reference in New Issue
Block a user