Skip to content

Commit 52d1783

Browse files
committed
add support for label and annotation inclusion and exclusion lists
Signed-off-by: Alex Price <[email protected]>
1 parent 480c99b commit 52d1783

File tree

4 files changed

+273
-12
lines changed

4 files changed

+273
-12
lines changed

docs/api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,11 @@ Following is the supported API format for network transformations:
266266
output: entry output field
267267
assignee: value needs to assign to output field
268268
labels_prefix: labels prefix to use to copy input labels, if empty labels will not be copied
269+
label_inclusions: labels to include, if empty all labels will be included. Only used if labels_prefix is specified
270+
label_exclusions: labels to exclude, if empty no labels will be excluded. Only used if labels_prefix is specified
269271
annotations_prefix: annotations prefix to use to copy input annotations, if empty annotations will not be copied
272+
annotation_inclusions: annotations to include, if empty all annotations will be included. Only used if annotations_prefix is specified
273+
annotation_exclusions: annotations to exclude, if empty no annotations will be excluded. Only used if annotations_prefix is specified
270274
add_zone: if true the rule will add the zone
271275
add_subnet: Add subnet rule configuration
272276
input: entry input field

pkg/api/transform_network.go

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,24 @@ type K8sReference struct {
9898
}
9999

100100
type K8sRule struct {
101-
IPField string `yaml:"ipField,omitempty" json:"ipField,omitempty" doc:"entry IP input field"`
102-
InterfacesField string `yaml:"interfacesField,omitempty" json:"interfacesField,omitempty" doc:"entry Interfaces input field"`
103-
UDNsField string `yaml:"udnsField,omitempty" json:"udnsField,omitempty" doc:"entry UDNs input field"`
104-
MACField string `yaml:"macField,omitempty" json:"macField,omitempty" doc:"entry MAC input field"`
105-
Output string `yaml:"output,omitempty" json:"output,omitempty" doc:"entry output field"`
106-
Assignee string `yaml:"assignee,omitempty" json:"assignee,omitempty" doc:"value needs to assign to output field"`
107-
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"`
108-
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"`
109-
AddZone bool `yaml:"add_zone,omitempty" json:"add_zone,omitempty" doc:"if true the rule will add the zone"`
110-
OutputKeys K8SOutputKeys `yaml:"-" json:"-"`
101+
IPField string `yaml:"ipField,omitempty" json:"ipField,omitempty" doc:"entry IP input field"`
102+
InterfacesField string `yaml:"interfacesField,omitempty" json:"interfacesField,omitempty" doc:"entry Interfaces input field"`
103+
UDNsField string `yaml:"udnsField,omitempty" json:"udnsField,omitempty" doc:"entry UDNs input field"`
104+
MACField string `yaml:"macField,omitempty" json:"macField,omitempty" doc:"entry MAC input field"`
105+
Output string `yaml:"output,omitempty" json:"output,omitempty" doc:"entry output field"`
106+
Assignee string `yaml:"assignee,omitempty" json:"assignee,omitempty" doc:"value needs to assign to output field"`
107+
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"`
108+
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"`
109+
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"`
110+
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"`
111+
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"`
112+
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"`
113+
AddZone bool `yaml:"add_zone,omitempty" json:"add_zone,omitempty" doc:"if true the rule will add the zone"`
114+
OutputKeys K8SOutputKeys `yaml:"-" json:"-"`
115+
LabelInclusionsMap map[string]struct{} `yaml:"-" json:"-"`
116+
LabelExclusionsMap map[string]struct{} `yaml:"-" json:"-"`
117+
AnnotationInclusionsMap map[string]struct{} `yaml:"-" json:"-"`
118+
AnnotationExclusionsMap map[string]struct{} `yaml:"-" json:"-"`
111119
}
112120

