From 39b47cc123389fae5efd13f81dcde4a3a922e216 Mon Sep 17 00:00:00 2001 From: Danny Date: Tue, 25 Oct 2022 14:50:39 -0400 Subject: [PATCH] Gc mock package (#1313) ## Description Command line utility that allows for the printing of mockData based on the m365ID. Supports: - `exchange.Mail` - `exchange.Contacts` - `exchange.Events` ## Type of change - [x] :sunflower: Feature ## Issue(s) Related to: * #1255 * #999 * #705 --- src/cmd/getM365/getItem.go | 178 ++++++++++++++++++ .../connector/exchange/query_options.go | 2 +- .../connector/exchange/service_iterators.go | 2 +- .../connector/exchange/service_restore.go | 2 +- .../connector/graph/service_helper.go | 18 ++ 5 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 src/cmd/getM365/getItem.go diff --git a/src/cmd/getM365/getItem.go b/src/cmd/getM365/getItem.go new file mode 100644 index 000000000..84daa2f2f --- /dev/null +++ b/src/cmd/getM365/getItem.go @@ -0,0 +1,178 @@ +// getItem.go is a source file designed to retrieve an m365 object from an +// existing M365 account. Data displayed is representative of the current +// serialization abstraction versioning used by Microsoft Graph and stored by Corso. + +package main + +import ( + "bytes" + "context" + "fmt" + "os" + + kw "github.com/microsoft/kiota-serialization-json-go" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + . "github.com/alcionai/corso/src/cli/print" + "github.com/alcionai/corso/src/cli/utils" + "github.com/alcionai/corso/src/internal/common" + "github.com/alcionai/corso/src/internal/connector" + "github.com/alcionai/corso/src/internal/connector/exchange" + "github.com/alcionai/corso/src/internal/connector/graph" + "github.com/alcionai/corso/src/internal/connector/support" + "github.com/alcionai/corso/src/internal/data" + "github.com/alcionai/corso/src/pkg/account" + "github.com/alcionai/corso/src/pkg/credentials" + "github.com/alcionai/corso/src/pkg/logger" + "github.com/alcionai/corso/src/pkg/path" +) + +var getCmd = &cobra.Command{ + Use: "get", + Short: "Get a M365ID item JSON", + RunE: handleGetCommand, +} + +// Required inputs from user for command execution +var ( + tenant, user, m365ID, category string +) + +// main function will produce the JSON String for a given m365 object of a +// user. Displayed Objects can be used as inputs for Mockable data +// Supports: +// - exchange (contacts, email, and events) +// Input: go run ./getItem.go --user +// --m365ID --category +func main() { + ctx, _ := logger.SeedLevel(context.Background(), logger.Development) + ctx = SetRootCmd(ctx, getCmd) + + defer logger.Flush(ctx) + + fs := getCmd.PersistentFlags() + fs.StringVar(&user, "user", "", "m365 user id of M365 user") + fs.StringVar(&tenant, "tenant", "", + "m365 Tenant: m365 identifier for the tenant, not required if active in OS Environment") + fs.StringVar(&m365ID, "m365ID", "", "m365 identifier for object to be created") + fs.StringVar(&category, "category", "", "type of M365 data (contacts, email, events or files)") // files not supported + + cobra.CheckErr(getCmd.MarkPersistentFlagRequired("user")) + cobra.CheckErr(getCmd.MarkPersistentFlagRequired("m365ID")) + cobra.CheckErr(getCmd.MarkPersistentFlagRequired("category")) + + if err := getCmd.ExecuteContext(ctx); err != nil { + os.Exit(1) + } +} + +func handleGetCommand(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + if utils.HasNoFlagsAndShownHelp(cmd) { + return nil + } + + gc, err := getGC(ctx) + if err != nil { + return err + } + + err = runDisplayM365JSON( + ctx, + gc) + if err != nil { + return Only(ctx, errors.Wrapf(err, "unable to create mock from M365: %s", m365ID)) + } + + return nil +} + +func runDisplayM365JSON( + ctx context.Context, + gs graph.Service, +) error { + var ( + get exchange.GraphRetrievalFunc + serializeFunc exchange.GraphSerializeFunc + cat = graph.StringToPathCategory(category) + ) + + switch cat { + case path.EmailCategory, path.EventsCategory, path.ContactsCategory: + get, serializeFunc = exchange.GetQueryAndSerializeFunc(exchange.CategoryToOptionIdentifier(cat)) + default: + return fmt.Errorf("unable to process category: %s", cat) + } + + channel := make(chan data.Stream, 1) + + sw := kw.NewJsonSerializationWriter() + + response, err := get(ctx, gs, user, m365ID) + if err != nil { + return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) + } + + // First return is the number of bytes that were serialized. Ignored + _, err = serializeFunc(ctx, gs.Client(), sw, channel, response, user) + close(channel) + + if err != nil { + return err + } + + for item := range channel { + buf := &bytes.Buffer{} + + _, err := buf.ReadFrom(item.ToReader()) + if err != nil { + return errors.Wrapf(err, "unable to parse given data: %s", m365ID) + } + + byteArray := buf.Bytes() + newValue := string(byteArray) + + err = sw.WriteStringValue("", &newValue) + if err != nil { + return errors.Wrapf(err, "unable to %s to string value", m365ID) + } + + array, err := sw.GetSerializedContent() + if err != nil { + return errors.Wrapf(err, "unable to serialize new value from M365:%s", m365ID) + } + + fmt.Println(string(array)) + + return nil + } + + // This should never happen + return errors.New("m365 object not serialized") +} + +//------------------------------------------------------------------------------- +// Helpers +//------------------------------------------------------------------------------- + +func getGC(ctx context.Context) (*connector.GraphConnector, error) { + // get account info + m365Cfg := account.M365Config{ + M365: credentials.GetM365(), + AzureTenantID: common.First(tenant, os.Getenv(account.AzureTenantID)), + } + + acct, err := account.NewAccount(account.ProviderM365, m365Cfg) + if err != nil { + return nil, Only(ctx, errors.Wrap(err, "finding m365 account details")) + } + + gc, err := connector.NewGraphConnector(ctx, acct) + if err != nil { + return nil, Only(ctx, errors.Wrap(err, "connecting to graph API")) + } + + return gc, nil +} diff --git a/src/internal/connector/exchange/query_options.go b/src/internal/connector/exchange/query_options.go index b6fcad0f7..d3f6e93d1 100644 --- a/src/internal/connector/exchange/query_options.go +++ b/src/internal/connector/exchange/query_options.go @@ -105,7 +105,7 @@ const ( contacts ) -func categoryToOptionIdentifier(category path.CategoryType) optionIdentifier { +func CategoryToOptionIdentifier(category path.CategoryType) optionIdentifier { switch category { case path.EmailCategory: return messages diff --git a/src/internal/connector/exchange/service_iterators.go b/src/internal/connector/exchange/service_iterators.go index dbd29a948..727e5ff03 100644 --- a/src/internal/connector/exchange/service_iterators.go +++ b/src/internal/connector/exchange/service_iterators.go @@ -29,7 +29,7 @@ func FilterContainersAndFillCollections( ) error { var ( category = graph.ScopeToPathCategory(qp.Scope) - collectionType = categoryToOptionIdentifier(category) + collectionType = CategoryToOptionIdentifier(category) errs error ) diff --git a/src/internal/connector/exchange/service_restore.go b/src/internal/connector/exchange/service_restore.go index 23e90936c..eabf8d3d0 100644 --- a/src/internal/connector/exchange/service_restore.go +++ b/src/internal/connector/exchange/service_restore.go @@ -36,7 +36,7 @@ func RestoreExchangeObject( return nil, fmt.Errorf("restore policy: %s not supported for RestoreExchangeObject", policy) } - setting := categoryToOptionIdentifier(category) + setting := CategoryToOptionIdentifier(category) switch setting { case messages: diff --git a/src/internal/connector/graph/service_helper.go b/src/internal/connector/graph/service_helper.go index 32c5b05e5..f11ee1265 100644 --- a/src/internal/connector/graph/service_helper.go +++ b/src/internal/connector/graph/service_helper.go @@ -5,6 +5,7 @@ import ( nethttp "net/http" "net/http/httputil" "os" + "strings" "time" az "github.com/Azure/azure-sdk-for-go/sdk/azidentity" @@ -85,3 +86,20 @@ func ScopeToPathCategory(scope selectors.ExchangeScope) path.CategoryType { return path.UnknownCategory } + +func StringToPathCategory(input string) path.CategoryType { + param := strings.ToLower(input) + + switch param { + case "email": + return path.EmailCategory + case "contacts": + return path.ContactsCategory + case "events": + return path.EventsCategory + case "files": + return path.FilesCategory + default: + return path.UnknownCategory + } +}