Wrapper for Collections to make them RestoreCollections (#2431)
## Description Create a wrapper struct that provides a `Fetch(ctx, name) (Stream, error)` function that always returns `ErrNotFound`. A future PR is going to expand the `RestoreCollection` interface to include that function and I wanted to reduce the amount of chaff that would come out of it This PR just creates the wrapper and moves `ErrNotFound` from the kopia package to `data` package to avoid import cycles ## Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [x] ⛔ No ## Type of change - [x] 🌻 Feature - [ ] 🐛 Bugfix - [ ] 🗺️ Documentation - [x] 🤖 Test - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup ## Issue(s) * #1944 ## Test Plan - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
3d244c9fea
commit
c63aa94204
@ -12,7 +12,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/kopia"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/model"
|
||||
"github.com/alcionai/corso/src/pkg/backup"
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
@ -323,16 +323,16 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func exchangeBackupCreateSelectors(userIDs, data []string) *selectors.ExchangeBackup {
|
||||
func exchangeBackupCreateSelectors(userIDs, cats []string) *selectors.ExchangeBackup {
|
||||
sel := selectors.NewExchangeBackup(userIDs)
|
||||
|
||||
if len(data) == 0 {
|
||||
if len(cats) == 0 {
|
||||
sel.Include(sel.ContactFolders(selectors.Any()))
|
||||
sel.Include(sel.MailFolders(selectors.Any()))
|
||||
sel.Include(sel.EventCalendars(selectors.Any()))
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
for _, d := range cats {
|
||||
switch d {
|
||||
case dataContacts:
|
||||
sel.Include(sel.ContactFolders(selectors.Any()))
|
||||
@ -346,12 +346,12 @@ func exchangeBackupCreateSelectors(userIDs, data []string) *selectors.ExchangeBa
|
||||
return sel
|
||||
}
|
||||
|
||||
func validateExchangeBackupCreateFlags(userIDs, data []string) error {
|
||||
func validateExchangeBackupCreateFlags(userIDs, cats []string) error {
|
||||
if len(userIDs) == 0 {
|
||||
return errors.New("--user requires one or more email addresses or the wildcard '*'")
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
for _, d := range cats {
|
||||
if d != dataContacts && d != dataEmail && d != dataEvents {
|
||||
return errors.New(
|
||||
d + " is an unrecognized data type; must be one of " + dataContacts + ", " + dataEmail + ", or " + dataEvents)
|
||||
@ -394,7 +394,7 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(backupID) > 0 {
|
||||
b, err := r.Backup(ctx, model.StableID(backupID))
|
||||
if err != nil {
|
||||
if errors.Is(err, kopia.ErrNotFound) {
|
||||
if errors.Is(err, data.ErrNotFound) {
|
||||
return Only(ctx, errors.Errorf("No backup exists with the id %s", backupID))
|
||||
}
|
||||
|
||||
@ -502,7 +502,7 @@ func runDetailsExchangeCmd(
|
||||
d, _, errs := r.BackupDetails(ctx, backupID)
|
||||
// TODO: log/track recoverable errors
|
||||
if errs.Err() != nil {
|
||||
if errors.Is(errs.Err(), kopia.ErrNotFound) {
|
||||
if errors.Is(errs.Err(), data.ErrNotFound) {
|
||||
return nil, errors.Errorf("No backup exists with the id %s", backupID)
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,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/kopia"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/model"
|
||||
"github.com/alcionai/corso/src/pkg/backup"
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
@ -294,7 +294,7 @@ func listOneDriveCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(backupID) > 0 {
|
||||
b, err := r.Backup(ctx, model.StableID(backupID))
|
||||
if err != nil {
|
||||
if errors.Is(err, kopia.ErrNotFound) {
|
||||
if errors.Is(err, data.ErrNotFound) {
|
||||
return Only(ctx, errors.Errorf("No backup exists with the id %s", backupID))
|
||||
}
|
||||
|
||||
@ -394,7 +394,7 @@ func runDetailsOneDriveCmd(
|
||||
d, _, errs := r.BackupDetails(ctx, backupID)
|
||||
// TODO: log/track recoverable errors
|
||||
if errs.Err() != nil {
|
||||
if errors.Is(errs.Err(), kopia.ErrNotFound) {
|
||||
if errors.Is(errs.Err(), data.ErrNotFound) {
|
||||
return nil, errors.Errorf("no backup exists with the id %s", backupID)
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"github.com/alcionai/corso/src/cli/utils"
|
||||
"github.com/alcionai/corso/src/internal/connector"
|
||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||
"github.com/alcionai/corso/src/internal/kopia"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/model"
|
||||
"github.com/alcionai/corso/src/pkg/backup"
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
@ -266,7 +266,7 @@ func createSharePointCmd(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSharePointBackupCreateFlags(sites, weburls, data []string) error {
|
||||
func validateSharePointBackupCreateFlags(sites, weburls, cats []string) error {
|
||||
if len(sites) == 0 && len(weburls) == 0 {
|
||||
return errors.New(
|
||||
"requires one or more --" +
|
||||
@ -276,7 +276,7 @@ func validateSharePointBackupCreateFlags(sites, weburls, data []string) error {
|
||||
)
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
for _, d := range cats {
|
||||
if d != dataLibraries && d != dataPages {
|
||||
return errors.New(
|
||||
d + " is an unrecognized data type; either " + dataLibraries + "or " + dataPages,
|
||||
@ -290,7 +290,7 @@ func validateSharePointBackupCreateFlags(sites, weburls, data []string) error {
|
||||
// TODO: users might specify a data type, this only supports AllData().
|
||||
func sharePointBackupCreateSelectors(
|
||||
ctx context.Context,
|
||||
sites, weburls, data []string,
|
||||
sites, weburls, cats []string,
|
||||
gc *connector.GraphConnector,
|
||||
) (*selectors.SharePointBackup, error) {
|
||||
if len(sites) == 0 && len(weburls) == 0 {
|
||||
@ -321,13 +321,13 @@ func sharePointBackupCreateSelectors(
|
||||
}
|
||||
|
||||
sel := selectors.NewSharePointBackup(union)
|
||||
if len(data) == 0 {
|
||||
if len(cats) == 0 {
|
||||
sel.Include(sel.AllData())
|
||||
|
||||
return sel, nil
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
for _, d := range cats {
|
||||
switch d {
|
||||
case dataLibraries:
|
||||
sel.Include(sel.Libraries(selectors.Any()))
|
||||
@ -372,7 +372,7 @@ func listSharePointCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(backupID) > 0 {
|
||||
b, err := r.Backup(ctx, model.StableID(backupID))
|
||||
if err != nil {
|
||||
if errors.Is(err, kopia.ErrNotFound) {
|
||||
if errors.Is(err, data.ErrNotFound) {
|
||||
return Only(ctx, errors.Errorf("No backup exists with the id %s", backupID))
|
||||
}
|
||||
|
||||
@ -513,7 +513,7 @@ func runDetailsSharePointCmd(
|
||||
d, _, errs := r.BackupDetails(ctx, backupID)
|
||||
// TODO: log/track recoverable errors
|
||||
if errs.Err() != nil {
|
||||
if errors.Is(errs.Err(), kopia.ErrNotFound) {
|
||||
if errors.Is(errs.Err(), data.ErrNotFound) {
|
||||
return nil, errors.Errorf("no backup exists with the id %s", backupID)
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
. "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/kopia"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
"github.com/alcionai/corso/src/pkg/repository"
|
||||
)
|
||||
@ -228,7 +228,7 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
ds, err := ro.Run(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, kopia.ErrNotFound) {
|
||||
if errors.Is(err, data.ErrNotFound) {
|
||||
return Only(ctx, errors.Errorf("Backup or backup details missing for id %s", backupID))
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
. "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/kopia"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
"github.com/alcionai/corso/src/pkg/repository"
|
||||
)
|
||||
@ -171,7 +171,7 @@ func restoreOneDriveCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
ds, err := ro.Run(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, kopia.ErrNotFound) {
|
||||
if errors.Is(err, data.ErrNotFound) {
|
||||
return Only(ctx, errors.Errorf("Backup or backup details missing for id %s", backupID))
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
. "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/kopia"
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/pkg/control"
|
||||
"github.com/alcionai/corso/src/pkg/repository"
|
||||
)
|
||||
@ -166,7 +166,7 @@ func restoreSharePointCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
ds, err := ro.Run(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, kopia.ErrNotFound) {
|
||||
if errors.Is(err, data.ErrNotFound) {
|
||||
return Only(ctx, errors.Errorf("Backup or backup details missing for id %s", backupID))
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
@ -12,6 +14,8 @@ import (
|
||||
// standard ifaces
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
type CollectionState int
|
||||
|
||||
const (
|
||||
@ -66,6 +70,16 @@ type RestoreCollection interface {
|
||||
Collection
|
||||
}
|
||||
|
||||
// NotFoundRestoreCollection is a wrapper for a Collection that returns
|
||||
// ErrNotFound for all Fetch calls.
|
||||
type NotFoundRestoreCollection struct {
|
||||
Collection
|
||||
}
|
||||
|
||||
func (c NotFoundRestoreCollection) Fetch(context.Context, string) (Stream, error) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// Stream represents a single item within a Collection
|
||||
// that can be consumed as a stream (it embeds io.Reader)
|
||||
type Stream interface {
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/model"
|
||||
)
|
||||
|
||||
@ -20,7 +21,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
errNoModelStoreID = errors.New("model has no ModelStoreID")
|
||||
errNoStableID = errors.New("model has no StableID")
|
||||
errBadTagKey = errors.New("tag key overlaps with required key")
|
||||
@ -281,7 +281,7 @@ func (ms *ModelStore) getModelStoreID(
|
||||
}
|
||||
|
||||
if len(metadata) == 0 {
|
||||
return "", errors.Wrap(ErrNotFound, "getting ModelStoreID")
|
||||
return "", errors.Wrap(data.ErrNotFound, "getting ModelStoreID")
|
||||
}
|
||||
|
||||
if len(metadata) != 1 {
|
||||
@ -302,7 +302,7 @@ func (ms *ModelStore) Get(
|
||||
ctx context.Context,
|
||||
s model.Schema,
|
||||
id model.StableID,
|
||||
data model.Model,
|
||||
m model.Model,
|
||||
) error {
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
@ -313,7 +313,7 @@ func (ms *ModelStore) Get(
|
||||
return err
|
||||
}
|
||||
|
||||
return transmuteErr(ms.GetWithModelStoreID(ctx, s, modelID, data))
|
||||
return transmuteErr(ms.GetWithModelStoreID(ctx, s, modelID, m))
|
||||
}
|
||||
|
||||
// GetWithModelStoreID deserializes the model with the given ModelStoreID into
|
||||
@ -323,7 +323,7 @@ func (ms *ModelStore) GetWithModelStoreID(
|
||||
ctx context.Context,
|
||||
s model.Schema,
|
||||
id manifest.ID,
|
||||
data model.Model,
|
||||
m model.Model,
|
||||
) error {
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
@ -333,7 +333,7 @@ func (ms *ModelStore) GetWithModelStoreID(
|
||||
return errors.WithStack(errNoModelStoreID)
|
||||
}
|
||||
|
||||
metadata, err := ms.c.GetManifest(ctx, id, data)
|
||||
metadata, err := ms.c.GetManifest(ctx, id, m)
|
||||
if err != nil {
|
||||
return errors.Wrap(transmuteErr(err), "getting model data")
|
||||
}
|
||||
@ -343,7 +343,7 @@ func (ms *ModelStore) GetWithModelStoreID(
|
||||
}
|
||||
|
||||
return errors.Wrap(
|
||||
ms.populateBaseModelFromMetadata(data.Base(), metadata),
|
||||
ms.populateBaseModelFromMetadata(m.Base(), metadata),
|
||||
"getting model by ID",
|
||||
)
|
||||
}
|
||||
@ -457,7 +457,7 @@ func (ms *ModelStore) Delete(ctx context.Context, s model.Schema, id model.Stabl
|
||||
|
||||
latest, err := ms.getModelStoreID(ctx, s, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
if errors.Is(err, data.ErrNotFound) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -490,7 +490,7 @@ func (ms *ModelStore) DeleteWithModelStoreID(ctx context.Context, id manifest.ID
|
||||
func transmuteErr(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, manifest.ErrNotFound):
|
||||
return ErrNotFound
|
||||
return data.ErrNotFound
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/data"
|
||||
"github.com/alcionai/corso/src/internal/model"
|
||||
"github.com/alcionai/corso/src/internal/tester"
|
||||
"github.com/alcionai/corso/src/pkg/backup"
|
||||
@ -360,9 +361,9 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet_WithTags() {
|
||||
func (suite *ModelStoreIntegrationSuite) TestGet_NotFoundErrors() {
|
||||
t := suite.T()
|
||||
|
||||
assert.ErrorIs(t, suite.m.Get(suite.ctx, model.BackupOpSchema, "baz", nil), ErrNotFound)
|
||||
assert.ErrorIs(t, suite.m.Get(suite.ctx, model.BackupOpSchema, "baz", nil), data.ErrNotFound)
|
||||
assert.ErrorIs(
|
||||
t, suite.m.GetWithModelStoreID(suite.ctx, model.BackupOpSchema, "baz", nil), ErrNotFound)
|
||||
t, suite.m.GetWithModelStoreID(suite.ctx, model.BackupOpSchema, "baz", nil), data.ErrNotFound)
|
||||
}
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestPutGetOfTypeBadVersion() {
|
||||
@ -630,7 +631,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate() {
|
||||
}
|
||||
|
||||
err = m.GetWithModelStoreID(ctx, theModelType, oldModelID, nil)
|
||||
assert.ErrorIs(t, err, ErrNotFound)
|
||||
assert.ErrorIs(t, err, data.ErrNotFound)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -691,7 +692,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutDelete() {
|
||||
|
||||
returned := &fooModel{}
|
||||
err := suite.m.GetWithModelStoreID(suite.ctx, theModelType, foo.ModelStoreID, returned)
|
||||
assert.ErrorIs(t, err, ErrNotFound)
|
||||
assert.ErrorIs(t, err, data.ErrNotFound)
|
||||
}
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestPutDelete_BadIDsNoop() {
|
||||
@ -775,7 +776,7 @@ func (suite *ModelStoreRegressionSuite) TestFailDuringWriteSessionHasNoVisibleEf
|
||||
assert.ErrorIs(t, err, assert.AnError)
|
||||
|
||||
err = m.GetWithModelStoreID(ctx, theModelType, newID, nil)
|
||||
assert.ErrorIs(t, err, ErrNotFound)
|
||||
assert.ErrorIs(t, err, data.ErrNotFound)
|
||||
|
||||
returned := &fooModel{}
|
||||
require.NoError(
|
||||
|
||||
@ -317,7 +317,7 @@ func getItemStream(
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "entry not found") {
|
||||
err = errors.Wrap(ErrNotFound, err.Error())
|
||||
err = errors.Wrap(data.ErrNotFound, err.Error())
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(err, "getting nested object handle")
|
||||
|
||||
@ -500,7 +500,7 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_ReaderError() {
|
||||
// Files that had an error shouldn't make a dir entry in kopia. If they do we
|
||||
// may run into kopia-assisted incrementals issues because only mod time and
|
||||
// not file size is checked for StreamingFiles.
|
||||
assert.ErrorIs(t, err, ErrNotFound, "errored file is restorable")
|
||||
assert.ErrorIs(t, err, data.ErrNotFound, "errored file is restorable")
|
||||
}
|
||||
|
||||
type backedupFile struct {
|
||||
|
||||
@ -97,7 +97,7 @@ func produceManifestsAndMetadata(
|
||||
if err != nil {
|
||||
// if no backup exists for any of the complete manifests, we want
|
||||
// to fall back to a complete backup.
|
||||
if errors.Is(err, kopia.ErrNotFound) {
|
||||
if errors.Is(err, data.ErrNotFound) {
|
||||
logger.Ctx(ctx).Infow("backup missing, falling back to full backup", clues.In(mctx).Slice()...)
|
||||
return ms, nil, false, nil
|
||||
}
|
||||
@ -118,7 +118,7 @@ func produceManifestsAndMetadata(
|
||||
}
|
||||
|
||||
colls, err := collectMetadata(mctx, mr, man, metadataFiles, tenantID)
|
||||
if err != nil && !errors.Is(err, kopia.ErrNotFound) {
|
||||
if err != nil && !errors.Is(err, data.ErrNotFound) {
|
||||
// prior metadata isn't guaranteed to exist.
|
||||
// if it doesn't, we'll just have to do a
|
||||
// full backup for that data.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user