config.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. package opencost
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/opencost/opencost/core/pkg/util/cloudutil"
  6. "github.com/opencost/opencost/core/pkg/util/promutil"
  7. )
  8. // LabelConfig is a port of type AnalyzerConfig. We need to be more thoughtful
  9. // about design at some point, but this is a stop-gap measure, which is required
  10. // because AnalyzerConfig is defined in package main, so it can't be imported.
  11. type LabelConfig struct {
  12. DepartmentLabel string `json:"department_label"`
  13. EnvironmentLabel string `json:"environment_label"`
  14. OwnerLabel string `json:"owner_label"`
  15. ProductLabel string `json:"product_label"`
  16. TeamLabel string `json:"team_label"`
  17. ClusterExternalLabel string `json:"cluster_external_label"`
  18. NamespaceExternalLabel string `json:"namespace_external_label"`
  19. ControllerExternalLabel string `json:"controller_external_label"`
  20. DaemonsetExternalLabel string `json:"daemonset_external_label"`
  21. DeploymentExternalLabel string `json:"deployment_external_label"`
  22. StatefulsetExternalLabel string `json:"statefulset_external_label"`
  23. ServiceExternalLabel string `json:"service_external_label"`
  24. PodExternalLabel string `json:"pod_external_label"`
  25. DepartmentExternalLabel string `json:"department_external_label"`
  26. EnvironmentExternalLabel string `json:"environment_external_label"`
  27. OwnerExternalLabel string `json:"owner_external_label"`
  28. ProductExternalLabel string `json:"product_external_label"`
  29. TeamExternalLabel string `json:"team_external_label"`
  30. }
  31. // NewLabelConfig creates a new LabelConfig instance with default values.
  32. func NewLabelConfig() *LabelConfig {
  33. return &LabelConfig{
  34. DepartmentLabel: "department",
  35. EnvironmentLabel: "env",
  36. OwnerLabel: "owner",
  37. ProductLabel: "app",
  38. TeamLabel: "team",
  39. ClusterExternalLabel: "kubernetes_cluster",
  40. NamespaceExternalLabel: "kubernetes_namespace",
  41. ControllerExternalLabel: "kubernetes_controller",
  42. DaemonsetExternalLabel: "kubernetes_daemonset",
  43. DeploymentExternalLabel: "kubernetes_deployment",
  44. StatefulsetExternalLabel: "kubernetes_statefulset",
  45. ServiceExternalLabel: "kubernetes_service",
  46. PodExternalLabel: "kubernetes_pod",
  47. DepartmentExternalLabel: "kubernetes_label_department",
  48. EnvironmentExternalLabel: "kubernetes_label_env",
  49. OwnerExternalLabel: "kubernetes_label_owner",
  50. ProductExternalLabel: "kubernetes_label_app",
  51. TeamExternalLabel: "kubernetes_label_team",
  52. }
  53. }
  54. // Map returns the config as a basic string map, with default values if not set
  55. func (lc *LabelConfig) Map() map[string]string {
  56. // Start with default values
  57. m := map[string]string{
  58. "department_label": "department",
  59. "environment_label": "env",
  60. "owner_label": "owner",
  61. "product_label": "app",
  62. "team_label": "team",
  63. "cluster_external_label": "kubernetes_cluster",
  64. "namespace_external_label": "kubernetes_namespace",
  65. "controller_external_label": "kubernetes_controller",
  66. "daemonset_external_label": "kubernetes_daemonset",
  67. "deployment_external_label": "kubernetes_deployment",
  68. "statefulset_external_label": "kubernetes_statefulset",
  69. "service_external_label": "kubernetes_service",
  70. "pod_external_label": "kubernetes_pod",
  71. "department_external_label": "kubernetes_label_department",
  72. "environment_external_label": "kubernetes_label_env",
  73. "owner_external_label": "kubernetes_label_owner",
  74. "product_external_label": "kubernetes_label_app",
  75. "team_external_label": "kubernetes_label_team",
  76. }
  77. if lc == nil {
  78. return m
  79. }
  80. if lc.DepartmentLabel != "" {
  81. m["department_label"] = lc.DepartmentLabel
  82. }
  83. if lc.EnvironmentLabel != "" {
  84. m["environment_label"] = lc.EnvironmentLabel
  85. }
  86. if lc.OwnerLabel != "" {
  87. m["owner_label"] = lc.OwnerLabel
  88. }
  89. if lc.ProductLabel != "" {
  90. m["product_label"] = lc.ProductLabel
  91. }
  92. if lc.TeamLabel != "" {
  93. m["team_label"] = lc.TeamLabel
  94. }
  95. if lc.ClusterExternalLabel != "" {
  96. m["cluster_external_label"] = lc.ClusterExternalLabel
  97. }
  98. if lc.NamespaceExternalLabel != "" {
  99. m["namespace_external_label"] = lc.NamespaceExternalLabel
  100. }
  101. if lc.ControllerExternalLabel != "" {
  102. m["controller_external_label"] = lc.ControllerExternalLabel
  103. }
  104. if lc.DaemonsetExternalLabel != "" {
  105. m["daemonset_external_label"] = lc.DaemonsetExternalLabel
  106. }
  107. if lc.DeploymentExternalLabel != "" {
  108. m["deployment_external_label"] = lc.DeploymentExternalLabel
  109. }
  110. if lc.StatefulsetExternalLabel != "" {
  111. m["statefulset_external_label"] = lc.StatefulsetExternalLabel
  112. }
  113. if lc.ServiceExternalLabel != "" {
  114. m["service_external_label"] = lc.ServiceExternalLabel
  115. }
  116. if lc.PodExternalLabel != "" {
  117. m["pod_external_label"] = lc.PodExternalLabel
  118. }
  119. if lc.DepartmentExternalLabel != "" {
  120. m["department_external_label"] = lc.DepartmentExternalLabel
  121. } else if lc.DepartmentLabel != "" {
  122. m["department_external_label"] = "kubernetes_label_" + lc.DepartmentLabel
  123. }
  124. if lc.EnvironmentExternalLabel != "" {
  125. m["environment_external_label"] = lc.EnvironmentExternalLabel
  126. } else if lc.EnvironmentLabel != "" {
  127. m["environment_external_label"] = "kubernetes_label_" + lc.EnvironmentLabel
  128. }
  129. if lc.OwnerExternalLabel != "" {
  130. m["owner_external_label"] = lc.OwnerExternalLabel
  131. } else if lc.OwnerLabel != "" {
  132. m["owner_external_label"] = "kubernetes_label_" + lc.OwnerLabel
  133. }
  134. if lc.ProductExternalLabel != "" {
  135. m["product_external_label"] = lc.ProductExternalLabel
  136. } else if lc.ProductLabel != "" {
  137. m["product_external_label"] = "kubernetes_label_" + lc.ProductLabel
  138. }
  139. if lc.TeamExternalLabel != "" {
  140. m["team_external_label"] = lc.TeamExternalLabel
  141. } else if lc.TeamLabel != "" {
  142. m["team_external_label"] = "kubernetes_label_" + lc.TeamLabel
  143. }
  144. return m
  145. }
  146. // Sanitize returns a sanitized version of the given string, which converts
  147. // all illegal characters to underscores. Illegal characters are those that
  148. // Prometheus does not support; i.e. [^a-zA-Z0-9_]
  149. func (lc *LabelConfig) Sanitize(label string) string {
  150. return promutil.SanitizeLabelName(strings.TrimSpace(label))
  151. }
  152. // GetExternalAllocationName derives an external allocation name from a set of
  153. // labels, given an aggregation property. If the aggregation property is,
  154. // itself, a label (e.g. label:app) then this function looks for a
  155. // corresponding value under "app" and returns "app=thatvalue". If the
  156. // aggregation property is not a label but a Kubernetes concept
  157. // (e.g. namespace) then this function first finds the "external label"
  158. // configured to represent it (e.g. NamespaceExternalLabel: "kubens") and uses
  159. // that label to determine an external allocation name. If no label value can
  160. // be found, return an empty string.
  161. func (lc *LabelConfig) GetExternalAllocationName(labels map[string]string, aggregateBy string) string {
  162. labelNames := []string{}
  163. aggByLabel := false
  164. // Determine if the aggregation property is, itself, a label or not. If
  165. // not, determine the label associated with the given aggregation property.
  166. if strings.HasPrefix(aggregateBy, "label:") {
  167. labelNames = append(labelNames, promutil.SanitizeLabelName(strings.TrimPrefix(aggregateBy, "label:")))
  168. aggByLabel = true
  169. } else {
  170. // If lc is nil, use a default LabelConfig to do a best-effort match
  171. if lc == nil {
  172. lc = NewLabelConfig()
  173. }
  174. switch strings.ToLower(aggregateBy) {
  175. case AllocationClusterProp:
  176. labelNames = strings.Split(lc.ClusterExternalLabel, ",")
  177. case AllocationControllerProp:
  178. labelNames = strings.Split(lc.ControllerExternalLabel, ",")
  179. case AllocationNamespaceProp:
  180. labelNames = strings.Split(lc.NamespaceExternalLabel, ",")
  181. case AllocationPodProp:
  182. labelNames = strings.Split(lc.PodExternalLabel, ",")
  183. case AllocationServiceProp:
  184. labelNames = strings.Split(lc.ServiceExternalLabel, ",")
  185. case AllocationDeploymentProp:
  186. labelNames = strings.Split(lc.DeploymentExternalLabel, ",")
  187. case AllocationStatefulSetProp:
  188. labelNames = strings.Split(lc.StatefulsetExternalLabel, ",")
  189. case AllocationDaemonSetProp:
  190. labelNames = strings.Split(lc.DaemonsetExternalLabel, ",")
  191. case AllocationDepartmentProp:
  192. labelNames = strings.Split(lc.DepartmentExternalLabel, ",")
  193. case AllocationEnvironmentProp:
  194. labelNames = strings.Split(lc.EnvironmentExternalLabel, ",")
  195. case AllocationOwnerProp:
  196. labelNames = strings.Split(lc.OwnerExternalLabel, ",")
  197. case AllocationProductProp:
  198. labelNames = strings.Split(lc.ProductExternalLabel, ",")
  199. case AllocationTeamProp:
  200. labelNames = strings.Split(lc.TeamExternalLabel, ",")
  201. }
  202. for i, labelName := range labelNames {
  203. labelNames[i] = promutil.SanitizeLabelName(strings.TrimSpace(labelName))
  204. }
  205. }
  206. // No label is set for the given aggregation property.
  207. if len(labelNames) == 0 {
  208. return ""
  209. }
  210. // The relevant label is not present in the set of labels provided.
  211. labelName := ""
  212. labelValue := ""
  213. for _, ln := range labelNames {
  214. if lv, ok := labels[ln]; ok {
  215. // Match found for given label
  216. labelName = ln
  217. labelValue = lv
  218. break
  219. } else {
  220. // Convert the label name to a format compatible with AWS Glue and
  221. // Athena column naming and check again. If not found after that,
  222. // then consider the label not present.
  223. ln = cloudutil.ConvertToGlueColumnFormat(ln)
  224. if lv, ok = labels[ln]; ok {
  225. // Match found for given label after converting to AWS format
  226. labelName = ln
  227. labelValue = lv
  228. break
  229. }
  230. }
  231. }
  232. // No match found
  233. if labelName == "" {
  234. return ""
  235. }
  236. // When aggregating by some label (i.e. not by a Kubernetes concept),
  237. // prepend the label value with the label name (e.g. "app=cost-analyzer").
  238. // This step is not necessary for Kubernetes concepts (e.g. for namespace,
  239. // we do not need "namespace=kubecost"; just "kubecost" will do).
  240. if aggByLabel {
  241. return fmt.Sprintf("%s=%s", labelName, labelValue)
  242. }
  243. return labelValue
  244. }