assetprops.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. package opencost
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/opencost/opencost/core/pkg/util/promutil"
  6. )
  7. // AssetProperty is a kind of property belonging to an Asset
  8. type AssetProperty string
  9. const (
  10. // AssetNilProp is the zero-value of AssetProperty
  11. AssetNilProp AssetProperty = ""
  12. // AssetAccountProp describes the account of the Asset
  13. AssetAccountProp AssetProperty = "account"
  14. // AssetCategoryProp describes the category of the Asset
  15. AssetCategoryProp AssetProperty = "category"
  16. // AssetClusterProp describes the cluster of the Asset
  17. AssetClusterProp AssetProperty = "cluster"
  18. // AssetNameProp describes the name of the Asset
  19. AssetNameProp AssetProperty = "name"
  20. // AssetNodeProp describes the node of the Asset
  21. AssetNodeProp AssetProperty = "node"
  22. // AssetProjectProp describes the project of the Asset
  23. AssetProjectProp AssetProperty = "project"
  24. // AssetProviderProp describes the provider of the Asset
  25. AssetProviderProp AssetProperty = "provider"
  26. // AssetProviderIDProp describes the providerID of the Asset
  27. AssetProviderIDProp AssetProperty = "providerID"
  28. // AssetServiceProp describes the service of the Asset
  29. AssetServiceProp AssetProperty = "service"
  30. // AssetTypeProp describes the type of the Asset
  31. AssetTypeProp AssetProperty = "type"
  32. // AssetLabelProp describes a single label within an Asset.
  33. AssetLabelProp AssetProperty = "label"
  34. // AssetDepartmentProp describes the department of the Asset
  35. AssetDepartmentProp AssetProperty = "department"
  36. // AssetEnvironmentProp describes the environment of the Asset
  37. AssetEnvironmentProp AssetProperty = "environment"
  38. // AssetOwnerProp describes the owner of the Asset
  39. AssetOwnerProp AssetProperty = "owner"
  40. // AssetProductProp describes the product of the Asset
  41. AssetProductProp AssetProperty = "product"
  42. // AssetTeamProp describes the team of the Asset
  43. AssetTeamProp AssetProperty = "team"
  44. )
  45. // IsLabel returns true if the allocation property has a label prefix
  46. func (apt *AssetProperty) IsLabel() bool {
  47. return strings.HasPrefix(string(*apt), "label:")
  48. }
  49. // GetLabel returns the label string associated with the label property if it exists.
  50. // Otherwise, empty string is returned.
  51. func (apt *AssetProperty) GetLabel() string {
  52. if apt.IsLabel() {
  53. return strings.TrimSpace(strings.TrimPrefix(string(*apt), "label:"))
  54. }
  55. return ""
  56. }
  57. func ParseAssetProperties(values []string) ([]AssetProperty, error) {
  58. props := []AssetProperty{}
  59. for _, value := range values {
  60. p, err := ParseAssetProperty(value)
  61. if err != nil {
  62. return nil, err
  63. }
  64. props = append(props, p)
  65. }
  66. return props, nil
  67. }
  68. // ParseAssetProperty attempts to parse a string into an AssetProperty
  69. func ParseAssetProperty(text string) (AssetProperty, error) {
  70. switch strings.TrimSpace(strings.ToLower(text)) {
  71. case "account":
  72. return AssetAccountProp, nil
  73. case "category":
  74. return AssetCategoryProp, nil
  75. case "cluster":
  76. return AssetClusterProp, nil
  77. case "name":
  78. return AssetNameProp, nil
  79. case "project":
  80. return AssetProjectProp, nil
  81. case "provider":
  82. return AssetProviderProp, nil
  83. case "providerid":
  84. return AssetProviderIDProp, nil
  85. case "service":
  86. return AssetServiceProp, nil
  87. case "type":
  88. return AssetTypeProp, nil
  89. case "department":
  90. return AssetDepartmentProp, nil
  91. case "environment":
  92. return AssetEnvironmentProp, nil
  93. case "owner":
  94. return AssetOwnerProp, nil
  95. case "product":
  96. return AssetProductProp, nil
  97. case "team":
  98. return AssetTeamProp, nil
  99. }
  100. if strings.HasPrefix(text, "label:") {
  101. label := promutil.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "label:")))
  102. return AssetProperty(fmt.Sprintf("label:%s", label)), nil
  103. }
  104. return AssetNilProp, fmt.Errorf("invalid asset property: %s", text)
  105. }
  106. // Category options
  107. // ComputeCategory signifies the Compute Category
  108. const ComputeCategory = "Compute"
  109. // StorageCategory signifies the Storage Category
  110. const StorageCategory = "Storage"
  111. // NetworkCategory signifies the Network Category
  112. const NetworkCategory = "Network"
  113. // ManagementCategory signifies the Management Category
  114. const ManagementCategory = "Management"
  115. // SharedCategory signifies an unassigned Category
  116. const SharedCategory = "Shared"
  117. // OtherCategory signifies an unassigned Category
  118. const OtherCategory = "Other"
  119. // Provider options
  120. // AWSProvider describes the provider AWS
  121. const AWSProvider = "AWS"
  122. // describes how AWS labels nodepool nodes
  123. const EKSNodepoolLabel = "eks.amazonaws.com/nodegroup"
  124. // GCPProvider describes the provider GCP
  125. const GCPProvider = "GCP"
  126. // describes how nodepool nodes are labeled in GKE
  127. const GKENodePoolLabel = "cloud.google.com/gke-nodepool"
  128. // AzureProvider describes the provider Azure
  129. const AzureProvider = "Azure"
  130. // describes how Azure labels nodepool nodes
  131. const AKSNodepoolLabel = "kubernetes.azure.com/agentpool"
  132. // AlibabaProvider describes the provider for Alibaba Cloud
  133. const AlibabaProvider = "Alibaba"
  134. // CSVProvider describes the provider a CSV
  135. const CSVProvider = "CSV"
  136. // CustomProvider describes a custom provider
  137. const CustomProvider = "custom"
  138. // ScalewayProvider describes the provider Scaleway
  139. const ScalewayProvider = "Scaleway"
  140. // OracleProvider describes the provider Oracle
  141. const OracleProvider = "Oracle"
  142. // OTCProvider describes the provider OTC
  143. const OTCProvider = "OTC"
  144. // DigitalOceanProvider describes the provider DigitalOcean
  145. const DigitalOceanProvider = "DigitalOcean"
  146. // OVHProvider describes the provider OVH
  147. const OVHProvider = "OVH"
  148. // NilProvider describes unknown provider
  149. const NilProvider = "-"
  150. // Service options
  151. const KubernetesService = "Kubernetes"
  152. // ParseProvider attempts to parse and return a known provider, given a string
  153. func ParseProvider(str string) string {
  154. switch strings.ToLower(strings.TrimSpace(str)) {
  155. case "aws", "eks", "amazon":
  156. return AWSProvider
  157. case "gcp", "gke", "google":
  158. return GCPProvider
  159. case "azure":
  160. return AzureProvider
  161. case "scaleway", "scw", "kapsule":
  162. return ScalewayProvider
  163. case "oci", "oracle":
  164. return OracleProvider
  165. case "digitalocean", "doks", "do":
  166. return DigitalOceanProvider
  167. case "ovh", "ovhcloud", "ovh-mks":
  168. return OVHProvider
  169. default:
  170. return NilProvider
  171. }
  172. }
  173. // AssetProperties describes all properties assigned to an Asset.
  174. type AssetProperties struct {
  175. Category string `json:"category,omitempty"`
  176. Provider string `json:"provider,omitempty"`
  177. Account string `json:"account,omitempty"`
  178. Project string `json:"project,omitempty"`
  179. Service string `json:"service,omitempty"`
  180. Cluster string `json:"cluster,omitempty"`
  181. Name string `json:"name,omitempty"`
  182. ProviderID string `json:"providerID,omitempty"`
  183. }
  184. // Clone returns a cloned instance of the given AssetProperties
  185. func (ap *AssetProperties) Clone() *AssetProperties {
  186. if ap == nil {
  187. return nil
  188. }
  189. clone := &AssetProperties{}
  190. clone.Category = ap.Category
  191. clone.Provider = ap.Provider
  192. clone.Account = ap.Account
  193. clone.Project = ap.Project
  194. clone.Service = ap.Service
  195. clone.Cluster = ap.Cluster
  196. clone.Name = ap.Name
  197. clone.ProviderID = ap.ProviderID
  198. return clone
  199. }
  200. // Equal returns true only if both AssetProperties are matches
  201. func (ap *AssetProperties) Equal(that *AssetProperties) bool {
  202. if ap == nil && that == nil {
  203. return true
  204. }
  205. if ap == nil || that == nil {
  206. return false
  207. }
  208. if ap.Category != that.Category {
  209. return false
  210. }
  211. if ap.Provider != that.Provider {
  212. return false
  213. }
  214. if ap.Account != that.Account {
  215. return false
  216. }
  217. if ap.Project != that.Project {
  218. return false
  219. }
  220. if ap.Service != that.Service {
  221. return false
  222. }
  223. if ap.Cluster != that.Cluster {
  224. return false
  225. }
  226. if ap.Name != that.Name {
  227. return false
  228. }
  229. if ap.ProviderID != that.ProviderID {
  230. return false
  231. }
  232. return true
  233. }
  234. // Keys returns the list of string values used to key the Asset based on the
  235. // list of properties provided.
  236. func (ap *AssetProperties) Keys(props []AssetProperty) []string {
  237. keys := []string{}
  238. if ap == nil {
  239. return keys
  240. }
  241. if (props == nil || hasProp(props, AssetCategoryProp)) && ap.Category != "" {
  242. keys = append(keys, ap.Category)
  243. }
  244. if (props == nil || hasProp(props, AssetProviderProp)) && ap.Provider != "" {
  245. keys = append(keys, ap.Provider)
  246. }
  247. if (props == nil || hasProp(props, AssetAccountProp)) && ap.Account != "" {
  248. keys = append(keys, ap.Account)
  249. }
  250. if (props == nil || hasProp(props, AssetProjectProp)) && ap.Project != "" {
  251. keys = append(keys, ap.Project)
  252. }
  253. if (props == nil || hasProp(props, AssetServiceProp)) && ap.Service != "" {
  254. keys = append(keys, ap.Service)
  255. }
  256. if (props == nil || hasProp(props, AssetClusterProp)) && ap.Cluster != "" {
  257. keys = append(keys, ap.Cluster)
  258. }
  259. if (props == nil || hasProp(props, AssetNameProp)) && ap.Name != "" {
  260. keys = append(keys, ap.Name)
  261. }
  262. if (props == nil || hasProp(props, AssetProviderIDProp)) && ap.ProviderID != "" {
  263. keys = append(keys, ap.ProviderID)
  264. }
  265. return keys
  266. }
  267. // Merge retains only the properties shared with the given AssetProperties
  268. func (ap *AssetProperties) Merge(that *AssetProperties) *AssetProperties {
  269. if ap == nil || that == nil {
  270. return nil
  271. }
  272. result := &AssetProperties{}
  273. if ap.Category == that.Category {
  274. result.Category = ap.Category
  275. }
  276. if ap.Provider == that.Provider {
  277. result.Provider = ap.Provider
  278. }
  279. if ap.Account == that.Account {
  280. result.Account = ap.Account
  281. }
  282. if ap.Project == that.Project {
  283. result.Project = ap.Project
  284. }
  285. if ap.Service == that.Service {
  286. result.Service = ap.Service
  287. }
  288. if ap.Cluster == that.Cluster {
  289. result.Cluster = ap.Cluster
  290. }
  291. if ap.Name == that.Name {
  292. result.Name = ap.Name
  293. }
  294. if ap.ProviderID == that.ProviderID {
  295. result.ProviderID = ap.ProviderID
  296. }
  297. return result
  298. }
  299. // String represents the properties as a string
  300. func (ap *AssetProperties) String() string {
  301. if ap == nil {
  302. return "<nil>"
  303. }
  304. strs := []string{}
  305. if ap.Category != "" {
  306. strs = append(strs, "Category:"+ap.Category)
  307. }
  308. if ap.Provider != "" {
  309. strs = append(strs, "Provider:"+ap.Provider)
  310. }
  311. if ap.Account != "" {
  312. strs = append(strs, "Account:"+ap.Account)
  313. }
  314. if ap.Project != "" {
  315. strs = append(strs, "Project:"+ap.Project)
  316. }
  317. if ap.Service != "" {
  318. strs = append(strs, "Service:"+ap.Service)
  319. }
  320. if ap.Cluster != "" {
  321. strs = append(strs, "Cluster:"+ap.Cluster)
  322. }
  323. if ap.Name != "" {
  324. strs = append(strs, "Name:"+ap.Name)
  325. }
  326. if ap.ProviderID != "" {
  327. strs = append(strs, "ProviderID:"+ap.ProviderID)
  328. }
  329. return strings.Join(strs, ",")
  330. }
  331. func hasProp(props []AssetProperty, prop AssetProperty) bool {
  332. for _, p := range props {
  333. if p == prop {
  334. return true
  335. }
  336. }
  337. return false
  338. }