add final basic cli tests (#578)
Adds the base CLI integration tests for backup, list, details, and restore. Also refactors out the global root command value in favor of a ctx-bound reference so that tests may control safely overwriting stdout to scrutinize output.
This commit is contained in:
parent
9049f3c2bf
commit
a1b9f876ae
@ -171,12 +171,12 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)
|
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m365, err := acct.M365Config()
|
m365, err := acct.M365Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to parse m365 account config"))
|
return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Ctx(ctx).Debugw(
|
logger.Ctx(ctx).Debugw(
|
||||||
@ -187,7 +187,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
r, err := repository.Connect(ctx, acct, s)
|
r, err := repository.Connect(ctx, acct, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider)
|
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
|
||||||
}
|
}
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
@ -195,20 +195,20 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
bo, err := r.NewBackup(ctx, sel, options.Control())
|
bo, err := r.NewBackup(ctx, sel, options.Control())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to initialize Exchange backup"))
|
return Only(ctx, errors.Wrap(err, "Failed to initialize Exchange backup"))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bo.Run(ctx)
|
err = bo.Run(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to run Exchange backup"))
|
return Only(ctx, errors.Wrap(err, "Failed to run Exchange backup"))
|
||||||
}
|
}
|
||||||
|
|
||||||
bu, err := r.Backup(ctx, bo.Results.BackupID)
|
bu, err := r.Backup(ctx, bo.Results.BackupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Unable to retrieve backup results from storage")
|
return Only(ctx, errors.Wrap(err, "Unable to retrieve backup results from storage"))
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputBackup(*bu)
|
OutputBackup(ctx, *bu)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,12 +272,12 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)
|
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m365, err := acct.M365Config()
|
m365, err := acct.M365Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to parse m365 account config"))
|
return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Ctx(ctx).Debugw(
|
logger.Ctx(ctx).Debugw(
|
||||||
@ -286,16 +286,16 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
r, err := repository.Connect(ctx, acct, s)
|
r, err := repository.Connect(ctx, acct, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
|
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
|
||||||
}
|
}
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
rps, err := r.Backups(ctx)
|
rps, err := r.Backups(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to list backups in the repository"))
|
return Only(ctx, errors.Wrap(err, "Failed to list backups in the repository"))
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputBackups(rps)
|
OutputBackups(ctx, rps)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,12 +335,12 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)
|
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m365, err := acct.M365Config()
|
m365, err := acct.M365Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to parse m365 account config"))
|
return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Ctx(ctx).Debugw(
|
logger.Ctx(ctx).Debugw(
|
||||||
@ -349,13 +349,13 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
r, err := repository.Connect(ctx, acct, s)
|
r, err := repository.Connect(ctx, acct, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
|
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
|
||||||
}
|
}
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
d, _, err := r.BackupDetails(ctx, backupID)
|
d, _, err := r.BackupDetails(ctx, backupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to get backup details in the repository"))
|
return Only(ctx, errors.Wrap(err, "Failed to get backup details in the repository"))
|
||||||
}
|
}
|
||||||
|
|
||||||
sel := selectors.NewExchangeRestore()
|
sel := selectors.NewExchangeRestore()
|
||||||
@ -381,10 +381,10 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
ds := sel.Reduce(d)
|
ds := sel.Reduce(d)
|
||||||
if len(ds.Entries) == 0 {
|
if len(ds.Entries) == 0 {
|
||||||
return Only(errors.New("nothing to display: no items in the backup match the provided selectors"))
|
return Only(ctx, errors.New("nothing to display: no items in the backup match the provided selectors"))
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputEntries(ds.Entries)
|
OutputEntries(ctx, ds.Entries)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,43 @@
|
|||||||
package backup_test
|
package backup_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/cli"
|
"github.com/alcionai/corso/cli"
|
||||||
"github.com/alcionai/corso/cli/config"
|
"github.com/alcionai/corso/cli/config"
|
||||||
|
"github.com/alcionai/corso/cli/print"
|
||||||
|
"github.com/alcionai/corso/internal/operations"
|
||||||
"github.com/alcionai/corso/internal/tester"
|
"github.com/alcionai/corso/internal/tester"
|
||||||
|
"github.com/alcionai/corso/pkg/account"
|
||||||
|
"github.com/alcionai/corso/pkg/control"
|
||||||
"github.com/alcionai/corso/pkg/repository"
|
"github.com/alcionai/corso/pkg/repository"
|
||||||
|
"github.com/alcionai/corso/pkg/selectors"
|
||||||
|
"github.com/alcionai/corso/pkg/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExchangeIntegrationSuite struct {
|
// ---------------------------------------------------------------------------
|
||||||
|
// tests with no prior backup
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type BackupExchangeIntegrationSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
acct account.Account
|
||||||
|
st storage.Storage
|
||||||
|
vpr *viper.Viper
|
||||||
|
cfgFP string
|
||||||
|
repo *repository.Repository
|
||||||
|
m365UserID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExchangeIntegrationSuite(t *testing.T) {
|
func TestBackupExchangeIntegrationSuite(t *testing.T) {
|
||||||
if err := tester.RunOnAny(
|
if err := tester.RunOnAny(
|
||||||
tester.CorsoCITests,
|
tester.CorsoCITests,
|
||||||
tester.CorsoCLITests,
|
tester.CorsoCLITests,
|
||||||
@ -24,26 +45,21 @@ func TestExchangeIntegrationSuite(t *testing.T) {
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
t.Skip(err)
|
t.Skip(err)
|
||||||
}
|
}
|
||||||
suite.Run(t, new(ExchangeIntegrationSuite))
|
suite.Run(t, new(BackupExchangeIntegrationSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeIntegrationSuite) SetupSuite() {
|
func (suite *BackupExchangeIntegrationSuite) SetupSuite() {
|
||||||
_, err := tester.GetRequiredEnvVars(
|
|
||||||
append(
|
|
||||||
tester.AWSStorageCredEnvs,
|
|
||||||
tester.M365AcctCredEnvs...,
|
|
||||||
)...,
|
|
||||||
)
|
|
||||||
require.NoError(suite.T(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *ExchangeIntegrationSuite) TestExchangeBackupCmd() {
|
|
||||||
ctx := tester.NewContext()
|
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
_, err := tester.GetRequiredEnvSls(
|
||||||
|
tester.AWSStorageCredEnvs,
|
||||||
|
tester.M365AcctCredEnvs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
acct := tester.NewM365Account(t)
|
// prepare common details
|
||||||
st := tester.NewPrefixedS3Storage(t)
|
suite.acct = tester.NewM365Account(t)
|
||||||
cfg, err := st.S3Config()
|
suite.st = tester.NewPrefixedS3Storage(t)
|
||||||
|
|
||||||
|
cfg, err := suite.st.S3Config()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
force := map[string]string{
|
force := map[string]string{
|
||||||
@ -51,25 +67,155 @@ func (suite *ExchangeIntegrationSuite) TestExchangeBackupCmd() {
|
|||||||
tester.TestCfgStorageProvider: "S3",
|
tester.TestCfgStorageProvider: "S3",
|
||||||
tester.TestCfgPrefix: cfg.Prefix,
|
tester.TestCfgPrefix: cfg.Prefix,
|
||||||
}
|
}
|
||||||
vpr, configFP, err := tester.MakeTempTestConfigClone(t, force)
|
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ctx = config.SetViper(ctx, vpr)
|
ctx := config.SetViper(tester.NewContext(), suite.vpr)
|
||||||
|
|
||||||
|
suite.m365UserID = tester.M365UserID(t)
|
||||||
|
|
||||||
// init the repo first
|
// init the repo first
|
||||||
_, err = repository.Initialize(ctx, acct, st)
|
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
m365UserID := tester.M365UserID(t)
|
func (suite *BackupExchangeIntegrationSuite) TestExchangeBackupCmd() {
|
||||||
|
ctx := config.SetViper(tester.NewContext(), suite.vpr)
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
// then test it
|
|
||||||
cmd := tester.StubRootCmd(
|
cmd := tester.StubRootCmd(
|
||||||
"backup", "create", "exchange",
|
"backup", "create", "exchange",
|
||||||
"--config-file", configFP,
|
"--config-file", suite.cfgFP,
|
||||||
"--user", m365UserID,
|
"--user", suite.m365UserID,
|
||||||
"--data", "email",
|
"--data", "email")
|
||||||
)
|
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
|
var recorder strings.Builder
|
||||||
|
cmd.SetOut(&recorder)
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
// run the command
|
// run the command
|
||||||
require.NoError(t, cmd.ExecuteContext(ctx))
|
require.NoError(t, cmd.ExecuteContext(ctx))
|
||||||
|
|
||||||
|
// as an offhand check: the result should contain a string with the current hour
|
||||||
|
result := recorder.String()
|
||||||
|
assert.Contains(t, result, time.Now().UTC().Format("2006-01-02T15"))
|
||||||
|
// and the m365 user id
|
||||||
|
assert.Contains(t, result, suite.m365UserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// tests prepared with a previous backup
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type PreparedBackupExchangeIntegrationSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
acct account.Account
|
||||||
|
st storage.Storage
|
||||||
|
vpr *viper.Viper
|
||||||
|
cfgFP string
|
||||||
|
repo *repository.Repository
|
||||||
|
m365UserID string
|
||||||
|
backupOp operations.BackupOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreparedBackupExchangeIntegrationSuite(t *testing.T) {
|
||||||
|
if err := tester.RunOnAny(
|
||||||
|
tester.CorsoCITests,
|
||||||
|
tester.CorsoCLITests,
|
||||||
|
tester.CorsoCLIBackupTests,
|
||||||
|
); err != nil {
|
||||||
|
t.Skip(err)
|
||||||
|
}
|
||||||
|
suite.Run(t, new(PreparedBackupExchangeIntegrationSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PreparedBackupExchangeIntegrationSuite) SetupSuite() {
|
||||||
|
t := suite.T()
|
||||||
|
_, err := tester.GetRequiredEnvSls(
|
||||||
|
tester.AWSStorageCredEnvs,
|
||||||
|
tester.M365AcctCredEnvs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// prepare common details
|
||||||
|
suite.acct = tester.NewM365Account(t)
|
||||||
|
suite.st = tester.NewPrefixedS3Storage(t)
|
||||||
|
|
||||||
|
cfg, err := suite.st.S3Config()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
force := map[string]string{
|
||||||
|
tester.TestCfgAccountProvider: "M365",
|
||||||
|
tester.TestCfgStorageProvider: "S3",
|
||||||
|
tester.TestCfgPrefix: cfg.Prefix,
|
||||||
|
}
|
||||||
|
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ctx := config.SetViper(tester.NewContext(), suite.vpr)
|
||||||
|
|
||||||
|
suite.m365UserID = tester.M365UserID(t)
|
||||||
|
|
||||||
|
// init the repo first
|
||||||
|
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// some tests require an existing backup
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
// TODO: only backup the inbox
|
||||||
|
sel.Include(sel.Users([]string{suite.m365UserID}))
|
||||||
|
suite.backupOp, err = suite.repo.NewBackup(
|
||||||
|
ctx,
|
||||||
|
sel.Selector,
|
||||||
|
control.NewOptions(false))
|
||||||
|
require.NoError(t, suite.backupOp.Run(ctx))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeListCmd() {
|
||||||
|
ctx := config.SetViper(tester.NewContext(), suite.vpr)
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
cmd := tester.StubRootCmd(
|
||||||
|
"backup", "list", "exchange",
|
||||||
|
"--config-file", suite.cfgFP)
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
var recorder strings.Builder
|
||||||
|
cmd.SetOut(&recorder)
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
require.NoError(t, cmd.ExecuteContext(ctx))
|
||||||
|
|
||||||
|
// compare the output
|
||||||
|
result := recorder.String()
|
||||||
|
assert.Contains(t, result, suite.backupOp.Results.BackupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
|
||||||
|
ctx := config.SetViper(tester.NewContext(), suite.vpr)
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
// fetch the details from the repo first
|
||||||
|
deets, _, err := suite.repo.BackupDetails(ctx, string(suite.backupOp.Results.BackupID))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cmd := tester.StubRootCmd(
|
||||||
|
"backup", "details", "exchange",
|
||||||
|
"--config-file", suite.cfgFP,
|
||||||
|
"--backup", string(suite.backupOp.Results.BackupID))
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
var recorder strings.Builder
|
||||||
|
cmd.SetOut(&recorder)
|
||||||
|
ctx = print.SetRootCmd(ctx, cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
require.NoError(t, cmd.ExecuteContext(ctx))
|
||||||
|
|
||||||
|
// compare the output
|
||||||
|
result := recorder.String()
|
||||||
|
for i, ent := range deets.Entries {
|
||||||
|
t.Run(fmt.Sprintf("detail %d", i), func(t *testing.T) {
|
||||||
|
assert.Contains(t, result, ent.RepoRef)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ var (
|
|||||||
// Produces the same output as `corso --help`.
|
// Produces the same output as `corso --help`.
|
||||||
func handleCorsoCmd(cmd *cobra.Command, args []string) error {
|
func handleCorsoCmd(cmd *cobra.Command, args []string) error {
|
||||||
if version {
|
if version {
|
||||||
print.Infof("Corso\nversion:\tpre-alpha\n")
|
print.Infof(cmd.Context(), "Corso\nversion:\tpre-alpha\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return cmd.Help()
|
return cmd.Help()
|
||||||
@ -73,9 +73,9 @@ func BuildCommandTree(cmd *cobra.Command) {
|
|||||||
// Handle builds and executes the cli processor.
|
// Handle builds and executes the cli processor.
|
||||||
func Handle() {
|
func Handle() {
|
||||||
ctx := config.Seed(context.Background())
|
ctx := config.Seed(context.Background())
|
||||||
|
ctx = print.SetRootCmd(ctx, corsoCmd)
|
||||||
|
|
||||||
BuildCommandTree(corsoCmd)
|
BuildCommandTree(corsoCmd)
|
||||||
print.SetRootCommand(corsoCmd)
|
|
||||||
|
|
||||||
ctx, log := logger.Seed(ctx)
|
ctx, log := logger.Seed(ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@ -35,7 +35,7 @@ func AddConfigFileFlag(cmd *cobra.Command) {
|
|||||||
fs := cmd.PersistentFlags()
|
fs := cmd.PersistentFlags()
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Err("finding $HOME directory (default) for config file")
|
Err(cmd.Context(), "finding $HOME directory (default) for config file")
|
||||||
}
|
}
|
||||||
fs.StringVar(
|
fs.StringVar(
|
||||||
&configFilePath,
|
&configFilePath,
|
||||||
@ -133,7 +133,7 @@ func GetViper(ctx context.Context) *viper.Viper {
|
|||||||
// set up properly.
|
// set up properly.
|
||||||
func Read(ctx context.Context) error {
|
func Read(ctx context.Context) error {
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
Info("Using config file:", viper.ConfigFileUsed())
|
Info(ctx, "Using config file:", viper.ConfigFileUsed())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package print
|
package print
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -18,10 +19,24 @@ var (
|
|||||||
outputAsJSONDebug bool
|
outputAsJSONDebug bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{}
|
type rootCmdCtx struct{}
|
||||||
|
|
||||||
func SetRootCommand(root *cobra.Command) {
|
// Adds a root cobra command to the context.
|
||||||
rootCmd = root
|
// Used to amend output controls like SilenceUsage or to retrieve
|
||||||
|
// the command's output writer.
|
||||||
|
func SetRootCmd(ctx context.Context, root *cobra.Command) context.Context {
|
||||||
|
return context.WithValue(ctx, rootCmdCtx{}, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the root cobra command from the context.
|
||||||
|
// If no command is found, returns a new, blank command.
|
||||||
|
func getRootCmd(ctx context.Context) *cobra.Command {
|
||||||
|
cmdIface := ctx.Value(rootCmdCtx{})
|
||||||
|
cmd, ok := cmdIface.(*cobra.Command)
|
||||||
|
if cmd == nil || !ok {
|
||||||
|
return &cobra.Command{}
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds the persistent flag --output to the provided command.
|
// adds the persistent flag --output to the provided command.
|
||||||
@ -38,16 +53,16 @@ func AddOutputFlag(parent *cobra.Command) {
|
|||||||
|
|
||||||
// Only tells the CLI to only display this error, preventing the usage
|
// Only tells the CLI to only display this error, preventing the usage
|
||||||
// (ie, help) menu from displaying as well.
|
// (ie, help) menu from displaying as well.
|
||||||
func Only(e error) error {
|
func Only(ctx context.Context, e error) error {
|
||||||
rootCmd.SilenceUsage = true
|
getRootCmd(ctx).SilenceUsage = true
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err prints the params to cobra's error writer (stdErr by default)
|
// Err prints the params to cobra's error writer (stdErr by default)
|
||||||
// if s is nil, prints nothing.
|
// if s is nil, prints nothing.
|
||||||
// Prepends the message with "Error: "
|
// Prepends the message with "Error: "
|
||||||
func Err(s ...any) {
|
func Err(ctx context.Context, s ...any) {
|
||||||
err(rootCmd.ErrOrStderr(), s...)
|
err(getRootCmd(ctx).ErrOrStderr(), s...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// err is the testable core of Err()
|
// err is the testable core of Err()
|
||||||
@ -61,8 +76,8 @@ func err(w io.Writer, s ...any) {
|
|||||||
|
|
||||||
// Info prints the params to cobra's error writer (stdErr by default)
|
// Info prints the params to cobra's error writer (stdErr by default)
|
||||||
// if s is nil, prints nothing.
|
// if s is nil, prints nothing.
|
||||||
func Info(s ...any) {
|
func Info(ctx context.Context, s ...any) {
|
||||||
info(rootCmd.ErrOrStderr(), s...)
|
info(getRootCmd(ctx).ErrOrStderr(), s...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// info is the testable core of Info()
|
// info is the testable core of Info()
|
||||||
@ -76,8 +91,8 @@ func info(w io.Writer, s ...any) {
|
|||||||
|
|
||||||
// Info prints the formatted strings to cobra's error writer (stdErr by default)
|
// Info prints the formatted strings to cobra's error writer (stdErr by default)
|
||||||
// if t is empty, prints nothing.
|
// if t is empty, prints nothing.
|
||||||
func Infof(t string, s ...any) {
|
func Infof(ctx context.Context, t string, s ...any) {
|
||||||
infof(rootCmd.ErrOrStderr(), t, s...)
|
infof(getRootCmd(ctx).ErrOrStderr(), t, s...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// infof is the testable core of Infof()
|
// infof is the testable core of Infof()
|
||||||
@ -105,25 +120,25 @@ type Printable interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//revive:disable:redefines-builtin-id
|
//revive:disable:redefines-builtin-id
|
||||||
func print(p Printable) {
|
func print(w io.Writer, p Printable) {
|
||||||
if outputAsJSON || outputAsJSONDebug {
|
if outputAsJSON || outputAsJSONDebug {
|
||||||
outputJSON(p, outputAsJSONDebug)
|
outputJSON(w, p, outputAsJSONDebug)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
outputTable([]Printable{p})
|
outputTable(w, []Printable{p})
|
||||||
}
|
}
|
||||||
|
|
||||||
// printAll prints the slice of printable items,
|
// printAll prints the slice of printable items,
|
||||||
// according to the caller's requested format.
|
// according to the caller's requested format.
|
||||||
func printAll(ps []Printable) {
|
func printAll(w io.Writer, ps []Printable) {
|
||||||
if len(ps) == 0 {
|
if len(ps) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if outputAsJSON || outputAsJSONDebug {
|
if outputAsJSON || outputAsJSONDebug {
|
||||||
outputJSONArr(ps, outputAsJSONDebug)
|
outputJSONArr(w, ps, outputAsJSONDebug)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
outputTable(ps)
|
outputTable(w, ps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
@ -131,26 +146,26 @@ func printAll(ps []Printable) {
|
|||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Prints the backup to the terminal with stdout.
|
// Prints the backup to the terminal with stdout.
|
||||||
func OutputBackup(b backup.Backup) {
|
func OutputBackup(ctx context.Context, b backup.Backup) {
|
||||||
print(b)
|
print(getRootCmd(ctx).OutOrStdout(), b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints the backups to the terminal with stdout.
|
// Prints the backups to the terminal with stdout.
|
||||||
func OutputBackups(bs []backup.Backup) {
|
func OutputBackups(ctx context.Context, bs []backup.Backup) {
|
||||||
ps := []Printable{}
|
ps := []Printable{}
|
||||||
for _, b := range bs {
|
for _, b := range bs {
|
||||||
ps = append(ps, b)
|
ps = append(ps, b)
|
||||||
}
|
}
|
||||||
printAll(ps)
|
printAll(getRootCmd(ctx).OutOrStdout(), ps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints the entries to the terminal with stdout.
|
// Prints the entries to the terminal with stdout.
|
||||||
func OutputEntries(des []details.DetailsEntry) {
|
func OutputEntries(ctx context.Context, des []details.DetailsEntry) {
|
||||||
ps := []Printable{}
|
ps := []Printable{}
|
||||||
for _, de := range des {
|
for _, de := range des {
|
||||||
ps = append(ps, de)
|
ps = append(ps, de)
|
||||||
}
|
}
|
||||||
printAll(ps)
|
printAll(getRootCmd(ctx).OutOrStdout(), ps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
@ -158,7 +173,7 @@ func OutputEntries(des []details.DetailsEntry) {
|
|||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// output to stdout the list of printable structs in a table
|
// output to stdout the list of printable structs in a table
|
||||||
func outputTable(ps []Printable) {
|
func outputTable(w io.Writer, ps []Printable) {
|
||||||
t := table.Table{
|
t := table.Table{
|
||||||
Headers: ps[0].Headers(),
|
Headers: ps[0].Headers(),
|
||||||
Rows: [][]string{},
|
Rows: [][]string{},
|
||||||
@ -167,7 +182,7 @@ func outputTable(ps []Printable) {
|
|||||||
t.Rows = append(t.Rows, p.Values())
|
t.Rows = append(t.Rows, p.Values())
|
||||||
}
|
}
|
||||||
_ = t.WriteTable(
|
_ = t.WriteTable(
|
||||||
rootCmd.OutOrStdout(),
|
w,
|
||||||
&table.Config{
|
&table.Config{
|
||||||
ShowIndex: false,
|
ShowIndex: false,
|
||||||
Color: false,
|
Color: false,
|
||||||
@ -179,15 +194,15 @@ func outputTable(ps []Printable) {
|
|||||||
// JSON
|
// JSON
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
func outputJSON(p Printable, debug bool) {
|
func outputJSON(w io.Writer, p Printable, debug bool) {
|
||||||
if debug {
|
if debug {
|
||||||
printJSON(p)
|
printJSON(w, p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
printJSON(p.MinimumPrintable())
|
printJSON(w, p.MinimumPrintable())
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputJSONArr(ps []Printable, debug bool) {
|
func outputJSONArr(w io.Writer, ps []Printable, debug bool) {
|
||||||
sl := make([]any, 0, len(ps))
|
sl := make([]any, 0, len(ps))
|
||||||
for _, p := range ps {
|
for _, p := range ps {
|
||||||
if debug {
|
if debug {
|
||||||
@ -196,17 +211,15 @@ func outputJSONArr(ps []Printable, debug bool) {
|
|||||||
sl = append(sl, p.MinimumPrintable())
|
sl = append(sl, p.MinimumPrintable())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printJSON(sl)
|
printJSON(w, sl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// output to stdout the list of printable structs as json.
|
// output to stdout the list of printable structs as json.
|
||||||
func printJSON(a any) {
|
func printJSON(w io.Writer, a any) {
|
||||||
bs, err := json.Marshal(a)
|
bs, err := json.Marshal(a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(rootCmd.OutOrStderr(), "error formatting results to json: %v\n", err)
|
fmt.Fprintf(w, "error formatting results to json: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintln(
|
fmt.Fprintln(w, string(pretty.Pretty(bs)))
|
||||||
rootCmd.OutOrStdout(),
|
|
||||||
string(pretty.Pretty(bs)))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/internal/tester"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PrintUnitSuite struct {
|
type PrintUnitSuite struct {
|
||||||
@ -18,12 +20,11 @@ func TestPrintUnitSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PrintUnitSuite) TestOnly() {
|
func (suite *PrintUnitSuite) TestOnly() {
|
||||||
|
ctx := tester.NewContext()
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
c := &cobra.Command{}
|
c := &cobra.Command{}
|
||||||
oldRoot := rootCmd
|
ctx = SetRootCmd(ctx, c)
|
||||||
defer SetRootCommand(oldRoot)
|
assert.NoError(t, Only(ctx, nil))
|
||||||
SetRootCommand(c)
|
|
||||||
assert.NoError(t, Only(nil))
|
|
||||||
assert.True(t, c.SilenceUsage)
|
assert.True(t, c.SilenceUsage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,16 +78,16 @@ func initS3Cmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
s, a, err := config.GetStorageAndAccount(ctx, false, s3Overrides())
|
s, a, err := config.GetStorageAndAccount(ctx, false, s3Overrides())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
s3Cfg, err := s.S3Config()
|
s3Cfg, err := s.S3Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Retrieving s3 configuration"))
|
return Only(ctx, errors.Wrap(err, "Retrieving s3 configuration"))
|
||||||
}
|
}
|
||||||
|
|
||||||
m365, err := a.M365Config()
|
m365, err := a.M365Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to parse m365 account config"))
|
return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugw(
|
log.Debugw(
|
||||||
@ -103,14 +103,14 @@ func initS3Cmd(cmd *cobra.Command, args []string) error {
|
|||||||
if succeedIfExists && kopia.IsRepoAlreadyExistsError(err) {
|
if succeedIfExists && kopia.IsRepoAlreadyExistsError(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return Only(errors.Wrap(err, "Failed to initialize a new S3 repository"))
|
return Only(ctx, errors.Wrap(err, "Failed to initialize a new S3 repository"))
|
||||||
}
|
}
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
Infof("Initialized a S3 repository within bucket %s.", s3Cfg.Bucket)
|
Infof(ctx, "Initialized a S3 repository within bucket %s.", s3Cfg.Bucket)
|
||||||
|
|
||||||
if err = config.WriteRepoConfig(ctx, s3Cfg, m365); err != nil {
|
if err = config.WriteRepoConfig(ctx, s3Cfg, m365); err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to write repository configuration"))
|
return Only(ctx, errors.Wrap(err, "Failed to write repository configuration"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -141,15 +141,15 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
s, a, err := config.GetStorageAndAccount(ctx, true, s3Overrides())
|
s, a, err := config.GetStorageAndAccount(ctx, true, s3Overrides())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
s3Cfg, err := s.S3Config()
|
s3Cfg, err := s.S3Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Retrieving s3 configuration"))
|
return Only(ctx, errors.Wrap(err, "Retrieving s3 configuration"))
|
||||||
}
|
}
|
||||||
m365, err := a.M365Config()
|
m365, err := a.M365Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to parse m365 account config"))
|
return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugw(
|
log.Debugw(
|
||||||
@ -162,14 +162,14 @@ func connectS3Cmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
r, err := repository.Connect(ctx, a, s)
|
r, err := repository.Connect(ctx, a, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to connect to the S3 repository"))
|
return Only(ctx, errors.Wrap(err, "Failed to connect to the S3 repository"))
|
||||||
}
|
}
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
Infof("Connected to S3 bucket %s.", s3Cfg.Bucket)
|
Infof(ctx, "Connected to S3 bucket %s.", s3Cfg.Bucket)
|
||||||
|
|
||||||
if err = config.WriteRepoConfig(ctx, s3Cfg, m365); err != nil {
|
if err = config.WriteRepoConfig(ctx, s3Cfg, m365); err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to write repository configuration"))
|
return Only(ctx, errors.Wrap(err, "Failed to write repository configuration"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,12 +125,12 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
s, a, err := config.GetStorageAndAccount(ctx, true, nil)
|
s, a, err := config.GetStorageAndAccount(ctx, true, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m365, err := a.M365Config()
|
m365, err := a.M365Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to parse m365 account config"))
|
return Only(ctx, errors.Wrap(err, "Failed to parse m365 account config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Ctx(ctx).Debugw(
|
logger.Ctx(ctx).Debugw(
|
||||||
@ -142,7 +142,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
r, err := repository.Connect(ctx, a, s)
|
r, err := repository.Connect(ctx, a, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
|
return Only(ctx, errors.Wrapf(err, "Failed to connect to the %s repository", s.Provider))
|
||||||
}
|
}
|
||||||
defer utils.CloseRepo(ctx, r)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
@ -169,14 +169,14 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
ro, err := r.NewRestore(ctx, backupID, sel.Selector, options.Control())
|
ro, err := r.NewRestore(ctx, backupID, sel.Selector, options.Control())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to initialize Exchange restore"))
|
return Only(ctx, errors.Wrap(err, "Failed to initialize Exchange restore"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ro.Run(ctx); err != nil {
|
if err := ro.Run(ctx); err != nil {
|
||||||
return Only(errors.Wrap(err, "Failed to run Exchange restore"))
|
return Only(ctx, errors.Wrap(err, "Failed to run Exchange restore"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Infof("Restored Exchange in %s for user %s.\n", s.Provider, user)
|
Infof(ctx, "Restored Exchange in %s for user %s.\n", s.Provider, user)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
100
src/cli/restore/exchange_integration_test.go
Normal file
100
src/cli/restore/exchange_integration_test.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package restore_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/cli"
|
||||||
|
"github.com/alcionai/corso/cli/config"
|
||||||
|
"github.com/alcionai/corso/internal/operations"
|
||||||
|
"github.com/alcionai/corso/internal/tester"
|
||||||
|
"github.com/alcionai/corso/pkg/account"
|
||||||
|
"github.com/alcionai/corso/pkg/control"
|
||||||
|
"github.com/alcionai/corso/pkg/repository"
|
||||||
|
"github.com/alcionai/corso/pkg/selectors"
|
||||||
|
"github.com/alcionai/corso/pkg/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BackupExchangeIntegrationSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
acct account.Account
|
||||||
|
st storage.Storage
|
||||||
|
vpr *viper.Viper
|
||||||
|
cfgFP string
|
||||||
|
repo *repository.Repository
|
||||||
|
m365UserID string
|
||||||
|
backupOp operations.BackupOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupExchangeIntegrationSuite(t *testing.T) {
|
||||||
|
if err := tester.RunOnAny(
|
||||||
|
tester.CorsoCITests,
|
||||||
|
tester.CorsoCLITests,
|
||||||
|
tester.CorsoCLIBackupTests,
|
||||||
|
); err != nil {
|
||||||
|
t.Skip(err)
|
||||||
|
}
|
||||||
|
suite.Run(t, new(BackupExchangeIntegrationSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupExchangeIntegrationSuite) SetupSuite() {
|
||||||
|
t := suite.T()
|
||||||
|
_, err := tester.GetRequiredEnvSls(
|
||||||
|
tester.AWSStorageCredEnvs,
|
||||||
|
tester.M365AcctCredEnvs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// aggregate required details
|
||||||
|
|
||||||
|
suite.acct = tester.NewM365Account(t)
|
||||||
|
suite.st = tester.NewPrefixedS3Storage(t)
|
||||||
|
|
||||||
|
cfg, err := suite.st.S3Config()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
force := map[string]string{
|
||||||
|
tester.TestCfgAccountProvider: "M365",
|
||||||
|
tester.TestCfgStorageProvider: "S3",
|
||||||
|
tester.TestCfgPrefix: cfg.Prefix,
|
||||||
|
}
|
||||||
|
suite.vpr, suite.cfgFP, err = tester.MakeTempTestConfigClone(t, force)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ctx := config.SetViper(tester.NewContext(), suite.vpr)
|
||||||
|
|
||||||
|
suite.m365UserID = tester.M365UserID(t)
|
||||||
|
|
||||||
|
// init the repo first
|
||||||
|
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// restoration requires an existing backup
|
||||||
|
sel := selectors.NewExchangeBackup()
|
||||||
|
// TODO: only backup the inbox
|
||||||
|
sel.Include(sel.Users([]string{suite.m365UserID}))
|
||||||
|
suite.backupOp, err = suite.repo.NewBackup(
|
||||||
|
ctx,
|
||||||
|
sel.Selector,
|
||||||
|
control.NewOptions(false))
|
||||||
|
require.NoError(t, suite.backupOp.Run(ctx))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BackupExchangeIntegrationSuite) TestExchangeRestoreCmd() {
|
||||||
|
ctx := config.SetViper(tester.NewContext(), suite.vpr)
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
cmd := tester.StubRootCmd(
|
||||||
|
"restore", "exchange",
|
||||||
|
"--config-file", suite.cfgFP,
|
||||||
|
"--backup", string(suite.backupOp.Results.BackupID))
|
||||||
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
|
// run the command
|
||||||
|
require.NoError(t, cmd.ExecuteContext(ctx))
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -30,6 +31,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func doFolderPurge(cmd *cobra.Command, args []string) error {
|
func doFolderPurge(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
|
||||||
if utils.HasNoFlagsAndShownHelp(cmd) {
|
if utils.HasNoFlagsAndShownHelp(cmd) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -41,19 +44,19 @@ func doFolderPurge(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
acct, err := account.NewAccount(account.ProviderM365, m365Cfg)
|
acct, err := account.NewAccount(account.ProviderM365, m365Cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "finding m365 account details"))
|
return Only(ctx, errors.Wrap(err, "finding m365 account details"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// build a graph connector
|
// build a graph connector
|
||||||
gc, err := connector.NewGraphConnector(acct)
|
gc, err := connector.NewGraphConnector(acct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "connecting to graph api"))
|
return Only(ctx, errors.Wrap(err, "connecting to graph api"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// get them folders
|
// get them folders
|
||||||
mfs, err := exchange.GetAllMailFolders(gc.Service(), user, prefix)
|
mfs, err := exchange.GetAllMailFolders(gc.Service(), user, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "retrieving mail folders"))
|
return Only(ctx, errors.Wrap(err, "retrieving mail folders"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// format the time input
|
// format the time input
|
||||||
@ -61,7 +64,7 @@ func doFolderPurge(cmd *cobra.Command, args []string) error {
|
|||||||
if len(before) > 0 {
|
if len(before) > 0 {
|
||||||
beforeTime, err = common.ParseTime(before)
|
beforeTime, err = common.ParseTime(before)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(errors.Wrap(err, "parsing before flag to time"))
|
return Only(ctx, errors.Wrap(err, "parsing before flag to time"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stLen := len(common.SimpleDateTimeFormat)
|
stLen := len(common.SimpleDateTimeFormat)
|
||||||
@ -76,7 +79,7 @@ func doFolderPurge(cmd *cobra.Command, args []string) error {
|
|||||||
dnSuff := mf.DisplayName[dnLen-stLen:]
|
dnSuff := mf.DisplayName[dnLen-stLen:]
|
||||||
dnTime, err := common.ParseTime(dnSuff)
|
dnTime, err := common.ParseTime(dnSuff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Info(errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName))
|
Info(ctx, errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
delete = dnTime.Before(beforeTime)
|
delete = dnTime.Before(beforeTime)
|
||||||
@ -86,10 +89,10 @@ func doFolderPurge(cmd *cobra.Command, args []string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
Info("Deleting folder: ", mf.DisplayName)
|
Info(ctx, "Deleting folder: ", mf.DisplayName)
|
||||||
err = exchange.DeleteMailFolder(gc.Service(), user, mf.ID)
|
err = exchange.DeleteMailFolder(gc.Service(), user, mf.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Info(errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName))
|
Info(ctx, errors.Wrapf(err, "Error: deleting folder [%s]", mf.DisplayName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +100,7 @@ func doFolderPurge(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
ctx := SetRootCmd(context.Background(), purgeCmd)
|
||||||
fs := purgeCmd.Flags()
|
fs := purgeCmd.Flags()
|
||||||
fs.StringVar(&before, "before", "", "folders older than this date are deleted. (default: now in UTC)")
|
fs.StringVar(&before, "before", "", "folders older than this date are deleted. (default: now in UTC)")
|
||||||
fs.StringVar(&user, "user", "", "m365 user id whose folders will be deleted")
|
fs.StringVar(&user, "user", "", "m365 user id whose folders will be deleted")
|
||||||
@ -105,8 +109,8 @@ func main() {
|
|||||||
fs.StringVar(&prefix, "prefix", "", "filters mail folders by displayName prefix")
|
fs.StringVar(&prefix, "prefix", "", "filters mail folders by displayName prefix")
|
||||||
cobra.CheckErr(purgeCmd.MarkFlagRequired("prefix"))
|
cobra.CheckErr(purgeCmd.MarkFlagRequired("prefix"))
|
||||||
|
|
||||||
if err := purgeCmd.Execute(); err != nil {
|
if err := purgeCmd.ExecuteContext(ctx); err != nil {
|
||||||
Info("Error: ", err.Error())
|
Info(purgeCmd.Context(), "Error: ", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user