utilize format validation check (#4288)
adds usage of the export format validation check to export cli commands. Also moves the restore cfg validation check out of the service flags validation checks and into the generic restore runner for better separation of concerns. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🌻 Feature #### Issue(s) * #3988 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
c3e63413d4
commit
6159668b1d
@ -61,6 +61,10 @@ func runExport(
|
|||||||
sel selectors.Selector,
|
sel selectors.Selector,
|
||||||
backupID, serviceName string,
|
backupID, serviceName string,
|
||||||
) error {
|
) error {
|
||||||
|
if err := utils.ValidateExportConfigFlags(&ueco); err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
r, _, _, _, err := utils.GetAccountAndConnectWithOverrides(
|
r, _, _, _, err := utils.GetAccountAndConnectWithOverrides(
|
||||||
ctx,
|
ctx,
|
||||||
cmd,
|
cmd,
|
||||||
|
|||||||
@ -1,13 +1,7 @@
|
|||||||
package flags
|
package flags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
|
||||||
"github.com/alcionai/corso/src/pkg/filters"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -27,20 +21,3 @@ func AddExportConfigFlags(cmd *cobra.Command) {
|
|||||||
fs.StringVar(&FormatFV, FormatFN, "", "Specify the export file format")
|
fs.StringVar(&FormatFV, FormatFN, "", "Specify the export file format")
|
||||||
cobra.CheckErr(fs.MarkHidden(FormatFN))
|
cobra.CheckErr(fs.MarkHidden(FormatFN))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateExportConfigFlags ensures all export config flags that utilize
|
|
||||||
// enumerated values match a well-known value.
|
|
||||||
func ValidateExportConfigFlags() error {
|
|
||||||
acceptedFormatTypes := []string{
|
|
||||||
string(control.DefaultFormat),
|
|
||||||
string(control.JSONFormat),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !filters.Equal(acceptedFormatTypes).Compare(FormatFV) {
|
|
||||||
return clues.New("unrecognized format type: " + FormatFV)
|
|
||||||
}
|
|
||||||
|
|
||||||
FormatFV = strings.ToLower(FormatFV)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
package flags
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExportUnitSuite struct {
|
|
||||||
tester.Suite
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExportUnitSuite(t *testing.T) {
|
|
||||||
suite.Run(t, &ExportUnitSuite{Suite: tester.NewUnitSuite(t)})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *ExportUnitSuite) TestValidateExportConfigFlags() {
|
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
FormatFV = ""
|
|
||||||
|
|
||||||
err := ValidateExportConfigFlags()
|
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
FormatFV = "json"
|
|
||||||
|
|
||||||
err = ValidateExportConfigFlags()
|
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
FormatFV = "JsoN"
|
|
||||||
|
|
||||||
err = ValidateExportConfigFlags()
|
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
FormatFV = "fnerds"
|
|
||||||
|
|
||||||
err = ValidateExportConfigFlags()
|
|
||||||
assert.Error(t, err, clues.ToCore(err))
|
|
||||||
}
|
|
||||||
@ -94,6 +94,10 @@ func runRestore(
|
|||||||
sel selectors.Selector,
|
sel selectors.Selector,
|
||||||
backupID, serviceName string,
|
backupID, serviceName string,
|
||||||
) error {
|
) error {
|
||||||
|
if err := utils.ValidateRestoreConfigFlags(urco); err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
r, _, _, _, err := utils.GetAccountAndConnectWithOverrides(
|
r, _, _, _, err := utils.GetAccountAndConnectWithOverrides(
|
||||||
ctx,
|
ctx,
|
||||||
cmd,
|
cmd,
|
||||||
|
|||||||
@ -139,7 +139,7 @@ func ValidateExchangeRestoreFlags(backupID string, opts ExchangeOpts) error {
|
|||||||
return clues.New("invalid format for event-recurs")
|
return clues.New("invalid format for event-recurs")
|
||||||
}
|
}
|
||||||
|
|
||||||
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncludeExchangeRestoreDataSelectors builds the common data-selector
|
// IncludeExchangeRestoreDataSelectors builds the common data-selector
|
||||||
|
|||||||
@ -2,11 +2,14 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/cli/flags"
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExportCfgOpts struct {
|
type ExportCfgOpts struct {
|
||||||
@ -39,3 +42,23 @@ func MakeExportConfig(
|
|||||||
|
|
||||||
return exportCfg
|
return exportCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateExportConfigFlags ensures all export config flags that utilize
|
||||||
|
// enumerated values match a well-known value.
|
||||||
|
func ValidateExportConfigFlags(opts *ExportCfgOpts) error {
|
||||||
|
acceptedFormatTypes := []string{
|
||||||
|
string(control.DefaultFormat),
|
||||||
|
string(control.JSONFormat),
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, populated := opts.Populated[flags.FormatFN]; !populated {
|
||||||
|
opts.Format = string(control.DefaultFormat)
|
||||||
|
} else if !filters.Equal(acceptedFormatTypes).Compare(opts.Format) {
|
||||||
|
opts.Format = string(control.DefaultFormat)
|
||||||
|
return clues.New("unrecognized format type: " + opts.Format)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Format = strings.ToLower(opts.Format)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
@ -52,3 +53,57 @@ func (suite *ExportCfgUnitSuite) TestMakeExportConfig() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *ExportCfgUnitSuite) TestValidateExportConfigFlags() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
input ExportCfgOpts
|
||||||
|
expectErr assert.ErrorAssertionFunc
|
||||||
|
expectFormat control.FormatType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
input: ExportCfgOpts{
|
||||||
|
Format: string(control.DefaultFormat),
|
||||||
|
Populated: flags.PopulatedFlags{flags.FormatFN: struct{}{}},
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
expectFormat: control.DefaultFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "json",
|
||||||
|
input: ExportCfgOpts{
|
||||||
|
Format: string(control.JSONFormat),
|
||||||
|
Populated: flags.PopulatedFlags{flags.FormatFN: struct{}{}},
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
expectFormat: control.JSONFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad format",
|
||||||
|
input: ExportCfgOpts{
|
||||||
|
Format: "smurfs",
|
||||||
|
Populated: flags.PopulatedFlags{flags.FormatFN: struct{}{}},
|
||||||
|
},
|
||||||
|
expectErr: assert.Error,
|
||||||
|
expectFormat: control.DefaultFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad format unpopulated",
|
||||||
|
input: ExportCfgOpts{
|
||||||
|
Format: "smurfs",
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
expectFormat: control.DefaultFormat,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
err := ValidateExportConfigFlags(&test.input)
|
||||||
|
|
||||||
|
test.expectErr(t, err, clues.ToCore(err))
|
||||||
|
assert.Equal(t, test.expectFormat, control.FormatType(test.input.Format))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -138,7 +138,7 @@ func ValidateGroupsRestoreFlags(backupID string, opts GroupsOpts) error {
|
|||||||
return clues.New("invalid time format for " + flags.MessageLastReplyBeforeFN)
|
return clues.New("invalid time format for " + flags.MessageLastReplyBeforeFN)
|
||||||
}
|
}
|
||||||
|
|
||||||
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddGroupsFilter adds the scope of the provided values to the selector's
|
// AddGroupsFilter adds the scope of the provided values to the selector's
|
||||||
|
|||||||
@ -67,7 +67,7 @@ func ValidateOneDriveRestoreFlags(backupID string, opts OneDriveOpts) error {
|
|||||||
return clues.New("invalid time format for " + flags.FileModifiedBeforeFN)
|
return clues.New("invalid time format for " + flags.FileModifiedBeforeFN)
|
||||||
}
|
}
|
||||||
|
|
||||||
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOneDriveFilter adds the scope of the provided values to the selector's
|
// AddOneDriveFilter adds the scope of the provided values to the selector's
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -40,14 +41,13 @@ func makeRestoreCfgOpts(cmd *cobra.Command) RestoreCfgOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateRestoreConfigFlags checks common restore flags for
|
// ValidateRestoreConfigFlags checks common restore flags for correctness and interdependencies.
|
||||||
// correctness and interdependencies.
|
func ValidateRestoreConfigFlags(opts RestoreCfgOpts) error {
|
||||||
func validateRestoreConfigFlags(fv string, opts RestoreCfgOpts) error {
|
|
||||||
_, populated := opts.Populated[flags.CollisionsFN]
|
_, populated := opts.Populated[flags.CollisionsFN]
|
||||||
_, foundInValidSet := control.ValidCollisionPolicies()[control.CollisionPolicy(fv)]
|
isValid := control.IsValidCollisionPolicy(control.CollisionPolicy(opts.Collisions))
|
||||||
|
|
||||||
if populated && !foundInValidSet {
|
if populated && !isValid {
|
||||||
return clues.New("invalid entry for " + flags.CollisionsFN)
|
return clues.New(fmt.Sprintf("invalid collision policy: %s", flags.CollisionsFN))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -23,13 +23,11 @@ func TestRestoreCfgUnitSuite(t *testing.T) {
|
|||||||
func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
|
func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
fv string
|
|
||||||
opts RestoreCfgOpts
|
opts RestoreCfgOpts
|
||||||
expect assert.ErrorAssertionFunc
|
expect assert.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no error",
|
name: "no error",
|
||||||
fv: string(control.Skip),
|
|
||||||
opts: RestoreCfgOpts{
|
opts: RestoreCfgOpts{
|
||||||
Collisions: string(control.Skip),
|
Collisions: string(control.Skip),
|
||||||
Populated: flags.PopulatedFlags{
|
Populated: flags.PopulatedFlags{
|
||||||
@ -40,7 +38,6 @@ func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bad but not populated",
|
name: "bad but not populated",
|
||||||
fv: "foo",
|
|
||||||
opts: RestoreCfgOpts{
|
opts: RestoreCfgOpts{
|
||||||
Collisions: "foo",
|
Collisions: "foo",
|
||||||
Populated: flags.PopulatedFlags{},
|
Populated: flags.PopulatedFlags{},
|
||||||
@ -49,7 +46,6 @@ func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error",
|
name: "error",
|
||||||
fv: "foo",
|
|
||||||
opts: RestoreCfgOpts{
|
opts: RestoreCfgOpts{
|
||||||
Collisions: "foo",
|
Collisions: "foo",
|
||||||
Populated: flags.PopulatedFlags{
|
Populated: flags.PopulatedFlags{
|
||||||
@ -61,7 +57,7 @@ func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
|
|||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
err := validateRestoreConfigFlags(test.fv, test.opts)
|
err := ValidateRestoreConfigFlags(test.opts)
|
||||||
test.expect(suite.T(), err, clues.ToCore(err))
|
test.expect(suite.T(), err, clues.ToCore(err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,7 +97,7 @@ func ValidateSharePointRestoreFlags(backupID string, opts SharePointOpts) error
|
|||||||
return clues.New("invalid time format for " + flags.FileModifiedBeforeFN)
|
return clues.New("invalid time format for " + flags.FileModifiedBeforeFN)
|
||||||
}
|
}
|
||||||
|
|
||||||
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSharePointInfo adds the scope of the provided values to the selector's
|
// AddSharePointInfo adds the scope of the provided values to the selector's
|
||||||
|
|||||||
@ -7,8 +7,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
@ -29,12 +27,13 @@ const (
|
|||||||
Replace CollisionPolicy = "replace"
|
Replace CollisionPolicy = "replace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidCollisionPolicies() map[CollisionPolicy]struct{} {
|
func IsValidCollisionPolicy(cp CollisionPolicy) bool {
|
||||||
return map[CollisionPolicy]struct{}{
|
switch cp {
|
||||||
Skip: {},
|
case Skip, Copy, Replace:
|
||||||
Copy: {},
|
return true
|
||||||
Replace: {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const RootLocation = "/"
|
const RootLocation = "/"
|
||||||
@ -84,7 +83,7 @@ func EnsureRestoreConfigDefaults(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rc RestoreConfig,
|
rc RestoreConfig,
|
||||||
) RestoreConfig {
|
) RestoreConfig {
|
||||||
if !slices.Contains(maps.Keys(ValidCollisionPolicies()), rc.OnCollision) {
|
if !IsValidCollisionPolicy(rc.OnCollision) {
|
||||||
logger.Ctx(ctx).
|
logger.Ctx(ctx).
|
||||||
With(
|
With(
|
||||||
"bad_collision_policy", rc.OnCollision,
|
"bad_collision_policy", rc.OnCollision,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user