feat: include or exclude namespaces to watch based on their labels

This commit is contained in:
Paul Laffitte
2025-01-10 18:58:59 +01:00
committed by Paul Laffitte
parent 5bc2da352f
commit fc47f9b1ab
6 changed files with 400 additions and 304 deletions

View File

@@ -69,6 +69,12 @@ func main() {
kubeExcludeNamespaces := stringArrayFlag{}
getopt.FlagLong(&kubeExcludeNamespaces, "exclude-namespace", 0, "removes the given kube namespace from the watch list (applied after --include-namespace)")
kubeIncludeNamespaceLabels := stringArrayFlag{}
getopt.FlagLong(&kubeIncludeNamespaceLabels, "include-namespace-label", 0, "add the kube namespaces with the given label (or label value if specified) to the watch list (when used, all namespaces are excluded by default)")
kubeExcludeNamespaceLabels := stringArrayFlag{}
getopt.FlagLong(&kubeExcludeNamespaceLabels, "exclude-namespace-label", 0, "removes the kube namespaces with the given label (or label value if specified) from the watch list (applied after --include-namespace-label)")
kubeIncludeLabels := stringArrayFlag{}
getopt.FlagLong(&kubeIncludeLabels, "include-label", 0, "add the kube secrets with the given label (or label value if specified) to the watch list (when used, all secrets are excluded by default)")
@@ -128,23 +134,25 @@ func main() {
}
exporter := internal.Exporter{
ListenAddress: *listenAddress,
SystemdSocket: *systemdSocket,
ConfigFile: *configFile,
Files: files,
Directories: directories,
YAMLs: yamls,
YAMLPaths: internal.DefaultYamlPaths,
TrimPathComponents: *trimPathComponents,
MaxCacheDuration: time.Duration(maxCacheDuration),
ExposeRelativeMetrics: *exposeRelativeMetrics,
ExposeErrorMetrics: *exposeErrorMetrics,
KubeSecretTypes: kubeSecretTypes,
ConfigMapKeys: kubeConfigMapKeys,
KubeIncludeNamespaces: kubeIncludeNamespaces,
KubeExcludeNamespaces: kubeExcludeNamespaces,
KubeIncludeLabels: kubeIncludeLabels,
KubeExcludeLabels: kubeExcludeLabels,
ListenAddress: *listenAddress,
SystemdSocket: *systemdSocket,
ConfigFile: *configFile,
Files: files,
Directories: directories,
YAMLs: yamls,
YAMLPaths: internal.DefaultYamlPaths,
TrimPathComponents: *trimPathComponents,
MaxCacheDuration: time.Duration(maxCacheDuration),
ExposeRelativeMetrics: *exposeRelativeMetrics,
ExposeErrorMetrics: *exposeErrorMetrics,
KubeSecretTypes: kubeSecretTypes,
ConfigMapKeys: kubeConfigMapKeys,
KubeIncludeNamespaces: kubeIncludeNamespaces,
KubeExcludeNamespaces: kubeExcludeNamespaces,
KubeIncludeNamespaceLabels: kubeIncludeNamespaceLabels,
KubeExcludeNamespaceLabels: kubeExcludeNamespaceLabels,
KubeIncludeLabels: kubeIncludeLabels,
KubeExcludeLabels: kubeExcludeLabels,
}
if getopt.Lookup("expose-labels").Seen() {

View File

@@ -114,6 +114,12 @@ spec:
{{- range .Values.secretsExporter.excludeNamespaces }}
- --exclude-namespace={{ . | trim }}
{{- end }}
{{- range .Values.secretsExporter.includeNamespaceLabels }}
- --include-namespace-label={{ . | trim }}
{{- end }}
{{- range .Values.secretsExporter.excludeNamespaceLabels }}
- --exclude-namespace-label={{ . | trim }}
{{- end }}
{{- range .Values.secretsExporter.includeLabels }}
- --include-label={{ . | trim }}
{{- end }}

View File

@@ -122,6 +122,10 @@ secretsExporter:
includeNamespaces: []
# -- Exclude namespaces from being scanned by the TLS Secrets exporter (evaluated after `includeNamespaces`)
excludeNamespaces: []
# -- Only watch namespaces having these labels (all namespaces if empty). Items can be keys such as `my-label` or also require a value with syntax `my-label=my-value`.
includeNamespaceLabels: []
# -- Exclude namespaces having these labels. Items can be keys such as `my-label` or also require a value with syntax `my-label=my-value`.
excludeNamespaceLabels: []
# -- Only watch TLS Secrets having these labels (all secrets if empty). Items can be keys such as `my-label` or also require a value with syntax `my-label=my-value`.
includeLabels: []
# -- Exclude TLS Secrets having these labels. Items can be keys such as `my-label` or also require a value with syntax `my-label=my-value`.

View File

@@ -27,24 +27,26 @@ import (
// Exporter : Configuration (from command-line)
type Exporter struct {
ListenAddress string
SystemdSocket bool
ConfigFile string
Files []string
Directories []string
YAMLs []string
YAMLPaths []YAMLCertRef
TrimPathComponents int
MaxCacheDuration time.Duration
ExposeRelativeMetrics bool
ExposeErrorMetrics bool
ExposeLabels []string
ConfigMapKeys []string
KubeSecretTypes []KubeSecretType
KubeIncludeNamespaces []string
KubeExcludeNamespaces []string
KubeIncludeLabels []string
KubeExcludeLabels []string
ListenAddress string
SystemdSocket bool
ConfigFile string
Files []string
Directories []string
YAMLs []string
YAMLPaths []YAMLCertRef
TrimPathComponents int
MaxCacheDuration time.Duration
ExposeRelativeMetrics bool
ExposeErrorMetrics bool
ExposeLabels []string
ConfigMapKeys []string
KubeSecretTypes []KubeSecretType
KubeIncludeNamespaces []string
KubeExcludeNamespaces []string
KubeIncludeNamespaceLabels []string
KubeExcludeNamespaceLabels []string
KubeIncludeLabels []string
KubeExcludeLabels []string
kubeClient *kubernetes.Clientset
listener net.Listener

View File

@@ -109,36 +109,72 @@ func (exporter *Exporter) parseAllKubeObjects() ([]*certificateRef, []error) {
}
func (exporter *Exporter) listNamespacesToWatch() ([]string, error) {
includedNamespaces := exporter.KubeIncludeNamespaces
if len(includedNamespaces) < 1 {
allNamespaces, err := exporter.kubeClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, ns := range allNamespaces.Items {
includedNamespaces = append(includedNamespaces, ns.Name)
}
_, includedLabelsWithValue := exporter.prepareLabelFilters(exporter.KubeIncludeNamespaceLabels)
labelSelector := metav1.LabelSelector{MatchLabels: includedLabelsWithValue}
namespaces, err := exporter.kubeClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{
LabelSelector: labels.Set(labelSelector.MatchLabels).String(),
})
if err != nil {
return nil, err
}
namespaces := []string{}
for _, includeNs := range includedNamespaces {
found := false
return exporter.filterNamespaces(namespaces.Items), nil
}
func (exporter *Exporter) filterNamespaces(namespaces []v1.Namespace) []string {
filteredNamespaces := []*v1.Namespace{}
for _, namespace := range namespaces {
found := false
for _, includeNs := range exporter.KubeIncludeNamespaces {
if namespace.Name == includeNs {
found = true
break
}
}
if len(exporter.KubeIncludeNamespaces) > 0 && !found {
continue
}
found = false
for _, excludeNs := range exporter.KubeExcludeNamespaces {
if includeNs == excludeNs {
if namespace.Name == excludeNs {
found = true
break
}
}
if !found {
namespaces = append(namespaces, includeNs)
filteredNamespaces = append(filteredNamespaces, &namespace)
}
}
return namespaces, nil
includedLabels, _ := exporter.prepareLabelFilters(exporter.KubeIncludeNamespaceLabels)
excludedLabels, excludedLabelsWithValue := exporter.prepareLabelFilters(exporter.KubeExcludeNamespaceLabels)
filteredNamespaces = filterObjects(filteredNamespaces, includedLabels, excludedLabels, excludedLabelsWithValue)
namespacesNames := []string{}
for _, namespace := range filteredNamespaces {
namespacesNames = append(namespacesNames, namespace.Name)
}
return namespacesNames
}
func (exporter *Exporter) prepareLabelFilters(labels []string) ([]string, map[string]string) {
labelsWithValue := map[string]string{}
labelsWithoutValue := []string{}
for _, label := range labels {
parts := strings.Split(label, "=")
if len(parts) < 2 {
labelsWithoutValue = append(labelsWithoutValue, label)
} else {
labelsWithValue[parts[0]] = parts[1]
}
}
return labelsWithoutValue, labelsWithValue
}
func (exporter *Exporter) getWatchedConfigMaps(namespace string) ([]v1.ConfigMap, error) {
@@ -163,28 +199,7 @@ func (exporter *Exporter) getWatchedSecrets(namespace string) ([]v1.Secret, erro
return cachedSecrets.([]v1.Secret), nil
}
includedLabelsWithValue := map[string]string{}
includedLabelsWithoutValue := []string{}
for _, label := range exporter.KubeIncludeLabels {
parts := strings.Split(label, "=")
if len(parts) < 2 {
includedLabelsWithoutValue = append(includedLabelsWithoutValue, label)
} else {
includedLabelsWithValue[parts[0]] = parts[1]
}
}
excludedLabelsWithValue := map[string]string{}
excludedLabelsWithoutValue := []string{}
for _, label := range exporter.KubeExcludeLabels {
parts := strings.Split(label, "=")
if len(parts) < 2 {
excludedLabelsWithoutValue = append(excludedLabelsWithoutValue, label)
} else {
excludedLabelsWithValue[parts[0]] = parts[1]
}
}
_, includedLabelsWithValue := exporter.prepareLabelFilters(exporter.KubeIncludeLabels)
labelSelector := metav1.LabelSelector{MatchLabels: includedLabelsWithValue}
secrets, err := exporter.kubeClient.CoreV1().Secrets(namespace).List(context.Background(), metav1.ListOptions{
LabelSelector: labels.Set(labelSelector.MatchLabels).String(),
@@ -193,7 +208,7 @@ func (exporter *Exporter) getWatchedSecrets(namespace string) ([]v1.Secret, erro
return nil, err
}
filteredSecrets, err := exporter.filterSecrets(secrets.Items, includedLabelsWithoutValue, excludedLabelsWithoutValue, excludedLabelsWithValue)
filteredSecrets, err := exporter.filterSecrets(secrets.Items)
if err != nil {
return nil, err
}
@@ -209,8 +224,11 @@ func (exporter *Exporter) getWatchedSecrets(namespace string) ([]v1.Secret, erro
return shrinkedSecrets, nil
}
func (exporter *Exporter) filterSecrets(secrets []v1.Secret, includedLabels, excludedLabels []string, excludedLabelsWithValue map[string]string) ([]v1.Secret, error) {
filteredSecrets := []v1.Secret{}
func (exporter *Exporter) filterSecrets(secrets []v1.Secret) ([]v1.Secret, error) {
filteredSecrets := []*v1.Secret{}
includedLabels, _ := exporter.prepareLabelFilters(exporter.KubeIncludeLabels)
excludedLabels, excludedLabelsWithValue := exporter.prepareLabelFilters(exporter.KubeExcludeLabels)
for _, secret := range secrets {
hasIncludedType, err := exporter.checkHasIncludedType(&secret)
@@ -222,41 +240,15 @@ func (exporter *Exporter) filterSecrets(secrets []v1.Secret, includedLabels, exc
continue
}
validKeyCount := 0
for _, expectedKey := range includedLabels {
for key := range secret.GetLabels() {
if key == expectedKey {
validKeyCount++
break
}
}
}
forbiddenKeyCount := 0
for _, forbiddenKey := range excludedLabels {
for key := range secret.GetLabels() {
if key == forbiddenKey {
forbiddenKeyCount++
break
}
}
}
for forbiddenKey, forbiddenValue := range excludedLabelsWithValue {
for key, value := range secret.GetLabels() {
if key == forbiddenKey && value == forbiddenValue {
forbiddenKeyCount++
break
}
}
}
if validKeyCount >= len(includedLabels) && forbiddenKeyCount == 0 {
filteredSecrets = append(filteredSecrets, secret)
}
filteredSecrets = append(filteredSecrets, &secret)
}
return filteredSecrets, nil
filteredSecrets = filterObjects(filteredSecrets, includedLabels, excludedLabels, excludedLabelsWithValue)
for i, filteredSecret := range filteredSecrets {
secrets[i] = *filteredSecret
}
return secrets[:len(filteredSecrets)], nil
}
func (exporter *Exporter) checkHasIncludedType(secret *v1.Secret) (bool, error) {
@@ -334,3 +326,44 @@ func getKubeClient(config *rest.Config) (*kubernetes.Clientset, error) {
return kubeClient, nil
}
func filterObjects[T metav1.Object](objects []T, includedLabels []string, excludedLabels []string, excludedLabelsWithValue map[string]string) []T {
filteredObjects := []T{}
for _, object := range objects {
validKeyCount := 0
for _, expectedKey := range includedLabels {
for key := range object.GetLabels() {
if key == expectedKey {
validKeyCount++
break
}
}
}
forbiddenKeyCount := 0
for _, forbiddenKey := range excludedLabels {
for key := range object.GetLabels() {
if key == forbiddenKey {
forbiddenKeyCount++
break
}
}
}
for forbiddenKey, forbiddenValue := range excludedLabelsWithValue {
for key, value := range object.GetLabels() {
if key == forbiddenKey && value == forbiddenValue {
forbiddenKeyCount++
break
}
}
}
if validKeyCount >= len(includedLabels) && forbiddenKeyCount == 0 {
filteredObjects = append(filteredObjects, object)
}
}
return filteredObjects
}

View File

@@ -88,202 +88,144 @@ func TestMain(m *testing.M) {
os.Exit(status)
}
func TestKubeAllSecrets(t *testing.T) {
testRequestKube(t, &Exporter{}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 21)
metrics := getMetricsForName(m, "x509_read_errors")
assert.Equal(t, 1., metrics[0].GetGauge().GetValue())
})
}
func TestKubeIncludeNamespace(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeNamespaces: []string{"default"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 11)
})
}
func TestKubeIncludeMultipleNamespaces(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeNamespaces: []string{"default", "kube-system"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 21)
})
}
func TestKubeExcludeNamespace(t *testing.T) {
testRequestKube(t, &Exporter{
KubeExcludeNamespaces: []string{"default"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 10)
})
}
func TestKubeExcludeMultipleNamespaces(t *testing.T) {
testRequestKube(t, &Exporter{
KubeExcludeNamespaces: []string{"default", "kube-system"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 0)
})
}
func TestKubeIncludeExcludeNamespaceMix(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeNamespaces: []string{"default"},
KubeExcludeNamespaces: []string{"default"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 0)
})
}
func TestKubeIncludeExcludeNamespaceMix2(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeNamespaces: []string{"default", "kube-system"},
KubeExcludeNamespaces: []string{"kube-system"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 11)
})
}
func TestKubeIncludeExistingLabelWithoutValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"test"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 20)
})
}
func TestKubeIncludeNonExistingLabelWithoutValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"xxxx"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 0)
})
}
func TestKubeIncludeExistingLabelWithValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"aze=abc"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 20)
})
}
func TestKubeIncludeNonExistingLabelWithValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"xxx=xxx"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 0)
})
}
func TestKubeIncludeExistingLabelWithNonExistingValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"aze=xxx"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 0)
})
}
func TestKubeExcludeExistingLabelWithoutValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeExcludeLabels: []string{"test"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 1)
})
}
func TestKubeExcludeNonExistingLabelWithoutValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeExcludeLabels: []string{"xxxx"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 21)
})
}
func TestKubeExcludeExistingLabelWithValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeExcludeLabels: []string{"aze=abc"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 1)
})
}
func TestKubeExcludeNonExistingLabelWithValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeExcludeLabels: []string{"xxx=xxx"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 21)
})
}
func TestKubeExcludeExistingLabelWithNonExistingValue(t *testing.T) {
testRequestKube(t, &Exporter{
KubeExcludeLabels: []string{"aze=xxx"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 21)
})
}
func TestKubeIncludeExcludeLabelMix(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"aze=abc"},
KubeExcludeLabels: []string{"aze=abc"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 0)
})
}
func TestKubeIncludeExcludeLabelMix2(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"test"},
KubeExcludeLabels: []string{"index=0"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 18)
})
}
func TestKubeIncludeExcludeLabelMix3(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"test"},
KubeExcludeLabels: []string{"xxxxx"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 20)
})
}
func TestKubeIncludeExcludeLabelMix4(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeLabels: []string{"index=0", "test"},
KubeExcludeLabels: []string{"index=1"},
}, func(m []model.MetricFamily) {
checkMetricsCount(t, m, 2)
})
}
func TestKubeCustomSecret(t *testing.T) {
testRequestKube(t, &Exporter{
KubeSecretTypes: []KubeSecretType{
{Type: "istio.io/cert-and-key", Regexp: regexp.MustCompile(`cert-chain\.pem`)},
{Type: "istio.io/cert-and-key", Regexp: regexp.MustCompile(`root-cert\.pem`)},
func TestKubeNamespaceAndSecretsFiltering(t *testing.T) {
tests := []struct {
Name string
Exporter Exporter
MetricCount int
AdditionnalCheck func(m []model.MetricFamily)
}{
{
Name: "All secrets (no filtering)",
MetricCount: 21,
AdditionnalCheck: func(m []model.MetricFamily) {
metrics := getMetricsForName(m, "x509_read_errors")
assert.Equal(t, 1., metrics[0].GetGauge().GetValue())
},
}, {
Name: "Include existing label without value",
Exporter: Exporter{
KubeIncludeLabels: []string{"test"},
},
MetricCount: 20,
}, {
Name: "Include non-existing label without value",
Exporter: Exporter{
KubeIncludeLabels: []string{"xxxx"},
},
MetricCount: 0,
}, {
Name: "Include existing label with value",
Exporter: Exporter{
KubeIncludeLabels: []string{"aze=abc"},
},
MetricCount: 20,
}, {
Name: "Include non-existing label with value",
Exporter: Exporter{
KubeIncludeLabels: []string{"xxx=xxx"},
},
MetricCount: 0,
}, {
Name: "Include existing label with non-existing value",
Exporter: Exporter{
KubeIncludeLabels: []string{"aze=xxx"},
},
MetricCount: 0,
}, {
Name: "Exclude existing label without value",
Exporter: Exporter{
KubeExcludeLabels: []string{"test"},
},
MetricCount: 1,
}, {
Name: "Exclude non-existing label without value",
Exporter: Exporter{
KubeExcludeLabels: []string{"xxxx"},
},
MetricCount: 21,
}, {
Name: "Exclude existing label with value",
Exporter: Exporter{
KubeExcludeLabels: []string{"aze=abc"},
},
MetricCount: 1,
}, {
Name: "Exclude non-existing label with value",
Exporter: Exporter{
KubeExcludeLabels: []string{"xxx=xxx"},
},
MetricCount: 21,
}, {
Name: "Exclude existing label with non-existing value",
Exporter: Exporter{
KubeExcludeLabels: []string{"aze=xxx"},
},
MetricCount: 21,
}, {
Name: "Include and exclude label mix",
Exporter: Exporter{
KubeIncludeLabels: []string{"aze=abc"},
KubeExcludeLabels: []string{"aze=abc"},
},
MetricCount: 0,
}, {
Name: "Include and exclude label mix 2",
Exporter: Exporter{
KubeIncludeLabels: []string{"test"},
KubeExcludeLabels: []string{"index=0"},
},
MetricCount: 18,
}, {
Name: "Include and exclude label mix 3",
Exporter: Exporter{
KubeIncludeLabels: []string{"test"},
KubeExcludeLabels: []string{"xxxxx"},
},
MetricCount: 20,
}, {
Name: "Include and exclude label mix 4",
Exporter: Exporter{
KubeIncludeLabels: []string{"index=0", "test"},
KubeExcludeLabels: []string{"index=1"},
},
MetricCount: 2,
}, {
Name: "Custom secret",
Exporter: Exporter{
KubeSecretTypes: []KubeSecretType{
{Type: "istio.io/cert-and-key", Regexp: regexp.MustCompile(`cert-chain\.pem`)},
{Type: "istio.io/cert-and-key", Regexp: regexp.MustCompile(`root-cert\.pem`)},
},
},
MetricCount: 2,
AdditionnalCheck: func(m []model.MetricFamily) {
metric := getMetricsForName(m, "x509_cert_expired")
assert.Len(t, metric, 2)
checkLabels(t, metric[0].GetLabel(), "k8s/default/test-custom-type", true, 15)
checkLabels(t, metric[1].GetLabel(), "k8s/default/test-custom-type", true, 15)
},
}, {
Name: "Metric labels",
Exporter: Exporter{
KubeIncludeNamespaces: []string{"default"},
KubeIncludeLabels: []string{"index=0"},
},
MetricCount: 1,
AdditionnalCheck: func(m []model.MetricFamily) {
metric := getMetricsForName(m, "x509_cert_expired")[0]
checkLabels(t, metric.GetLabel(), "k8s/default/test-default-0.crt", true, 15)
},
},
}, func(m []model.MetricFamily) {
metric := getMetricsForName(m, "x509_cert_expired")
assert.Len(t, metric, 2)
checkLabels(t, metric[0].GetLabel(), "k8s/default/test-custom-type", true, 15)
checkLabels(t, metric[1].GetLabel(), "k8s/default/test-custom-type", true, 15)
})
}
}
func TestKubeMetricLabels(t *testing.T) {
testRequestKube(t, &Exporter{
KubeIncludeNamespaces: []string{"default"},
KubeIncludeLabels: []string{"index=0"},
}, func(m []model.MetricFamily) {
metric := getMetricsForName(m, "x509_cert_expired")[0]
checkLabels(t, metric.GetLabel(), "k8s/default/test-default-0.crt", true, 15)
})
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
testRequestKube(t, &tt.Exporter, func(m []model.MetricFamily) {
checkMetricsCount(t, m, tt.MetricCount)
})
})
}
}
func TestKubeNamespaceListFailure(t *testing.T) {
@@ -374,6 +316,107 @@ func TestKubeConnectionFromInsideFailure(t *testing.T) {
assert.NotNil(t, err)
}
func TestExporterFilterNamespaces(t *testing.T) {
tests := []struct {
Name string
Exporter Exporter
ExpectedNamespaces []string
}{
{
Name: "All namespaces (no filtering)",
ExpectedNamespaces: []string{"default", "kube-system", "x509-exporter"},
}, {
Name: "Include namespace",
Exporter: Exporter{
KubeIncludeNamespaces: []string{"default"},
},
ExpectedNamespaces: []string{"default"},
}, {
Name: "Include multiple namespaces",
Exporter: Exporter{
KubeIncludeNamespaces: []string{"default", "kube-system"},
},
ExpectedNamespaces: []string{"default", "kube-system"},
}, {
Name: "Exclude namespace",
Exporter: Exporter{
KubeExcludeNamespaces: []string{"default"},
},
ExpectedNamespaces: []string{"kube-system", "x509-exporter"},
}, {
Name: "Exclude multiple namespaces",
Exporter: Exporter{
KubeExcludeNamespaces: []string{"default", "kube-system"},
},
ExpectedNamespaces: []string{"x509-exporter"},
}, {
Name: "Include and exclude namespace mix",
Exporter: Exporter{
KubeIncludeNamespaces: []string{"default"},
KubeExcludeNamespaces: []string{"default"},
},
ExpectedNamespaces: []string{},
}, {
Name: "Include and exclude namespace mix 2",
Exporter: Exporter{
KubeIncludeNamespaces: []string{"default", "kube-system"},
KubeExcludeNamespaces: []string{"kube-system"},
},
ExpectedNamespaces: []string{"default"},
}, {
Name: "Exlucde labels",
Exporter: Exporter{
KubeExcludeNamespaceLabels: []string{"foo"},
},
ExpectedNamespaces: []string{"kube-system", "x509-exporter"},
},
{
Name: "Exclude labels with value",
Exporter: Exporter{
KubeExcludeNamespaceLabels: []string{"group=foo"},
},
ExpectedNamespaces: []string{"x509-exporter"},
},
{
Name: "Include namespaces and exclude labels with value",
Exporter: Exporter{
KubeIncludeNamespaces: []string{"default", "kube-system"},
KubeExcludeNamespaceLabels: []string{"foo=bar"},
},
ExpectedNamespaces: []string{"kube-system"},
},
}
namespaces := []v1.Namespace{
{ObjectMeta: metav1.ObjectMeta{
Name: "default",
Labels: map[string]string{
"foo": "bar",
"group": "foo",
},
}},
{ObjectMeta: metav1.ObjectMeta{
Name: "kube-system",
Labels: map[string]string{
"group": "foo",
},
}},
{ObjectMeta: metav1.ObjectMeta{
Name: "x509-exporter",
Labels: map[string]string{
"group": "bar",
},
}},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
filteredNamespaces := tt.Exporter.filterNamespaces(namespaces)
assert.Equal(t, tt.ExpectedNamespaces, filteredNamespaces)
})
}
}
func testRequestKube(t *testing.T, e *Exporter, f func(metrics []model.MetricFamily)) {
e.kubeClient = sharedKubeClient
testRequest(t, e, f)