adds the primary `debug` command to the cli as a hidden command option. Also includes the scaffold for a `metadata-files` command that could be used to print out the metadata files in the backup. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🌻 Feature #### Test Plan - [x] ⚡ Unit test
301 lines
7.8 KiB
Go
301 lines
7.8 KiB
Go
package utils
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/alcionai/clues"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
|
|
"github.com/alcionai/corso/src/cli/config"
|
|
"github.com/alcionai/corso/src/cli/flags"
|
|
"github.com/alcionai/corso/src/internal/events"
|
|
"github.com/alcionai/corso/src/pkg/account"
|
|
"github.com/alcionai/corso/src/pkg/control"
|
|
"github.com/alcionai/corso/src/pkg/logger"
|
|
"github.com/alcionai/corso/src/pkg/path"
|
|
"github.com/alcionai/corso/src/pkg/repository"
|
|
"github.com/alcionai/corso/src/pkg/selectors"
|
|
"github.com/alcionai/corso/src/pkg/storage"
|
|
)
|
|
|
|
type RepoDetailsAndOpts struct {
|
|
Repo config.RepoDetails
|
|
Opts control.Options
|
|
}
|
|
|
|
var ErrNotYetImplemented = clues.New("not yet implemented")
|
|
|
|
// GetAccountAndConnect is a wrapper for GetAccountAndConnectWithOverrides
|
|
// that automatically gets the storage provider and any storage provider specific
|
|
// flag overrides from the command line.
|
|
func GetAccountAndConnect(
|
|
ctx context.Context,
|
|
cmd *cobra.Command,
|
|
pst path.ServiceType,
|
|
) (repository.Repositoryer, RepoDetailsAndOpts, error) {
|
|
provider, overrides, err := GetStorageProviderAndOverrides(ctx, cmd)
|
|
if err != nil {
|
|
return nil, RepoDetailsAndOpts{}, clues.Stack(err)
|
|
}
|
|
|
|
return GetAccountAndConnectWithOverrides(ctx, pst, provider, overrides)
|
|
}
|
|
|
|
func GetAccountAndConnectWithOverrides(
|
|
ctx context.Context,
|
|
pst path.ServiceType,
|
|
provider storage.ProviderType,
|
|
overrides map[string]string,
|
|
) (repository.Repositoryer, RepoDetailsAndOpts, error) {
|
|
cfg, err := config.GetConfigRepoDetails(
|
|
ctx,
|
|
provider,
|
|
true,
|
|
true,
|
|
overrides)
|
|
if err != nil {
|
|
return nil, RepoDetailsAndOpts{}, err
|
|
}
|
|
|
|
repoID := cfg.RepoID
|
|
if len(repoID) == 0 {
|
|
repoID = events.RepoIDNotFound
|
|
}
|
|
|
|
opts := ControlWithConfig(cfg)
|
|
|
|
r, err := repository.New(
|
|
ctx,
|
|
cfg.Account,
|
|
cfg.Storage,
|
|
opts,
|
|
repoID)
|
|
if err != nil {
|
|
return nil, RepoDetailsAndOpts{}, clues.Wrap(err, "creating a repository controller")
|
|
}
|
|
|
|
if err := r.Connect(ctx, repository.ConnConfig{Service: pst}); err != nil {
|
|
return nil, RepoDetailsAndOpts{}, clues.Wrap(err, "connecting to the "+cfg.Storage.Provider.String()+" repository")
|
|
}
|
|
|
|
rdao := RepoDetailsAndOpts{
|
|
Repo: cfg,
|
|
Opts: opts,
|
|
}
|
|
|
|
return r, rdao, nil
|
|
}
|
|
|
|
func AccountConnectAndWriteRepoConfig(
|
|
ctx context.Context,
|
|
cmd *cobra.Command,
|
|
pst path.ServiceType,
|
|
) (repository.Repositoryer, *account.Account, error) {
|
|
r, rdao, err := GetAccountAndConnect(ctx, cmd, pst)
|
|
if err != nil {
|
|
logger.CtxErr(ctx, err).Info("getting and connecting account")
|
|
return nil, nil, err
|
|
}
|
|
|
|
sc, err := rdao.Repo.Storage.StorageConfig()
|
|
if err != nil {
|
|
logger.CtxErr(ctx, err).Info("getting storage configuration")
|
|
return nil, nil, err
|
|
}
|
|
|
|
m365Config, err := rdao.Repo.Account.M365Config()
|
|
if err != nil {
|
|
logger.CtxErr(ctx, err).Info("getting m365 configuration")
|
|
return nil, nil, err
|
|
}
|
|
|
|
// repo config gets set during repo connect and init.
|
|
// This call confirms we have the correct values.
|
|
err = config.WriteRepoConfig(ctx, sc, m365Config, rdao.Opts.Repo, r.GetID())
|
|
if err != nil {
|
|
logger.CtxErr(ctx, err).Info("writing to repository configuration")
|
|
return nil, nil, err
|
|
}
|
|
|
|
return r, &rdao.Repo.Account, nil
|
|
}
|
|
|
|
// CloseRepo handles closing a repo.
|
|
func CloseRepo(ctx context.Context, r repository.Repositoryer) {
|
|
if err := r.Close(ctx); err != nil {
|
|
fmt.Print("Error closing repository:", err)
|
|
}
|
|
}
|
|
|
|
// HasNoFlagsAndShownHelp shows the Help output if no flags
|
|
// were provided to the command. Returns true if the help
|
|
// was shown.
|
|
// Use for when the non-flagged usage of a command
|
|
// (ex: corso backup restore exchange) is expected to no-op.
|
|
func HasNoFlagsAndShownHelp(cmd *cobra.Command) bool {
|
|
if cmd.Flags().NFlag() == 0 {
|
|
cobra.CheckErr(cmd.Help())
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type cmdCfg struct {
|
|
hidden bool
|
|
preRelease bool
|
|
preview bool
|
|
debug bool
|
|
}
|
|
|
|
type cmdOpt func(*cmdCfg)
|
|
|
|
func (cc *cmdCfg) populate(opts ...cmdOpt) {
|
|
for _, opt := range opts {
|
|
opt(cc)
|
|
}
|
|
}
|
|
|
|
func HideCommand() cmdOpt {
|
|
return func(cc *cmdCfg) {
|
|
cc.hidden = true
|
|
}
|
|
}
|
|
|
|
func MarkPreReleaseCommand() cmdOpt {
|
|
return func(cc *cmdCfg) {
|
|
cc.hidden = true
|
|
cc.preRelease = true
|
|
}
|
|
}
|
|
|
|
func MarkPreviewCommand() cmdOpt {
|
|
return func(cc *cmdCfg) {
|
|
cc.preview = true
|
|
}
|
|
}
|
|
|
|
func MarkDebugCommand() cmdOpt {
|
|
return func(cc *cmdCfg) {
|
|
cc.hidden = true
|
|
cc.debug = true
|
|
}
|
|
}
|
|
|
|
// AddCommand adds a clone of the subCommand to the parent,
|
|
// and returns both the clone and its pflags.
|
|
func AddCommand(parent, c *cobra.Command, opts ...cmdOpt) (*cobra.Command, *pflag.FlagSet) {
|
|
cc := &cmdCfg{}
|
|
cc.populate(opts...)
|
|
|
|
parent.AddCommand(c)
|
|
c.Hidden = cc.hidden
|
|
|
|
var deprecatedMsgOverride string
|
|
|
|
switch true {
|
|
case cc.preRelease:
|
|
deprecatedMsgOverride = "THIS IS A PRE-RELEASE COMMAND THAT MAY NOT FUNCTION PROPERLY, OR AT ALL"
|
|
case cc.preview:
|
|
deprecatedMsgOverride = "THIS IS A FEATURE PREVIEW THAT MAY NOT FUNCTION PROPERLY AND MAY BREAK ACROSS RELEASES"
|
|
case cc.debug:
|
|
deprecatedMsgOverride = "THIS IS A DEBUGGING COMMAND - IT IS NOT PART OF STANDARD CORSO OPERATION"
|
|
}
|
|
|
|
if len(deprecatedMsgOverride) > 0 {
|
|
// There is a default deprecated message that always shows so we do some terminal magic to overwrite it
|
|
c.Deprecated = "\n\033[1F\033[K" +
|
|
"==================================================================================================\n" +
|
|
"\tWARNING!!! " + deprecatedMsgOverride + "\n" +
|
|
"==================================================================================================\n"
|
|
}
|
|
|
|
c.Flags().SortFlags = false
|
|
|
|
return c, c.Flags()
|
|
}
|
|
|
|
// separates the provided folders into two sets: folders that use a pathContains
|
|
// comparison (the default), and folders that use a pathPrefix comparison.
|
|
// Any element beginning with a path.PathSeparator (ie: '/') is moved to the prefix
|
|
// comparison set. If folders is nil, returns only containsFolders with the any matcher.
|
|
func splitFoldersIntoContainsAndPrefix(folders []string) ([]string, []string) {
|
|
var (
|
|
containsFolders = []string{}
|
|
prefixFolders = []string{}
|
|
)
|
|
|
|
if len(folders) == 0 {
|
|
return selectors.Any(), nil
|
|
}
|
|
|
|
// separate folder selection inputs by behavior.
|
|
// any input beginning with a '/' character acts as a prefix match.
|
|
for _, f := range folders {
|
|
if len(f) == 0 {
|
|
continue
|
|
}
|
|
|
|
if f[0] == path.PathSeparator {
|
|
prefixFolders = append(prefixFolders, f)
|
|
} else {
|
|
containsFolders = append(containsFolders, f)
|
|
}
|
|
}
|
|
|
|
return containsFolders, prefixFolders
|
|
}
|
|
|
|
// GetStorageProviderAndOverrides returns the storage provider type and
|
|
// any flags specified on the command line which are storage provider specific.
|
|
func GetStorageProviderAndOverrides(
|
|
ctx context.Context,
|
|
cmd *cobra.Command,
|
|
) (storage.ProviderType, map[string]string, error) {
|
|
provider, err := config.GetStorageProviderFromConfigFile(ctx)
|
|
if err != nil {
|
|
return provider, nil, clues.Stack(err)
|
|
}
|
|
|
|
switch provider {
|
|
case storage.ProviderS3:
|
|
return provider, flags.S3FlagOverrides(cmd), nil
|
|
case storage.ProviderFilesystem:
|
|
return provider, flags.FilesystemFlagOverrides(cmd), nil
|
|
}
|
|
|
|
return provider, nil, clues.New("unknown storage provider: " + provider.String())
|
|
}
|
|
|
|
// MakeAbsoluteFilePath does directory path expansions & conversions, namely:
|
|
// 1. Expands "~" prefix to the user's home directory, and converts to absolute path.
|
|
// 2. Relative paths are converted to absolute paths.
|
|
// 3. Absolute paths are returned as-is.
|
|
// 4. Empty paths are not allowed, an error is returned.
|
|
func MakeAbsoluteFilePath(p string) (string, error) {
|
|
if len(p) == 0 {
|
|
return "", clues.New("empty path")
|
|
}
|
|
|
|
// Special case handling for "~". filepath.Abs will not expand it.
|
|
if p[0] == '~' {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", clues.Wrap(err, "getting user home directory")
|
|
}
|
|
|
|
p = filepath.Join(homeDir, p[1:])
|
|
}
|
|
|
|
abs, err := filepath.Abs(p)
|
|
if err != nil {
|
|
return "", clues.Stack(err)
|
|
}
|
|
|
|
return abs, nil
|
|
}
|