diff --git a/src/cmd/factory/factory.go b/src/cmd/factory/factory.go index 30474f32c..d7bb14620 100644 --- a/src/cmd/factory/factory.go +++ b/src/cmd/factory/factory.go @@ -3,25 +3,12 @@ package main import ( "context" "os" - "strings" - "time" - "github.com/google/uuid" - "github.com/pkg/errors" "github.com/spf13/cobra" . "github.com/alcionai/corso/src/cli/print" - "github.com/alcionai/corso/src/internal/common" - "github.com/alcionai/corso/src/internal/connector" - "github.com/alcionai/corso/src/internal/connector/mockconnector" - "github.com/alcionai/corso/src/internal/data" - "github.com/alcionai/corso/src/pkg/account" - "github.com/alcionai/corso/src/pkg/backup/details" - "github.com/alcionai/corso/src/pkg/control" - "github.com/alcionai/corso/src/pkg/credentials" + "github.com/alcionai/corso/src/cmd/factory/impl" "github.com/alcionai/corso/src/pkg/logger" - "github.com/alcionai/corso/src/pkg/path" - "github.com/alcionai/corso/src/pkg/selectors" ) var factoryCmd = &cobra.Command{ @@ -42,17 +29,6 @@ var oneDriveCmd = &cobra.Command{ RunE: handleOneDriveFactory, } -var ( - count int - destination string - tenant string - user string -) - -// TODO: ErrGenerating = errors.New("not all items were successfully generated") - -var ErrNotYetImplemeted = errors.New("not yet implemented") - // ------------------------------------------------------------------------------------------ // CLI command handlers // ------------------------------------------------------------------------------------------ @@ -65,18 +41,18 @@ func main() { // persistent flags that are common to all use cases fs := factoryCmd.PersistentFlags() - fs.StringVar(&tenant, "tenant", "", "m365 tenant containing the user") - fs.StringVar(&user, "user", "", "m365 user owning the new data") + fs.StringVar(&impl.Tenant, "tenant", "", "m365 tenant containing the user") + fs.StringVar(&impl.User, "user", "", "m365 user owning the new data") cobra.CheckErr(factoryCmd.MarkPersistentFlagRequired("user")) - fs.IntVar(&count, "count", 0, "count of items to produce") + fs.IntVar(&impl.Count, "count", 0, "count of items to produce") cobra.CheckErr(factoryCmd.MarkPersistentFlagRequired("count")) - fs.StringVar(&destination, "destination", "", "destination of the new data (will create as needed)") + fs.StringVar(&impl.Destination, "destination", "", "destination of the new data (will create as needed)") cobra.CheckErr(factoryCmd.MarkPersistentFlagRequired("destination")) factoryCmd.AddCommand(exchangeCmd) - addExchangeCommands(exchangeCmd) + impl.AddExchangeCommands(exchangeCmd) factoryCmd.AddCommand(oneDriveCmd) - addOneDriveCommands(oneDriveCmd) + impl.AddOneDriveCommands(oneDriveCmd) if err := factoryCmd.ExecuteContext(ctx); err != nil { logger.Flush(ctx) @@ -85,180 +61,16 @@ func main() { } func handleFactoryRoot(cmd *cobra.Command, args []string) error { - Err(cmd.Context(), ErrNotYetImplemeted) + Err(cmd.Context(), impl.ErrNotYetImplemeted) return cmd.Help() } func handleExchangeFactory(cmd *cobra.Command, args []string) error { - Err(cmd.Context(), ErrNotYetImplemeted) + Err(cmd.Context(), impl.ErrNotYetImplemeted) return cmd.Help() } func handleOneDriveFactory(cmd *cobra.Command, args []string) error { - Err(cmd.Context(), ErrNotYetImplemeted) + Err(cmd.Context(), impl.ErrNotYetImplemeted) return cmd.Help() } - -// ------------------------------------------------------------------------------------------ -// Restoration -// ------------------------------------------------------------------------------------------ - -type dataBuilderFunc func(id, now, subject, body string) []byte - -func generateAndRestoreItems( - ctx context.Context, - gc *connector.GraphConnector, - acct account.Account, - service path.ServiceType, - cat path.CategoryType, - sel selectors.Selector, - userID, destFldr string, - howMany int, - dbf dataBuilderFunc, -) (*details.Details, error) { - items := make([]item, 0, howMany) - - for i := 0; i < howMany; i++ { - var ( - now = common.Now() - nowLegacy = common.FormatLegacyTime(time.Now()) - id = uuid.NewString() - subject = "automated " + now[:16] + " - " + id[:8] - body = "automated " + cat.String() + " generation for " + userID + " at " + now + " - " + id - ) - - items = append(items, item{ - name: id, - data: dbf(id, nowLegacy, subject, body), - }) - } - - collections := []collection{{ - pathElements: []string{destFldr}, - category: cat, - items: items, - }} - - // TODO: fit the desination to the containers - dest := control.DefaultRestoreDestination(common.SimpleTimeTesting) - dest.ContainerName = destFldr - - dataColls, err := buildCollections( - service, - acct.ID(), userID, - dest, - collections, - ) - if err != nil { - return nil, err - } - - Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, destination) - - return gc.RestoreDataCollections(ctx, acct, sel, dest, dataColls) -} - -// ------------------------------------------------------------------------------------------ -// Common Helpers -// ------------------------------------------------------------------------------------------ - -func getGCAndVerifyUser(ctx context.Context, userID string) (*connector.GraphConnector, account.Account, error) { - tid := common.First(tenant, os.Getenv(account.AzureTenantID)) - - // get account info - m365Cfg := account.M365Config{ - M365: credentials.GetM365(), - AzureTenantID: tid, - } - - acct, err := account.NewAccount(account.ProviderM365, m365Cfg) - if err != nil { - return nil, account.Account{}, errors.Wrap(err, "finding m365 account details") - } - - // build a graph connector - gc, err := connector.NewGraphConnector(ctx, acct, connector.Users) - if err != nil { - return nil, account.Account{}, errors.Wrap(err, "connecting to graph api") - } - - normUsers := map[string]struct{}{} - - for k := range gc.Users { - normUsers[strings.ToLower(k)] = struct{}{} - } - - if _, ok := normUsers[strings.ToLower(user)]; !ok { - return nil, account.Account{}, errors.New("user not found within tenant") - } - - return gc, acct, nil -} - -type item struct { - name string - data []byte -} - -type collection struct { - // Elements (in order) for the path representing this collection. Should - // only contain elements after the prefix that corso uses for the path. For - // example, a collection for the Inbox folder in exchange mail would just be - // "Inbox". - pathElements []string - category path.CategoryType - items []item -} - -func buildCollections( - service path.ServiceType, - tenant, user string, - dest control.RestoreDestination, - colls []collection, -) ([]data.Collection, error) { - collections := make([]data.Collection, 0, len(colls)) - - for _, c := range colls { - pth, err := toDataLayerPath( - service, - tenant, - user, - c.category, - c.pathElements, - false, - ) - if err != nil { - return nil, err - } - - mc := mockconnector.NewMockExchangeCollection(pth, len(c.items)) - - for i := 0; i < len(c.items); i++ { - mc.Names[i] = c.items[i].name - mc.Data[i] = c.items[i].data - } - - collections = append(collections, mc) - } - - return collections, nil -} - -func toDataLayerPath( - service path.ServiceType, - tenant, user string, - category path.CategoryType, - elements []string, - isItem bool, -) (path.Path, error) { - pb := path.Builder{}.Append(elements...) - - switch service { - case path.ExchangeService: - return pb.ToDataLayerExchangePathForCategory(tenant, user, category, isItem) - case path.OneDriveService: - return pb.ToDataLayerOneDrivePath(tenant, user, isItem) - } - - return nil, errors.Errorf("unknown service %s", service.String()) -} diff --git a/src/cmd/factory/impl/common.go b/src/cmd/factory/impl/common.go new file mode 100644 index 000000000..369d80d20 --- /dev/null +++ b/src/cmd/factory/impl/common.go @@ -0,0 +1,198 @@ +package impl + +import ( + "context" + "os" + "strings" + "time" + + "github.com/google/uuid" + "github.com/pkg/errors" + + . "github.com/alcionai/corso/src/cli/print" + "github.com/alcionai/corso/src/internal/common" + "github.com/alcionai/corso/src/internal/connector" + "github.com/alcionai/corso/src/internal/connector/mockconnector" + "github.com/alcionai/corso/src/internal/data" + "github.com/alcionai/corso/src/pkg/account" + "github.com/alcionai/corso/src/pkg/backup/details" + "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/credentials" + "github.com/alcionai/corso/src/pkg/path" + "github.com/alcionai/corso/src/pkg/selectors" +) + +var ( + Count int + Destination string + Tenant string + User string +) + +// TODO: ErrGenerating = errors.New("not all items were successfully generated") + +var ErrNotYetImplemeted = errors.New("not yet implemented") + +// ------------------------------------------------------------------------------------------ +// Restoration +// ------------------------------------------------------------------------------------------ + +type dataBuilderFunc func(id, now, subject, body string) []byte + +func generateAndRestoreItems( + ctx context.Context, + gc *connector.GraphConnector, + acct account.Account, + service path.ServiceType, + cat path.CategoryType, + sel selectors.Selector, + tenantID, userID, destFldr string, + howMany int, + dbf dataBuilderFunc, +) (*details.Details, error) { + items := make([]item, 0, howMany) + + for i := 0; i < howMany; i++ { + var ( + now = common.Now() + nowLegacy = common.FormatLegacyTime(time.Now()) + id = uuid.NewString() + subject = "automated " + now[:16] + " - " + id[:8] + body = "automated " + cat.String() + " generation for " + userID + " at " + now + " - " + id + ) + + items = append(items, item{ + name: id, + data: dbf(id, nowLegacy, subject, body), + }) + } + + collections := []collection{{ + pathElements: []string{destFldr}, + category: cat, + items: items, + }} + + // TODO: fit the desination to the containers + dest := control.DefaultRestoreDestination(common.SimpleTimeTesting) + dest.ContainerName = destFldr + + dataColls, err := buildCollections( + service, + tenantID, userID, + dest, + collections, + ) + if err != nil { + return nil, err + } + + Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination) + + return gc.RestoreDataCollections(ctx, acct, sel, dest, dataColls) +} + +// ------------------------------------------------------------------------------------------ +// Common Helpers +// ------------------------------------------------------------------------------------------ + +func getGCAndVerifyUser(ctx context.Context, userID string) (*connector.GraphConnector, account.Account, error) { + tid := common.First(Tenant, os.Getenv(account.AzureTenantID)) + + // get account info + m365Cfg := account.M365Config{ + M365: credentials.GetM365(), + AzureTenantID: tid, + } + + acct, err := account.NewAccount(account.ProviderM365, m365Cfg) + if err != nil { + return nil, account.Account{}, errors.Wrap(err, "finding m365 account details") + } + + // build a graph connector + gc, err := connector.NewGraphConnector(ctx, acct, connector.Users) + if err != nil { + return nil, account.Account{}, errors.Wrap(err, "connecting to graph api") + } + + normUsers := map[string]struct{}{} + + for k := range gc.Users { + normUsers[strings.ToLower(k)] = struct{}{} + } + + if _, ok := normUsers[strings.ToLower(User)]; !ok { + return nil, account.Account{}, errors.New("user not found within tenant") + } + + return gc, acct, nil +} + +type item struct { + name string + data []byte +} + +type collection struct { + // Elements (in order) for the path representing this collection. Should + // only contain elements after the prefix that corso uses for the path. For + // example, a collection for the Inbox folder in exchange mail would just be + // "Inbox". + pathElements []string + category path.CategoryType + items []item +} + +func buildCollections( + service path.ServiceType, + tenant, user string, + dest control.RestoreDestination, + colls []collection, +) ([]data.Collection, error) { + collections := make([]data.Collection, 0, len(colls)) + + for _, c := range colls { + pth, err := toDataLayerPath( + service, + tenant, + user, + c.category, + c.pathElements, + false, + ) + if err != nil { + return nil, err + } + + mc := mockconnector.NewMockExchangeCollection(pth, len(c.items)) + + for i := 0; i < len(c.items); i++ { + mc.Names[i] = c.items[i].name + mc.Data[i] = c.items[i].data + } + + collections = append(collections, mc) + } + + return collections, nil +} + +func toDataLayerPath( + service path.ServiceType, + tenant, user string, + category path.CategoryType, + elements []string, + isItem bool, +) (path.Path, error) { + pb := path.Builder{}.Append(elements...) + + switch service { + case path.ExchangeService: + return pb.ToDataLayerExchangePathForCategory(tenant, user, category, isItem) + case path.OneDriveService: + return pb.ToDataLayerOneDrivePath(tenant, user, isItem) + } + + return nil, errors.Errorf("unknown service %s", service.String()) +} diff --git a/src/cmd/factory/exchange.go b/src/cmd/factory/impl/exchange.go similarity index 83% rename from src/cmd/factory/exchange.go rename to src/cmd/factory/impl/exchange.go index 6a292a8dc..26f7eef09 100644 --- a/src/cmd/factory/exchange.go +++ b/src/cmd/factory/impl/exchange.go @@ -1,4 +1,4 @@ -package main +package impl import ( "github.com/spf13/cobra" @@ -30,7 +30,7 @@ var ( } ) -func addExchangeCommands(cmd *cobra.Command) { +func AddExchangeCommands(cmd *cobra.Command) { cmd.AddCommand(emailsCmd) cmd.AddCommand(eventsCmd) cmd.AddCommand(contactsCmd) @@ -47,7 +47,7 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error { return nil } - gc, acct, err := getGCAndVerifyUser(ctx, user) + gc, acct, err := getGCAndVerifyUser(ctx, User) if err != nil { return Only(ctx, err) } @@ -58,12 +58,12 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error { acct, service, category, - selectors.NewExchangeRestore([]string{user}).Selector, - user, destination, - count, + selectors.NewExchangeRestore([]string{User}).Selector, + Tenant, User, Destination, + Count, func(id, now, subject, body string) []byte { return mockconnector.GetMockMessageWith( - user, user, user, + User, User, User, subject, body, body, now, now, now, now) }, @@ -88,7 +88,7 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error return nil } - gc, acct, err := getGCAndVerifyUser(ctx, user) + gc, acct, err := getGCAndVerifyUser(ctx, User) if err != nil { return Only(ctx, err) } @@ -99,12 +99,12 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error acct, service, category, - selectors.NewExchangeRestore([]string{user}).Selector, - user, destination, - count, + selectors.NewExchangeRestore([]string{User}).Selector, + Tenant, User, Destination, + Count, func(id, now, subject, body string) []byte { return mockconnector.GetMockEventWith( - user, subject, body, body, + User, subject, body, body, now, now, false) }, ) @@ -128,7 +128,7 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error { return nil } - gc, acct, err := getGCAndVerifyUser(ctx, user) + gc, acct, err := getGCAndVerifyUser(ctx, User) if err != nil { return Only(ctx, err) } @@ -139,9 +139,9 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error { acct, service, category, - selectors.NewExchangeRestore([]string{user}).Selector, - user, destination, - count, + selectors.NewExchangeRestore([]string{User}).Selector, + Tenant, User, Destination, + Count, func(id, now, subject, body string) []byte { given, mid, sur := id[:8], id[9:13], id[len(id)-12:] diff --git a/src/cmd/factory/onedrive.go b/src/cmd/factory/impl/onedrive.go similarity index 88% rename from src/cmd/factory/onedrive.go rename to src/cmd/factory/impl/onedrive.go index a76d222b9..f6bef0edf 100644 --- a/src/cmd/factory/onedrive.go +++ b/src/cmd/factory/impl/onedrive.go @@ -1,4 +1,4 @@ -package main +package impl import ( "github.com/spf13/cobra" @@ -13,7 +13,7 @@ var filesCmd = &cobra.Command{ RunE: handleOneDriveFileFactory, } -func addOneDriveCommands(cmd *cobra.Command) { +func AddOneDriveCommands(cmd *cobra.Command) { cmd.AddCommand(filesCmd) }