config.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package kubecost
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/kubecost/cost-model/pkg/prom"
  6. "github.com/kubecost/cost-model/pkg/util/cloudutil"
  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. // GetExternalAllocationName derives an external allocation name from a set of
  147. // labels, given an aggregation property. If the aggregation property is,
  148. // itself, a label (e.g. label:app) then this function looks for a
  149. // corresponding value under "app" and returns "app=thatvalue". If the
  150. // aggregation property is not a label but a Kubernetes concept
  151. // (e.g. namespace) then this function first finds the "external label"
  152. // configured to represent it (e.g. NamespaceExternalLabel: "kubens") and uses
  153. // that label to determine an external allocation name. If no label value can
  154. // be found, return an empty string.
  155. func (lc *LabelConfig) GetExternalAllocationName(labels map[string]string, aggregateBy string) string {
  156. labelNames := []string{}
  157. aggByLabel := false
  158. // Determine if the aggregation property is, itself, a label or not. If
  159. // not, determine the label associated with the given aggregation property.
  160. if strings.HasPrefix(aggregateBy, "label:") {
  161. labelNames = append(labelNames, prom.SanitizeLabelName(strings.TrimPrefix(aggregateBy, "label:")))
  162. aggByLabel = true
  163. } else {
  164. // If lc is nil, use a default LabelConfig to do a best-effort match
  165. if lc == nil {
  166. lc = NewLabelConfig()
  167. }
  168. switch strings.ToLower(aggregateBy) {
  169. case AllocationClusterProp:
  170. labelNames = strings.Split(lc.ClusterExternalLabel, ",")
  171. case AllocationControllerProp:
  172. labelNames = strings.Split(lc.ControllerExternalLabel, ",")
  173. case AllocationNamespaceProp:
  174. labelNames = strings.Split(lc.NamespaceExternalLabel, ",")
  175. case AllocationPodProp:
  176. labelNames = strings.Split(lc.PodExternalLabel, ",")
  177. case AllocationServiceProp:
  178. labelNames = strings.Split(lc.ServiceExternalLabel, ",")
  179. case AllocationDeploymentProp:
  180. labelNames = strings.Split(lc.DeploymentExternalLabel, ",")
  181. case AllocationStatefulSetProp:
  182. labelNames = strings.Split(lc.StatefulsetExternalLabel, ",")
  183. case AllocationDaemonSetProp:
  184. labelNames = strings.Split(lc.DaemonsetExternalLabel, ",")
  185. case AllocationDepartmentProp:
  186. labelNames = strings.Split(lc.DepartmentExternalLabel, ",")
  187. case AllocationEnvironmentProp:
  188. labelNames = strings.Split(lc.EnvironmentExternalLabel, ",")
  189. case AllocationOwnerProp:
  190. labelNames = strings.Split(lc.OwnerExternalLabel, ",")
  191. case AllocationProductProp:
  192. labelNames = strings.Split(lc.ProductExternalLabel, ",")
  193. case AllocationTeamProp:
  194. labelNames = strings.Split(lc.TeamExternalLabel, ",")
  195. }
  196. for i, labelName := range labelNames {
  197. labelNames[i] = prom.SanitizeLabelName(strings.TrimSpace(labelName))
  198. }
  199. }
  200. // No label is set for the given aggregation property.
  201. if len(labelNames) == 0 {
  202. return ""
  203. }
  204. // The relevant label is not present in the set of labels provided.
  205. labelName := ""
  206. labelValue := ""
  207. for _, ln := range labelNames {
  208. if lv, ok := labels[ln]; ok {
  209. // Match found for given label
  210. labelName = ln
  211. labelValue = lv
  212. break
  213. } else {
  214. // Convert the label name to a format compatible with AWS Glue and
  215. // Athena column naming and check again. If not found after that,
  216. // then consider the label not present.
  217. ln = cloudutil.ConvertToGlueColumnFormat(ln)
  218. if lv, ok = labels[ln]; ok {
  219. // Match found for given label after converting to AWS format
  220. labelName = ln
  221. labelValue = lv
  222. break
  223. }
  224. }
  225. }
  226. // No match found
  227. if labelName == "" {
  228. return ""
  229. }
  230. // When aggregating by some label (i.e. not by a Kubernetes concept),
  231. // prepend the label value with the label name (e.g. "app=cost-analyzer").
  232. // This step is not necessary for Kubernetes concepts (e.g. for namespace,
  233. // we do not need "namespace=kubecost"; just "kubecost" will do).
  234. if aggByLabel {
  235. return fmt.Sprintf("%s=%s", labelName, labelValue)
  236. }
  237. return labelValue
  238. }