From 62572821172664171eacd3158eade188df7c8565 Mon Sep 17 00:00:00 2001 From: Lyndon-Li Date: Tue, 12 May 2026 09:51:47 +0800 Subject: [PATCH] add listsnapshot method Signed-off-by: Lyndon-Li --- pkg/repository/udmrepo/kopialib/lib_repo.go | 33 ++++++ .../udmrepo/kopialib/lib_repo_test.go | 110 ++++++++++++++++++ pkg/repository/udmrepo/mocks/BackupRepo.go | 68 +++++++++++ pkg/repository/udmrepo/repo.go | 3 + 4 files changed, 214 insertions(+) diff --git a/pkg/repository/udmrepo/kopialib/lib_repo.go b/pkg/repository/udmrepo/kopialib/lib_repo.go index 5c89785cf..2cc29e0de 100644 --- a/pkg/repository/udmrepo/kopialib/lib_repo.go +++ b/pkg/repository/udmrepo/kopialib/lib_repo.go @@ -527,6 +527,39 @@ func (kr *kopiaRepository) DeleteSnapshot(ctx context.Context, id udmrepo.ID) er return kr.DeleteManifest(ctx, id) } +func (kr *kopiaRepository) ListSnapshot(ctx context.Context, source string) ([]udmrepo.Snapshot, error) { + mani, err := snapshot.ListSnapshots(ctx, kr.rawRepo, snapshot.SourceInfo{ + Host: udmrepo.GetRepoDomain(), + UserName: udmrepo.GetRepoUser(), + Path: source, + }) + if err != nil { + return nil, errors.Wrapf(err, "error listing snapshot manifest for source %s", source) + } + + snapshots := []udmrepo.Snapshot{} + for _, snap := range mani { + snapshots = append(snapshots, udmrepo.Snapshot{ + Source: snap.Source.Path, + Description: snap.Description, + StartTime: snap.StartTime.ToTime(), + EndTime: snap.EndTime.ToTime(), + Tags: snap.Tags, + RootObject: udmrepo.ObjectMetadata{ + ID: udmrepo.ID(snap.RootEntry.ObjectID.String()), + Type: udmrepo.ObjectDataTypeMetadata, + Size: snap.RootEntry.FileSize, + ModTime: snap.RootEntry.ModTime.ToTime(), + Permissions: int(snap.RootEntry.Permissions), + UserID: snap.RootEntry.UserID, + GroupID: snap.RootEntry.GroupID, + }, + }) + } + + return snapshots, nil +} + func (kr *kopiaRepository) Flush(ctx context.Context) error { if kr.rawWriter == nil { return errors.New("repo writer is closed or not open") diff --git a/pkg/repository/udmrepo/kopialib/lib_repo_test.go b/pkg/repository/udmrepo/kopialib/lib_repo_test.go index 3ca962991..750d2b9eb 100644 --- a/pkg/repository/udmrepo/kopialib/lib_repo_test.go +++ b/pkg/repository/udmrepo/kopialib/lib_repo_test.go @@ -1555,3 +1555,113 @@ func TestDeleteSnapshot(t *testing.T) { } } +func TestListSnapshot(t *testing.T) { + expectedTime := time.Now() + rawObjID, _ := object.ParseID("I123456") + + mockMani := &snapshot.Manifest{ + Source: snapshot.SourceInfo{Path: "fake-source"}, + Description: "fake-desc", + StartTime: fs.UTCTimestampFromTime(expectedTime), + EndTime: fs.UTCTimestampFromTime(expectedTime.Add(time.Minute)), + RootEntry: &snapshot.DirEntry{ + ObjectID: rawObjID, + FileSize: 100, + ModTime: fs.UTCTimestampFromTime(expectedTime), + Permissions: 0o644, + UserID: 1000, + GroupID: 1000, + }, + Tags: map[string]string{"tag1": "val1"}, + } + + testCases := []struct { + name string + rawRepo *repomocks.MockRepository + source string + findRetErr error + setRepoMock bool + expectedErr string + expectedSnaps []udmrepo.Snapshot + }{ + { + name: "find manifest fail", + rawRepo: repomocks.NewMockRepository(t), + source: "fake-source", + findRetErr: errors.New("fake-find-error"), + setRepoMock: true, + expectedErr: "error listing snapshot manifest for source fake-source: unable to find manifest entries: fake-find-error", + }, + { + name: "succeed", + rawRepo: repomocks.NewMockRepository(t), + source: "fake-source", + setRepoMock: true, + expectedSnaps: []udmrepo.Snapshot{ + { + Source: "fake-source", + Description: "fake-desc", + StartTime: mockMani.StartTime.ToTime(), + EndTime: mockMani.EndTime.ToTime(), + RootObject: udmrepo.ObjectMetadata{ + ID: udmrepo.ID("I123456"), + Type: udmrepo.ObjectDataTypeMetadata, + Size: mockMani.RootEntry.FileSize, + ModTime: mockMani.RootEntry.ModTime.ToTime(), + Permissions: int(mockMani.RootEntry.Permissions), + UserID: mockMani.RootEntry.UserID, + GroupID: mockMani.RootEntry.GroupID, + }, + Tags: map[string]string{"tag1": "val1"}, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kr := &kopiaRepository{} + + if tc.rawRepo != nil { + if tc.setRepoMock { + tc.rawRepo.On("FindManifests", mock.Anything, mock.Anything).Return([]*manifest.EntryMetadata{ + { + ID: "fake-id", + Labels: map[string]string{ + manifest.TypeLabelKey: snapshot.ManifestType, + "hostname": udmrepo.GetRepoDomain(), + "username": udmrepo.GetRepoUser(), + "path": tc.source, + }, + }, + }, tc.findRetErr) + + tc.rawRepo.On("GetManifest", mock.Anything, mock.Anything, mock.Anything).Return(&manifest.EntryMetadata{ + Labels: map[string]string{ + manifest.TypeLabelKey: snapshot.ManifestType, + }, + }, nil).Run(func(args mock.Arguments) { + payload := args.Get(2) + if ptr, ok := payload.(*snapshot.Manifest); ok { + *ptr = *mockMani + } else { + b, _ := json.Marshal(mockMani) + json.Unmarshal(b, payload) + } + }).Maybe() + } + kr.rawRepo = tc.rawRepo + } + + snaps, err := kr.ListSnapshot(t.Context(), tc.source) + + if tc.expectedErr == "" { + require.NoError(t, err) + assert.Equal(t, tc.expectedSnaps, snaps) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + diff --git a/pkg/repository/udmrepo/mocks/BackupRepo.go b/pkg/repository/udmrepo/mocks/BackupRepo.go index e495b6b00..623c4d70d 100644 --- a/pkg/repository/udmrepo/mocks/BackupRepo.go +++ b/pkg/repository/udmrepo/mocks/BackupRepo.go @@ -562,6 +562,74 @@ func (_c *BackupRepo_GetSnapshot_Call) RunAndReturn(run func(ctx context.Context return _c } +// ListSnapshot provides a mock function for the type BackupRepo +func (_mock *BackupRepo) ListSnapshot(ctx context.Context, source string) ([]udmrepo.Snapshot, error) { + ret := _mock.Called(ctx, source) + + if len(ret) == 0 { + panic("no return value specified for ListSnapshot") + } + + var r0 []udmrepo.Snapshot + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string) ([]udmrepo.Snapshot, error)); ok { + return returnFunc(ctx, source) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string) []udmrepo.Snapshot); ok { + r0 = returnFunc(ctx, source) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]udmrepo.Snapshot) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, source) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// BackupRepo_ListSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListSnapshot' +type BackupRepo_ListSnapshot_Call struct { + *mock.Call +} + +// ListSnapshot is a helper method to define mock.On call +// - ctx context.Context +// - source string +func (_e *BackupRepo_Expecter) ListSnapshot(ctx interface{}, source interface{}) *BackupRepo_ListSnapshot_Call { + return &BackupRepo_ListSnapshot_Call{Call: _e.mock.On("ListSnapshot", ctx, source)} +} + +func (_c *BackupRepo_ListSnapshot_Call) Run(run func(ctx context.Context, source string)) *BackupRepo_ListSnapshot_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *BackupRepo_ListSnapshot_Call) Return(snapshots []udmrepo.Snapshot, err error) *BackupRepo_ListSnapshot_Call { + _c.Call.Return(snapshots, err) + return _c +} + +func (_c *BackupRepo_ListSnapshot_Call) RunAndReturn(run func(ctx context.Context, source string) ([]udmrepo.Snapshot, error)) *BackupRepo_ListSnapshot_Call { + _c.Call.Return(run) + return _c +} + // NewObjectWriter provides a mock function for the type BackupRepo func (_mock *BackupRepo) NewObjectWriter(ctx context.Context, opt udmrepo.ObjectWriteOptions) (udmrepo.ObjectWriter, error) { ret := _mock.Called(ctx, opt) diff --git a/pkg/repository/udmrepo/repo.go b/pkg/repository/udmrepo/repo.go index 2be015a89..a87ba30db 100644 --- a/pkg/repository/udmrepo/repo.go +++ b/pkg/repository/udmrepo/repo.go @@ -180,6 +180,9 @@ type BackupRepo interface { // DeleteSnapshot deletes a repo snapshot DeleteSnapshot(ctx context.Context, id ID) error + // ListSnapshot lists all snapshots in repo for the given source + ListSnapshot(ctx context.Context, source string) ([]Snapshot, error) + // Close closes the backup repository Close(ctx context.Context) error }