introduce id-name lookup maps (#2955)

Adds two maps to resource-owner handling:
id-to-name and name-to-id.  Expectation is that
these maps will either get populated by a caller
as a pre-process before initializing the gc client, or
gc will (later pr) be able to look up the owner and
populate those maps itself.  The maps are
used to set the selector id and name for iface
compliance.  Only supported by exchange in this PR.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🌻 Feature

#### Issue(s)

* #2825

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-04-04 14:21:58 -06:00 committed by GitHub
parent fbb867a08d
commit 2341d61842
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 350 additions and 68 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/alcionai/corso/src/cli/options"
. "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/data"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/backup"
@ -194,6 +195,7 @@ func runBackups(
r repository.Repository,
serviceName, resourceOwnerType string,
selectorSet []selectors.Selector,
ins common.IDNameSwapper,
) error {
var (
bIDs []string
@ -203,21 +205,21 @@ func runBackups(
for _, discSel := range selectorSet {
var (
owner = discSel.DiscreteOwner
bctx = clues.Add(ctx, "resource_owner", owner)
ictx = clues.Add(ctx, "resource_owner", owner)
)
bo, err := r.NewBackup(bctx, discSel)
bo, err := r.NewBackup(ictx, discSel, ins)
if err != nil {
errs = append(errs, clues.Wrap(err, owner).WithClues(bctx))
Errf(bctx, "%v\n", err)
errs = append(errs, clues.Wrap(err, owner).WithClues(ictx))
Errf(ictx, "%v\n", err)
continue
}
err = bo.Run(bctx)
err = bo.Run(ictx)
if err != nil {
errs = append(errs, clues.Wrap(err, owner).WithClues(bctx))
Errf(bctx, "%v\n", err)
errs = append(errs, clues.Wrap(err, owner).WithClues(ictx))
Errf(ictx, "%v\n", err)
continue
}

View File

@ -164,14 +164,14 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
// TODO: log/print recoverable errors
errs := fault.New(false)
users, err := m365.UserPNs(ctx, *acct, errs)
ins, err := m365.UsersMap(ctx, *acct, errs)
if err != nil {
return Only(ctx, clues.Wrap(err, "Failed to retrieve M365 user(s)"))
}
selectorSet := []selectors.Selector{}
for _, discSel := range sel.SplitByResourceOwner(users) {
for _, discSel := range sel.SplitByResourceOwner(ins.IDs()) {
selectorSet = append(selectorSet, discSel.Selector)
}
@ -180,7 +180,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
r,
"Exchange", "user",
selectorSet,
)
ins)
}
func exchangeBackupCreateSelectors(userIDs, cats []string) *selectors.ExchangeBackup {

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/cli/config"
"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/exchange"
"github.com/alcionai/corso/src/internal/operations"
"github.com/alcionai/corso/src/internal/tester"
@ -300,7 +301,15 @@ func (suite *PreparedBackupExchangeE2ESuite) SetupSuite() {
suite.backupOps = make(map[path.CategoryType]string)
users := []string{suite.m365UserID}
var (
users = []string{suite.m365UserID}
idToName = map[string]string{suite.m365UserID: "todo-name-" + suite.m365UserID}
nameToID = map[string]string{"todo-name-" + suite.m365UserID: suite.m365UserID}
ins = common.IDsNames{
IDToName: idToName,
NameToID: nameToID,
}
)
for _, set := range backupDataSets {
var (
@ -321,7 +330,7 @@ func (suite *PreparedBackupExchangeE2ESuite) SetupSuite() {
sel.Include(scopes)
bop, err := suite.repo.NewBackup(ctx, sel.Selector)
bop, err := suite.repo.NewBackup(ctx, sel.Selector, ins)
require.NoError(t, err, clues.ToCore(err))
err = bop.Run(ctx)
@ -546,7 +555,7 @@ func (suite *BackupDeleteExchangeE2ESuite) SetupSuite() {
sel := selectors.NewExchangeBackup(users)
sel.Include(sel.MailFolders([]string{exchange.DefaultMailFolder}, selectors.PrefixMatch()))
suite.backupOp, err = suite.repo.NewBackup(ctx, sel.Selector)
suite.backupOp, err = suite.repo.NewBackup(ctx, sel.Selector, nil)
require.NoError(t, err, clues.ToCore(err))
err = suite.backupOp.Run(ctx)

View File

@ -148,14 +148,14 @@ func createOneDriveCmd(cmd *cobra.Command, args []string) error {
// TODO: log/print recoverable errors
errs := fault.New(false)
users, err := m365.UserPNs(ctx, *acct, errs)
ins, err := m365.UsersMap(ctx, *acct, errs)
if err != nil {
return Only(ctx, clues.Wrap(err, "Failed to retrieve M365 users"))
}
selectorSet := []selectors.Selector{}
for _, discSel := range sel.SplitByResourceOwner(users) {
for _, discSel := range sel.SplitByResourceOwner(ins.IDs()) {
selectorSet = append(selectorSet, discSel.Selector)
}
@ -164,7 +164,7 @@ func createOneDriveCmd(cmd *cobra.Command, args []string) error {
r,
"OneDrive", "user",
selectorSet,
)
ins)
}
func validateOneDriveBackupCreateFlags(users []string) error {

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/cli/config"
"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/operations"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account"
@ -205,14 +206,22 @@ func (suite *BackupDeleteOneDriveE2ESuite) SetupSuite() {
})
require.NoError(t, err, clues.ToCore(err))
m365UserID := tester.M365UserID(t)
users := []string{m365UserID}
var (
m365UserID = tester.M365UserID(t)
users = []string{m365UserID}
idToName = map[string]string{m365UserID: "todo-name-" + m365UserID}
nameToID = map[string]string{"todo-name-" + m365UserID: m365UserID}
ins = common.IDsNames{
IDToName: idToName,
NameToID: nameToID,
}
)
// some tests require an existing backup
sel := selectors.NewOneDriveBackup(users)
sel.Include(sel.Folders(selectors.Any()))
suite.backupOp, err = suite.repo.NewBackup(ctx, sel.Selector)
suite.backupOp, err = suite.repo.NewBackup(ctx, sel.Selector, ins)
require.NoError(t, err, clues.ToCore(err))
err = suite.backupOp.Run(ctx)

View File

@ -154,6 +154,7 @@ func createSharePointCmd(cmd *cobra.Command, args []string) error {
// TODO: log/print recoverable errors
errs := fault.New(false)
// TODO: discovery of sharepoint sites instead of early GC construction.
gc, err := connector.NewGraphConnector(ctx, graph.HTTPClient(graph.NoTimeout()), *acct, connector.Sites, errs)
if err != nil {
return Only(ctx, clues.Wrap(err, "Failed to connect to Microsoft APIs"))
@ -175,7 +176,7 @@ func createSharePointCmd(cmd *cobra.Command, args []string) error {
r,
"SharePoint", "site",
selectorSet,
)
nil) // TODO: prepopulate ids,names
}
func validateSharePointBackupCreateFlags(sites, weburls, cats []string) error {

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/cli/config"
"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/operations"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account"
@ -157,14 +158,22 @@ func (suite *BackupDeleteSharePointE2ESuite) SetupSuite() {
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st, control.Options{})
require.NoError(t, err, clues.ToCore(err))
m365SiteID := tester.M365SiteID(t)
sites := []string{m365SiteID}
var (
m365SiteID = tester.M365SiteID(t)
sites = []string{m365SiteID}
idToName = map[string]string{m365SiteID: "todo-name-" + m365SiteID}
nameToID = map[string]string{"todo-name-" + m365SiteID: m365SiteID}
ins = common.IDsNames{
IDToName: idToName,
NameToID: nameToID,
}
)
// some tests require an existing backup
sel := selectors.NewSharePointBackup(sites)
sel.Include(testdata.SharePointBackupFolderScope(sel))
suite.backupOp, err = suite.repo.NewBackup(ctx, sel.Selector)
suite.backupOp, err = suite.repo.NewBackup(ctx, sel.Selector, ins)
require.NoError(t, err, clues.ToCore(err))
err = suite.backupOp.Run(ctx)

View File

@ -12,6 +12,7 @@ import (
"github.com/alcionai/corso/src/cli"
"github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/connector/exchange"
"github.com/alcionai/corso/src/internal/operations"
"github.com/alcionai/corso/src/internal/tester"
@ -73,7 +74,16 @@ func (suite *RestoreExchangeE2ESuite) SetupSuite() {
suite.vpr, suite.cfgFP = tester.MakeTempTestConfigClone(t, force)
suite.m365UserID = tester.M365UserID(t)
users := []string{suite.m365UserID}
var (
users = []string{suite.m365UserID}
idToName = map[string]string{suite.m365UserID: "todo-name-" + suite.m365UserID}
nameToID = map[string]string{"todo-name-" + suite.m365UserID: suite.m365UserID}
ins = common.IDsNames{
IDToName: idToName,
NameToID: nameToID,
}
)
// init the repo first
suite.repo, err = repository.Initialize(ctx, suite.acct, suite.st, control.Options{})
@ -100,7 +110,7 @@ func (suite *RestoreExchangeE2ESuite) SetupSuite() {
sel.Include(scopes)
bop, err := suite.repo.NewBackup(ctx, sel.Selector)
bop, err := suite.repo.NewBackup(ctx, sel.Selector, ins)
require.NoError(t, err, clues.ToCore(err))
err = bop.Run(ctx)

View File

@ -126,12 +126,12 @@ func getGCAndVerifyUser(ctx context.Context, userID string) (*connector.GraphCon
errs := fault.New(false)
normUsers := map[string]struct{}{}
users, err := m365.UserPNs(ctx, acct, errs)
ins, err := m365.UsersMap(ctx, acct, errs)
if err != nil {
return nil, account.Account{}, clues.Wrap(err, "getting tenant users")
}
for _, k := range users {
for _, k := range ins.IDs() {
normUsers[strings.ToLower(k)] = struct{}{}
}

View File

@ -1,5 +1,7 @@
package common
import "golang.org/x/exp/maps"
type IDNamer interface {
// the canonical id of the thing, generated and usable
// by whichever system has ownership of it.
@ -7,3 +9,39 @@ type IDNamer interface {
// the human-readable name of the thing.
Name() string
}
type IDNameSwapper interface {
IDOf(name string) (string, bool)
NameOf(id string) (string, bool)
IDs() []string
Names() []string
}
var _ IDNameSwapper = &IDsNames{}
type IDsNames struct {
IDToName map[string]string
NameToID map[string]string
}
// IDOf returns the id associated with the given name.
func (in IDsNames) IDOf(name string) (string, bool) {
id, ok := in.NameToID[name]
return id, ok
}
// NameOf returns the name associated with the given id.
func (in IDsNames) NameOf(id string) (string, bool) {
name, ok := in.IDToName[id]
return name, ok
}
// IDs returns all known ids.
func (in IDsNames) IDs() []string {
return maps.Keys(in.IDToName)
}
// Names returns all known names.
func (in IDsNames) Names() []string {
return maps.Keys(in.NameToID)
}

View File

@ -17,6 +17,7 @@ import (
"github.com/pkg/errors"
"golang.org/x/exp/maps"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/discovery/api"
"github.com/alcionai/corso/src/internal/connector/graph"
@ -49,9 +50,16 @@ type GraphConnector struct {
itemClient *http.Client // configured to handle large item downloads
tenant string
Sites map[string]string // webURL -> siteID and siteID -> webURL
credentials account.M365Config
// TODO: remove in favor of the maps below.
Sites map[string]string // webURL -> siteID and siteID -> webURL
// lookup for resource owner ids to names, and names to ids.
// not guaranteed to be populated, only here as a post-population
// reference for processes that choose to populate the values.
IDNameLookup common.IDNameSwapper
// wg is used to track completion of GC tasks
wg *sync.WaitGroup
region *trace.Region
@ -108,6 +116,63 @@ func NewGraphConnector(
return &gc, nil
}
// PopulateOwnerIDAndNamesFrom takes the provided owner identifier and produces
// the owner's name and ID from that value. Returns an error if the owner is
// not recognized by the current tenant.
//
// The id-name swapper is optional. Some processes will look up all owners in
// the tenant before reaching this step. In that case, the data gets handed
// down for this func to consume instead of performing further queries. The
// maps get stored inside the gc instance for later re-use.
//
// TODO: If the maps are nil or empty, this func will perform a lookup on the given
// owner, and populate each map with that owner's id and name for downstream
// guarantees about that data being present. Optional performance enhancement
// idea: downstream from here, we should _only_ need the given user's id and name,
// and could store minimal map copies with that info instead of the whole tenant.
func (gc *GraphConnector) PopulateOwnerIDAndNamesFrom(
owner string, // input value, can be either id or name
ins common.IDNameSwapper,
) (string, string, error) {
// move this to GC method
id, name, err := getOwnerIDAndNameFrom(owner, ins)
if err != nil {
return "", "", errors.Wrap(err, "resolving resource owner details")
}
gc.IDNameLookup = ins
if ins == nil || (len(ins.IDs()) == 0 && len(ins.Names()) == 0) {
gc.IDNameLookup = common.IDsNames{
IDToName: map[string]string{id: name},
NameToID: map[string]string{name: id},
}
}
return id, name, nil
}
func getOwnerIDAndNameFrom(
owner string,
ins common.IDNameSwapper,
) (string, string, error) {
if ins == nil {
return owner, owner, nil
}
if n, ok := ins.NameOf(owner); ok {
return owner, n, nil
} else if i, ok := ins.IDOf(owner); ok {
return i, owner, nil
}
// TODO: look-up user by owner, either id or name,
// and populate with maps as a result. Only
// return owner, owner as a very last resort.
return owner, owner, nil
}
// createService constructor for graphService component
func (gc *GraphConnector) createService() (*graph.Service, error) {
adapter, err := graph.CreateAdapter(

View File

@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/suite"
"golang.org/x/exp/maps"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/mockconnector"
"github.com/alcionai/corso/src/internal/connector/support"
@ -123,11 +124,11 @@ func (suite *GraphConnectorUnitSuite) TestUnionSiteIDsAndWebURLs() {
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext()
defer flush()
t := suite.T()
result, err := gc.UnionSiteIDsAndWebURLs(ctx, test.ids, test.urls, fault.New(true))
assert.NoError(t, err, clues.ToCore(err))
assert.ElementsMatch(t, test.expect, result)
@ -135,6 +136,126 @@ func (suite *GraphConnectorUnitSuite) TestUnionSiteIDsAndWebURLs() {
}
}
func (suite *GraphConnectorUnitSuite) TestPopulateOwnerIDAndNamesFrom() {
const (
ownerID = "owner-id"
ownerName = "owner-name"
)
var (
itn = map[string]string{ownerID: ownerName}
nti = map[string]string{ownerName: ownerID}
)
table := []struct {
name string
owner string
ins common.IDsNames
expectID string
expectName string
}{
{
name: "nil ins",
owner: ownerID,
expectID: ownerID,
expectName: ownerID,
},
{
name: "only id map with owner id",
owner: ownerID,
ins: common.IDsNames{
IDToName: itn,
NameToID: nil,
},
expectID: ownerID,
expectName: ownerName,
},
{
name: "only name map with owner id",
owner: ownerID,
ins: common.IDsNames{
IDToName: nil,
NameToID: nti,
},
expectID: ownerID,
expectName: ownerID,
},
{
name: "only id map with owner name",
owner: ownerName,
ins: common.IDsNames{
IDToName: itn,
NameToID: nil,
},
expectID: ownerName,
expectName: ownerName,
},
{
name: "only name map with owner name",
owner: ownerName,
ins: common.IDsNames{
IDToName: nil,
NameToID: nti,
},
expectID: ownerID,
expectName: ownerName,
},
{
name: "both maps with owner id",
owner: ownerID,
ins: common.IDsNames{
IDToName: itn,
NameToID: nti,
},
expectID: ownerID,
expectName: ownerName,
},
{
name: "both maps with owner name",
owner: ownerName,
ins: common.IDsNames{
IDToName: itn,
NameToID: nti,
},
expectID: ownerID,
expectName: ownerName,
},
{
name: "non-matching maps with owner id",
owner: ownerID,
ins: common.IDsNames{
IDToName: map[string]string{"foo": "bar"},
NameToID: map[string]string{"fnords": "smarf"},
},
expectID: ownerID,
expectName: ownerID,
},
{
name: "non-matching with owner name",
owner: ownerName,
ins: common.IDsNames{
IDToName: map[string]string{"foo": "bar"},
NameToID: map[string]string{"fnords": "smarf"},
},
expectID: ownerName,
expectName: ownerName,
},
}
for _, test := range table {
suite.Run(test.name, func() {
var (
t = suite.T()
gc = &GraphConnector{}
)
id, name, err := gc.PopulateOwnerIDAndNamesFrom(test.owner, test.ins)
require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, test.expectID, id)
assert.Equal(t, test.expectName, name)
})
}
}
func (suite *GraphConnectorUnitSuite) TestGraphConnector_Wait() {
ctx, flush := tester.NewContext()
defer flush()

View File

@ -110,6 +110,9 @@ func New(
},
},
ResourceOwnerID: ownerID,
ResourceOwnerName: ownerName,
Version: version.Backup,
SnapshotID: snapshotID,
StreamStoreID: streamStoreID,
@ -242,10 +245,20 @@ func (b Backup) Values() []string {
status += (")")
}
name := b.ResourceOwnerName
if len(name) == 0 {
name = b.ResourceOwnerID
}
if len(name) == 0 {
name = b.Selector.DiscreteOwner
}
return []string{
common.FormatTabularDisplayTime(b.StartedAt),
string(b.ID),
status,
b.Selector.DiscreteOwner,
name,
}
}

View File

@ -24,7 +24,7 @@ func TestBackupUnitSuite(t *testing.T) {
suite.Run(t, &BackupUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func stubBackup(t time.Time) backup.Backup {
func stubBackup(t time.Time, ownerID, ownerName string) backup.Backup {
sel := selectors.NewExchangeBackup([]string{"test"})
sel.Include(sel.AllData())
@ -63,7 +63,7 @@ func (suite *BackupUnitSuite) TestBackup_HeadersValues() {
var (
t = suite.T()
now = time.Now()
b = stubBackup(now)
b = stubBackup(now, "id", "name")
expectHs = []string{
"Started At",
"ID",
@ -190,7 +190,7 @@ func (suite *BackupUnitSuite) TestBackup_Values_statusVariations() {
func (suite *BackupUnitSuite) TestBackup_MinimumPrintable() {
t := suite.T()
now := time.Now()
b := stubBackup(now)
b := stubBackup(now, "id", "name")
resultIface := b.MinimumPrintable()
result, ok := resultIface.(backup.Printable)

View File

@ -120,7 +120,7 @@ func runLoadTest(
) {
//revive:enable:context-as-argument
t.Run(prefix+"_load_test_main", func(t *testing.T) {
b, err := r.NewBackup(ctx, bupSel)
b, err := r.NewBackup(ctx, bupSel, nil)
require.NoError(t, err, clues.ToCore(err))
runBackupLoadTest(t, ctx, &b, service, usersUnderTest)
@ -447,8 +447,7 @@ func (suite *LoadExchangeSuite) TestExchange() {
"all_users", "exchange",
suite.usersUnderTest,
sel, sel, // same selection for backup and restore
true,
)
true)
}
// single user, lots of data
@ -500,8 +499,7 @@ func (suite *IndividualLoadExchangeSuite) TestExchange() {
"single_user", "exchange",
suite.usersUnderTest,
sel, sel, // same selection for backup and restore
true,
)
true)
}
// ------------------------------------------------------------------------------------------------
@ -553,8 +551,7 @@ func (suite *LoadOneDriveSuite) TestOneDrive() {
"all_users", "one_drive",
suite.usersUnderTest,
sel, sel, // same selection for backup and restore
false,
)
false)
}
type IndividualLoadOneDriveSuite struct {
@ -601,8 +598,7 @@ func (suite *IndividualLoadOneDriveSuite) TestOneDrive() {
"single_user", "one_drive",
suite.usersUnderTest,
sel, sel, // same selection for backup and restore
false,
)
false)
}
// ------------------------------------------------------------------------------------------------
@ -654,8 +650,7 @@ func (suite *LoadSharePointSuite) TestSharePoint() {
"all_sites", "share_point",
suite.sitesUnderTest,
sel, sel, // same selection for backup and restore
false,
)
false)
}
type IndividualLoadSharePointSuite struct {
@ -703,6 +698,5 @@ func (suite *IndividualLoadSharePointSuite) TestSharePoint() {
"single_site", "share_point",
suite.sitesUnderTest,
sel, sel, // same selection for backup and restore
false,
)
false)
}

View File

@ -8,6 +8,7 @@ import (
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/common/crash"
"github.com/alcionai/corso/src/internal/connector"
"github.com/alcionai/corso/src/internal/connector/graph"
@ -58,6 +59,7 @@ type Repository interface {
NewBackup(
ctx context.Context,
self selectors.Selector,
ins common.IDNameSwapper,
) (operations.BackupOperation, error)
NewRestore(
ctx context.Context,
@ -287,17 +289,25 @@ func (r *repository) Close(ctx context.Context) error {
}
// NewBackup generates a BackupOperation runner.
// ownerIDToName and ownerNameToID are optional populations, in case the caller has
// already generated those values.
func (r repository) NewBackup(
ctx context.Context,
sel selectors.Selector,
ins common.IDNameSwapper,
) (operations.BackupOperation, error) {
gc, err := connectToM365(ctx, sel, r.Account, fault.New(true))
if err != nil {
return operations.BackupOperation{}, errors.Wrap(err, "connecting to m365")
}
ownerID, ownerName, err := gc.PopulateOwnerIDAndNamesFrom(sel.DiscreteOwner, ins)
if err != nil {
return operations.BackupOperation{}, errors.Wrap(err, "resolving resource owner details")
}
// TODO: retrieve display name from gc
sel = sel.SetDiscreteOwnerIDName(sel.DiscreteOwner, "")
sel = sel.SetDiscreteOwnerIDName(ownerID, ownerName)
return operations.NewBackupOperation(
ctx,

View File

@ -198,7 +198,7 @@ func (suite *RepositoryIntegrationSuite) TestNewBackup() {
r, err := repository.Initialize(ctx, acct, st, control.Options{})
require.NoError(t, err, clues.ToCore(err))
bo, err := r.NewBackup(ctx, selectors.Selector{DiscreteOwner: "test"})
bo, err := r.NewBackup(ctx, selectors.Selector{DiscreteOwner: "test"}, nil)
require.NoError(t, err, clues.ToCore(err))
require.NotNil(t, bo)
}

View File

@ -6,6 +6,7 @@ import (
"github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector"
"github.com/alcionai/corso/src/internal/connector/discovery"
@ -56,34 +57,34 @@ func Users(ctx context.Context, acct account.Account, errs *fault.Bus) ([]*User,
return ret, nil
}
func UserIDs(ctx context.Context, acct account.Account, errs *fault.Bus) ([]string, error) {
// UsersMap retrieves all users in the tenant, and returns two maps: one id-to-principalName,
// and one principalName-to-id.
func UsersMap(
ctx context.Context,
acct account.Account,
errs *fault.Bus,
) (common.IDsNames, error) {
users, err := Users(ctx, acct, errs)
if err != nil {
return nil, err
return common.IDsNames{}, err
}
ret := make([]string, 0, len(users))
var (
idToName = make(map[string]string, len(users))
nameToID = make(map[string]string, len(users))
)
for _, u := range users {
ret = append(ret, u.ID)
idToName[u.ID] = u.PrincipalName
nameToID[u.PrincipalName] = u.ID
}
return ret, nil
ins := common.IDsNames{
IDToName: idToName,
NameToID: nameToID,
}
// UserPNs retrieves all user principleNames in the tenant. Principle Names
// can be used analogous userIDs in graph API queries.
func UserPNs(ctx context.Context, acct account.Account, errs *fault.Bus) ([]string, error) {
users, err := Users(ctx, acct, errs)
if err != nil {
return nil, err
}
ret := make([]string, 0, len(users))
for _, u := range users {
ret = append(ret, u.PrincipalName)
}
return ret, nil
return ins, nil
}
type Site struct {