package storage import ( gofs "io/fs" "io/ioutil" "os" gopath "path" "path/filepath" "github.com/kubecost/cost-model/pkg/util/fileutil" "github.com/pkg/errors" ) // FileStorage leverages the file system to write data to disk. type FileStorage struct { baseDir string } // NewFileStorage returns a new storage API which leverages the file system. func NewFileStorage(baseDir string) Storage { return &FileStorage{baseDir} } // FullPath returns the storage working path combined with the path provided func (fs *FileStorage) FullPath(path string) string { return gopath.Join(fs.baseDir, path) } // Stat returns the StorageStats for the specific path. func (fs *FileStorage) Stat(path string) (*StorageInfo, error) { f := gopath.Join(fs.baseDir, path) st, err := os.Stat(f) if err != nil { if os.IsNotExist(err) { return nil, DoesNotExistError } return nil, errors.Wrap(err, "Failed to stat file") } return FileToStorageInfo(st), nil } // List uses the relative path of the storage combined with the provided path to return // storage information for the files. func (fs *FileStorage) List(path string) ([]*StorageInfo, error) { p := gopath.Join(fs.baseDir, path) // Read files in the backup path files, err := ioutil.ReadDir(p) if err != nil { return nil, err } return FilesToStorageInfo(files), nil } // Read uses the relative path of the storage combined with the provided path to // read the contents. func (fs *FileStorage) Read(path string) ([]byte, error) { f := gopath.Join(fs.baseDir, path) b, err := ioutil.ReadFile(f) if err != nil { if os.IsNotExist(err) { return nil, DoesNotExistError } return nil, errors.Wrap(err, "Failed to read file") } return b, nil } // Write uses the relative path of the storage combined with the provided path // to write a new file or overwrite an existing file. func (fs *FileStorage) Write(path string, data []byte) error { f := fs.prepare(path) err := ioutil.WriteFile(f, data, os.ModePerm) if err != nil { return errors.Wrap(err, "Failed to write file") } return nil } // Remove uses the relative path of the storage combined with the provided path to // remove a file from storage permanently. func (fs *FileStorage) Remove(path string) error { f := gopath.Join(fs.baseDir, path) err := os.Remove(f) if err != nil { if os.IsNotExist(err) { return DoesNotExistError } return errors.Wrap(err, "Failed to remove file") } return nil } // Exists uses the relative path of the storage combined with the provided path to // determine if the file exists. func (fs *FileStorage) Exists(path string) (bool, error) { f := gopath.Join(fs.baseDir, path) return fileutil.FileExists(f) } // prepare checks to see if the directory being written to should be created before writing // the file, and then returns the correct full path. func (fs *FileStorage) prepare(path string) string { f := gopath.Join(fs.baseDir, path) dir := filepath.Dir(f) if _, e := os.Stat(dir); e != nil && os.IsNotExist(e) { os.MkdirAll(dir, os.ModePerm) } return f } // FilesToStorageInfo maps a []fs.FileInfo to []*storage.StorageInfo func FilesToStorageInfo(fileInfo []gofs.FileInfo) []*StorageInfo { var stats []*StorageInfo for _, info := range fileInfo { stats = append(stats, FileToStorageInfo(info)) } return stats } // FileToStorageInfo maps a fs.FileInfo to *storage.StorageInfo func FileToStorageInfo(fileInfo gofs.FileInfo) *StorageInfo { return &StorageInfo{ Name: fileInfo.Name(), Size: fileInfo.Size(), ModTime: fileInfo.ModTime(), } }