adds store package for wrapping model_store (#346)

* adds store package for wrapping model_store

Introduces the pkg/store package, which contains funcs
for wrapping the model_store with common requests.
This package choice was made for its combination
of being in an accessible place, centralizing functionality
and not introducing circular dependencies.
This commit is contained in:
Keepers 2022-07-19 11:54:53 -06:00 committed by GitHub
parent 7f60c00466
commit 6c22d5c0ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 686 additions and 202 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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]]
}

View File

@ -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.

View File

@ -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())
})
}
}

View File

@ -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]]
}

View File

@ -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")
}

View File

@ -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)

View File

@ -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

View File

@ -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())
})
}

View File

@ -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

View File

@ -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)

View File

@ -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))
}

64
src/pkg/store/backup.go Normal file
View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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
}

32
src/pkg/store/store.go Normal file
View File

@ -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}
}