move onedrive.MetadataItem to metadata.Item (#3235)

In keeping with other changes that migrate shared metadata to the onedrive/metadata pkg for exported access.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #3135

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-05-03 14:27:14 -06:00 committed by GitHub
parent d067a79483
commit 7326730e0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 74 additions and 83 deletions

View File

@ -722,14 +722,14 @@ func permissionEqual(expected metadata.Permission, got metadata.Permission) bool
return true return true
} }
func compareOneDriveItem( func compareDriveItem(
t *testing.T, t *testing.T,
expected map[string][]byte, expected map[string][]byte,
item data.Stream, item data.Stream,
restorePermissions bool, restorePermissions bool,
rootDir bool, rootDir bool,
) bool { ) bool {
// Skip OneDrive permissions in the folder that used to be the root. We don't // Skip Drive permissions in the folder that used to be the root. We don't
// have a good way to materialize these in the test right now. // have a good way to materialize these in the test right now.
if rootDir && item.UUID() == metadata.DirMetaFileSuffix { if rootDir && item.UUID() == metadata.DirMetaFileSuffix {
return false return false
@ -747,7 +747,7 @@ func compareOneDriveItem(
) )
if isMeta { if isMeta {
var itemType *onedrive.MetadataItem var itemType *metadata.Item
assert.IsType(t, itemType, item) assert.IsType(t, itemType, item)
} else { } else {
@ -824,8 +824,7 @@ func compareOneDriveItem(
t, t,
expectedMeta.Permissions, expectedMeta.Permissions,
itemPerms, itemPerms,
permissionEqual, permissionEqual)
)
return true return true
} }
@ -887,7 +886,7 @@ func compareItem(
} }
case path.OneDriveService: case path.OneDriveService:
return compareOneDriveItem(t, expected, item, restorePermissions, rootDir) return compareDriveItem(t, expected, item, restorePermissions, rootDir)
case path.SharePointService: case path.SharePointService:
if category != path.LibrariesCategory { if category != path.LibrariesCategory {
@ -895,7 +894,7 @@ func compareItem(
} }
// SharePoint libraries reuses OneDrive code. // SharePoint libraries reuses OneDrive code.
return compareOneDriveItem(t, expected, item, restorePermissions, rootDir) return compareDriveItem(t, expected, item, restorePermissions, rootDir)
default: default:
assert.FailNowf(t, "unexpected service: %s", service.String()) assert.FailNowf(t, "unexpected service: %s", service.String())

View File

@ -37,8 +37,8 @@ var (
_ data.Stream = &Item{} _ data.Stream = &Item{}
_ data.StreamInfo = &Item{} _ data.StreamInfo = &Item{}
_ data.StreamModTime = &Item{} _ data.StreamModTime = &Item{}
_ data.Stream = &MetadataItem{} _ data.Stream = &metadata.Item{}
_ data.StreamModTime = &MetadataItem{} _ data.StreamModTime = &metadata.Item{}
) )
// Collection represents a set of OneDrive objects retrieved from M365 // Collection represents a set of OneDrive objects retrieved from M365
@ -120,12 +120,12 @@ func pathToLocation(p path.Path) (*path.Builder, error) {
return nil, nil return nil, nil
} }
odp, err := path.ToOneDrivePath(p) dp, err := path.ToDrivePath(p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return path.Builder{}.Append(odp.Root).Append(odp.Folders...), nil return path.Builder{}.Append(dp.Root).Append(dp.Folders...), nil
} }
// NewCollection creates a Collection // NewCollection creates a Collection
@ -306,53 +306,14 @@ type Item struct {
info details.ItemInfo info details.ItemInfo
} }
func (od *Item) UUID() string {
return od.id
}
func (od *Item) ToReader() io.ReadCloser {
return od.data
}
// Deleted implements an interface function. However, OneDrive items are marked // Deleted implements an interface function. However, OneDrive items are marked
// as deleted by adding them to the exclude list so this can always return // as deleted by adding them to the exclude list so this can always return
// false. // false.
func (od Item) Deleted() bool { func (i Item) Deleted() bool { return false }
return false func (i *Item) UUID() string { return i.id }
} func (i *Item) ToReader() io.ReadCloser { return i.data }
func (i *Item) Info() details.ItemInfo { return i.info }
func (od *Item) Info() details.ItemInfo { func (i *Item) ModTime() time.Time { return i.info.Modified() }
return od.info
}
func (od *Item) ModTime() time.Time {
return od.info.Modified()
}
type MetadataItem struct {
id string
data io.ReadCloser
modTime time.Time
}
func (od *MetadataItem) UUID() string {
return od.id
}
func (od *MetadataItem) ToReader() io.ReadCloser {
return od.data
}
// Deleted implements an interface function. However, OneDrive items are marked
// as deleted by adding them to the exclude list so this can always return
// false.
func (od MetadataItem) Deleted() bool {
return false
}
func (od *MetadataItem) ModTime() time.Time {
return od.modTime
}
// getDriveItemContent fetch drive item's contents with retries // getDriveItemContent fetch drive item's contents with retries
func (oc *Collection) getDriveItemContent( func (oc *Collection) getDriveItemContent(
@ -602,12 +563,12 @@ func (oc *Collection) populateItems(ctx context.Context, errs *fault.Bus) {
return progReader, nil return progReader, nil
}) })
oc.data <- &MetadataItem{ oc.data <- &metadata.Item{
id: metaFileName + metaSuffix, ID: metaFileName + metaSuffix,
data: metaReader, Data: metaReader,
// Metadata file should always use the latest time as // Metadata file should always use the latest time as
// permissions change does not update mod time. // permissions change does not update mod time.
modTime: time.Now(), Mod: time.Now(),
} }
// Item read successfully, add to collection // Item read successfully, add to collection

