diff --git a/docs/api.md b/docs/api.md index 8d811cc2c..27eec18e9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -265,7 +265,14 @@ Following is the supported API format for network transformations: macField: entry MAC input field output: entry output field assignee: value needs to assign to output field - labels_prefix: labels prefix to use to copy input lables, if empty labels will not be copied + labels_prefix: labels prefix to use to copy input labels, if empty labels will not be copied + label_inclusions: labels to include, if empty all labels will be included. Only used if labels_prefix is specified + label_exclusions: labels to exclude, if empty no labels will be excluded. Only used if labels_prefix is specified + label_value_max_length: label value max length, if specified, will trim label values to this length + annotations_prefix: annotations prefix to use to copy input annotations, if empty annotations will not be copied + annotation_inclusions: annotations to include, if empty all annotations will be included. Only used if annotations_prefix is specified + annotation_exclusions: annotations to exclude, if empty no annotations will be excluded. Only used if annotations_prefix is specified + annotation_value_max_length: annotation value max length, if specified, will trim annotation values to this length add_zone: if true the rule will add the zone add_subnet: Add subnet rule configuration input: entry input field diff --git a/pkg/api/transform_network.go b/pkg/api/transform_network.go index 68a2e99c0..b2e845528 100644 --- a/pkg/api/transform_network.go +++ b/pkg/api/transform_network.go @@ -98,15 +98,26 @@ type K8sReference struct { } type K8sRule struct { - IPField string `yaml:"ipField,omitempty" json:"ipField,omitempty" doc:"entry IP input field"` - InterfacesField string `yaml:"interfacesField,omitempty" json:"interfacesField,omitempty" doc:"entry Interfaces input field"` - UDNsField string `yaml:"udnsField,omitempty" json:"udnsField,omitempty" doc:"entry UDNs input field"` - MACField string `yaml:"macField,omitempty" json:"macField,omitempty" doc:"entry MAC input field"` - Output string `yaml:"output,omitempty" json:"output,omitempty" doc:"entry output field"` - Assignee string `yaml:"assignee,omitempty" json:"assignee,omitempty" doc:"value needs to assign to output field"` - LabelsPrefix string `yaml:"labels_prefix,omitempty" json:"labels_prefix,omitempty" doc:"labels prefix to use to copy input lables, if empty labels will not be copied"` - AddZone bool `yaml:"add_zone,omitempty" json:"add_zone,omitempty" doc:"if true the rule will add the zone"` - OutputKeys K8SOutputKeys `yaml:"-" json:"-"` + IPField string `yaml:"ipField,omitempty" json:"ipField,omitempty" doc:"entry IP input field"` + InterfacesField string `yaml:"interfacesField,omitempty" json:"interfacesField,omitempty" doc:"entry Interfaces input field"` + UDNsField string `yaml:"udnsField,omitempty" json:"udnsField,omitempty" doc:"entry UDNs input field"` + MACField string `yaml:"macField,omitempty" json:"macField,omitempty" doc:"entry MAC input field"` + Output string `yaml:"output,omitempty" json:"output,omitempty" doc:"entry output field"` + Assignee string `yaml:"assignee,omitempty" json:"assignee,omitempty" doc:"value needs to assign to output field"` + LabelsPrefix string `yaml:"labels_prefix,omitempty" json:"labels_prefix,omitempty" doc:"labels prefix to use to copy input labels, if empty labels will not be copied"` + LabelInclusions []string `yaml:"label_inclusions,omitempty" json:"label_inclusions,omitempty" doc:"labels to include, if empty all labels will be included. Only used if labels_prefix is specified"` + LabelExclusions []string `yaml:"label_exclusions,omitempty" json:"label_exclusions,omitempty" doc:"labels to exclude, if empty no labels will be excluded. Only used if labels_prefix is specified"` + LabelValueMaxLength *int `yaml:"label_value_max_length,omitempty" json:"label_value_max_length,omitempty" doc:"label value max length, if specified, will trim label values to this length"` + AnnotationsPrefix string `yaml:"annotations_prefix,omitempty" json:"annotations_prefix,omitempty" doc:"annotations prefix to use to copy input annotations, if empty annotations will not be copied"` + AnnotationInclusions []string `yaml:"annotation_inclusions,omitempty" json:"annotation_inclusions,omitempty" doc:"annotations to include, if empty all annotations will be included. Only used if annotations_prefix is specified"` + AnnotationExclusions []string `yaml:"annotation_exclusions,omitempty" json:"annotation_exclusions,omitempty" doc:"annotations to exclude, if empty no annotations will be excluded. Only used if annotations_prefix is specified"` + AnnotationValueMaxLength *int `yaml:"annotation_value_max_length,omitempty" json:"annotation_value_max_length,omitempty" doc:"annotation value max length, if specified, will trim annotation values to this length"` + AddZone bool `yaml:"add_zone,omitempty" json:"add_zone,omitempty" doc:"if true the rule will add the zone"` + OutputKeys K8SOutputKeys `yaml:"-" json:"-"` + LabelInclusionsMap map[string]struct{} `yaml:"-" json:"-"` + LabelExclusionsMap map[string]struct{} `yaml:"-" json:"-"` + AnnotationInclusionsMap map[string]struct{} `yaml:"-" json:"-"` + AnnotationExclusionsMap map[string]struct{} `yaml:"-" json:"-"` } type K8SOutputKeys struct { @@ -150,6 +161,11 @@ func (r *K8sRule) preprocess() { Zone: r.Output + "_Zone", } } + + r.LabelInclusionsMap = buildStringMap(r.LabelInclusions) + r.LabelExclusionsMap = buildStringMap(r.LabelExclusions) + r.AnnotationInclusionsMap = buildStringMap(r.AnnotationInclusions) + r.AnnotationExclusionsMap = buildStringMap(r.AnnotationExclusions) } type SecondaryNetwork struct { @@ -199,3 +215,11 @@ type NetworkTransformSubnetLabel struct { CIDRs []string `yaml:"cidrs,omitempty" json:"cidrs,omitempty" doc:"list of CIDRs to match a label"` Name string `yaml:"name,omitempty" json:"name,omitempty" doc:"name of the label"` } + +func buildStringMap(items []string) map[string]struct{} { + m := make(map[string]struct{}, len(items)) + for _, item := range items { + m[item] = struct{}{} + } + return m +} diff --git a/pkg/pipeline/transform/kubernetes/enrich.go b/pkg/pipeline/transform/kubernetes/enrich.go index 34b9570a0..d655e0fdd 100644 --- a/pkg/pipeline/transform/kubernetes/enrich.go +++ b/pkg/pipeline/transform/kubernetes/enrich.go @@ -15,6 +15,10 @@ import ( var ds *datasource.Datasource var infConfig informers.Config +const ( + truncateSuffix = "..." +) + // For testing func MockInformers() { infConfig = informers.NewConfig(api.NetworkTransformKubeConfig{}) @@ -54,7 +58,24 @@ func Enrich(outputEntry config.GenericMap, rule *api.K8sRule) { outputEntry[rule.OutputKeys.NetworkName] = kubeInfo.NetworkName if rule.LabelsPrefix != "" { for labelKey, labelValue := range kubeInfo.Labels { - outputEntry[rule.LabelsPrefix+"_"+labelKey] = labelValue + if shouldInclude(labelKey, rule.LabelInclusionsMap, rule.LabelExclusionsMap) { + outputEntry[rule.LabelsPrefix+"_"+labelKey] = truncateWithSuffix( + labelValue, + rule.LabelValueMaxLength, + truncateSuffix, + ) + } + } + } + if rule.AnnotationsPrefix != "" { + for annotationKey, annotationValue := range kubeInfo.Annotations { + if shouldInclude(annotationKey, rule.AnnotationInclusionsMap, rule.AnnotationExclusionsMap) { + outputEntry[rule.AnnotationsPrefix+"_"+annotationKey] = truncateWithSuffix( + annotationValue, + rule.AnnotationValueMaxLength, + truncateSuffix, + ) + } } } if kubeInfo.HostIP != "" { @@ -142,3 +163,36 @@ func objectIsApp(namespace, name string, rule *api.K8sInfraRule) bool { } return true } + +// shouldInclude determines if an item should be included based on inclusion/exclusion maps. +func shouldInclude(key string, inclusions, exclusions map[string]struct{}) bool { + if _, excluded := exclusions[key]; excluded { + return false + } + if len(inclusions) > 0 { + _, included := inclusions[key] + return included + } + return true +} + +// truncateWithSuffix truncates s to max runes, including the suffix. +func truncateWithSuffix(s string, maxRunes *int, suffix string) string { + if maxRunes == nil { + return s + } + m := *maxRunes + if m <= 0 { + return "" + } + r := []rune(s) + sr := []rune(suffix) + if len(r) <= m { + return s + } + if m <= len(sr) { + return string(sr[:m]) + } + keep := m - len(sr) + return string(r[:keep]) + suffix +} diff --git a/pkg/pipeline/transform/kubernetes/enrich_test.go b/pkg/pipeline/transform/kubernetes/enrich_test.go index fc3f43598..a4acebb32 100644 --- a/pkg/pipeline/transform/kubernetes/enrich_test.go +++ b/pkg/pipeline/transform/kubernetes/enrich_test.go @@ -581,3 +581,614 @@ func TestEnrichUsingUDN(t *testing.T) { "DstK8s_NetworkName": "ns-2/primary-udn", }, entry) } + +func TestEnrich_LabelsAndAnnotationsPrefixes(t *testing.T) { + testData := map[string]*model.ResourceMetaData{ + "10.0.0.10": { + ObjectMeta: v1.ObjectMeta{ + Name: "test-pod", + Namespace: "test-ns", + Labels: map[string]string{"app": "web", "tier": "backend"}, + Annotations: map[string]string{ + "owner": "team-a", + "prometheus.io/scrape": "true", + }, + }, + Kind: "Pod", + }, + } + setupStubs(testData, nil, nodes) + + tests := []struct { + name string + labelsPrefix string + annotationsPrefix string + expectLabels map[string]string + expectAnnotations map[string]string + notExpect []string + }{ + { + name: "both prefixes", + labelsPrefix: "K8s_Labels", + annotationsPrefix: "K8s_Annotations", + expectLabels: map[string]string{"K8s_Labels_app": "web", "K8s_Labels_tier": "backend"}, + expectAnnotations: map[string]string{"K8s_Annotations_owner": "team-a", "K8s_Annotations_prometheus.io/scrape": "true"}, + }, + { + name: "labels only", + labelsPrefix: "K8s_Labels", + expectLabels: map[string]string{"K8s_Labels_app": "web"}, + notExpect: []string{"K8s_Annotations_owner"}, + }, + { + name: "annotations only", + annotationsPrefix: "K8s_Annotations", + expectAnnotations: map[string]string{"K8s_Annotations_owner": "team-a"}, + notExpect: []string{"K8s_Labels_app"}, + }, + { + name: "no prefixes", + notExpect: []string{"K8s_Labels_app", "K8s_Annotations_owner"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rule := api.TransformNetwork{ + Rules: api.NetworkTransformRules{{ + Type: api.NetworkAddKubernetes, + Kubernetes: &api.K8sRule{ + IPField: "SrcAddr", + Output: "K8s", + LabelsPrefix: tt.labelsPrefix, + AnnotationsPrefix: tt.annotationsPrefix, + }, + }}, + } + rule.Preprocess() + + entry := config.GenericMap{"SrcAddr": "10.0.0.10"} + Enrich(entry, rule.Rules[0].Kubernetes) + + assert.Equal(t, "test-pod", entry["K8s_Name"]) + for k, v := range tt.expectLabels { + assert.Equal(t, v, entry[k]) + } + for k, v := range tt.expectAnnotations { + assert.Equal(t, v, entry[k]) + } + for _, k := range tt.notExpect { + assert.NotContains(t, entry, k) + } + }) + } +} + +func TestEnrich_LabelsAnnotationsFiltering(t *testing.T) { + testData := map[string]*model.ResourceMetaData{ + "10.0.0.10": { + ObjectMeta: v1.ObjectMeta{ + Name: "test-pod", + Namespace: "test-ns", + Labels: map[string]string{ + "app": "myapp", + "version": "v1", + "environment": "prod", + "team": "backend", + }, + Annotations: map[string]string{ + "annotation1": "value1", + "annotation2": "value2", + "annotation3": "value3", + "annotation4": "value4", + }, + }, + Kind: "Pod", + }, + } + + setupStubs(testData, nil, nodes) + + tests := []struct { + name string + labelsPrefix string + labelInclusions []string + labelExclusions []string + annotationsPrefix string + annotationInclusions []string + annotationExclusions []string + expectedLabels []string + expectedAnnotations []string + notExpect []string + }{ + { + name: "No filtering - all labels and annotations included", + labelsPrefix: "k8s_labels", + annotationsPrefix: "k8s_annotations", + expectedLabels: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team"}, + expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"}, + }, + { + name: "Only label inclusions specified", + labelsPrefix: "k8s_labels", + labelInclusions: []string{"app", "version"}, + annotationsPrefix: "k8s_annotations", + expectedLabels: []string{"k8s_labels_app", "k8s_labels_version"}, + expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"}, + notExpect: []string{"k8s_labels_environment", "k8s_labels_team"}, + }, + { + name: "Only label exclusions specified", + labelsPrefix: "k8s_labels", + labelExclusions: []string{"environment", "team"}, + annotationsPrefix: "k8s_annotations", + expectedLabels: []string{"k8s_labels_app", "k8s_labels_version"}, + expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"}, + notExpect: []string{"k8s_labels_environment", "k8s_labels_team"}, + }, + { + name: "Both label inclusions and exclusions - exclusions take precedence", + labelsPrefix: "k8s_labels", + labelInclusions: []string{"app", "version", "environment"}, + labelExclusions: []string{"environment"}, + annotationsPrefix: "k8s_annotations", + expectedLabels: []string{"k8s_labels_app", "k8s_labels_version"}, + expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"}, + notExpect: []string{"k8s_labels_environment", "k8s_labels_team"}, + }, + { + name: "Only annotation inclusions specified", + labelsPrefix: "k8s_labels", + annotationsPrefix: "k8s_annotations", + annotationInclusions: []string{"annotation1", "annotation3"}, + expectedLabels: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team"}, + expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation3"}, + notExpect: []string{"k8s_annotations_annotation2", "k8s_annotations_annotation4"}, + }, + { + name: "Only annotation exclusions specified", + labelsPrefix: "k8s_labels", + annotationsPrefix: "k8s_annotations", + annotationExclusions: []string{"annotation2", "annotation4"}, + expectedLabels: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team"}, + expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation3"}, + notExpect: []string{"k8s_annotations_annotation2", "k8s_annotations_annotation4"}, + }, + { + name: "Both annotation inclusions and exclusions - exclusions take precedence", + labelsPrefix: "k8s_labels", + annotationsPrefix: "k8s_annotations", + annotationInclusions: []string{"annotation1", "annotation2", "annotation3"}, + annotationExclusions: []string{"annotation2"}, + expectedLabels: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team"}, + expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation3"}, + notExpect: []string{"k8s_annotations_annotation2", "k8s_annotations_annotation4"}, + }, + { + name: "Combined filtering for both labels and annotations", + labelsPrefix: "k8s_labels", + labelInclusions: []string{"app", "version", "team"}, + labelExclusions: []string{"team"}, + annotationsPrefix: "k8s_annotations", + annotationInclusions: []string{"annotation1", "annotation2"}, + annotationExclusions: []string{"annotation1"}, + expectedLabels: []string{"k8s_labels_app", "k8s_labels_version"}, + expectedAnnotations: []string{"k8s_annotations_annotation2"}, + notExpect: []string{"k8s_labels_environment", "k8s_labels_team", "k8s_annotations_annotation1", "k8s_annotations_annotation3", "k8s_annotations_annotation4"}, + }, + { + name: "Empty prefix - no labels or annotations added", + labelsPrefix: "", + labelInclusions: []string{"app"}, + annotationsPrefix: "", + annotationInclusions: []string{"annotation1"}, + notExpect: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team", "k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rule := api.TransformNetwork{ + Rules: api.NetworkTransformRules{{ + Type: api.NetworkAddKubernetes, + Kubernetes: &api.K8sRule{ + IPField: "SrcAddr", + Output: "SrcK8s", + LabelsPrefix: tt.labelsPrefix, + LabelInclusions: tt.labelInclusions, + LabelExclusions: tt.labelExclusions, + AnnotationsPrefix: tt.annotationsPrefix, + AnnotationInclusions: tt.annotationInclusions, + AnnotationExclusions: tt.annotationExclusions, + }, + }}, + } + rule.Preprocess() + + entry := config.GenericMap{ + "SrcAddr": "10.0.0.10", + } + + Enrich(entry, rule.Rules[0].Kubernetes) + + for _, label := range tt.expectedLabels { + assert.Contains(t, entry, label, "Expected label %s to be present", label) + } + for _, annotation := range tt.expectedAnnotations { + assert.Contains(t, entry, annotation, "Expected annotation %s to be present", annotation) + } + for _, k := range tt.notExpect { + assert.NotContains(t, entry, k) + } + }) + } +} + +func TestShouldInclude(t *testing.T) { + tests := []struct { + name string + key string + inclusions map[string]struct{} + exclusions map[string]struct{} + expected bool + }{ + { + name: "No inclusions or exclusions - should include", + key: "test", + inclusions: map[string]struct{}{}, + exclusions: map[string]struct{}{}, + expected: true, + }, + { + name: "Key in inclusions - should include", + key: "test", + inclusions: map[string]struct{}{"test": {}}, + exclusions: map[string]struct{}{}, + expected: true, + }, + { + name: "Key not in inclusions - should not include", + key: "test", + inclusions: map[string]struct{}{"other": {}}, + exclusions: map[string]struct{}{}, + expected: false, + }, + { + name: "Key in exclusions - should not include", + key: "test", + inclusions: map[string]struct{}{}, + exclusions: map[string]struct{}{"test": {}}, + expected: false, + }, + { + name: "Key in both inclusions and exclusions - exclusions win", + key: "test", + inclusions: map[string]struct{}{"test": {}}, + exclusions: map[string]struct{}{"test": {}}, + expected: false, + }, + { + name: "Key not in exclusions, no inclusions - should include", + key: "test", + inclusions: map[string]struct{}{}, + exclusions: map[string]struct{}{"other": {}}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := shouldInclude(tt.key, tt.inclusions, tt.exclusions) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestTruncateWithSuffix(t *testing.T) { + maxLen10 := 10 + maxLen0 := 0 + + tests := []struct { + name string + input string + maxLen *int + suffix string + expected string + }{ + { + name: "No max length - no truncation", + input: "this is a long string", + maxLen: nil, + suffix: "...", + expected: "this is a long string", + }, + { + name: "String shorter than max - no truncation", + input: "short", + maxLen: &maxLen10, + suffix: "...", + expected: "short", + }, + { + name: "String longer than max - truncate with suffix", + input: "this is a very long string", + maxLen: &maxLen10, + suffix: "...", + expected: "this is...", + }, + { + name: "Max length 0 - return empty", + input: "test", + maxLen: &maxLen0, + suffix: "...", + expected: "", + }, + { + name: "Empty string", + input: "", + maxLen: &maxLen10, + suffix: "...", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := truncateWithSuffix(tt.input, tt.maxLen, tt.suffix) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestEnrich_LabelsValueTrimming(t *testing.T) { + testData := map[string]*model.ResourceMetaData{ + "10.0.0.20": { + ObjectMeta: v1.ObjectMeta{ + Name: "test-pod", + Namespace: "test-ns", + Labels: map[string]string{ + "short": "val", + "medium": "this is medium", + "long": "this is a very long label value that should be trimmed", + }, + }, + Kind: "Pod", + }, + } + setupStubs(testData, nil, nodes) + + maxLen10 := 10 + maxLen20 := 20 + + tests := []struct { + name string + labelValueMaxLen *int + expectedLabels map[string]string + }{ + { + name: "No trimming", + labelValueMaxLen: nil, + expectedLabels: map[string]string{ + "k8s_labels_short": "val", + "k8s_labels_medium": "this is medium", + "k8s_labels_long": "this is a very long label value that should be trimmed", + }, + }, + { + name: "Trim to 10 chars", + labelValueMaxLen: &maxLen10, + expectedLabels: map[string]string{ + "k8s_labels_short": "val", + "k8s_labels_medium": "this is...", + "k8s_labels_long": "this is...", + }, + }, + { + name: "Trim to 20 chars", + labelValueMaxLen: &maxLen20, + expectedLabels: map[string]string{ + "k8s_labels_short": "val", + "k8s_labels_medium": "this is medium", + "k8s_labels_long": "this is a very lo...", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rule := api.TransformNetwork{ + Rules: api.NetworkTransformRules{{ + Type: api.NetworkAddKubernetes, + Kubernetes: &api.K8sRule{ + IPField: "SrcAddr", + Output: "k8s", + LabelsPrefix: "k8s_labels", + LabelValueMaxLength: tt.labelValueMaxLen, + }, + }}, + } + rule.Preprocess() + + entry := config.GenericMap{"SrcAddr": "10.0.0.20"} + Enrich(entry, rule.Rules[0].Kubernetes) + + for k, v := range tt.expectedLabels { + assert.Equal(t, v, entry[k]) + } + }) + } +} + +func TestEnrich_AnnotationsValueTrimming(t *testing.T) { + testData := map[string]*model.ResourceMetaData{ + "10.0.0.21": { + ObjectMeta: v1.ObjectMeta{ + Name: "test-pod", + Namespace: "test-ns", + Annotations: map[string]string{ + "short": "val", + "medium": "this is medium", + "long": "this is a very long annotation value that should be trimmed", + }, + }, + Kind: "Pod", + }, + } + setupStubs(testData, nil, nodes) + + maxLen10 := 10 + maxLen20 := 20 + + tests := []struct { + name string + annotationValueMaxLen *int + expectedAnnotations map[string]string + }{ + { + name: "No trimming", + annotationValueMaxLen: nil, + expectedAnnotations: map[string]string{ + "k8s_annotations_short": "val", + "k8s_annotations_medium": "this is medium", + "k8s_annotations_long": "this is a very long annotation value that should be trimmed", + }, + }, + { + name: "Trim to 10 chars", + annotationValueMaxLen: &maxLen10, + expectedAnnotations: map[string]string{ + "k8s_annotations_short": "val", + "k8s_annotations_medium": "this is...", + "k8s_annotations_long": "this is...", + }, + }, + { + name: "Trim to 20 chars", + annotationValueMaxLen: &maxLen20, + expectedAnnotations: map[string]string{ + "k8s_annotations_short": "val", + "k8s_annotations_medium": "this is medium", + "k8s_annotations_long": "this is a very lo...", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rule := api.TransformNetwork{ + Rules: api.NetworkTransformRules{{ + Type: api.NetworkAddKubernetes, + Kubernetes: &api.K8sRule{ + IPField: "SrcAddr", + Output: "k8s", + AnnotationsPrefix: "k8s_annotations", + AnnotationValueMaxLength: tt.annotationValueMaxLen, + }, + }}, + } + rule.Preprocess() + + entry := config.GenericMap{"SrcAddr": "10.0.0.21"} + Enrich(entry, rule.Rules[0].Kubernetes) + + for k, v := range tt.expectedAnnotations { + assert.Equal(t, v, entry[k]) + } + }) + } +} + +func TestEnrich_LabelsAndAnnotationsTrimming_Combined(t *testing.T) { + testData := map[string]*model.ResourceMetaData{ + "10.0.0.22": { + ObjectMeta: v1.ObjectMeta{ + Name: "test-pod", + Namespace: "test-ns", + Labels: map[string]string{ + "app": "myapp-with-very-long-name", + "version": "v1", + }, + Annotations: map[string]string{ + "description": "This is a very long description that should be trimmed", + "owner": "team-backend", + }, + }, + Kind: "Pod", + }, + } + setupStubs(testData, nil, nodes) + + maxLen15 := 15 + maxLen20 := 20 + + tests := []struct { + name string + labelValueMaxLen *int + annotationValueMaxLen *int + labelInclusions []string + expectedLabels map[string]string + expectedAnnotations map[string]string + notExpect []string + }{ + { + name: "Trim both with different max lengths", + labelValueMaxLen: &maxLen15, + annotationValueMaxLen: &maxLen20, + expectedLabels: map[string]string{ + "k8s_labels_app": "myapp-with-v...", + "k8s_labels_version": "v1", + }, + expectedAnnotations: map[string]string{ + "k8s_annotations_description": "This is a very lo...", + "k8s_annotations_owner": "team-backend", + }, + }, + { + name: "Trim with filtering", + labelValueMaxLen: &maxLen15, + annotationValueMaxLen: &maxLen20, + labelInclusions: []string{"app"}, + expectedLabels: map[string]string{ + "k8s_labels_app": "myapp-with-v...", + }, + expectedAnnotations: map[string]string{ + "k8s_annotations_description": "This is a very lo...", + "k8s_annotations_owner": "team-backend", + }, + notExpect: []string{"k8s_labels_version"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rule := api.TransformNetwork{ + Rules: api.NetworkTransformRules{{ + Type: api.NetworkAddKubernetes, + Kubernetes: &api.K8sRule{ + IPField: "SrcAddr", + Output: "k8s", + LabelsPrefix: "k8s_labels", + LabelValueMaxLength: tt.labelValueMaxLen, + LabelInclusions: tt.labelInclusions, + AnnotationsPrefix: "k8s_annotations", + AnnotationValueMaxLength: tt.annotationValueMaxLen, + }, + }}, + } + rule.Preprocess() + + entry := config.GenericMap{"SrcAddr": "10.0.0.22"} + Enrich(entry, rule.Rules[0].Kubernetes) + + for k, v := range tt.expectedLabels { + assert.Equal(t, v, entry[k]) + } + for k, v := range tt.expectedAnnotations { + assert.Equal(t, v, entry[k]) + } + for _, k := range tt.notExpect { + assert.NotContains(t, entry, k) + } + }) + } +} diff --git a/pkg/pipeline/transform/kubernetes/informers/informers.go b/pkg/pipeline/transform/kubernetes/informers/informers.go index 546b65b8a..d605f6d73 100644 --- a/pkg/pipeline/transform/kubernetes/informers/informers.go +++ b/pkg/pipeline/transform/kubernetes/informers/informers.go @@ -234,8 +234,9 @@ func (k *Informers) initNodeInformer(informerFactory inf.SharedInformerFactory, return &model.ResourceMetaData{ ObjectMeta: metav1.ObjectMeta{ - Name: node.Name, - Labels: node.Labels, + Name: node.Name, + Labels: node.Labels, + Annotations: node.Annotations, }, Kind: model.KindNode, OwnerName: node.Name, @@ -294,6 +295,7 @@ func (k *Informers) initPodInformer(informerFactory inf.SharedInformerFactory, c Name: pod.Name, Namespace: pod.Namespace, Labels: pod.Labels, + Annotations: pod.Annotations, OwnerReferences: pod.OwnerReferences, }, Kind: model.KindPod, @@ -335,9 +337,10 @@ func (k *Informers) initServiceInformer(informerFactory inf.SharedInformerFactor } return &model.ResourceMetaData{ ObjectMeta: metav1.ObjectMeta{ - Name: svc.Name, - Namespace: svc.Namespace, - Labels: svc.Labels, + Name: svc.Name, + Namespace: svc.Namespace, + Labels: svc.Labels, + Annotations: svc.Annotations, }, Kind: model.KindService, OwnerName: svc.Name,