mirror of https://github.com/databricks/cli.git
Make filer.Filer return fs.DirEntry from ReadDir (#415)
## Changes This allows for compatibility with the stdlib functions in io/fs. ## Tests Integration tests pass.
This commit is contained in:
parent
27df4e765c
commit
42cd8daee0
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -75,6 +76,7 @@ func runFilerReadWriteTest(t *testing.T, ctx context.Context, f filer.Filer) {
|
||||||
|
|
||||||
func runFilerReadDirTest(t *testing.T, ctx context.Context, f filer.Filer) {
|
func runFilerReadDirTest(t *testing.T, ctx context.Context, f filer.Filer) {
|
||||||
var err error
|
var err error
|
||||||
|
var info fs.FileInfo
|
||||||
|
|
||||||
// We start with an empty directory.
|
// We start with an empty directory.
|
||||||
entries, err := f.ReadDir(ctx, ".")
|
entries, err := f.ReadDir(ctx, ".")
|
||||||
|
@ -105,23 +107,32 @@ func runFilerReadDirTest(t *testing.T, ctx context.Context, f filer.Filer) {
|
||||||
entries, err = f.ReadDir(ctx, ".")
|
entries, err = f.ReadDir(ctx, ".")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, entries, 2)
|
assert.Len(t, entries, 2)
|
||||||
assert.Equal(t, "dir", entries[0].Name)
|
assert.Equal(t, "dir", entries[0].Name())
|
||||||
assert.Equal(t, "hello.txt", entries[1].Name)
|
assert.True(t, entries[0].IsDir())
|
||||||
assert.Greater(t, entries[1].ModTime.Unix(), int64(0))
|
assert.Equal(t, "hello.txt", entries[1].Name())
|
||||||
|
assert.False(t, entries[1].IsDir())
|
||||||
|
info, err = entries[1].Info()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Greater(t, info.ModTime().Unix(), int64(0))
|
||||||
|
|
||||||
// Expect two entries in the directory.
|
// Expect two entries in the directory.
|
||||||
entries, err = f.ReadDir(ctx, "/dir")
|
entries, err = f.ReadDir(ctx, "/dir")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, entries, 2)
|
assert.Len(t, entries, 2)
|
||||||
assert.Equal(t, "a", entries[0].Name)
|
assert.Equal(t, "a", entries[0].Name())
|
||||||
assert.Equal(t, "world.txt", entries[1].Name)
|
assert.True(t, entries[0].IsDir())
|
||||||
assert.Greater(t, entries[1].ModTime.Unix(), int64(0))
|
assert.Equal(t, "world.txt", entries[1].Name())
|
||||||
|
assert.False(t, entries[1].IsDir())
|
||||||
|
info, err = entries[1].Info()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Greater(t, info.ModTime().Unix(), int64(0))
|
||||||
|
|
||||||
// Expect a single entry in the nested path.
|
// Expect a single entry in the nested path.
|
||||||
entries, err = f.ReadDir(ctx, "/dir/a/b")
|
entries, err = f.ReadDir(ctx, "/dir/a/b")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, entries, 1)
|
assert.Len(t, entries, 1)
|
||||||
assert.Equal(t, "c", entries[0].Name)
|
assert.Equal(t, "c", entries[0].Name())
|
||||||
|
assert.True(t, entries[0].IsDir())
|
||||||
}
|
}
|
||||||
|
|
||||||
func temporaryWorkspaceDir(t *testing.T, w *databricks.WorkspaceClient) string {
|
func temporaryWorkspaceDir(t *testing.T, w *databricks.WorkspaceClient) string {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -15,6 +16,52 @@ import (
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Type that implements fs.DirEntry for DBFS.
|
||||||
|
type dbfsDirEntry struct {
|
||||||
|
dbfsFileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry dbfsDirEntry) Type() fs.FileMode {
|
||||||
|
typ := fs.ModePerm
|
||||||
|
if entry.fi.IsDir {
|
||||||
|
typ |= fs.ModeDir
|
||||||
|
}
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry dbfsDirEntry) Info() (fs.FileInfo, error) {
|
||||||
|
return entry.dbfsFileInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type that implements fs.FileInfo for DBFS.
|
||||||
|
type dbfsFileInfo struct {
|
||||||
|
fi files.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info dbfsFileInfo) Name() string {
|
||||||
|
return path.Base(info.fi.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info dbfsFileInfo) Size() int64 {
|
||||||
|
return info.fi.FileSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info dbfsFileInfo) Mode() fs.FileMode {
|
||||||
|
return fs.ModePerm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info dbfsFileInfo) ModTime() time.Time {
|
||||||
|
return time.UnixMilli(info.fi.ModificationTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info dbfsFileInfo) IsDir() bool {
|
||||||
|
return info.fi.IsDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info dbfsFileInfo) Sys() any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DbfsClient implements the [Filer] interface for the DBFS backend.
|
// DbfsClient implements the [Filer] interface for the DBFS backend.
|
||||||
type DbfsClient struct {
|
type DbfsClient struct {
|
||||||
workspaceClient *databricks.WorkspaceClient
|
workspaceClient *databricks.WorkspaceClient
|
||||||
|
@ -152,7 +199,7 @@ func (w *DbfsClient) Delete(ctx context.Context, name string) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *DbfsClient) ReadDir(ctx context.Context, name string) ([]FileInfo, error) {
|
func (w *DbfsClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {
|
||||||
absPath, err := w.root.Join(name)
|
absPath, err := w.root.Join(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -175,17 +222,13 @@ func (w *DbfsClient) ReadDir(ctx context.Context, name string) ([]FileInfo, erro
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := make([]FileInfo, len(res.Files))
|
info := make([]fs.DirEntry, len(res.Files))
|
||||||
for i, v := range res.Files {
|
for i, v := range res.Files {
|
||||||
info[i] = FileInfo{
|
info[i] = dbfsDirEntry{dbfsFileInfo: dbfsFileInfo{fi: v}}
|
||||||
Name: path.Base(v.Path),
|
|
||||||
Size: v.FileSize,
|
|
||||||
ModTime: time.UnixMilli(v.ModificationTime),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by name for parity with os.ReadDir.
|
// Sort by name for parity with os.ReadDir.
|
||||||
sort.Slice(info, func(i, j int) bool { return info[i].Name < info[j].Name })
|
sort.Slice(info, func(i, j int) bool { return info[i].Name() < info[j].Name() })
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"io/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WriteMode int
|
type WriteMode int
|
||||||
|
@ -14,22 +14,6 @@ const (
|
||||||
CreateParentDirectories = iota << 1
|
CreateParentDirectories = iota << 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileInfo abstracts over file information from different file systems.
|
|
||||||
// Inspired by https://pkg.go.dev/io/fs#FileInfo.
|
|
||||||
type FileInfo struct {
|
|
||||||
// The type of the file in workspace.
|
|
||||||
Type string
|
|
||||||
|
|
||||||
// Base name.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Size in bytes.
|
|
||||||
Size int64
|
|
||||||
|
|
||||||
// Modification time.
|
|
||||||
ModTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileAlreadyExistsError struct {
|
type FileAlreadyExistsError struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
@ -68,7 +52,7 @@ type Filer interface {
|
||||||
Delete(ctx context.Context, path string) error
|
Delete(ctx context.Context, path string) error
|
||||||
|
|
||||||
// Return contents of directory at `path`.
|
// Return contents of directory at `path`.
|
||||||
ReadDir(ctx context.Context, path string) ([]FileInfo, error)
|
ReadDir(ctx context.Context, path string) ([]fs.DirEntry, error)
|
||||||
|
|
||||||
// Creates directory at `path`, creating any intermediate directories as required.
|
// Creates directory at `path`, creating any intermediate directories as required.
|
||||||
Mkdir(ctx context.Context, path string) error
|
Mkdir(ctx context.Context, path string) error
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
@ -20,6 +21,53 @@ import (
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Type that implements fs.DirEntry for WSFS.
|
||||||
|
type wsfsDirEntry struct {
|
||||||
|
wsfsFileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry wsfsDirEntry) Type() fs.FileMode {
|
||||||
|
return entry.wsfsFileInfo.Mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry wsfsDirEntry) Info() (fs.FileInfo, error) {
|
||||||
|
return entry.wsfsFileInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type that implements fs.FileInfo for WSFS.
|
||||||
|
type wsfsFileInfo struct {
|
||||||
|
oi workspace.ObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info wsfsFileInfo) Name() string {
|
||||||
|
return path.Base(info.oi.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info wsfsFileInfo) Size() int64 {
|
||||||
|
return info.oi.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info wsfsFileInfo) Mode() fs.FileMode {
|
||||||
|
switch info.oi.ObjectType {
|
||||||
|
case workspace.ObjectTypeDirectory:
|
||||||
|
return fs.ModeDir
|
||||||
|
default:
|
||||||
|
return fs.ModePerm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info wsfsFileInfo) ModTime() time.Time {
|
||||||
|
return time.UnixMilli(info.oi.ModifiedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info wsfsFileInfo) IsDir() bool {
|
||||||
|
return info.oi.ObjectType == workspace.ObjectTypeDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info wsfsFileInfo) Sys() any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// WorkspaceFilesClient implements the files-in-workspace API.
|
// WorkspaceFilesClient implements the files-in-workspace API.
|
||||||
|
|
||||||
// NOTE: This API is available for files under /Repos if a workspace has files-in-repos enabled.
|
// NOTE: This API is available for files under /Repos if a workspace has files-in-repos enabled.
|
||||||
|
@ -165,7 +213,7 @@ func (w *WorkspaceFilesClient) Delete(ctx context.Context, name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WorkspaceFilesClient) ReadDir(ctx context.Context, name string) ([]FileInfo, error) {
|
func (w *WorkspaceFilesClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {
|
||||||
absPath, err := w.root.Join(name)
|
absPath, err := w.root.Join(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -189,18 +237,13 @@ func (w *WorkspaceFilesClient) ReadDir(ctx context.Context, name string) ([]File
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := make([]FileInfo, len(objects))
|
info := make([]fs.DirEntry, len(objects))
|
||||||
for i, v := range objects {
|
for i, v := range objects {
|
||||||
info[i] = FileInfo{
|
info[i] = wsfsDirEntry{wsfsFileInfo{oi: v}}
|
||||||
Type: string(v.ObjectType),
|
|
||||||
Name: path.Base(v.Path),
|
|
||||||
Size: v.Size,
|
|
||||||
ModTime: time.UnixMilli(v.ModifiedAt),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by name for parity with os.ReadDir.
|
// Sort by name for parity with os.ReadDir.
|
||||||
sort.Slice(info, func(i, j int) bool { return info[i].Name < info[j].Name })
|
sort.Slice(info, func(i, j int) bool { return info[i].Name() < info[j].Name() })
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue