소스 검색

Support comma-separated values and label aliases in parsing external allocation naming

Niko Kovacevic 4 년 전
부모
커밋
29d9f25a92
2개의 변경된 파일59개의 추가작업 그리고 21개의 파일을 삭제
  1. 50 20
      pkg/kubecost/config.go
  2. 9 1
      pkg/kubecost/config_test.go

+ 50 - 20
pkg/kubecost/config.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/kubecost/cost-model/pkg/prom"
 	"github.com/kubecost/cost-model/pkg/util/cloudutil"
 )
 
@@ -178,13 +179,13 @@ func (lc *LabelConfig) Map() map[string]string {
 // that label to determine an external allocation name. If no label value can
 // be found, return an empty string.
 func (lc *LabelConfig) GetExternalAllocationName(labels map[string]string, aggregateBy string) string {
-	labelName := ""
+	labelNames := []string{}
 	aggByLabel := false
 
 	// Determine if the aggregation property is, itself, a label or not. If
 	// not, determine the label associated with the given aggregation property.
 	if strings.HasPrefix(aggregateBy, "label:") {
-		labelName = strings.TrimPrefix(aggregateBy, "label:")
+		labelNames = append(labelNames, prom.SanitizeLabelName(strings.TrimPrefix(aggregateBy, "label:")))
 		aggByLabel = true
 	} else {
 		// If lc is nil, use a default LabelConfig to do a best-effort match
@@ -194,42 +195,71 @@ func (lc *LabelConfig) GetExternalAllocationName(labels map[string]string, aggre
 
 		switch strings.ToLower(aggregateBy) {
 		case AllocationClusterProp:
-			labelName = lc.ClusterExternalLabel
+			labelNames = strings.Split(lc.ClusterExternalLabel, ",")
 		case AllocationControllerProp:
-			labelName = lc.ControllerExternalLabel
+			labelNames = strings.Split(lc.ControllerExternalLabel, ",")
 		case AllocationNamespaceProp:
-			labelName = lc.NamespaceExternalLabel
+			labelNames = strings.Split(lc.NamespaceExternalLabel, ",")
 		case AllocationPodProp:
-			labelName = lc.PodExternalLabel
+			labelNames = strings.Split(lc.PodExternalLabel, ",")
 		case AllocationServiceProp:
-			labelName = lc.ServiceExternalLabel
+			labelNames = strings.Split(lc.ServiceExternalLabel, ",")
 		case AllocationDeploymentProp:
-			labelName = lc.DeploymentExternalLabel
+			labelNames = strings.Split(lc.DeploymentExternalLabel, ",")
 		case AllocationStatefulSetProp:
-			labelName = lc.StatefulsetExternalLabel
+			labelNames = strings.Split(lc.StatefulsetExternalLabel, ",")
 		case AllocationDaemonSetProp:
-			labelName = lc.DaemonsetExternalLabel
+			labelNames = strings.Split(lc.DaemonsetExternalLabel, ",")
+		case AllocationDepartmentProp:
+			labelNames = strings.Split(lc.DepartmentExternalLabel, ",")
+		case AllocationEnvironmentProp:
+			labelNames = strings.Split(lc.EnvironmentExternalLabel, ",")
+		case AllocationOwnerProp:
+			labelNames = strings.Split(lc.OwnerExternalLabel, ",")
+		case AllocationProductProp:
+			labelNames = strings.Split(lc.ProductExternalLabel, ",")
+		case AllocationTeamProp:
+			labelNames = strings.Split(lc.TeamExternalLabel, ",")
+		}
+
+		for i, labelName := range labelNames {
+			labelNames[i] = prom.SanitizeLabelName(strings.TrimSpace(labelName))
 		}
 	}
 
 	// No label is set for the given aggregation property.
-	if labelName == "" {
+	if len(labelNames) == 0 {
 		return ""
 	}
 
 	// The relevant label is not present in the set of labels provided.
-	labelValue, ok := labels[labelName]
-	if !ok {
-		// Convert the label name to a format compatible with AWS Glue and
-		// Athena column naming and check again. If not found after that, then
-		// consider the label not present.
-		labelName = cloudutil.ConvertToGlueColumnFormat(labelName)
-		labelValue, ok = labels[labelName]
-		if !ok {
-			return ""
+	labelName := ""
+	labelValue := ""
+	for _, ln := range labelNames {
+		if lv, ok := labels[ln]; ok {
+			// Match found for given label
+			labelName = ln
+			labelValue = lv
+			break
+		} else {
+			// Convert the label name to a format compatible with AWS Glue and
+			// Athena column naming and check again. If not found after that,
+			// then consider the label not present.
+			ln = cloudutil.ConvertToGlueColumnFormat(ln)
+			if lv, ok = labels[ln]; ok {
+				// Match found for given label after converting to AWS format
+				labelName = ln
+				labelValue = lv
+				break
+			}
 		}
 	}
 
+	// No match found
+	if labelName == "" {
+		return ""
+	}
+
 	// When aggregating by some label (i.e. not by a Kubernetes concept),
 	// prepend the label value with the label name (e.g. "app=cost-analyzer").
 	// This step is not necessary for Kubernetes concepts (e.g. for namespace,

+ 9 - 1
pkg/kubecost/config_test.go

@@ -45,7 +45,9 @@ func TestLabelConfig_GetExternalAllocationName(t *testing.T) {
 		"kubeowner":                   "kubecost-owner",
 		"env":                         "env1",
 		"app":                         "app1",
+		"team":                        "team1",
 		glueFormattedLabel:            "glue",
+		"prom_sanitization_test":      "pass",
 		"kubernetes_cluster":          "cluster-one",
 		"kubernetes_namespace":        "kubecost",
 		"kubernetes_controller":       "kubecost-controller",
@@ -102,8 +104,11 @@ func TestLabelConfig_GetExternalAllocationName(t *testing.T) {
 
 	// Change the external label for namespace and confirm it still works
 	lc.NamespaceExternalLabel = "kubens"
+	lc.ServiceExternalLabel = "prom-sanitization-test"
 	lc.PodExternalLabel = "Non__GlueFormattedLabel"
 	lc.OwnerExternalLabel = "kubeowner"
+	lc.DepartmentExternalLabel = "doesntexist,env"
+	lc.TeamExternalLabel = "team,env"
 
 	// TODO how is e.g. OwnerExternalLabel supposed to work?
 
@@ -112,8 +117,11 @@ func TestLabelConfig_GetExternalAllocationName(t *testing.T) {
 		expected string
 	}{
 		{"namespace", "kubecost-staging"},
+		{"service", "pass"},
 		{"pod", "glue"},
-		// {"owner", "kubeowner"},
+		{"owner", "kubecost-owner"},
+		{"department", "env1"},
+		{"team", "team1"},
 	}
 	for _, tc := range testCases {
 		actual := lc.GetExternalAllocationName(labels, tc.aggBy)