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.
|
// if s is nil, prints nothing.
|
||||||
// Prepends the message with "Error: "
|
// Prepends the message with "Error: "
|
||||||
func Errf(ctx context.Context, tmpl string, s ...any) {
|
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)
|
// Out prints the params to cobra's output writer (stdOut by default)
|
||||||
|
|||||||
@ -1,15 +1,8 @@
|
|||||||
package control
|
package control
|
||||||
|
|
||||||
import (
|
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/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/extensions"
|
"github.com/alcionai/corso/src/pkg/extensions"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options holds the optional configurations for a process
|
// 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
|
// 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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -10,15 +10,15 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OptionsUnitSuite struct {
|
type RestoreUnitSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOptionsUnitSuite(t *testing.T) {
|
func TestRestoreUnitSuite(t *testing.T) {
|
||||||
suite.Run(t, &OptionsUnitSuite{Suite: tester.NewUnitSuite(t)})
|
suite.Run(t, &RestoreUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
func (suite *RestoreUnitSuite) TestEnsureRestoreConfigDefaults() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
input control.RestoreConfig
|
input control.RestoreConfig
|
||||||
@ -69,6 +69,21 @@ func (suite *OptionsUnitSuite) TestEnsureRestoreConfigDefaults() {
|
|||||||
Drive: "",
|
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 {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
Loading…
x
Reference in New Issue
Block a user