113121
type K8SOutputKeys struct {
@@ -151,6 +159,11 @@ func (r *K8sRule) preprocess() {
151159
Zone: r.Output + "_Zone",
152160
}
153161
}
162+
163+
r.LabelInclusionsMap = buildStringMap(r.LabelInclusions)
164+
r.LabelExclusionsMap = buildStringMap(r.LabelExclusions)
165+
r.AnnotationInclusionsMap = buildStringMap(r.AnnotationInclusions)
166+
r.AnnotationExclusionsMap = buildStringMap(r.AnnotationExclusions)
154167
}
155168

156169
type SecondaryNetwork struct {
@@ -200,3 +213,11 @@ type NetworkTransformSubnetLabel struct {
200213
CIDRs []string `yaml:"cidrs,omitempty" json:"cidrs,omitempty" doc:"list of CIDRs to match a label"`
201214
Name string `yaml:"name,omitempty" json:"name,omitempty" doc:"name of the label"`
202215
}
216+
217+
func buildStringMap(items []string) map[string]struct{} {
218+
m := make(map[string]struct{}, len(items))
219+
for _, item := range items {
220+
m[item] = struct{}{}
221+
}
222+
return m
223+
}

pkg/pipeline/transform/kubernetes/enrich.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,16 @@ func Enrich(outputEntry config.GenericMap, rule *api.K8sRule) {
5454
outputEntry[rule.OutputKeys.NetworkName] = kubeInfo.NetworkName
5555
if rule.LabelsPrefix != "" {
5656
for labelKey, labelValue := range kubeInfo.Labels {
57-
outputEntry[rule.LabelsPrefix+"_"+labelKey] = labelValue
57+
if shouldInclude(labelKey, rule.LabelInclusionsMap, rule.LabelExclusionsMap) {
58+
outputEntry[rule.LabelsPrefix+"_"+labelKey] = labelValue
59+
}
5860
}
5961
}
6062
if rule.AnnotationsPrefix != "" {
6163
for annotationKey, annotationValue := range kubeInfo.Annotations {
62-
outputEntry[rule.AnnotationsPrefix+"_"+annotationKey] = annotationValue
64+
if shouldInclude(annotationKey, rule.AnnotationInclusionsMap, rule.AnnotationExclusionsMap) {
65+
outputEntry[rule.AnnotationsPrefix+"_"+annotationKey] = annotationValue
66+
}
6367
}
6468
}
6569
if kubeInfo.HostIP != "" {
@@ -147,3 +151,15 @@ func objectIsApp(namespace, name string, rule *api.K8sInfraRule) bool {
147151
}
148152
return true
149153
}
154+
155+
// shouldInclude determines if an item should be included based on inclusion/exclusion maps.
156+
func shouldInclude(key string, inclusions, exclusions map[string]struct{}) bool {
157+
if _, excluded := exclusions[key]; excluded {
158+
return false
159+
}
160+
if len(inclusions) > 0 {
161+
_, included := inclusions[key]
162+
return included
163+
}
164+
return true
165+
}

pkg/pipeline/transform/kubernetes/enrich_test.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,3 +663,223 @@ func TestEnrich_LabelsAndAnnotationsPrefixes(t *testing.T) {
663663
})
664664
}
665665
}
666+
667+
func TestEnrich_LabelsAnnotationsFiltering(t *testing.T) {
668+
testData := map[string]*model.ResourceMetaData{
669+
"10.0.0.10": {
670+
ObjectMeta: v1.ObjectMeta{
671+
Name: "test-pod",
672+
Namespace: "test-ns",
673+
Labels: map[string]string{
674+
"app": "myapp",
675+
"version": "v1",
676+
"environment": "prod",
677+
"team": "backend",
678+
},
679+
Annotations: map[string]string{
680+
"annotation1": "value1",
681+
"annotation2": "value2",
682+
"annotation3": "value3",
683+
"annotation4": "value4",
684+
},
685+
},
686+
Kind: "Pod",
687+
},
688+
}
689+
690+
setupStubs(testData, nil, nodes)
691+
692+
tests := []struct {
693+
name string
694+
labelsPrefix string
695+
labelInclusions []string
696+
labelExclusions []string
697+
annotationsPrefix string
698+
annotationInclusions []string
699+
annotationExclusions []string
700+
expectedLabels []string
701+
expectedAnnotations []string
702+
notExpect []string
703+
}{
704+
{
705+
name: "No filtering - all labels and annotations included",
706+
labelsPrefix: "k8s_labels",
707+
annotationsPrefix: "k8s_annotations",
708+
expectedLabels: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team"},
709+
expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"},
710+
},
711+
{
712+
name: "Only label inclusions specified",
713+
labelsPrefix: "k8s_labels",
714+
labelInclusions: []string{"app", "version"},
715+
annotationsPrefix: "k8s_annotations",
716+
expectedLabels: []string{"k8s_labels_app", "k8s_labels_version"},
717+
expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"},
718+
notExpect: []string{"k8s_labels_environment", "k8s_labels_team"},
719+
},
720+
{
721+
name: "Only label exclusions specified",
722+
labelsPrefix: "k8s_labels",
723+
labelExclusions: []string{"environment", "team"},
724+
annotationsPrefix: "k8s_annotations",
725+
expectedLabels: []string{"k8s_labels_app", "k8s_labels_version"},
726+
expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"},
727+
notExpect: []string{"k8s_labels_environment", "k8s_labels_team"},
728+
},
729+
{
730+
name: "Both label inclusions and exclusions - exclusions take precedence",
731+
labelsPrefix: "k8s_labels",
732+
labelInclusions: []string{"app", "version", "environment"},
733+
labelExclusions: []string{"environment"},
734+
annotationsPrefix: "k8s_annotations",
735+
expectedLabels: []string{"k8s_labels_app", "k8s_labels_version"},
736+
expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation2", "k8s_annotations_annotation3", "k8s_annotations_annotation4"},
737+
notExpect: []string{"k8s_labels_environment", "k8s_labels_team"},
738+
},
739+
{
740+
name: "Only annotation inclusions specified",
741+
labelsPrefix: "k8s_labels",
742+
annotationsPrefix: "k8s_annotations",
743+
annotationInclusions: []string{"annotation1", "annotation3"},
744+
expectedLabels: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team"},
745+
expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation3"},
746+
notExpect: []string{"k8s_annotations_annotation2", "k8s_annotations_annotation4"},
747+
},
748+
{
749+
name: "Only annotation exclusions specified",
750+
labelsPrefix: "k8s_labels",
751+
annotationsPrefix: "k8s_annotations",
752+
annotationExclusions: []string{"annotation2", "annotation4"},
753+
expectedLabels: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team"},
754+
expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation3"},
755+
notExpect: []string{"k8s_annotations_annotation2", "k8s_annotations_annotation4"},
756+
},
757+
{
758+
name: "Both annotation inclusions and exclusions - exclusions take precedence",
759+
labelsPrefix: "k8s_labels",
760+
annotationsPrefix: "k8s_annotations",
761+
annotationInclusions: []string{"annotation1", "annotation2", "annotation3"},
762+
annotationExclusions: []string{"annotation2"},
763+
expectedLabels: []string{"k8s_labels_app", "k8s_labels_version", "k8s_labels_environment", "k8s_labels_team"},
764+
expectedAnnotations: []string{"k8s_annotations_annotation1", "k8s_annotations_annotation3"},
765+
notExpect: []string{"k8s_annotations_annotation2", "k8s_annotations_annotation4"},
766+
},
767+
{
768+
name: "Combined filtering for both labels and annotations",
769+
labelsPrefix: "k8s_labels",
770+
labelInclusions: []string{"app", "version", "team"},
771+
labelExclusions: []string{"team"},
772+
annotationsPrefix: "k8s_annotations",
773+
annotationInclusions: []string{"annotation1", "annotation2"},
774+
annotationExclusions: []string{"annotation1"},
775+
expectedLabels: []string{"k8s_labels_app", "k8s_labels_version"},
776+
expectedAnnotations: []string{"k8s_annotations_annotation2"},
777+
notExpect: []string{"k8s_labels_environment", "k8s_labels_team", "k8s_annotations_annotation1", "k8s_annotations_annotation3", "k8s_annotations_annotation4"},
778+
},
779+
{
780+
name: "Empty prefix - no labels or annotations added",
781+
labelsPrefix: "",
782+
labelInclusions: []string{"app"},
783+
annotationsPrefix: "",
784+
annotationInclusions: []string{"annotation1"},
785+
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"},
786+
},
787+
}
788+
789+
for _, tt := range tests {
790+
t.Run(tt.name, func(t *testing.T) {
791+
rule := api.TransformNetwork{
792+
Rules: api.NetworkTransformRules{{
793+
Type: api.NetworkAddKubernetes,
794+
Kubernetes: &api.K8sRule{
795+
IPField: "SrcAddr",
796+
Output: "SrcK8s",
797+
LabelsPrefix: tt.labelsPrefix,
798+
LabelInclusions: tt.labelInclusions,
799+
LabelExclusions: tt.labelExclusions,
800+
AnnotationsPrefix: tt.annotationsPrefix,
801+
AnnotationInclusions: tt.annotationInclusions,
802+
AnnotationExclusions: tt.annotationExclusions,
803+
},
804+
}},
805+
}
806+
rule.Preprocess()
807+
808+
entry := config.GenericMap{
809+
"SrcAddr": "10.0.0.10",
810+
}
811+
812+
Enrich(entry, rule.Rules[0].Kubernetes)
813+
814+
for _, label := range tt.expectedLabels {
815+
assert.Contains(t, entry, label, "Expected label %s to be present", label)
816+
}
817+
for _, annotation := range tt.expectedAnnotations {
818+
assert.Contains(t, entry, annotation, "Expected annotation %s to be present", annotation)
819+
}
820+
for _, k := range tt.notExpect {
821+
assert.NotContains(t, entry, k)
822+
}
823+
})
824+
}
825+
}
826+
827+
func TestShouldInclude(t *testing.T) {
828+
tests := []struct {
829+
name string
830+
key string
831+
inclusions map[string]struct{}
832+
exclusions map[string]struct{}
833+
expected bool
834+
}{
835+
{
836+
name: "No inclusions or exclusions - should include",
837+
key: "test",
838+
inclusions: map[string]struct{}{},
839+
exclusions: map[string]struct{}{},
840+
expected: true,
841+
},
842+
{
843+
name: "Key in inclusions - should include",
844+
key: "test",
845+
inclusions: map[string]struct{}{"test": {}},
846+
exclusions: map[string]struct{}{},
847+
expected: true,
848+
},
849+
{
850+
name: "Key not in inclusions - should not include",
851+
key: "test",
852+
inclusions: map[string]struct{}{"other": {}},
853+
exclusions: map[string]struct{}{},
854+
expected: false,
855+
},
856+
{
857+
name: "Key in exclusions - should not include",
858+
key: "test",
859+
inclusions: map[string]struct{}{},
860+
exclusions: map[string]struct{}{"test": {}},
861+
expected: false,
862+
},
863+
{
864+
name: "Key in both inclusions and exclusions - exclusions win",
865+
key: "test",
866+
inclusions: map[string]struct{}{"test": {}},
867+
exclusions: map[string]struct{}{"test": {}},
868+
expected: false,
869+
},
870+
{
871+
name: "Key not in exclusions, no inclusions - should include",
872+
key: "test",
873+
inclusions: map[string]struct{}{},
874+
exclusions: map[string]struct{}{"other": {}},
875+
expected: true,
876+
},
877+
}
878+
879+
for _, tt := range tests {
880+
t.Run(tt.name, func(t *testing.T) {
881+
result := shouldInclude(tt.key, tt.inclusions, tt.exclusions)
882+
assert.Equal(t, tt.expected, result)
883+
})
884+
}
885+
}

0 commit comments

Comments
 (0)