Compare commits

..

2 Commits

Author SHA1 Message Date
Wenkai Yin(尹文开)
a75775ef49 Merge pull request #9724 from Lyndon-Li/unified-repo-interface-extension
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m2s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 13s
Main CI / Build (push) Failing after 31s
Close stale issues and PRs / stale (push) Successful in 13s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m35s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m10s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m21s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m9s
Unified repo interface extension for block data mover
2026-04-17 14:27:52 +08:00
Lyndon-Li
455f3ba305 unified repo interface extension for block data mover
Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
2026-04-16 18:02:30 +08:00
10 changed files with 1387 additions and 418 deletions

View File

@@ -0,0 +1 @@
Fix issue #9723, extend Unified Repo Interface to support block uploader

2
go.mod
View File

@@ -144,7 +144,7 @@ require (
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.97 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/moby/spdystream v0.5.1 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect

4
go.sum
View File

@@ -550,8 +550,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=
github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

View File

@@ -388,9 +388,9 @@ func (kr *kopiaRepository) Close(ctx context.Context) error {
return nil
}
func (kr *kopiaRepository) NewObjectWriter(ctx context.Context, opt udmrepo.ObjectWriteOptions) udmrepo.ObjectWriter {
func (kr *kopiaRepository) NewObjectWriter(ctx context.Context, opt udmrepo.ObjectWriteOptions) (udmrepo.ObjectWriter, error) {
if kr.rawWriter == nil {
return nil
return nil, errors.New("repo writer is closed or not open")
}
writer := kr.rawWriter.NewObjectWriter(kopia.SetupKopiaLog(ctx, kr.logger), object.WriterOptions{
@@ -402,12 +402,22 @@ func (kr *kopiaRepository) NewObjectWriter(ctx context.Context, opt udmrepo.Obje
})
if writer == nil {
return nil
return nil, errors.Errorf("error creating writer for object %s", opt.Description)
}
return &kopiaObjectWriter{
rawWriter: writer,
}
}, nil
}
// TODO add implementation in following PRs
func (kr *kopiaRepository) WriteMetadata(ctx context.Context, meta *udmrepo.Metadata, opt udmrepo.ObjectWriteOptions) (udmrepo.ID, error) {
return "", errors.New("not supported")
}
// TODO add implementation in following PRs
func (kr *kopiaRepository) ReadMetadata(ctx context.Context, id udmrepo.ID) (*udmrepo.Metadata, error) {
return nil, errors.New("not supported")
}
func (kr *kopiaRepository) PutManifest(ctx context.Context, manifest udmrepo.RepoManifest) (udmrepo.ID, error) {
@@ -436,6 +446,21 @@ func (kr *kopiaRepository) DeleteManifest(ctx context.Context, id udmrepo.ID) er
return nil
}
// TODO add implementation in following PRs
func (kr *kopiaRepository) SaveSnapshot(ctx context.Context, snap udmrepo.Snapshot) (udmrepo.ID, error) {
return "", errors.New("not supported")
}
// TODO add implementation in following PRs
func (kr *kopiaRepository) GetSnapshot(ctx context.Context, id udmrepo.ID) (udmrepo.Snapshot, error) {
return udmrepo.Snapshot{}, errors.New("not supported")
}
// TODO add implementation in following PRs
func (kr *kopiaRepository) DeleteSnapshot(ctx context.Context, id udmrepo.ID) error {
return errors.New("not supported")
}
func (kr *kopiaRepository) Flush(ctx context.Context) error {
if kr.rawWriter == nil {
return errors.New("repo writer is closed or not open")
@@ -546,8 +571,9 @@ func (kow *kopiaObjectWriter) Write(p []byte) (int, error) {
return kow.rawWriter.Write(p)
}
func (kow *kopiaObjectWriter) Seek(offset int64, whence int) (int64, error) {
return -1, errors.New("not supported")
// TODO add implementation in following PRs
func (kow *kopiaObjectWriter) WriteAt(p []byte, offset int64) (int, error) {
return 0, errors.New("not supported")
}
func (kow *kopiaObjectWriter) Checkpoint() (udmrepo.ID, error) {

View File

@@ -663,13 +663,16 @@ func TestNewObjectWriter(t *testing.T) {
rawWriter *repomocks.MockRepositoryWriter
rawWriterRet object.Writer
expectedRet udmrepo.ObjectWriter
expectedErr string
}{
{
name: "raw writer is nil",
name: "raw writer is nil",
expectedErr: "repo writer is closed or not open",
},
{
name: "new object writer fail",
rawWriter: repomocks.NewMockRepositoryWriter(t),
name: "new object writer fail",
rawWriter: repomocks.NewMockRepositoryWriter(t),
expectedErr: "error creating writer for object ",
},
{
name: "succeed",
@@ -688,9 +691,14 @@ func TestNewObjectWriter(t *testing.T) {
kr.rawWriter = tc.rawWriter
}
ret := kr.NewObjectWriter(t.Context(), udmrepo.ObjectWriteOptions{})
ret, err := kr.NewObjectWriter(t.Context(), udmrepo.ObjectWriteOptions{})
assert.Equal(t, tc.expectedRet, ret)
if tc.expectedErr == "" {
require.NoError(t, err)
require.Equal(t, tc.expectedRet, ret)
} else {
require.EqualError(t, err, tc.expectedErr)
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,147 +1,14 @@
// Code generated by mockery v2.39.1. DO NOT EDIT.
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
mock "github.com/stretchr/testify/mock"
udmrepo "github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
)
// ObjectWriter is an autogenerated mock type for the ObjectWriter type
type ObjectWriter struct {
mock.Mock
}
// Checkpoint provides a mock function with given fields:
func (_m *ObjectWriter) Checkpoint() (udmrepo.ID, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Checkpoint")
}
var r0 udmrepo.ID
var r1 error
if rf, ok := ret.Get(0).(func() (udmrepo.ID, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() udmrepo.ID); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(udmrepo.ID)
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Close provides a mock function with given fields:
func (_m *ObjectWriter) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Result provides a mock function with given fields:
func (_m *ObjectWriter) Result() (udmrepo.ID, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Result")
}
var r0 udmrepo.ID
var r1 error
if rf, ok := ret.Get(0).(func() (udmrepo.ID, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() udmrepo.ID); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(udmrepo.ID)
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Seek provides a mock function with given fields: offset, whence
func (_m *ObjectWriter) Seek(offset int64, whence int) (int64, error) {
ret := _m.Called(offset, whence)
if len(ret) == 0 {
panic("no return value specified for Seek")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(int64, int) (int64, error)); ok {
return rf(offset, whence)
}
if rf, ok := ret.Get(0).(func(int64, int) int64); ok {
r0 = rf(offset, whence)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(int64, int) error); ok {
r1 = rf(offset, whence)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Write provides a mock function with given fields: p
func (_m *ObjectWriter) Write(p []byte) (int, error) {
ret := _m.Called(p)
if len(ret) == 0 {
panic("no return value specified for Write")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
return rf(p)
}
if rf, ok := ret.Get(0).(func([]byte) int); ok {
r0 = rf(p)
} else {
r0 = ret.Get(0).(int)
}
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(p)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewObjectWriter creates a new instance of ObjectWriter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewObjectWriter(t interface {
@@ -155,3 +22,292 @@ func NewObjectWriter(t interface {
return mock
}
// ObjectWriter is an autogenerated mock type for the ObjectWriter type
type ObjectWriter struct {
mock.Mock
}
type ObjectWriter_Expecter struct {
mock *mock.Mock
}
func (_m *ObjectWriter) EXPECT() *ObjectWriter_Expecter {
return &ObjectWriter_Expecter{mock: &_m.Mock}
}
// Checkpoint provides a mock function for the type ObjectWriter
func (_mock *ObjectWriter) Checkpoint() (udmrepo.ID, error) {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Checkpoint")
}
var r0 udmrepo.ID
var r1 error
if returnFunc, ok := ret.Get(0).(func() (udmrepo.ID, error)); ok {
return returnFunc()
}
if returnFunc, ok := ret.Get(0).(func() udmrepo.ID); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(udmrepo.ID)
}
if returnFunc, ok := ret.Get(1).(func() error); ok {
r1 = returnFunc()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ObjectWriter_Checkpoint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Checkpoint'
type ObjectWriter_Checkpoint_Call struct {
*mock.Call
}
// Checkpoint is a helper method to define mock.On call
func (_e *ObjectWriter_Expecter) Checkpoint() *ObjectWriter_Checkpoint_Call {
return &ObjectWriter_Checkpoint_Call{Call: _e.mock.On("Checkpoint")}
}
func (_c *ObjectWriter_Checkpoint_Call) Run(run func()) *ObjectWriter_Checkpoint_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *ObjectWriter_Checkpoint_Call) Return(iD udmrepo.ID, err error) *ObjectWriter_Checkpoint_Call {
_c.Call.Return(iD, err)
return _c
}
func (_c *ObjectWriter_Checkpoint_Call) RunAndReturn(run func() (udmrepo.ID, error)) *ObjectWriter_Checkpoint_Call {
_c.Call.Return(run)
return _c
}
// Close provides a mock function for the type ObjectWriter
func (_mock *ObjectWriter) Close() error {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func() error); ok {
r0 = returnFunc()
} else {
r0 = ret.Error(0)
}
return r0
}
// ObjectWriter_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
type ObjectWriter_Close_Call struct {
*mock.Call
}
// Close is a helper method to define mock.On call
func (_e *ObjectWriter_Expecter) Close() *ObjectWriter_Close_Call {
return &ObjectWriter_Close_Call{Call: _e.mock.On("Close")}
}
func (_c *ObjectWriter_Close_Call) Run(run func()) *ObjectWriter_Close_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *ObjectWriter_Close_Call) Return(err error) *ObjectWriter_Close_Call {
_c.Call.Return(err)
return _c
}
func (_c *ObjectWriter_Close_Call) RunAndReturn(run func() error) *ObjectWriter_Close_Call {
_c.Call.Return(run)
return _c
}
// Result provides a mock function for the type ObjectWriter
func (_mock *ObjectWriter) Result() (udmrepo.ID, error) {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Result")
}
var r0 udmrepo.ID
var r1 error
if returnFunc, ok := ret.Get(0).(func() (udmrepo.ID, error)); ok {
return returnFunc()
}
if returnFunc, ok := ret.Get(0).(func() udmrepo.ID); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(udmrepo.ID)
}
if returnFunc, ok := ret.Get(1).(func() error); ok {
r1 = returnFunc()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ObjectWriter_Result_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Result'
type ObjectWriter_Result_Call struct {
*mock.Call
}
// Result is a helper method to define mock.On call
func (_e *ObjectWriter_Expecter) Result() *ObjectWriter_Result_Call {
return &ObjectWriter_Result_Call{Call: _e.mock.On("Result")}
}
func (_c *ObjectWriter_Result_Call) Run(run func()) *ObjectWriter_Result_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *ObjectWriter_Result_Call) Return(iD udmrepo.ID, err error) *ObjectWriter_Result_Call {
_c.Call.Return(iD, err)
return _c
}
func (_c *ObjectWriter_Result_Call) RunAndReturn(run func() (udmrepo.ID, error)) *ObjectWriter_Result_Call {
_c.Call.Return(run)
return _c
}
// Write provides a mock function for the type ObjectWriter
func (_mock *ObjectWriter) Write(p []byte) (int, error) {
ret := _mock.Called(p)
if len(ret) == 0 {
panic("no return value specified for Write")
}
var r0 int
var r1 error
if returnFunc, ok := ret.Get(0).(func([]byte) (int, error)); ok {
return returnFunc(p)
}
if returnFunc, ok := ret.Get(0).(func([]byte) int); ok {
r0 = returnFunc(p)
} else {
r0 = ret.Get(0).(int)
}
if returnFunc, ok := ret.Get(1).(func([]byte) error); ok {
r1 = returnFunc(p)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ObjectWriter_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
type ObjectWriter_Write_Call struct {
*mock.Call
}
// Write is a helper method to define mock.On call
// - p []byte
func (_e *ObjectWriter_Expecter) Write(p interface{}) *ObjectWriter_Write_Call {
return &ObjectWriter_Write_Call{Call: _e.mock.On("Write", p)}
}
func (_c *ObjectWriter_Write_Call) Run(run func(p []byte)) *ObjectWriter_Write_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 []byte
if args[0] != nil {
arg0 = args[0].([]byte)
}
run(
arg0,
)
})
return _c
}
func (_c *ObjectWriter_Write_Call) Return(n int, err error) *ObjectWriter_Write_Call {
_c.Call.Return(n, err)
return _c
}
func (_c *ObjectWriter_Write_Call) RunAndReturn(run func(p []byte) (int, error)) *ObjectWriter_Write_Call {
_c.Call.Return(run)
return _c
}
// WriteAt provides a mock function for the type ObjectWriter
func (_mock *ObjectWriter) WriteAt(p []byte, off int64) (int, error) {
ret := _mock.Called(p, off)
if len(ret) == 0 {
panic("no return value specified for WriteAt")
}
var r0 int
var r1 error
if returnFunc, ok := ret.Get(0).(func([]byte, int64) (int, error)); ok {
return returnFunc(p, off)
}
if returnFunc, ok := ret.Get(0).(func([]byte, int64) int); ok {
r0 = returnFunc(p, off)
} else {
r0 = ret.Get(0).(int)
}
if returnFunc, ok := ret.Get(1).(func([]byte, int64) error); ok {
r1 = returnFunc(p, off)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ObjectWriter_WriteAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteAt'
type ObjectWriter_WriteAt_Call struct {
*mock.Call
}
// WriteAt is a helper method to define mock.On call
// - p []byte
// - off int64
func (_e *ObjectWriter_Expecter) WriteAt(p interface{}, off interface{}) *ObjectWriter_WriteAt_Call {
return &ObjectWriter_WriteAt_Call{Call: _e.mock.On("WriteAt", p, off)}
}
func (_c *ObjectWriter_WriteAt_Call) Run(run func(p []byte, off int64)) *ObjectWriter_WriteAt_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 []byte
if args[0] != nil {
arg0 = args[0].([]byte)
}
var arg1 int64
if args[1] != nil {
arg1 = args[1].(int64)
}
run(
arg0,
arg1,
)
})
return _c
}
func (_c *ObjectWriter_WriteAt_Call) Return(n int, err error) *ObjectWriter_WriteAt_Call {
_c.Call.Return(n, err)
return _c
}
func (_c *ObjectWriter_WriteAt_Call) RunAndReturn(run func(p []byte, off int64) (int, error)) *ObjectWriter_WriteAt_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -62,19 +62,41 @@ const (
// ObjectWriteOptions defines the options when creating an object for write
type ObjectWriteOptions struct {
FullPath string // Full logical path of the object
DataType int // OBJECT_DATA_TYPE_*
Description string // A description of the object, could be empty
Prefix ID // A prefix of the name used to save the object
AccessMode int // OBJECT_DATA_ACCESS_*
BackupMode int // OBJECT_DATA_BACKUP_*
AsyncWrites int // Num of async writes for the object, 0 means no async write
FullPath string // Full logical path of the object
DataType int // OBJECT_DATA_TYPE_*
Description string // A description of the object, could be empty
Prefix ID // A prefix of the name used to save the object
AccessMode int // OBJECT_DATA_ACCESS_*
BackupMode int // OBJECT_DATA_BACKUP_*
AsyncWrites int // Num of async writes for the object, 0 means no async write
ParentObject ID // The object in the previous snapshot, for incremental backup
}
type AdvancedFeatureInfo struct {
MultiPartBackup bool // if set to true, it means the repo supports multiple-part backup
}
type ObjectMetadata struct {
ID ID
Type int // OBJECT_DATA_TYPE_*
Size int64
}
type Metadata struct {
SubObjects []ObjectMetadata // For dir metadata only, the sub objects in this dir.
ExtraDataLen int // Extra data associated to this metadata.
ExtraData []byte
}
type Snapshot struct {
Source string
Description string
StartTime time.Time
EndTime time.Time
Tags map[string]string
RootObject ID
}
// BackupRepoService is used to initialize, open or maintain a backup repository
type BackupRepoService interface {
// Create creates a new backup repository.
@@ -119,7 +141,14 @@ type BackupRepo interface {
// NewObjectWriter creates a new object and return the object's writer interface.
// return: A unified identifier of the object on success.
NewObjectWriter(ctx context.Context, opt ObjectWriteOptions) ObjectWriter
NewObjectWriter(ctx context.Context, opt ObjectWriteOptions) (ObjectWriter, error)
// WriteMetadata writes metadata to the repo, metadata is used to describe data, e.g., file system
// dirs are saved as metadata
WriteMetadata(ctx context.Context, meta *Metadata, opt ObjectWriteOptions) (ID, error)
// ReadMetadata reads a metadata from repo by the metadata's object ID
ReadMetadata(ctx context.Context, id ID) (*Metadata, error)
// PutManifest saves a manifest object into the backup repository.
PutManifest(ctx context.Context, mani RepoManifest) (ID, error)
@@ -139,6 +168,15 @@ type BackupRepo interface {
// Time returns the local time of the backup repository. It may be different from the time of the caller
Time() time.Time
// SaveSnapshot saves a repo snapshot
SaveSnapshot(ctx context.Context, snapshot Snapshot) (ID, error)
// GetSnapshot returns a repo snapshot from snapshot ID
GetSnapshot(ctx context.Context, id ID) (Snapshot, error)
// DeleteSnapshot deletes a repo snapshot
DeleteSnapshot(ctx context.Context, id ID) error
// Close closes the backup repository
Close(ctx context.Context) error
}
@@ -154,8 +192,8 @@ type ObjectReader interface {
type ObjectWriter interface {
io.WriteCloser
// Seeker is used in the cases that the object is not written sequentially
io.Seeker
// WriterAt is used in the cases that the object is not written sequentially
io.WriterAt
// Checkpoint is periodically called to preserve the state of data written to the repo so far.
// Checkpoint returns a unified identifier that represent the current state.

View File

@@ -183,8 +183,8 @@ func (sr *shimRepository) NewObjectWriter(ctx context.Context, option object.Wri
opt.DataType = udmrepo.ObjectDataTypeData
}
writer := sr.udmRepo.NewObjectWriter(ctx, opt)
if writer == nil {
writer, err := sr.udmRepo.NewObjectWriter(ctx, opt)
if err != nil || writer == nil {
return nil
}

View File

@@ -66,7 +66,7 @@ func TestShimRepo(t *testing.T) {
backupRepo.On("Flush", mock.Anything).Return(nil)
NewShimRepo(backupRepo).Flush(ctx)
backupRepo.On("NewObjectWriter", mock.Anything, mock.Anything).Return(nil)
backupRepo.On("NewObjectWriter", mock.Anything, mock.Anything).Return(nil, nil)
NewShimRepo(backupRepo).NewObjectWriter(ctx, object.WriterOptions{})
}