storage.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. package storage
  2. import (
  3. "io"
  4. "os"
  5. "strings"
  6. "time"
  7. "github.com/opencost/opencost/core/pkg/log"
  8. "github.com/pkg/errors"
  9. )
  10. // DirDelim is the delimiter used to model a directory structure in an object store bucket.
  11. const DirDelim = "/"
  12. // DoesNotExistError is used as a generic error to return when a target path does not
  13. // exist in storage. Equivalent to os.ErrorNotExist such that it will work with os.IsNotExist(err)
  14. var DoesNotExistError = os.ErrNotExist
  15. // StorageInfo is a data object containing basic information about the path in storage.
  16. type StorageInfo struct {
  17. Name string // base name of the file
  18. Size int64 // length in bytes for regular files
  19. ModTime time.Time // modification time
  20. }
  21. // Storage provides an API for storing binary data
  22. type Storage interface {
  23. // StorageType returns a string identifier for the type of storage used by the implementation.
  24. StorageType() StorageType
  25. // FullPath returns the storage working path combined with the path provided
  26. FullPath(path string) string
  27. // Stat returns the StorageStats for the specific path.
  28. Stat(path string) (*StorageInfo, error)
  29. // Read uses the relative path of the storage combined with the provided path to
  30. // read the contents.
  31. Read(path string) ([]byte, error)
  32. // ReadStream returns an io.ReadCloser for the specified path. Implementations should
  33. // stream incrementally when possible to avoid loading entire objects into memory.
  34. ReadStream(path string) (io.ReadCloser, error)
  35. // ReadToLocalFile writes the specified file at path to destPath on the local file system.
  36. // Implementations may stream data to minimize RAM usage, but some backends may still buffer
  37. // data in memory depending on their capabilities. It is up to the caller to clean up the
  38. // local file when finished.
  39. ReadToLocalFile(path, destPath string) error
  40. // Write uses the relative path of the storage combined with the provided path
  41. // to write a new file or overwrite an existing file.
  42. Write(path string, data []byte) error
  43. // Remove uses the relative path of the storage combined with the provided path to
  44. // remove a file from storage permanently.
  45. Remove(path string) error
  46. // Exists uses the relative path of the storage combined with the provided path to
  47. // determine if the file exists.
  48. Exists(path string) (bool, error)
  49. // List uses the relative path of the storage combined with the provided path to return
  50. // storage information for the files.
  51. List(path string) ([]*StorageInfo, error)
  52. // ListDirectories uses the relative path of the storage combined with the provided path
  53. // to return storage information for only directories contained along the path. This
  54. // functions as List, but returns storage information for only directories.
  55. ListDirectories(path string) ([]*StorageInfo, error)
  56. }
  57. // Validate uses the provided storage implementation to write a test file to the store, followed by a removal.
  58. func Validate(storage Storage, validateWriteDelete bool) error {
  59. const testPath = "tmp/test.txt"
  60. const testContent = "test"
  61. log.Debug("validating storage")
  62. // attempt to read a path
  63. _, err := storage.Exists(testPath)
  64. if err != nil {
  65. return errors.Wrap(err, "Failed to check if path exists")
  66. }
  67. // attempt to list a path
  68. _, err = storage.List(testPath)
  69. if err != nil {
  70. return errors.Wrap(err, "Failed to list path")
  71. }
  72. if validateWriteDelete {
  73. // attempt to write a path
  74. err = storage.Write(testPath, []byte(testContent))
  75. if err != nil {
  76. return errors.Wrap(err, "Failed to write data to storage")
  77. }
  78. }
  79. // attempt to read the path
  80. // If we are not validating write and delete, the file won't exist since we never wrote it.
  81. // We only want to check read permissions, so ignore errors with "exist" and "404" in the error message to bypass the file not exist error.
  82. data, err := storage.Read(testPath)
  83. if err != nil && !strings.Contains(err.Error(), "exist") && !strings.Contains(err.Error(), "404") {
  84. return errors.Wrap(err, "Failed to read data from storage")
  85. }
  86. if validateWriteDelete {
  87. if string(data) != testContent {
  88. return errors.New("Failed to read the expected data from storage")
  89. }
  90. // delete the path
  91. err = storage.Remove(testPath)
  92. if err != nil {
  93. return errors.Wrap(err, "Failed to remove data from storage")
  94. }
  95. }
  96. return nil
  97. }
  98. // IsNotExist returns true if the error provided from a storage object is DoesNotExist
  99. func IsNotExist(err error) bool {
  100. if err == nil {
  101. return false
  102. }
  103. return err.Error() == DoesNotExistError.Error()
  104. }