bingenpath.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. package pathing
  2. import (
  3. "fmt"
  4. "path"
  5. "time"
  6. "github.com/opencost/opencost/core/pkg/exporter/pathing/pathutils"
  7. "github.com/opencost/opencost/core/pkg/opencost"
  8. "github.com/opencost/opencost/core/pkg/pipelines"
  9. "github.com/opencost/opencost/core/pkg/util/timeutil"
  10. )
  11. const (
  12. DefaultRootDir string = "federated"
  13. BaseStorageDir string = "etl/bingen"
  14. FinOpsAgentAppID string = "finops-agent"
  15. )
  16. // BingenStoragePathFormatter is an implementation of the StoragePathFormatter interface for
  17. // a cluster separated storage path of the format:
  18. //
  19. // <root>/<cluster>/etl/bingen/<pipeline>/<resolution>/<epoch-start>-<epoch-end>
  20. type BingenStoragePathFormatter struct {
  21. rootDir string
  22. clusterId string
  23. pipeline string
  24. resolution string
  25. }
  26. func NewDefaultStoragePathFormatter(clusterId, pipeline string, resolution *time.Duration) (StoragePathFormatter[opencost.Window], error) {
  27. res := "."
  28. if resolution != nil {
  29. res = timeutil.FormatStoreResolution(*resolution)
  30. }
  31. // KubeModel uses a distinct pathing pattern which breaks with the original
  32. // Allocations and Assets bingen pathing.
  33. if pipeline == pipelines.KubeModelPipelineName {
  34. return NewKubeModelStoragePathFormatter(FinOpsAgentAppID, clusterId, res)
  35. }
  36. return NewBingenStoragePathFormatter(DefaultRootDir, clusterId, pipeline, res)
  37. }
  38. // NewBingenStoragePathFormatter creates a StoragePathFormatter for a cluster separated storage path
  39. // with the given root directory, cluster id, pipeline, and resolution. To omit the resolution directory
  40. // structure, provide a `nil` resolution.
  41. func NewBingenStoragePathFormatter(rootDir, clusterId, pipeline, resolution string) (StoragePathFormatter[opencost.Window], error) {
  42. if clusterId == "" {
  43. return nil, fmt.Errorf("cluster id cannot be empty")
  44. }
  45. if pipeline == "" {
  46. return nil, fmt.Errorf("pipeline cannot be empty")
  47. }
  48. return &BingenStoragePathFormatter{
  49. rootDir: rootDir,
  50. clusterId: clusterId,
  51. pipeline: pipeline,
  52. resolution: resolution,
  53. }, nil
  54. }
  55. // RootDir returns the root directory of the storage path formatter.
  56. func (bsf *BingenStoragePathFormatter) RootDir() string {
  57. return bsf.rootDir
  58. }
  59. // Dir returns the director that files will be placed in
  60. func (bsf *BingenStoragePathFormatter) Dir() string {
  61. return path.Join(
  62. bsf.rootDir,
  63. bsf.clusterId,
  64. BaseStorageDir,
  65. bsf.pipeline,
  66. bsf.resolution,
  67. )
  68. }
  69. // ToFullPath returns the full path to a file name within the storage directory using the format:
  70. //
  71. // <root>/<cluster>/etl/bingen/<pipeline>/<resolution>/<prefix>.<start-epoch>-<end-epoch>
  72. func (bsf *BingenStoragePathFormatter) ToFullPath(prefix string, window opencost.Window, fileExt string) string {
  73. fileName := toBingenFileName(prefix, window, fileExt)
  74. return path.Join(
  75. bsf.rootDir,
  76. bsf.clusterId,
  77. BaseStorageDir,
  78. bsf.pipeline,
  79. bsf.resolution,
  80. fileName,
  81. )
  82. }
  83. // toBingenFileName formats the file name as <prefix>.<start-epoch>-<end-epoch> if a prefix is non-empty.
  84. // If prefix is an empty string, then just the format <start-epoch>-<end-epoch> is returned.
  85. func toBingenFileName(prefix string, window opencost.Window, fileExt string) string {
  86. start, end := derefTimeOrZero(window.Start()), derefTimeOrZero(window.End())
  87. suffix := pathutils.FormatEpochRange(start, end)
  88. if fileExt != "" {
  89. suffix = fmt.Sprintf("%s.%s", suffix, fileExt)
  90. }
  91. if prefix == "" {
  92. return suffix
  93. }
  94. return fmt.Sprintf("%s.%s", prefix, suffix)
  95. }
  96. // derefTimeOrZero dereferences a time.Time pointer and returns the zero value if the pointer is nil.
  97. // This prevents nil pointer dereference errors when using windows. This is mostly an assertion, as
  98. // generally windows for pathing will be pre-validated.
  99. func derefTimeOrZero(t *time.Time) time.Time {
  100. if t == nil {
  101. return time.Time{}
  102. }
  103. return *t
  104. }