github.com/databricks/cli@v0.203.0/libs/filer/local_client.go (about) 1 package filer 2 3 import ( 4 "context" 5 "io" 6 "io/fs" 7 "os" 8 "path/filepath" 9 10 "golang.org/x/exp/slices" 11 ) 12 13 // LocalClient implements the [Filer] interface for the local filesystem. 14 type LocalClient struct { 15 // File operations will be relative to this path. 16 root localRootPath 17 } 18 19 func NewLocalClient(root string) (Filer, error) { 20 return &LocalClient{ 21 root: NewLocalRootPath(root), 22 }, nil 23 } 24 25 func (w *LocalClient) Write(ctx context.Context, name string, reader io.Reader, mode ...WriteMode) error { 26 absPath, err := w.root.Join(name) 27 if err != nil { 28 return err 29 } 30 31 flags := os.O_WRONLY | os.O_CREATE 32 if slices.Contains(mode, OverwriteIfExists) { 33 flags |= os.O_TRUNC 34 } else { 35 flags |= os.O_EXCL 36 } 37 38 absPath = filepath.FromSlash(absPath) 39 f, err := os.OpenFile(absPath, flags, 0644) 40 if os.IsNotExist(err) && slices.Contains(mode, CreateParentDirectories) { 41 // Create parent directories if they don't exist. 42 err = os.MkdirAll(filepath.Dir(absPath), 0755) 43 if err != nil { 44 return err 45 } 46 // Try again. 47 f, err = os.OpenFile(absPath, flags, 0644) 48 } 49 50 if err != nil { 51 switch { 52 case os.IsNotExist(err): 53 return NoSuchDirectoryError{path: absPath} 54 case os.IsExist(err): 55 return FileAlreadyExistsError{path: absPath} 56 default: 57 return err 58 } 59 } 60 61 _, err = io.Copy(f, reader) 62 cerr := f.Close() 63 if err == nil { 64 err = cerr 65 } 66 67 return err 68 69 } 70 71 func (w *LocalClient) Read(ctx context.Context, name string) (io.ReadCloser, error) { 72 absPath, err := w.root.Join(name) 73 if err != nil { 74 return nil, err 75 } 76 77 // This stat call serves two purposes: 78 // 1. Checks file at path exists, and throws an error if it does not 79 // 2. Allows us to error out if the path is a directory 80 absPath = filepath.FromSlash(absPath) 81 stat, err := os.Stat(absPath) 82 if err != nil { 83 if os.IsNotExist(err) { 84 return nil, FileDoesNotExistError{path: absPath} 85 } 86 return nil, err 87 } 88 89 if stat.IsDir() { 90 return nil, NotAFile{path: absPath} 91 } 92 93 return os.Open(absPath) 94 } 95 96 func (w *LocalClient) Delete(ctx context.Context, name string, mode ...DeleteMode) error { 97 absPath, err := w.root.Join(name) 98 if err != nil { 99 return err 100 } 101 102 // Illegal to delete the root path. 103 if absPath == w.root.rootPath { 104 return CannotDeleteRootError{} 105 } 106 107 absPath = filepath.FromSlash(absPath) 108 err = os.Remove(absPath) 109 110 // Return early on success. 111 if err == nil { 112 return nil 113 } 114 115 if os.IsNotExist(err) { 116 return FileDoesNotExistError{path: absPath} 117 } 118 119 if os.IsExist(err) { 120 if slices.Contains(mode, DeleteRecursively) { 121 return os.RemoveAll(absPath) 122 } 123 return DirectoryNotEmptyError{path: absPath} 124 } 125 126 return err 127 } 128 129 func (w *LocalClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) { 130 absPath, err := w.root.Join(name) 131 if err != nil { 132 return nil, err 133 } 134 135 absPath = filepath.FromSlash(absPath) 136 stat, err := os.Stat(absPath) 137 if err != nil { 138 if os.IsNotExist(err) { 139 return nil, NoSuchDirectoryError{path: absPath} 140 } 141 return nil, err 142 } 143 144 if !stat.IsDir() { 145 return nil, NotADirectory{path: absPath} 146 } 147 148 return os.ReadDir(absPath) 149 } 150 151 func (w *LocalClient) Mkdir(ctx context.Context, name string) error { 152 dirPath, err := w.root.Join(name) 153 if err != nil { 154 return err 155 } 156 157 dirPath = filepath.FromSlash(dirPath) 158 return os.MkdirAll(dirPath, 0755) 159 } 160 161 func (w *LocalClient) Stat(ctx context.Context, name string) (fs.FileInfo, error) { 162 absPath, err := w.root.Join(name) 163 if err != nil { 164 return nil, err 165 } 166 167 absPath = filepath.FromSlash(absPath) 168 stat, err := os.Stat(absPath) 169 if os.IsNotExist(err) { 170 return nil, FileDoesNotExistError{path: absPath} 171 } 172 return stat, err 173 }