View File

@ -1,5 +1,10 @@
package metadata package metadata
import (
"io"
"time"
)
// ItemMeta contains metadata about the Item. It gets stored in a // ItemMeta contains metadata about the Item. It gets stored in a
// separate file in kopia // separate file in kopia
type Metadata struct { type Metadata struct {
@ -10,3 +15,17 @@ type Metadata struct {
SharingMode SharingMode `json:"permissionMode,omitempty"` SharingMode SharingMode `json:"permissionMode,omitempty"`
Permissions []Permission `json:"permissions,omitempty"` Permissions []Permission `json:"permissions,omitempty"`
} }
type Item struct {
ID string
Data io.ReadCloser
Mod time.Time
}
// Deleted implements an interface function. However, OneDrive items are marked
// as deleted by adding them to the exclude list so this can always return
// false.
func (i *Item) Deleted() bool { return false }
func (i *Item) UUID() string { return i.ID }
func (i *Item) ToReader() io.ReadCloser { return i.Data }
func (i *Item) ModTime() time.Time { return i.Mod }

View File

@ -22,12 +22,12 @@ func getParentMetadata(
) (metadata.Metadata, error) { ) (metadata.Metadata, error) {
parentMeta, ok := metas[parentPath.String()] parentMeta, ok := metas[parentPath.String()]
if !ok { if !ok {
onedrivePath, err := path.ToOneDrivePath(parentPath) drivePath, err := path.ToDrivePath(parentPath)
if err != nil { if err != nil {
return metadata.Metadata{}, clues.Wrap(err, "invalid restore path") return metadata.Metadata{}, clues.Wrap(err, "invalid restore path")
} }
if len(onedrivePath.Folders) != 0 { if len(drivePath.Folders) != 0 {
return metadata.Metadata{}, clues.Wrap(err, "computing item permissions") return metadata.Metadata{}, clues.Wrap(err, "computing item permissions")
} }
@ -88,7 +88,10 @@ func getCollectionMetadata(
// traversing folderMetas and finding the first item with custom // traversing folderMetas and finding the first item with custom
// permissions. folderMetas is expected to have all the parent // permissions. folderMetas is expected to have all the parent
// directory metas for this to work. // directory metas for this to work.
func computeParentPermissions(itemPath path.Path, folderMetas map[string]metadata.Metadata) (metadata.Metadata, error) { func computeParentPermissions(
itemPath path.Path,
folderMetas map[string]metadata.Metadata,
) (metadata.Metadata, error) {
var ( var (
parent path.Path parent path.Path
meta metadata.Metadata meta metadata.Metadata
@ -105,12 +108,12 @@ func computeParentPermissions(itemPath path.Path, folderMetas map[string]metadat
return metadata.Metadata{}, clues.New("getting parent") return metadata.Metadata{}, clues.New("getting parent")
} }
onedrivePath, err := path.ToOneDrivePath(parent) drivePath, err := path.ToDrivePath(parent)
if err != nil { if err != nil {
return metadata.Metadata{}, clues.New("get parent path") return metadata.Metadata{}, clues.New("get parent path")
} }
if len(onedrivePath.Folders) == 0 { if len(drivePath.Folders) == 0 {
return metadata.Metadata{}, nil return metadata.Metadata{}, nil
} }

View File

@ -23,27 +23,34 @@ func TestPermissionsUnitTestSuite(t *testing.T) {
} }
func (suite *PermissionsUnitTestSuite) TestComputeParentPermissions() { func (suite *PermissionsUnitTestSuite) TestComputeParentPermissions() {
runComputeParentPermissionsTest(suite, path.OneDriveService, path.FilesCategory, "user")
}
func runComputeParentPermissionsTest(
suite *PermissionsUnitTestSuite,
service path.ServiceType,
category path.CategoryType,
resourceOwner string,
) {
entryPath := fmt.Sprintf(rootDrivePattern, "drive-id") + "/level0/level1/level2/entry" entryPath := fmt.Sprintf(rootDrivePattern, "drive-id") + "/level0/level1/level2/entry"
rootEntryPath := fmt.Sprintf(rootDrivePattern, "drive-id") + "/entry" rootEntryPath := fmt.Sprintf(rootDrivePattern, "drive-id") + "/entry"
entry, err := path.Build( entry, err := path.Build(
"tenant", "tenant",
"user", resourceOwner,
path.OneDriveService, service,
path.FilesCategory, category,
false, false,
strings.Split(entryPath, "/")..., strings.Split(entryPath, "/")...)
)
require.NoError(suite.T(), err, "creating path") require.NoError(suite.T(), err, "creating path")
rootEntry, err := path.Build( rootEntry, err := path.Build(
"tenant", "tenant",
"user", resourceOwner,
path.OneDriveService, service,
path.FilesCategory, category,
false, false,
strings.Split(rootEntryPath, "/")..., strings.Split(rootEntryPath, "/")...)
)
require.NoError(suite.T(), err, "creating path") require.NoError(suite.T(), err, "creating path")
level2, err := entry.Dir() level2, err := entry.Dir()

View File

@ -152,7 +152,7 @@ func RestoreCollection(
ctx, end := diagnostics.Span(ctx, "gc:oneDrive:restoreCollection", diagnostics.Label("path", directory)) ctx, end := diagnostics.Span(ctx, "gc:oneDrive:restoreCollection", diagnostics.Label("path", directory))
defer end() defer end()
drivePath, err := path.ToOneDrivePath(directory) drivePath, err := path.ToDrivePath(directory)
if err != nil { if err != nil {
return metrics, clues.Wrap(err, "creating drive path").WithClues(ctx) return metrics, clues.Wrap(err, "creating drive path").WithClues(ctx)
} }
@ -791,12 +791,12 @@ func AugmentRestorePaths(backupVersion int, paths []path.Path) ([]path.Path, err
return nil, err return nil, err
} }
onedrivePath, err := path.ToOneDrivePath(np) drivePath, err := path.ToDrivePath(np)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(onedrivePath.Folders) == 0 { if len(drivePath.Folders) == 0 {
break break
} }

View File

@ -522,9 +522,9 @@ func (de Entry) ToLocationIDer(backupVersion int) (LocationIDer, error) {
return nil, clues.Wrap(err, "getting item RepoRef") return nil, clues.Wrap(err, "getting item RepoRef")
} }
p, err := path.ToOneDrivePath(rr) p, err := path.ToDrivePath(rr)
if err != nil { if err != nil {
return nil, clues.New("converting RepoRef to OneDrive path") return nil, clues.New("converting RepoRef to drive path")
} }
baseLoc := path.Builder{}.Append(p.Root).Append(p.Folders...) baseLoc := path.Builder{}.Append(p.Root).Append(p.Folders...)

View File

@ -8,19 +8,21 @@ import "github.com/alcionai/clues"
// //
// driveID is `b!X_8Z2zuXpkKkXZsr7gThk9oJpuj0yXVGnK5_VjRRPK-q725SX_8ZQJgFDK8PlFxA` and // driveID is `b!X_8Z2zuXpkKkXZsr7gThk9oJpuj0yXVGnK5_VjRRPK-q725SX_8ZQJgFDK8PlFxA` and
// folders[] is []{"Folder1", "Folder2"} // folders[] is []{"Folder1", "Folder2"}
//
// Should be compatible with all drive-based services (ex: oneDrive, sharePoint Libraries, etc)
type DrivePath struct { type DrivePath struct {
DriveID string DriveID string
Root string Root string
Folders Elements Folders Elements
} }
func ToOneDrivePath(p Path) (*DrivePath, error) { func ToDrivePath(p Path) (*DrivePath, error) {
folders := p.Folders() folders := p.Folders()
// Must be at least `drives/<driveID>/root:` // Must be at least `drives/<driveID>/root:`
if len(folders) < 3 { if len(folders) < 3 {
return nil, clues. return nil, clues.
New("folder path doesn't match expected format for OneDrive items"). New("folder path doesn't match expected format for Drive items").
With("path_folders", p.Folder(false)) With("path_folders", p.Folder(false))
} }
@ -29,7 +31,7 @@ func ToOneDrivePath(p Path) (*DrivePath, error) {
// Returns the path to the folder within the drive (i.e. under `root:`) // Returns the path to the folder within the drive (i.e. under `root:`)
func GetDriveFolderPath(p Path) (string, error) { func GetDriveFolderPath(p Path) (string, error) {
drivePath, err := ToOneDrivePath(p) drivePath, err := ToDrivePath(p)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -54,7 +54,7 @@ func (suite *OneDrivePathSuite) Test_ToOneDrivePath() {
p, err := path.Build("tenant", "user", path.OneDriveService, path.FilesCategory, false, tt.pathElements...) p, err := path.Build("tenant", "user", path.OneDriveService, path.FilesCategory, false, tt.pathElements...)
require.NoError(suite.T(), err, clues.ToCore(err)) require.NoError(suite.T(), err, clues.ToCore(err))
got, err := path.ToOneDrivePath(p) got, err := path.ToDrivePath(p)
tt.errCheck(t, err) tt.errCheck(t, err)
if err != nil { if err != nil {
return return