add teamschats restore shim for export support
This commit is contained in:
parent
b9f71280bf
commit
a7679554ab
100
src/internal/m365/service/teamschats/restore.go
Normal file
100
src/internal/m365/service/teamschats/restore.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package teamschats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConsumeRestoreCollections will restore the specified data collections
|
||||||
|
func (h *teamsChatsHandler) ConsumeRestoreCollections(
|
||||||
|
ctx context.Context,
|
||||||
|
rcc inject.RestoreConsumerConfig,
|
||||||
|
dcs []data.RestoreCollection,
|
||||||
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
|
) (*details.Details, *data.CollectionStats, error) {
|
||||||
|
if len(dcs) == 0 {
|
||||||
|
return nil, nil, clues.WrapWC(ctx, data.ErrNoData, "performing restore")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(ashmrtn): We should stop relying on the context for rate limiter stuff
|
||||||
|
// and instead configure this when we make the handler instance. We can't
|
||||||
|
// initialize it in the NewHandler call right now because those functions
|
||||||
|
// aren't (and shouldn't be) returning a context along with the handler. Since
|
||||||
|
// that call isn't directly calling into this function even if we did
|
||||||
|
// initialize the rate limiter there it would be lost because it wouldn't get
|
||||||
|
// stored in an ancestor of the context passed to this function.
|
||||||
|
ctx = graph.BindRateLimiterConfig(
|
||||||
|
ctx,
|
||||||
|
graph.LimiterCfg{Service: path.TeamsChatsService})
|
||||||
|
|
||||||
|
var (
|
||||||
|
deets = &details.Builder{}
|
||||||
|
restoreMetrics support.CollectionMetrics
|
||||||
|
el = errs.Local()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reorder collections so that the parents directories are created
|
||||||
|
// before the child directories; a requirement for permissions.
|
||||||
|
data.SortRestoreCollections(dcs)
|
||||||
|
|
||||||
|
// Iterate through the data collections and restore the contents of each
|
||||||
|
for _, dc := range dcs {
|
||||||
|
if el.Failure() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
category = dc.FullPath().Category()
|
||||||
|
metrics support.CollectionMetrics
|
||||||
|
ictx = clues.Add(ctx,
|
||||||
|
"category", category,
|
||||||
|
"restore_location", clues.Hide(rcc.RestoreConfig.Location),
|
||||||
|
"protected_resource", clues.Hide(dc.FullPath().ProtectedResource()),
|
||||||
|
"full_path", dc.FullPath())
|
||||||
|
)
|
||||||
|
|
||||||
|
switch dc.FullPath().Category() {
|
||||||
|
case path.ChatsCategory:
|
||||||
|
// chats cannot be restored using Graph API.
|
||||||
|
// a delegated token is required, and Corso has no
|
||||||
|
// good way of obtaining such a token.
|
||||||
|
logger.Ctx(ictx).Debug("Skipping restore for channel messages")
|
||||||
|
default:
|
||||||
|
return nil, nil, clues.NewWC(ictx, "data category not supported").
|
||||||
|
With("category", category)
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreMetrics = support.CombineMetrics(restoreMetrics, metrics)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
el.AddRecoverable(ictx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status := support.CreateStatus(
|
||||||
|
ctx,
|
||||||
|
support.Restore,
|
||||||
|
len(dcs),
|
||||||
|
restoreMetrics,
|
||||||
|
rcc.RestoreConfig.Location)
|
||||||
|
|
||||||
|
return deets.Details(), status.ToCollectionStats(), el.Failure()
|
||||||
|
}
|
||||||
54
src/internal/m365/service/teamschats/restore_test.go
Normal file
54
src/internal/m365/service/teamschats/restore_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package teamschats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
|
"github.com/alcionai/corso/src/internal/data/mock"
|
||||||
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RestoreUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestoreUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &RestoreUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreUnitSuite) TestConsumeRestoreCollections_noErrorOnChats() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
rcc := inject.RestoreConsumerConfig{}
|
||||||
|
pth, err := path.BuildPrefix(
|
||||||
|
"t",
|
||||||
|
"pr",
|
||||||
|
path.TeamsChatsService,
|
||||||
|
path.ChatsCategory)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
dcs := []data.RestoreCollection{
|
||||||
|
mock.Collection{Path: pth},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = NewTeamsChatsHandler(api.Client{}, nil).
|
||||||
|
ConsumeRestoreCollections(
|
||||||
|
ctx,
|
||||||
|
rcc,
|
||||||
|
dcs,
|
||||||
|
fault.New(false),
|
||||||
|
nil)
|
||||||
|
assert.NoError(t, err, "Chats restore")
|
||||||
|
}
|
||||||
@ -142,6 +142,7 @@ func makeRestorePathsForEntry(
|
|||||||
// * OneDrive/SharePoint (needs drive information)
|
// * OneDrive/SharePoint (needs drive information)
|
||||||
switch true {
|
switch true {
|
||||||
case ent.Exchange != nil ||
|
case ent.Exchange != nil ||
|
||||||
|
ent.TeamsChats != nil ||
|
||||||
(ent.Groups != nil && ent.Groups.ItemType == details.GroupsChannelMessage) ||
|
(ent.Groups != nil && ent.Groups.ItemType == details.GroupsChannelMessage) ||
|
||||||
(ent.SharePoint != nil && ent.SharePoint.ItemType == details.SharePointList):
|
(ent.SharePoint != nil && ent.SharePoint.ItemType == details.SharePointList):
|
||||||
// TODO(ashmrtn): Eventually make Events have it's own function to handle
|
// TODO(ashmrtn): Eventually make Events have it's own function to handle
|
||||||
|
|||||||
@ -399,6 +399,30 @@ func (suite *RestorePathTransformerUnitSuite) TestGetPaths() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "TeamsChats Chats",
|
||||||
|
backupVersion: version.Groups9Update,
|
||||||
|
input: []*details.Entry{
|
||||||
|
{
|
||||||
|
RepoRef: testdata.ExchangeEmailItemPath3.RR.String(),
|
||||||
|
LocationRef: testdata.ExchangeEmailItemPath3.Loc.String(),
|
||||||
|
ItemInfo: details.ItemInfo{
|
||||||
|
Exchange: &details.ExchangeInfo{
|
||||||
|
ItemType: details.ExchangeMail,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
expected: []expectPaths{
|
||||||
|
{
|
||||||
|
storage: testdata.ExchangeEmailItemPath3.RR.String(),
|
||||||
|
restore: toRestore(
|
||||||
|
testdata.ExchangeEmailItemPath3.RR,
|
||||||
|
testdata.ExchangeEmailItemPath3.Loc.Elements()...),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user