storage.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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. // WriteStream creates a new relative path and returns the `io.WriteCloser` that can be used to
  44. // write into the storage path. Ensure that Close() is run on the returned writer.
  45. WriteStream(path string) (io.WriteCloser, error)
  46. // Remove uses the relative path of the storage combined with the provided path to
  47. // remove a file from storage permanently.
  48. Remove(path string) error
  49. // Exists uses the relative path of the storage combined with the provided path to
  50. // determine if the file exists.
  51. Exists(path string) (bool, error)
  52. // List uses the relative path of the storage combined with the provided path to return
  53. // storage information for the files.
  54. List(path string) ([]*StorageInfo, error)
  55. // ListDirectories uses the relative path of the storage combined with the provided path
  56. // to return storage information for only directories contained along the path. This
  57. // functions as List, but returns storage information for only directories.
  58. ListDirectories(path string) ([]*StorageInfo, error)
  59. }
  60. // Validate uses the provided storage implementation to write a test file to the store, followed by a removal.
  61. func Validate(storage Storage, validateWriteDelete bool) error {
  62. const testPath = "tmp/test.txt"
  63. const testContent = "test"
  64. log.Debug("validating storage")
  65. // attempt to read a path
  66. _, err := storage.Exists(testPath)
  67. if err != nil {
  68. return errors.Wrap(err, "Failed to check if path exists")
  69. }
  70. // attempt to list a path
  71. _, err = storage.List(testPath)
  72. if err != nil {
  73. return errors.Wrap(err, "Failed to list path")
  74. }
  75. if validateWriteDelete {
  76. // attempt to write a path
  77. err = storage.Write(testPath, []byte(testContent))
  78. if err != nil {
  79. return errors.Wrap(err, "Failed to write data to storage")
  80. }
  81. }
  82. // attempt to read the path
  83. // If we are not validating write and delete, the file won't exist since we never wrote it.
  84. // 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.
  85. data, err := storage.Read(testPath)
  86. if err != nil && !strings.Contains(err.Error(), "exist") && !strings.Contains(err.Error(), "404") {
  87. return errors.Wrap(err, "Failed to read data from storage")
  88. }
  89. if validateWriteDelete {
  90. if string(data) != testContent {
  91. return errors.New("Failed to read the expected data from storage")
  92. }
  93. // delete the path
  94. err = storage.Remove(testPath)
  95. if err != nil {
  96. return errors.Wrap(err, "Failed to remove data from storage")
  97. }
  98. }
  99. return nil
  100. }
  101. // IsNotExist returns true if the error provided from a storage object is DoesNotExist
  102. func IsNotExist(err error) bool {
  103. if err == nil {
  104. return false
  105. }
  106. return err.Error() == DoesNotExistError.Error()
  107. }