diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index ed9e4b20d..83b455117 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -246,12 +246,12 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { } defer utils.CloseRepo(ctx, r) - rpd, err := r.BackupDetails(ctx, backupDetailsID) + d, _, err := r.BackupDetails(ctx, backupDetailsID) if err != nil { return errors.Wrap(err, "Failed to get backup details in the repository") } - print.Entries(rpd.Entries) + print.Entries(d.Entries) return nil } diff --git a/src/cli/print/print.go b/src/cli/print/print.go index b9dca5996..16aaef1ae 100644 --- a/src/cli/print/print.go +++ b/src/cli/print/print.go @@ -28,7 +28,7 @@ type Printable interface { } // Prints the backups to the terminal with stdout. -func Backups(bs []*backup.Backup) { +func Backups(bs []backup.Backup) { ps := []Printable{} for _, b := range bs { ps = append(ps, b) diff --git a/src/internal/kopia/model_store.go b/src/internal/kopia/model_store.go index 450611521..cf70f0070 100644 --- a/src/internal/kopia/model_store.go +++ b/src/internal/kopia/model_store.go @@ -14,21 +14,11 @@ import ( const stableIDKey = "stableID" var ( - errNoModelStoreID = errors.New("model has no ModelStoreID") - errNoStableID = errors.New("model has no StableID") - errBadTagKey = errors.New("tag key overlaps with required key") - errModelTypeMismatch = errors.New("model type doesn't match request") -) - -type modelType int - -//go:generate go run golang.org/x/tools/cmd/stringer -type=modelType -const ( - UnknownModel = modelType(iota) - BackupOpModel - RestoreOpModel - BackupModel - BackupDetailsModel + errNoModelStoreID = errors.New("model has no ModelStoreID") + errNoStableID = errors.New("model has no StableID") + errBadTagKey = errors.New("tag key overlaps with required key") + errModelTypeMismatch = errors.New("model type doesn't match request") + errUnrecognizedSchema = errors.New("unrecognized model schema") ) func NewModelStore(c *conn) (*ModelStore, error) { @@ -54,20 +44,16 @@ func (ms *ModelStore) Close(ctx context.Context) error { return errors.Wrap(err, "closing ModelStore") } -// tagsForModel creates a copy of tags and adds a tag for the model type to it. -// Returns an error if another tag has the same key as the model type or if a +// tagsForModel creates a copy of tags and adds a tag for the model schema to it. +// Returns an error if another tag has the same key as the model schema or if a // bad model type is given. -func tagsForModel(t modelType, tags map[string]string) (map[string]string, error) { - if t == UnknownModel { - return nil, errors.New("bad model type") - } - +func tagsForModel(s model.Schema, tags map[string]string) (map[string]string, error) { if _, ok := tags[manifest.TypeLabelKey]; ok { return nil, errors.WithStack(errBadTagKey) } res := make(map[string]string, len(tags)+1) - res[manifest.TypeLabelKey] = t.String() + res[manifest.TypeLabelKey] = s.String() for k, v := range tags { res[k] = v } @@ -79,15 +65,19 @@ func tagsForModel(t modelType, tags map[string]string) (map[string]string, error // StableID to it. Returns an error if another tag has the same key as the model // type or if a bad model type is given. func tagsForModelWithID( - t modelType, + s model.Schema, id model.ID, tags map[string]string, ) (map[string]string, error) { + if !s.Valid() { + return nil, errors.New("unrecognized model schema") + } + if len(id) == 0 { return nil, errors.WithStack(errNoStableID) } - res, err := tagsForModel(t, tags) + res, err := tagsForModel(s, tags) if err != nil { return nil, err } @@ -106,16 +96,20 @@ func tagsForModelWithID( func putInner( ctx context.Context, w repo.RepositoryWriter, - t modelType, + s model.Schema, m model.Model, create bool, ) error { + if !s.Valid() { + return errors.WithStack(errUnrecognizedSchema) + } + base := m.Base() if create { base.StableID = model.ID(uuid.NewString()) } - tmpTags, err := tagsForModelWithID(t, base.StableID, base.Tags) + tmpTags, err := tagsForModelWithID(s, base.StableID, base.Tags) if err != nil { // Will be wrapped at a higher layer. return err @@ -135,15 +129,18 @@ func putInner( // given to this function can later be used to help lookup the model. func (ms *ModelStore) Put( ctx context.Context, - t modelType, + s model.Schema, m model.Model, ) error { + if !s.Valid() { + return errors.WithStack(errUnrecognizedSchema) + } err := repo.WriteSession( ctx, ms.c, repo.WriteSessionOptions{Purpose: "ModelStorePut"}, func(innerCtx context.Context, w repo.RepositoryWriter) error { - err := putInner(innerCtx, w, t, m, true) + err := putInner(innerCtx, w, s, m, true) if err != nil { return err } @@ -181,14 +178,18 @@ func baseModelFromMetadata(m *manifest.EntryMetadata) (*model.BaseModel, error) // Update, or Delete. func (ms *ModelStore) GetIDsForType( ctx context.Context, - t modelType, + s model.Schema, tags map[string]string, ) ([]*model.BaseModel, error) { + if !s.Valid() { + return nil, errors.New("unrecognized model schema") + } + if _, ok := tags[stableIDKey]; ok { return nil, errors.WithStack(errBadTagKey) } - tmpTags, err := tagsForModel(t, tags) + tmpTags, err := tagsForModel(s, tags) if err != nil { return nil, errors.Wrap(err, "getting model metadata") } @@ -217,9 +218,13 @@ func (ms *ModelStore) GetIDsForType( // one model has the same StableID. func (ms *ModelStore) getModelStoreID( ctx context.Context, - t modelType, + s model.Schema, id model.ID, ) (manifest.ID, error) { + if !s.Valid() { + return "", errors.New("unrecognized model schema") + } + if len(id) == 0 { return "", errors.WithStack(errNoStableID) } @@ -236,7 +241,7 @@ func (ms *ModelStore) getModelStoreID( if len(metadata) != 1 { return "", errors.New("multiple models with same StableID") } - if metadata[0].Labels[manifest.TypeLabelKey] != t.String() { + if metadata[0].Labels[manifest.TypeLabelKey] != s.String() { return "", errors.WithStack(errModelTypeMismatch) } @@ -249,16 +254,20 @@ func (ms *ModelStore) getModelStoreID( // or if multiple models have the same StableID. func (ms *ModelStore) Get( ctx context.Context, - t modelType, + s model.Schema, id model.ID, data model.Model, ) error { - modelID, err := ms.getModelStoreID(ctx, t, id) + if !s.Valid() { + return errors.WithStack(errUnrecognizedSchema) + } + + modelID, err := ms.getModelStoreID(ctx, s, id) if err != nil { return err } - return ms.GetWithModelStoreID(ctx, t, modelID, data) + return ms.GetWithModelStoreID(ctx, s, modelID, data) } // GetWithModelStoreID deserializes the model with the given ModelStoreID into @@ -267,10 +276,14 @@ func (ms *ModelStore) Get( // expected. func (ms *ModelStore) GetWithModelStoreID( ctx context.Context, - t modelType, + s model.Schema, id manifest.ID, data model.Model, ) error { + if !s.Valid() { + return errors.WithStack(errUnrecognizedSchema) + } + if len(id) == 0 { return errors.WithStack(errNoModelStoreID) } @@ -282,7 +295,7 @@ func (ms *ModelStore) GetWithModelStoreID( return errors.Wrap(err, "getting model data") } - if metadata.Labels[manifest.TypeLabelKey] != t.String() { + if metadata.Labels[manifest.TypeLabelKey] != s.String() { return errors.WithStack(errModelTypeMismatch) } @@ -293,17 +306,21 @@ func (ms *ModelStore) GetWithModelStoreID( return nil } -// checkPrevModelVersion compares the modelType and ModelStoreID in this model +// checkPrevModelVersion compares the ModelType and ModelStoreID in this model // to model(s) previously stored in ModelStore that have the same StableID. // Returns an error if no models or more than one model has the same StableID or -// the modelType or ModelStoreID differ between the stored model and the given +// the ModelType or ModelStoreID differ between the stored model and the given // model. func (ms *ModelStore) checkPrevModelVersion( ctx context.Context, - t modelType, + s model.Schema, b *model.BaseModel, ) error { - id, err := ms.getModelStoreID(ctx, t, b.StableID) + if !s.Valid() { + return errors.WithStack(errUnrecognizedSchema) + } + + id, err := ms.getModelStoreID(ctx, s, b.StableID) if err != nil { return err } @@ -317,7 +334,7 @@ func (ms *ModelStore) checkPrevModelVersion( if meta.ID != b.ModelStoreID { return errors.New("updated model has different ModelStoreID") } - if meta.Labels[manifest.TypeLabelKey] != t.String() { + if meta.Labels[manifest.TypeLabelKey] != s.String() { return errors.New("updated model has different model type") } @@ -327,21 +344,25 @@ func (ms *ModelStore) checkPrevModelVersion( // Update adds the new version of the model with the given StableID to the model // store and deletes the version of the model with old ModelStoreID if the old // and new ModelStoreIDs do not match. Returns an error if another model has -// the same StableID but a different modelType or ModelStoreID or there is no +// the same StableID but a different ModelType or ModelStoreID or there is no // previous version of the model. If an error occurs no visible changes will be // made to the stored model. func (ms *ModelStore) Update( ctx context.Context, - t modelType, + s model.Schema, m model.Model, ) error { + if !s.Valid() { + return errors.WithStack(errUnrecognizedSchema) + } + base := m.Base() if len(base.ModelStoreID) == 0 { return errors.WithStack(errNoModelStoreID) } // TODO(ashmrtnz): Can remove if bottleneck. - if err := ms.checkPrevModelVersion(ctx, t, base); err != nil { + if err := ms.checkPrevModelVersion(ctx, s, base); err != nil { return err } @@ -359,7 +380,7 @@ func (ms *ModelStore) Update( } }() - if innerErr = putInner(innerCtx, w, t, m, false); innerErr != nil { + if innerErr = putInner(innerCtx, w, s, m, false); innerErr != nil { return innerErr } @@ -384,8 +405,12 @@ func (ms *ModelStore) Update( // Delete deletes the model with the given StableID. Turns into a noop if id is // not empty but the model does not exist. Returns an error if multiple models // have the same StableID. -func (ms *ModelStore) Delete(ctx context.Context, t modelType, id model.ID) error { - latest, err := ms.getModelStoreID(ctx, t, id) +func (ms *ModelStore) Delete(ctx context.Context, s model.Schema, id model.ID) error { + if !s.Valid() { + return errors.WithStack(errUnrecognizedSchema) + } + + latest, err := ms.getModelStoreID(ctx, s, id) if err != nil { if errors.Is(err, manifest.ErrNotFound) { return nil diff --git a/src/internal/kopia/model_store_test.go b/src/internal/kopia/model_store_test.go index 59b09233c..c0f4d3338 100644 --- a/src/internal/kopia/model_store_test.go +++ b/src/internal/kopia/model_store_test.go @@ -102,10 +102,10 @@ func (suite *ModelStoreIntegrationSuite) TestBadTagsErrors() { foo := &fooModel{Bar: uuid.NewString()} foo.Tags = test.tags - assert.Error(t, suite.m.Put(suite.ctx, BackupOpModel, foo)) - assert.Error(t, suite.m.Update(suite.ctx, BackupOpModel, foo)) + assert.Error(t, suite.m.Put(suite.ctx, model.BackupOpSchema, foo)) + assert.Error(t, suite.m.Update(suite.ctx, model.BackupOpSchema, foo)) - _, err := suite.m.GetIDsForType(suite.ctx, BackupOpModel, test.tags) + _, err := suite.m.GetIDsForType(suite.ctx, model.BackupOpSchema, test.tags) assert.Error(t, err) }) } @@ -113,7 +113,7 @@ func (suite *ModelStoreIntegrationSuite) TestBadTagsErrors() { func (suite *ModelStoreIntegrationSuite) TestNoIDsErrors() { t := suite.T() - theModelType := BackupOpModel + theModelType := model.BackupOpSchema noStableID := &fooModel{Bar: uuid.NewString()} noStableID.StableID = "" @@ -138,13 +138,13 @@ func (suite *ModelStoreIntegrationSuite) TestBadModelTypeErrors() { foo := &fooModel{Bar: uuid.NewString()} - assert.Error(t, suite.m.Put(suite.ctx, UnknownModel, foo)) + assert.Error(t, suite.m.Put(suite.ctx, model.UnknownSchema, foo)) - require.NoError(t, suite.m.Put(suite.ctx, BackupOpModel, foo)) + require.NoError(t, suite.m.Put(suite.ctx, model.BackupOpSchema, foo)) - _, err := suite.m.GetIDsForType(suite.ctx, UnknownModel, nil) + _, err := suite.m.GetIDsForType(suite.ctx, model.UnknownSchema, nil) require.Error(t, err) - assert.Contains(t, err.Error(), "model type") + assert.Contains(t, err.Error(), "schema") } func (suite *ModelStoreIntegrationSuite) TestBadTypeErrors() { @@ -152,51 +152,51 @@ func (suite *ModelStoreIntegrationSuite) TestBadTypeErrors() { foo := &fooModel{Bar: uuid.NewString()} - require.NoError(t, suite.m.Put(suite.ctx, BackupOpModel, foo)) + require.NoError(t, suite.m.Put(suite.ctx, model.BackupOpSchema, foo)) returned := &fooModel{} - assert.Error(t, suite.m.Get(suite.ctx, RestoreOpModel, foo.StableID, returned)) + assert.Error(t, suite.m.Get(suite.ctx, model.RestoreOpSchema, foo.StableID, returned)) assert.Error( - t, suite.m.GetWithModelStoreID(suite.ctx, RestoreOpModel, foo.ModelStoreID, returned)) + t, suite.m.GetWithModelStoreID(suite.ctx, model.RestoreOpSchema, foo.ModelStoreID, returned)) - assert.Error(t, suite.m.Delete(suite.ctx, RestoreOpModel, foo.StableID)) + assert.Error(t, suite.m.Delete(suite.ctx, model.RestoreOpSchema, foo.StableID)) } func (suite *ModelStoreIntegrationSuite) TestPutGet() { table := []struct { - t modelType + s model.Schema check require.ErrorAssertionFunc hasErr bool }{ { - t: UnknownModel, + s: model.UnknownSchema, check: require.Error, hasErr: true, }, { - t: BackupOpModel, + s: model.BackupOpSchema, check: require.NoError, hasErr: false, }, { - t: RestoreOpModel, + s: model.RestoreOpSchema, check: require.NoError, hasErr: false, }, { - t: BackupModel, + s: model.BackupSchema, check: require.NoError, hasErr: false, }, } for _, test := range table { - suite.T().Run(test.t.String(), func(t *testing.T) { + suite.T().Run(test.s.String(), func(t *testing.T) { foo := &fooModel{Bar: uuid.NewString()} // Avoid some silly test errors from comparing nil to empty map. foo.Tags = map[string]string{} - err := suite.m.Put(suite.ctx, test.t, foo) + err := suite.m.Put(suite.ctx, test.s, foo) test.check(t, err) if test.hasErr { @@ -207,11 +207,11 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet() { require.NotEmpty(t, foo.StableID) returned := &fooModel{} - err = suite.m.Get(suite.ctx, test.t, foo.StableID, returned) + err = suite.m.Get(suite.ctx, test.s, foo.StableID, returned) require.NoError(t, err) assert.Equal(t, foo, returned) - err = suite.m.GetWithModelStoreID(suite.ctx, test.t, foo.ModelStoreID, returned) + err = suite.m.GetWithModelStoreID(suite.ctx, test.s, foo.ModelStoreID, returned) require.NoError(t, err) assert.Equal(t, foo, returned) }) @@ -220,7 +220,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet() { func (suite *ModelStoreIntegrationSuite) TestPutGet_WithTags() { t := suite.T() - theModelType := BackupOpModel + theModelType := model.BackupOpSchema foo := &fooModel{Bar: uuid.NewString()} foo.Tags = map[string]string{ @@ -245,51 +245,51 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet_WithTags() { func (suite *ModelStoreIntegrationSuite) TestGet_NotFoundErrors() { t := suite.T() - assert.ErrorIs(t, suite.m.Get(suite.ctx, BackupOpModel, "baz", nil), manifest.ErrNotFound) + assert.ErrorIs(t, suite.m.Get(suite.ctx, model.BackupOpSchema, "baz", nil), manifest.ErrNotFound) assert.ErrorIs( - t, suite.m.GetWithModelStoreID(suite.ctx, BackupOpModel, "baz", nil), manifest.ErrNotFound) + t, suite.m.GetWithModelStoreID(suite.ctx, model.BackupOpSchema, "baz", nil), manifest.ErrNotFound) } func (suite *ModelStoreIntegrationSuite) TestPutGetOfType() { table := []struct { - t modelType + s model.Schema check require.ErrorAssertionFunc hasErr bool }{ { - t: UnknownModel, + s: model.UnknownSchema, check: require.Error, hasErr: true, }, { - t: BackupOpModel, + s: model.BackupOpSchema, check: require.NoError, hasErr: false, }, { - t: RestoreOpModel, + s: model.RestoreOpSchema, check: require.NoError, hasErr: false, }, { - t: BackupModel, + s: model.BackupSchema, check: require.NoError, hasErr: false, }, } for _, test := range table { - suite.T().Run(test.t.String(), func(t *testing.T) { + suite.T().Run(test.s.String(), func(t *testing.T) { foo := &fooModel{Bar: uuid.NewString()} - err := suite.m.Put(suite.ctx, test.t, foo) + err := suite.m.Put(suite.ctx, test.s, foo) test.check(t, err) if test.hasErr { return } - ids, err := suite.m.GetIDsForType(suite.ctx, test.t, nil) + ids, err := suite.m.GetIDsForType(suite.ctx, test.s, nil) require.NoError(t, err) assert.Len(t, ids, 1) @@ -322,7 +322,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate() { for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { ctx := context.Background() - theModelType := BackupOpModel + theModelType := model.BackupOpSchema m := getModelStore(t, ctx) defer func() { @@ -365,23 +365,23 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate() { } func (suite *ModelStoreIntegrationSuite) TestPutUpdate_FailsNotMatchingPrev() { - startModelType := BackupOpModel + startModelType := model.BackupOpSchema table := []struct { name string - t modelType + s model.Schema mutator func(m *fooModel) }{ { name: "DifferentModelStoreID", - t: startModelType, + s: startModelType, mutator: func(m *fooModel) { m.ModelStoreID = manifest.ID("bar") }, }, { name: "DifferentModelType", - t: RestoreOpModel, + s: model.RestoreOpSchema, mutator: func(m *fooModel) { }, }, @@ -402,14 +402,14 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate_FailsNotMatchingPrev() { test.mutator(foo) - assert.Error(t, m.Update(ctx, test.t, foo)) + assert.Error(t, m.Update(ctx, test.s, foo)) }) } } func (suite *ModelStoreIntegrationSuite) TestPutDelete() { t := suite.T() - theModelType := BackupOpModel + theModelType := model.BackupOpSchema foo := &fooModel{Bar: uuid.NewString()} @@ -425,7 +425,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutDelete() { func (suite *ModelStoreIntegrationSuite) TestPutDelete_BadIDsNoop() { t := suite.T() - assert.NoError(t, suite.m.Delete(suite.ctx, BackupOpModel, "foo")) + assert.NoError(t, suite.m.Delete(suite.ctx, model.BackupOpSchema, "foo")) assert.NoError(t, suite.m.DeleteWithModelStoreID(suite.ctx, "foo")) } @@ -472,7 +472,7 @@ func (suite *ModelStoreRegressionSuite) TestFailDuringWriteSessionHasNoVisibleEf // Avoid some silly test errors from comparing nil to empty map. foo.Tags = map[string]string{} - theModelType := BackupOpModel + theModelType := model.BackupOpSchema require.NoError(t, m.Put(ctx, theModelType, foo)) diff --git a/src/internal/kopia/modeltype_string.go b/src/internal/kopia/modeltype_string.go deleted file mode 100644 index 7026481f4..000000000 --- a/src/internal/kopia/modeltype_string.go +++ /dev/null @@ -1,27 +0,0 @@ -// Code generated by "stringer -type=modelType"; DO NOT EDIT. - -package kopia - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[UnknownModel-0] - _ = x[BackupOpModel-1] - _ = x[RestoreOpModel-2] - _ = x[BackupModel-3] - _ = x[BackupDetailsModel-4] -} - -const _modelType_name = "UnknownModelBackupOpModelRestoreOpModelBackupModelBackupDetailsModel" - -var _modelType_index = [...]uint8{0, 12, 25, 39, 50, 68} - -func (i modelType) String() string { - if i < 0 || i >= modelType(len(_modelType_index)-1) { - return "modelType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _modelType_name[_modelType_index[i]:_modelType_index[i+1]] -} diff --git a/src/internal/model/model.go b/src/internal/model/model.go index edfc269ca..a71b7fc7d 100644 --- a/src/internal/model/model.go +++ b/src/internal/model/model.go @@ -4,7 +4,24 @@ import ( "github.com/kopia/kopia/repo/manifest" ) -type ID string +type ( + ID string + Schema int +) + +//go:generate go run golang.org/x/tools/cmd/stringer -type=Schema +const ( + UnknownSchema = Schema(iota) + BackupOpSchema + RestoreOpSchema + BackupSchema + BackupDetailsSchema +) + +// Valid returns true if the ModelType value fits within the iota range. +func (mt Schema) Valid() bool { + return mt > 0 && mt < BackupDetailsSchema+1 +} type Model interface { // Returns a handle to the BaseModel for this model. diff --git a/src/internal/model/model_test.go b/src/internal/model/model_test.go new file mode 100644 index 000000000..6d779b5ca --- /dev/null +++ b/src/internal/model/model_test.go @@ -0,0 +1,37 @@ +package model_test + +import ( + "testing" + + "github.com/alcionai/corso/internal/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type ModelUnitSuite struct { + suite.Suite +} + +func TestModelUnitSuite(t *testing.T) { + suite.Run(t, new(ModelUnitSuite)) +} + +func (suite *ModelUnitSuite) TestValid() { + table := []struct { + mt model.Schema + expect assert.BoolAssertionFunc + }{ + {model.UnknownSchema, assert.False}, + {model.BackupOpSchema, assert.True}, + {model.RestoreOpSchema, assert.True}, + {model.BackupSchema, assert.True}, + {model.BackupDetailsSchema, assert.True}, + {model.Schema(-1), assert.False}, + {model.Schema(100), assert.False}, + } + for _, test := range table { + suite.T().Run(test.mt.String(), func(t *testing.T) { + test.expect(t, test.mt.Valid()) + }) + } +} diff --git a/src/internal/model/schema_string.go b/src/internal/model/schema_string.go new file mode 100644 index 000000000..b75b4387c --- /dev/null +++ b/src/internal/model/schema_string.go @@ -0,0 +1,27 @@ +// Code generated by "stringer -type=Schema"; DO NOT EDIT. + +package model + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[UnknownSchema-0] + _ = x[BackupOpSchema-1] + _ = x[RestoreOpSchema-2] + _ = x[BackupSchema-3] + _ = x[BackupDetailsSchema-4] +} + +const _Schema_name = "UnknownSchemaBackupOpSchemaRestoreOpSchemaBackupSchemaBackupDetailsSchema" + +var _Schema_index = [...]uint8{0, 13, 27, 42, 54, 73} + +func (i Schema) String() string { + if i < 0 || i >= Schema(len(_Schema_index)-1) { + return "Schema(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Schema_name[_Schema_index[i]:_Schema_index[i+1]] +} diff --git a/src/internal/operations/backup.go b/src/internal/operations/backup.go index fc97d16a2..c6cb2dd5f 100644 --- a/src/internal/operations/backup.go +++ b/src/internal/operations/backup.go @@ -13,6 +13,7 @@ import ( "github.com/alcionai/corso/pkg/account" "github.com/alcionai/corso/pkg/backup" "github.com/alcionai/corso/pkg/selectors" + "github.com/alcionai/corso/pkg/store" ) // BackupOperation wraps an operation with backup-specific props. @@ -38,12 +39,12 @@ func NewBackupOperation( ctx context.Context, opts Options, kw *kopia.Wrapper, - ms *kopia.ModelStore, + sw *store.Wrapper, acct account.Account, selector selectors.Selector, ) (BackupOperation, error) { op := BackupOperation{ - operation: newOperation(opts, kw, ms), + operation: newOperation(opts, kw, sw), Selectors: selector, Version: "v0", account: acct, @@ -109,14 +110,14 @@ func (op *BackupOperation) Run(ctx context.Context) error { } func (op *BackupOperation) createBackupModels(ctx context.Context, snapID string, details *backup.Details) error { - err := op.modelStore.Put(ctx, kopia.BackupDetailsModel, &details.DetailsModel) + err := op.store.Put(ctx, model.BackupDetailsSchema, &details.DetailsModel) if err != nil { return errors.Wrap(err, "creating backupdetails model") } bu := backup.New(snapID, string(details.ModelStoreID)) - err = op.modelStore.Put(ctx, kopia.BackupModel, bu) + err = op.store.Put(ctx, model.BackupSchema, bu) if err != nil { return errors.Wrap(err, "creating backup model") } diff --git a/src/internal/operations/backup_test.go b/src/internal/operations/backup_test.go index eacfa49db..43fa4c093 100644 --- a/src/internal/operations/backup_test.go +++ b/src/internal/operations/backup_test.go @@ -15,6 +15,7 @@ import ( ctesting "github.com/alcionai/corso/internal/testing" "github.com/alcionai/corso/pkg/account" "github.com/alcionai/corso/pkg/selectors" + "github.com/alcionai/corso/pkg/store" ) // --------------------------------------------------------------------------- @@ -37,7 +38,7 @@ func (suite *BackupOpSuite) TestBackupOperation_PersistResults() { var ( kw = &kopia.Wrapper{} - ms = &kopia.ModelStore{} + sw = &store.Wrapper{} acct = account.Account{} now = time.Now() stats = backupStats{ @@ -52,7 +53,7 @@ func (suite *BackupOpSuite) TestBackupOperation_PersistResults() { } ) - op, err := NewBackupOperation(ctx, Options{}, kw, ms, acct, selectors.Selector{}) + op, err := NewBackupOperation(ctx, Options{}, kw, sw, acct, selectors.Selector{}) require.NoError(t, err) op.persistResults(now, &stats) @@ -96,7 +97,7 @@ func (suite *BackupOpIntegrationSuite) SetupSuite() { func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() { kw := &kopia.Wrapper{} - ms := &kopia.ModelStore{} + sw := &store.Wrapper{} acct, err := ctesting.NewM365Account() require.NoError(suite.T(), err) @@ -104,13 +105,13 @@ func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() { name string opts Options kw *kopia.Wrapper - ms *kopia.ModelStore + sw *store.Wrapper acct account.Account targets []string errCheck assert.ErrorAssertionFunc }{ - {"good", Options{}, kw, ms, acct, nil, assert.NoError}, - {"missing kopia", Options{}, nil, ms, acct, nil, assert.Error}, + {"good", Options{}, kw, sw, acct, nil, assert.NoError}, + {"missing kopia", Options{}, nil, sw, acct, nil, assert.Error}, {"missing modelstore", Options{}, kw, nil, acct, nil, assert.Error}, } for _, test := range table { @@ -119,7 +120,7 @@ func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() { context.Background(), Options{}, test.kw, - test.ms, + test.sw, test.acct, selectors.Selector{}) test.errCheck(t, err) @@ -146,22 +147,24 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() { // to close here. defer k.Close(ctx) - w, err := kopia.NewWrapper(k) + kw, err := kopia.NewWrapper(k) require.NoError(t, err) - defer w.Close(ctx) + defer kw.Close(ctx) ms, err := kopia.NewModelStore(k) require.NoError(t, err) defer ms.Close(ctx) + sw := store.NewKopiaStore(ms) + sel := selectors.NewExchangeBackup() sel.Include(sel.Users(m365User)) bo, err := NewBackupOperation( ctx, Options{}, - w, - ms, + kw, + sw, acct, sel.Selector) require.NoError(t, err) diff --git a/src/internal/operations/operation.go b/src/internal/operations/operation.go index c92589643..5165496ee 100644 --- a/src/internal/operations/operation.go +++ b/src/internal/operations/operation.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/alcionai/corso/internal/kopia" + "github.com/alcionai/corso/pkg/store" ) type opStatus int @@ -31,8 +32,8 @@ type operation struct { Options Options `json:"options"` Status opStatus `json:"status"` - kopia *kopia.Wrapper - modelStore *kopia.ModelStore + kopia *kopia.Wrapper + store *store.Wrapper } // Options configure some parameters of the operation @@ -44,15 +45,15 @@ type Options struct { func newOperation( opts Options, kw *kopia.Wrapper, - ms *kopia.ModelStore, + sw *store.Wrapper, ) operation { return operation{ - ID: uuid.New(), - CreatedAt: time.Now(), - Options: opts, - kopia: kw, - modelStore: ms, - Status: InProgress, + ID: uuid.New(), + CreatedAt: time.Now(), + Options: opts, + kopia: kw, + store: sw, + Status: InProgress, } } @@ -60,7 +61,7 @@ func (op operation) validate() error { if op.kopia == nil { return errors.New("missing kopia connection") } - if op.modelStore == nil { + if op.store == nil { return errors.New("missing modelstore") } return nil diff --git a/src/internal/operations/operation_test.go b/src/internal/operations/operation_test.go index 23452de3b..4f44b1614 100644 --- a/src/internal/operations/operation_test.go +++ b/src/internal/operations/operation_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/alcionai/corso/internal/kopia" + "github.com/alcionai/corso/pkg/store" ) type OperationSuite struct { @@ -25,20 +26,20 @@ func (suite *OperationSuite) TestNewOperation() { func (suite *OperationSuite) TestOperation_Validate() { kwStub := &kopia.Wrapper{} - msStub := &kopia.ModelStore{} + swStub := &store.Wrapper{} table := []struct { name string kw *kopia.Wrapper - ms *kopia.ModelStore + sw *store.Wrapper errCheck assert.ErrorAssertionFunc }{ - {"good", kwStub, msStub, assert.NoError}, - {"missing kopia wrapper", nil, msStub, assert.Error}, - {"missing kopia modelstore", kwStub, nil, assert.Error}, + {"good", kwStub, swStub, assert.NoError}, + {"missing kopia wrapper", nil, swStub, assert.Error}, + {"missing store wrapper", kwStub, nil, assert.Error}, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - op := newOperation(Options{}, test.kw, test.ms) + op := newOperation(Options{}, test.kw, test.sw) test.errCheck(t, op.validate()) }) } diff --git a/src/internal/operations/restore.go b/src/internal/operations/restore.go index 2849f6fae..20a053944 100644 --- a/src/internal/operations/restore.go +++ b/src/internal/operations/restore.go @@ -4,7 +4,6 @@ import ( "context" "time" - "github.com/kopia/kopia/repo/manifest" "github.com/pkg/errors" "github.com/alcionai/corso/internal/connector" @@ -12,8 +11,8 @@ import ( "github.com/alcionai/corso/internal/kopia" "github.com/alcionai/corso/internal/model" "github.com/alcionai/corso/pkg/account" - "github.com/alcionai/corso/pkg/backup" "github.com/alcionai/corso/pkg/selectors" + "github.com/alcionai/corso/pkg/store" ) // RestoreOperation wraps an operation with restore-specific props. @@ -39,13 +38,13 @@ func NewRestoreOperation( ctx context.Context, opts Options, kw *kopia.Wrapper, - ms *kopia.ModelStore, + sw *store.Wrapper, acct account.Account, backupID model.ID, sel selectors.Selector, ) (RestoreOperation, error) { op := RestoreOperation{ - operation: newOperation(opts, kw, ms), + operation: newOperation(opts, kw, sw), BackupID: backupID, Selectors: sel, Version: "v0", @@ -82,17 +81,9 @@ func (op *RestoreOperation) Run(ctx context.Context) error { defer op.persistResults(time.Now(), &stats) // retrieve the restore point details - bu := backup.Backup{} - err := op.modelStore.Get(ctx, kopia.BackupModel, op.BackupID, &bu) + d, b, err := op.store.GetDetailsFromBackupID(ctx, op.BackupID) if err != nil { - stats.readErr = errors.Wrap(err, "retrieving restore point") - return stats.readErr - } - - backup := backup.Details{} - err = op.modelStore.GetWithModelStoreID(ctx, kopia.BackupDetailsModel, manifest.ID(bu.DetailsID), &backup) - if err != nil { - stats.readErr = errors.Wrap(err, "retrieving restore point details") + stats.readErr = errors.Wrap(err, "getting backup details for restore") return stats.readErr } @@ -103,8 +94,8 @@ func (op *RestoreOperation) Run(ctx context.Context) error { } // format the details and retrieve the items from kopia - fds := er.FilterDetails(&backup) - dcs, err := op.kopia.RestoreMultipleItems(ctx, bu.SnapshotID, fds) + fds := er.FilterDetails(d) + dcs, err := op.kopia.RestoreMultipleItems(ctx, b.SnapshotID, fds) if err != nil { stats.readErr = errors.Wrap(err, "retrieving service data") return stats.readErr diff --git a/src/internal/operations/restore_test.go b/src/internal/operations/restore_test.go index ba87c7ea5..da28c159d 100644 --- a/src/internal/operations/restore_test.go +++ b/src/internal/operations/restore_test.go @@ -16,6 +16,7 @@ import ( ctesting "github.com/alcionai/corso/internal/testing" "github.com/alcionai/corso/pkg/account" "github.com/alcionai/corso/pkg/selectors" + "github.com/alcionai/corso/pkg/store" ) // --------------------------------------------------------------------------- @@ -38,7 +39,7 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() { var ( kw = &kopia.Wrapper{} - ms = &kopia.ModelStore{} + sw = &store.Wrapper{} acct = account.Account{} now = time.Now() stats = restoreStats{ @@ -51,7 +52,7 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() { } ) - op, err := NewRestoreOperation(ctx, Options{}, kw, ms, acct, "foo", selectors.Selector{}) + op, err := NewRestoreOperation(ctx, Options{}, kw, sw, acct, "foo", selectors.Selector{}) require.NoError(t, err) op.persistResults(now, &stats) @@ -90,7 +91,7 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() { func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() { kw := &kopia.Wrapper{} - ms := &kopia.ModelStore{} + sw := &store.Wrapper{} acct, err := ctesting.NewM365Account() require.NoError(suite.T(), err) @@ -98,13 +99,13 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() { name string opts Options kw *kopia.Wrapper - ms *kopia.ModelStore + sw *store.Wrapper acct account.Account targets []string errCheck assert.ErrorAssertionFunc }{ - {"good", Options{}, kw, ms, acct, nil, assert.NoError}, - {"missing kopia", Options{}, nil, ms, acct, nil, assert.Error}, + {"good", Options{}, kw, sw, acct, nil, assert.NoError}, + {"missing kopia", Options{}, nil, sw, acct, nil, assert.Error}, {"missing modelstore", Options{}, kw, nil, acct, nil, assert.Error}, } for _, test := range table { @@ -113,7 +114,7 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() { context.Background(), Options{}, test.kw, - test.ms, + test.sw, test.acct, "backup-id", selectors.Selector{}) @@ -146,6 +147,8 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() { require.NoError(t, err) defer ms.Close(ctx) + sw := store.NewKopiaStore(ms) + bsel := selectors.NewExchangeBackup() bsel.Include(bsel.Users(m365User)) @@ -153,7 +156,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() { ctx, Options{}, w, - ms, + sw, acct, bsel.Selector) require.NoError(t, err) @@ -167,7 +170,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() { ctx, Options{}, w, - ms, + sw, acct, bo.Results.BackupID, rsel.Selector) diff --git a/src/pkg/repository/repository.go b/src/pkg/repository/repository.go index ff7adce43..53fdbc709 100644 --- a/src/pkg/repository/repository.go +++ b/src/pkg/repository/repository.go @@ -5,7 +5,6 @@ import ( "time" "github.com/google/uuid" - "github.com/kopia/kopia/repo/manifest" "github.com/pkg/errors" "github.com/alcionai/corso/internal/kopia" @@ -15,6 +14,7 @@ import ( "github.com/alcionai/corso/pkg/backup" "github.com/alcionai/corso/pkg/selectors" "github.com/alcionai/corso/pkg/storage" + "github.com/alcionai/corso/pkg/store" ) // Repository contains storage provider information. @@ -133,7 +133,7 @@ func (r Repository) NewBackup(ctx context.Context, selector selectors.Selector) ctx, operations.Options{}, r.dataLayer, - r.modelStore, + store.NewKopiaStore(r.modelStore), r.Account, selector) } @@ -144,36 +144,20 @@ func (r Repository) NewRestore(ctx context.Context, backupID string, sel selecto ctx, operations.Options{}, r.dataLayer, - r.modelStore, + store.NewKopiaStore(r.modelStore), r.Account, model.ID(backupID), sel) } // backups lists backups in a respository -func (r Repository) Backups(ctx context.Context) ([]*backup.Backup, error) { - bms, err := r.modelStore.GetIDsForType(ctx, kopia.BackupModel, nil) - if err != nil { - return nil, err - } - bus := make([]*backup.Backup, 0, len(bms)) - for _, bm := range bms { - bu := backup.Backup{} - err := r.modelStore.GetWithModelStoreID(ctx, kopia.BackupModel, bm.ModelStoreID, &bu) - if err != nil { - return nil, err - } - bus = append(bus, &bu) - } - return bus, nil +func (r Repository) Backups(ctx context.Context) ([]backup.Backup, error) { + sw := store.NewKopiaStore(r.modelStore) + return sw.GetBackups(ctx) } // BackupDetails returns the specified backup details object -func (r Repository) BackupDetails(ctx context.Context, rpDetailsID string) (*backup.Details, error) { - bud := backup.Details{} - err := r.modelStore.GetWithModelStoreID(ctx, kopia.BackupDetailsModel, manifest.ID(rpDetailsID), &bud) - if err != nil { - return nil, err - } - return &bud, nil +func (r Repository) BackupDetails(ctx context.Context, backupID string) (*backup.Details, *backup.Backup, error) { + sw := store.NewKopiaStore(r.modelStore) + return sw.GetDetailsFromBackupID(ctx, model.ID(backupID)) } diff --git a/src/pkg/store/backup.go b/src/pkg/store/backup.go new file mode 100644 index 000000000..9696cad48 --- /dev/null +++ b/src/pkg/store/backup.go @@ -0,0 +1,64 @@ +package store + +import ( + "context" + + "github.com/kopia/kopia/repo/manifest" + "github.com/pkg/errors" + + "github.com/alcionai/corso/internal/model" + "github.com/alcionai/corso/pkg/backup" +) + +// GetBackup gets a single backup by id. +func (w Wrapper) GetBackup(ctx context.Context, backupID model.ID) (*backup.Backup, error) { + b := backup.Backup{} + err := w.Get(ctx, model.BackupSchema, backupID, &b) + if err != nil { + return nil, errors.Wrap(err, "getting backup") + } + return &b, nil +} + +// GetDetailsFromBackupID retrieves all backups in the model store. +func (w Wrapper) GetBackups(ctx context.Context) ([]backup.Backup, error) { + bms, err := w.GetIDsForType(ctx, model.BackupSchema, nil) + if err != nil { + return nil, err + } + bs := make([]backup.Backup, len(bms)) + for i, bm := range bms { + b := backup.Backup{} + err := w.GetWithModelStoreID(ctx, model.BackupSchema, bm.ModelStoreID, &b) + if err != nil { + return nil, err + } + bs[i] = b + } + return bs, nil +} + +// GetDetails gets the backup details by ID. +func (w Wrapper) GetDetails(ctx context.Context, detailsID manifest.ID) (*backup.Details, error) { + d := backup.Details{} + err := w.GetWithModelStoreID(ctx, model.BackupDetailsSchema, detailsID, &d) + if err != nil { + return nil, errors.Wrap(err, "getting details") + } + return &d, nil +} + +// GetDetailsFromBackupID retrieves the backup.Details within the specified backup. +func (w Wrapper) GetDetailsFromBackupID(ctx context.Context, backupID model.ID) (*backup.Details, *backup.Backup, error) { + b, err := w.GetBackup(ctx, backupID) + if err != nil { + return nil, nil, err + } + + d, err := w.GetDetails(ctx, manifest.ID(b.DetailsID)) + if err != nil { + return nil, nil, err + } + + return d, b, nil +} diff --git a/src/pkg/store/backup_test.go b/src/pkg/store/backup_test.go new file mode 100644 index 000000000..cabec323a --- /dev/null +++ b/src/pkg/store/backup_test.go @@ -0,0 +1,180 @@ +package store_test + +import ( + "context" + "testing" + "time" + + "github.com/google/uuid" + "github.com/kopia/kopia/repo/manifest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/alcionai/corso/internal/model" + "github.com/alcionai/corso/pkg/backup" + "github.com/alcionai/corso/pkg/store" + storeMock "github.com/alcionai/corso/pkg/store/mock" +) + +// ------------------------------------------------------------ +// unit tests +// ------------------------------------------------------------ + +var ( + detailsID = uuid.NewString() + bu = backup.Backup{ + BaseModel: model.BaseModel{ + StableID: model.ID(uuid.NewString()), + ModelStoreID: manifest.ID(uuid.NewString()), + }, + CreationTime: time.Now(), + SnapshotID: uuid.NewString(), + DetailsID: detailsID, + } + deets = backup.Details{ + DetailsModel: backup.DetailsModel{ + BaseModel: model.BaseModel{ + StableID: model.ID(detailsID), + ModelStoreID: manifest.ID(uuid.NewString()), + }, + }, + } +) + +type StoreBackupUnitSuite struct { + suite.Suite +} + +func TestStoreBackupUnitSuite(t *testing.T) { + suite.Run(t, new(StoreBackupUnitSuite)) +} + +func (suite *StoreBackupUnitSuite) TestGetBackup() { + ctx := context.Background() + + table := []struct { + name string + mock *storeMock.MockModelStore + expect assert.ErrorAssertionFunc + }{ + { + name: "gets backup", + mock: storeMock.NewMock(&bu, nil, nil), + expect: assert.NoError, + }, + { + name: "errors", + mock: storeMock.NewMock(&bu, nil, assert.AnError), + expect: assert.Error, + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + store := &store.Wrapper{test.mock} + result, err := store.GetBackup(ctx, model.ID(uuid.NewString())) + test.expect(t, err) + if err != nil { + return + } + assert.Equal(t, bu.StableID, result.StableID) + }) + } +} + +func (suite *StoreBackupUnitSuite) TestGetBackups() { + ctx := context.Background() + + table := []struct { + name string + mock *storeMock.MockModelStore + expect assert.ErrorAssertionFunc + }{ + { + name: "gets backups", + mock: storeMock.NewMock(&bu, nil, nil), + expect: assert.NoError, + }, + { + name: "errors", + mock: storeMock.NewMock(&bu, nil, assert.AnError), + expect: assert.Error, + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + sm := &store.Wrapper{test.mock} + result, err := sm.GetBackups(ctx) + test.expect(t, err) + if err != nil { + return + } + assert.Equal(t, 1, len(result)) + assert.Equal(t, bu.StableID, result[0].StableID) + }) + } +} + +func (suite *StoreBackupUnitSuite) TestGetDetails() { + ctx := context.Background() + + table := []struct { + name string + mock *storeMock.MockModelStore + expect assert.ErrorAssertionFunc + }{ + { + name: "gets details", + mock: storeMock.NewMock(nil, &deets, nil), + expect: assert.NoError, + }, + { + name: "errors", + mock: storeMock.NewMock(nil, &deets, assert.AnError), + expect: assert.Error, + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + sm := &store.Wrapper{test.mock} + result, err := sm.GetDetails(ctx, manifest.ID(uuid.NewString())) + test.expect(t, err) + if err != nil { + return + } + assert.Equal(t, deets.StableID, result.StableID) + }) + } +} + +func (suite *StoreBackupUnitSuite) TestGetDetailsFromBackupID() { + ctx := context.Background() + + table := []struct { + name string + mock *storeMock.MockModelStore + expect assert.ErrorAssertionFunc + }{ + { + name: "gets details from backup id", + mock: storeMock.NewMock(&bu, &deets, nil), + expect: assert.NoError, + }, + { + name: "errors", + mock: storeMock.NewMock(&bu, &deets, assert.AnError), + expect: assert.Error, + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + store := &store.Wrapper{test.mock} + dResult, bResult, err := store.GetDetailsFromBackupID(ctx, model.ID(uuid.NewString())) + test.expect(t, err) + if err != nil { + return + } + assert.Equal(t, deets.StableID, dResult.StableID) + assert.Equal(t, bu.StableID, bResult.StableID) + }) + } +} diff --git a/src/pkg/store/mock/store_mock.go b/src/pkg/store/mock/store_mock.go new file mode 100644 index 000000000..3f122c42b --- /dev/null +++ b/src/pkg/store/mock/store_mock.go @@ -0,0 +1,145 @@ +package mock + +import ( + "context" + "encoding/json" + + "github.com/kopia/kopia/repo/manifest" + "github.com/pkg/errors" + + "github.com/alcionai/corso/internal/model" + "github.com/alcionai/corso/pkg/backup" +) + +// ------------------------------------------------------------ +// model wrapper model store +// ------------------------------------------------------------ + +type MockModelStore struct { + backup []byte + details []byte + err error +} + +func NewMock(b *backup.Backup, d *backup.Details, err error) *MockModelStore { + return &MockModelStore{ + backup: marshal(b), + details: marshal(d), + err: err, + } +} + +func marshal(a any) []byte { + bs, _ := json.Marshal(a) + return bs +} + +func unmarshal(b []byte, a any) { + //nolint:errcheck + json.Unmarshal(b, a) +} + +// ------------------------------------------------------------ +// deleter iface +// ------------------------------------------------------------ + +func (mms *MockModelStore) Delete(ctx context.Context, s model.Schema, id model.ID) error { + return mms.err +} + +func (mms *MockModelStore) DeleteWithModelStoreID(ctx context.Context, id manifest.ID) error { + return mms.err +} + +// ------------------------------------------------------------ +// getter iface +// ------------------------------------------------------------ + +func (mms *MockModelStore) Get( + ctx context.Context, + s model.Schema, + id model.ID, + data model.Model, +) error { + if mms.err != nil { + return mms.err + } + switch s { + case model.BackupSchema: + unmarshal(mms.backup, data) + case model.BackupDetailsSchema: + unmarshal(mms.details, data) + default: + return errors.Errorf("schema %s not supported by mock Get", s) + } + return nil +} + +func (mms *MockModelStore) GetIDsForType( + ctx context.Context, + s model.Schema, + tags map[string]string, +) ([]*model.BaseModel, error) { + if mms.err != nil { + return nil, mms.err + } + switch s { + case model.BackupSchema: + b := backup.Backup{} + unmarshal(mms.backup, &b) + return []*model.BaseModel{&b.BaseModel}, nil + case model.BackupDetailsSchema: + d := backup.Details{} + unmarshal(mms.backup, &d) + return []*model.BaseModel{&d.BaseModel}, nil + } + return nil, errors.Errorf("schema %s not supported by mock GetIDsForType", s) +} + +func (mms *MockModelStore) GetWithModelStoreID( + ctx context.Context, + s model.Schema, + id manifest.ID, + data model.Model, +) error { + if mms.err != nil { + return mms.err + } + switch s { + case model.BackupSchema: + unmarshal(mms.backup, data) + case model.BackupDetailsSchema: + unmarshal(mms.details, data) + default: + return errors.Errorf("schema %s not supported by mock GetWithModelStoreID", s) + } + return nil +} + +// ------------------------------------------------------------ +// updater iface +// ------------------------------------------------------------ + +func (mms *MockModelStore) Put(ctx context.Context, s model.Schema, m model.Model) error { + switch s { + case model.BackupSchema: + mms.backup = marshal(m) + case model.BackupDetailsSchema: + mms.details = marshal(m) + default: + return errors.Errorf("schema %s not supported by mock Put", s) + } + return mms.err +} + +func (mms *MockModelStore) Update(ctx context.Context, s model.Schema, m model.Model) error { + switch s { + case model.BackupSchema: + mms.backup = marshal(m) + case model.BackupDetailsSchema: + mms.details = marshal(m) + default: + return errors.Errorf("schema %s not supported by mock Update", s) + } + return mms.err +} diff --git a/src/pkg/store/store.go b/src/pkg/store/store.go new file mode 100644 index 000000000..8dee958e3 --- /dev/null +++ b/src/pkg/store/store.go @@ -0,0 +1,32 @@ +package store + +import ( + "context" + + "github.com/kopia/kopia/repo/manifest" + + "github.com/alcionai/corso/internal/kopia" + "github.com/alcionai/corso/internal/model" +) + +var _ Storer = &kopia.ModelStore{} + +type ( + Storer interface { + Delete(ctx context.Context, s model.Schema, id model.ID) error + DeleteWithModelStoreID(ctx context.Context, id manifest.ID) error + Get(ctx context.Context, s model.Schema, id model.ID, data model.Model) error + GetIDsForType(ctx context.Context, s model.Schema, tags map[string]string) ([]*model.BaseModel, error) + GetWithModelStoreID(ctx context.Context, s model.Schema, id manifest.ID, data model.Model) error + Put(ctx context.Context, s model.Schema, m model.Model) error + Update(ctx context.Context, s model.Schema, m model.Model) error + } +) + +type Wrapper struct { + Storer +} + +func NewKopiaStore(kMS *kopia.ModelStore) *Wrapper { + return &Wrapper{kMS} +}