diff --git a/docs/generated/checks.md b/docs/generated/checks.md index 930ee1ac0..690cb8f25 100644 --- a/docs/generated/checks.md +++ b/docs/generated/checks.md @@ -103,6 +103,15 @@ verbs: **Remediation**: Confirm that your service's selector correctly matches the labels on one of your deployments. **Template**: [dangling-service](templates.md#dangling-services) +## dangling-servicemonitor + +**Enabled by default**: No + +**Description**: Indicates when a service monitor's selectors don't match any service. ServiceMonitors are a custom resource only used by the Prometheus operator (https://prometheus-operator.dev/docs/operator/design/#servicemonitor). + +**Remediation**: Check selectors and your services. + +**Template**: [dangling-servicemonitor](templates.md#dangling-service-monitor) ## default-service-account **Enabled by default**: No diff --git a/docs/generated/templates.md b/docs/generated/templates.md index d45f86d70..57c67be8b 100644 --- a/docs/generated/templates.md +++ b/docs/generated/templates.md @@ -160,6 +160,15 @@ KubeLinter supports the following templates: type: array ``` +## Dangling Service Monitor + +**Key**: `dangling-servicemonitor` + +**Description**: Flag service monitors which do not match any service + +**Supported Objects**: ServiceMonitor + + ## Deprecated Service Account Field **Key**: `deprecated-service-account-field` diff --git a/e2etests/bats-tests.sh b/e2etests/bats-tests.sh index 8674d0ea0..8371d6ab1 100755 --- a/e2etests/bats-tests.sh +++ b/e2etests/bats-tests.sh @@ -145,6 +145,28 @@ get_value_from() { [[ "${count}" == "2" ]] } +@test "dangling-servicemonitor" { + tmp="tests/checks/dangling-servicemonitor.yml" + cmd="${KUBE_LINTER_BIN} lint --include dangling-servicemonitor --do-not-auto-add-defaults --format json ${tmp}" + run ${cmd} + + print_info "${status}" "${output}" "${cmd}" "${tmp}" + [ "$status" -eq 1 ] + + message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message') + message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message') + message3=$(get_value_from "${lines[0]}" '.Reports[2].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[2].Diagnostic.Message') + message4=$(get_value_from "${lines[0]}" '.Reports[3].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[3].Diagnostic.Message') + + count=$(get_value_from "${lines[0]}" '.Reports | length') + + [[ "${message1}" == "ServiceMonitor: no services found matching the service monitor's label selector (app.kubernetes.io/name=app) and namespace selector ([])" ]] + [[ "${message2}" == "ServiceMonitor: no services found matching the service monitor's label selector (app.kubernetes.io/name=app) and namespace selector ([])" ]] + [[ "${message3}" == "ServiceMonitor: no services found matching the service monitor's label selector () and namespace selector ([test2])" ]] + [[ "${message4}" == "ServiceMonitor: no services found matching the service monitor's label selector (app.kubernetes.io/name=app1) and namespace selector ([test2])" ]] + [[ "${count}" == "4" ]] +} + @test "default-service-account" { tmp="tests/checks/default-service-account.yml" cmd="${KUBE_LINTER_BIN} lint --include default-service-account --do-not-auto-add-defaults --format json ${tmp}" diff --git a/go.mod b/go.mod index dc1f92ab1..b0d3c10f8 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/openshift/api v0.0.0-20230406152840-ce21e3fe5da2 github.com/owenrumney/go-sarif/v2 v2.2.2 github.com/pkg/errors v0.9.1 + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.65.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 diff --git a/go.sum b/go.sum index 545b13f60..120ac65c6 100644 --- a/go.sum +++ b/go.sum @@ -553,6 +553,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.65.1 h1:9bqwpJD3v8eLW5szVHLAY7oASwo0sOBb5IoJEqN+Jxk= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.65.1/go.mod h1:xcfWyzl4BpEe5jnVkw7D1yCHU7GHjfjCERJsEfGbpSU= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.4.3 h1:P6NALOLV8BrWhm6PsqOraUK05E5h8IZnpXYJ+CIg+0U= diff --git a/pkg/builtinchecks/yamls/dangling-servicemonitor.yaml b/pkg/builtinchecks/yamls/dangling-servicemonitor.yaml new file mode 100644 index 000000000..4e05a2210 --- /dev/null +++ b/pkg/builtinchecks/yamls/dangling-servicemonitor.yaml @@ -0,0 +1,7 @@ +name: "dangling-servicemonitor" +description: "Indicates when a service monitor's selectors don't match any service. ServiceMonitors are a custom resource only used by the Prometheus operator (https://prometheus-operator.dev/docs/operator/design/#servicemonitor)." +remediation: "Check selectors and your services." +scope: + objectKinds: + - ServiceMonitor +template: "dangling-servicemonitor" diff --git a/pkg/lintcontext/mocks/servicemonitor.go b/pkg/lintcontext/mocks/servicemonitor.go new file mode 100644 index 000000000..ebb272c7a --- /dev/null +++ b/pkg/lintcontext/mocks/servicemonitor.go @@ -0,0 +1,30 @@ +package mocks + +import ( + "testing" + + k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/stretchr/testify/require" + "golang.stackrox.io/kube-linter/pkg/objectkinds" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AddMockServiceMonitor adds a mock ServiceMonitor to LintContext +func (l *MockLintContext) AddMockServiceMonitor(t *testing.T, name string) { + require.NotEmpty(t, name) + l.objects[name] = &k8sMonitoring.ServiceMonitor{ + TypeMeta: metaV1.TypeMeta{ + Kind: objectkinds.ServiceMonitor, + APIVersion: objectkinds.GetServiceMonitorAPIVersion(), + }, + ObjectMeta: metaV1.ObjectMeta{Name: name}, + Spec: k8sMonitoring.ServiceMonitorSpec{}, + } +} + +// ModifyServiceMonitor modifies a given servicemonitor in the context via the passed function +func (l *MockLintContext) ModifyServiceMonitor(t *testing.T, name string, f func(servicemonitor *k8sMonitoring.ServiceMonitor)) { + r, ok := l.objects[name].(*k8sMonitoring.ServiceMonitor) + require.True(t, ok) + f(r) +} diff --git a/pkg/lintcontext/parse_yaml.go b/pkg/lintcontext/parse_yaml.go index 6a6b6f351..6d3d139e6 100644 --- a/pkg/lintcontext/parse_yaml.go +++ b/pkg/lintcontext/parse_yaml.go @@ -13,6 +13,7 @@ import ( y "github.com/ghodss/yaml" ocsAppsV1 "github.com/openshift/api/apps/v1" "github.com/pkg/errors" + k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "golang.stackrox.io/kube-linter/pkg/k8sutil" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" @@ -41,7 +42,7 @@ func init() { clientScheme := scheme.Scheme // Add OpenShift and Autoscaling schema - schemeBuilder := runtime.NewSchemeBuilder(ocsAppsV1.AddToScheme, autoscalingV2Beta1.AddToScheme) + schemeBuilder := runtime.NewSchemeBuilder(ocsAppsV1.AddToScheme, autoscalingV2Beta1.AddToScheme, k8sMonitoring.AddToScheme) if err := schemeBuilder.AddToScheme(clientScheme); err != nil { panic(fmt.Sprintf("Can not add OpenShift schema %v", err)) } diff --git a/pkg/objectkinds/serviceMonitor.go b/pkg/objectkinds/serviceMonitor.go new file mode 100644 index 000000000..a239a2706 --- /dev/null +++ b/pkg/objectkinds/serviceMonitor.go @@ -0,0 +1,26 @@ +package objectkinds + +import ( + k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + // ServiceMonitor represents Prometheus Service Monitor objects. + ServiceMonitor = k8sMonitoring.ServiceMonitorsKind +) + +var ( + serviceMonitorGVK = k8sMonitoring.SchemeGroupVersion.WithKind(ServiceMonitor) +) + +func init() { + RegisterObjectKind(ServiceMonitor, MatcherFunc(func(gvk schema.GroupVersionKind) bool { + return gvk == serviceMonitorGVK + })) +} + +// GetServiceMonitorAPIVersion returns servicemonitor's apiversion +func GetServiceMonitorAPIVersion() string { + return serviceMonitorGVK.GroupVersion().String() +} diff --git a/pkg/templates/all/all.go b/pkg/templates/all/all.go index db88467cb..1834dc4e5 100644 --- a/pkg/templates/all/all.go +++ b/pkg/templates/all/all.go @@ -12,6 +12,7 @@ import ( _ "golang.stackrox.io/kube-linter/pkg/templates/danglingnetworkpolicy" _ "golang.stackrox.io/kube-linter/pkg/templates/danglingnetworkpolicypeer" _ "golang.stackrox.io/kube-linter/pkg/templates/danglingservice" + _ "golang.stackrox.io/kube-linter/pkg/templates/danglingservicemonitor" _ "golang.stackrox.io/kube-linter/pkg/templates/deprecatedserviceaccount" _ "golang.stackrox.io/kube-linter/pkg/templates/disallowedgvk" _ "golang.stackrox.io/kube-linter/pkg/templates/dnsconfigoptions" diff --git a/pkg/templates/danglingservicemonitor/internal/params/gen-params.go b/pkg/templates/danglingservicemonitor/internal/params/gen-params.go new file mode 100644 index 000000000..21bb3aa5c --- /dev/null +++ b/pkg/templates/danglingservicemonitor/internal/params/gen-params.go @@ -0,0 +1,52 @@ +// Code generated by kube-linter template codegen. DO NOT EDIT. +// +build !templatecodegen + +package params + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + "golang.stackrox.io/kube-linter/pkg/check" + "golang.stackrox.io/kube-linter/pkg/templates/util" +) + +var ( + // Use some imports in case they don't get used otherwise. + _ = util.MustParseParameterDesc + _ = fmt.Sprintf + + ParamDescs = []check.ParameterDesc{ + } +) + +func (p *Params) Validate() error { + var validationErrors []string + if len(validationErrors) > 0 { + return errors.Errorf("invalid parameters: %s", strings.Join(validationErrors, ", ")) + } + return nil +} + +// ParseAndValidate instantiates a Params object out of the passed map[string]interface{}, +// validates it, and returns it. +// The return type is interface{} to satisfy the type in the Template struct. +func ParseAndValidate(m map[string]interface{}) (interface{}, error) { + var p Params + if err := util.DecodeMapStructure(m, &p); err != nil { + return nil, err + } + if err := p.Validate(); err != nil { + return nil, err + } + return p, nil +} + +// WrapInstantiateFunc is a convenience wrapper that wraps an untyped instantiate function +// into a typed one. +func WrapInstantiateFunc(f func(p Params) (check.Func, error)) func (interface{}) (check.Func, error) { + return func(paramsInt interface{}) (check.Func, error) { + return f(paramsInt.(Params)) + } +} diff --git a/pkg/templates/danglingservicemonitor/internal/params/params.go b/pkg/templates/danglingservicemonitor/internal/params/params.go new file mode 100644 index 000000000..578cc3aa8 --- /dev/null +++ b/pkg/templates/danglingservicemonitor/internal/params/params.go @@ -0,0 +1,5 @@ +package params + +// Params represents the params accepted by this template. +type Params struct { +} diff --git a/pkg/templates/danglingservicemonitor/template.go b/pkg/templates/danglingservicemonitor/template.go new file mode 100644 index 000000000..52806dbb5 --- /dev/null +++ b/pkg/templates/danglingservicemonitor/template.go @@ -0,0 +1,88 @@ +package danglingservicemonitor + +import ( + "fmt" + + k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "golang.stackrox.io/kube-linter/pkg/check" + "golang.stackrox.io/kube-linter/pkg/config" + "golang.stackrox.io/kube-linter/pkg/diagnostic" + "golang.stackrox.io/kube-linter/pkg/lintcontext" + "golang.stackrox.io/kube-linter/pkg/objectkinds" + "golang.stackrox.io/kube-linter/pkg/templates" + "golang.stackrox.io/kube-linter/pkg/templates/danglingservicemonitor/internal/params" + v1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +func init() { + templates.Register(check.Template{ + HumanName: "Dangling Service Monitor", + Key: "dangling-servicemonitor", + Description: "Flag service monitors which do not match any service", + SupportedObjectKinds: config.ObjectKindsDesc{ + ObjectKinds: []string{objectkinds.ServiceMonitor}, + }, + Parameters: params.ParamDescs, + ParseAndValidateParams: params.ParseAndValidate, + Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) { + return func(lintCtx lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic { + serviceMonitor, ok := object.K8sObject.(*k8sMonitoring.ServiceMonitor) + if !ok { + return nil + } + nsSelector := serviceMonitor.Spec.NamespaceSelector + nsSelectorSet := len(nsSelector.MatchNames) != 0 || nsSelector.Any + + labelSelectors := serviceMonitor.Spec.Selector.MatchLabels + labelSelectorSet := len(labelSelectors) != 0 + if !labelSelectorSet && !nsSelectorSet { + return []diagnostic.Diagnostic{{ + Message: "service monitor has no selector specified", + }} + } + labelSelector, err := metaV1.LabelSelectorAsSelector(&metaV1.LabelSelector{MatchLabels: serviceMonitor.Spec.Selector.MatchLabels}) + if err != nil { + return []diagnostic.Diagnostic{{ + Message: fmt.Sprintf("service monitor has invalid label selector: %v", err), + }} + } + for _, obj := range lintCtx.Objects() { + services, found := obj.K8sObject.(*v1.Service) + if !found { + continue + } + if checkNamespaceSelector(nsSelector, services) { + if !labelSelectorSet { + return nil + } + if labelSelectorSet && labelSelector.Matches(labels.Set(services.Labels)) { + return nil + } else { + continue + } + } + if labelSelector.Matches(labels.Set(services.Labels)) && labelSelectorSet && !nsSelectorSet { + // Found! + return nil + } + + } + return []diagnostic.Diagnostic{{Message: fmt.Sprintf("no services found matching the service monitor's label selector (%s) and namespace selector (%s)", labelSelector, nsSelector.MatchNames)}} + }, nil + }), + }) +} + +func checkNamespaceSelector(namespaceSelector k8sMonitoring.NamespaceSelector, service *v1.Service) bool { + if namespaceSelector.Any { + return true + } + for _, ns := range namespaceSelector.MatchNames { + if ns == service.Namespace { + return true + } + } + return false +} diff --git a/pkg/templates/danglingservicemonitor/template_test.go b/pkg/templates/danglingservicemonitor/template_test.go new file mode 100644 index 000000000..0b622f0c7 --- /dev/null +++ b/pkg/templates/danglingservicemonitor/template_test.go @@ -0,0 +1,185 @@ +package danglingservicemonitor + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/suite" + "golang.stackrox.io/kube-linter/pkg/diagnostic" + "golang.stackrox.io/kube-linter/pkg/lintcontext/mocks" + "golang.stackrox.io/kube-linter/pkg/templates" + "golang.stackrox.io/kube-linter/pkg/templates/danglingservicemonitor/internal/params" + + k8sMonitoring "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + coreV1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + service1 = "service1" + service2 = "service2" + servicemonitor1 = "servicemonitor-matches-service1" + servicemonitor2 = "servicemonitor-matches-service2" + servicemonitorNone = "servicemonitor-matches-none" +) + +var emptyLabelSelector = metaV1.LabelSelector{ + MatchLabels: map[string]string{}, +} + +var labelselector1 = metaV1.LabelSelector{ + MatchLabels: map[string]string{"app": "service1-test"}, +} + +var labelselector2 = metaV1.LabelSelector{ + MatchLabels: map[string]string{"app": "service2-test"}, +} + +var invalidlabelselector = metaV1.LabelSelector{ + MatchLabels: map[string]string{"-incorrect-labelselector-and-it-is-too-long-for-kubernetes-today-": "test"}, +} +var namespaceselector1 = k8sMonitoring.NamespaceSelector{ + MatchNames: []string{"test1"}, +} + +var namespace1 = "test1" + +var namespace2 = "test2" + +func TestDanglingServiceMonitor(t *testing.T) { + suite.Run(t, new(DanglingServiceMonitorTestSuite)) +} + +type DanglingServiceMonitorTestSuite struct { + templates.TemplateTestSuite + + ctx *mocks.MockLintContext +} + +func (s *DanglingServiceMonitorTestSuite) SetupTest() { + s.Init("dangling-servicemonitor") + s.ctx = mocks.NewMockContext() +} + +func (s *DanglingServiceMonitorTestSuite) AddServiceMonitorWithLabelSelector(name string, labelSelector metaV1.LabelSelector) { + s.ctx.AddMockServiceMonitor(s.T(), name) + s.ctx.ModifyServiceMonitor(s.T(), name, func(servicemonitor *k8sMonitoring.ServiceMonitor) { + servicemonitor.Spec.Selector = labelSelector + }) +} + +func (s *DanglingServiceMonitorTestSuite) AddServiceMonitorWithNamespaceSelector(name string, namespaceSelector k8sMonitoring.NamespaceSelector) { + s.ctx.AddMockServiceMonitor(s.T(), name) + s.ctx.ModifyServiceMonitor(s.T(), name, func(servicemonitor *k8sMonitoring.ServiceMonitor) { + servicemonitor.Spec.NamespaceSelector = namespaceSelector + }) +} + +func (s *DanglingServiceMonitorTestSuite) AddServiceWithLabels(name string, labels *metaV1.LabelSelector) { + s.ctx.AddMockService(s.T(), name) + s.ctx.ModifyService(s.T(), name, func(service *coreV1.Service) { + service.Labels = labels.MatchLabels + }) +} + +func (s *DanglingServiceMonitorTestSuite) AddServiceWithNamespace(name, namespace string) { + s.ctx.AddMockService(s.T(), name) + s.ctx.ModifyService(s.T(), name, func(service *coreV1.Service) { + service.Namespace = namespace + }) +} + +func (s *DanglingServiceMonitorTestSuite) TestServiceMonitorEmpty() { + s.AddServiceWithLabels(service1, &labelselector1) + s.AddServiceWithLabels(service2, &labelselector2) + s.AddServiceMonitorWithLabelSelector(servicemonitorNone, emptyLabelSelector) + + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: map[string][]diagnostic.Diagnostic{ + servicemonitorNone: {{Message: "service monitor has no selector specified"}}, + }, + ExpectInstantiationError: false, + }, + }) +} + +func (s *DanglingServiceMonitorTestSuite) TestNoDanglingServiceMonitors() { + s.AddServiceWithLabels(service1, &labelselector1) + s.AddServiceWithLabels(service2, &labelselector2) + s.AddServiceMonitorWithLabelSelector(servicemonitor1, labelselector1) + s.AddServiceMonitorWithLabelSelector(servicemonitor2, labelselector2) + + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: map[string][]diagnostic.Diagnostic{ + servicemonitor1: {}, + servicemonitor2: {}, + }, + ExpectInstantiationError: false, + }, + }) +} + +func (s *DanglingServiceMonitorTestSuite) TestInvalidSelector() { + s.AddServiceWithLabels(service1, &invalidlabelselector) + s.AddServiceMonitorWithLabelSelector(servicemonitor1, invalidlabelselector) + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: map[string][]diagnostic.Diagnostic{ + servicemonitor1: {{Message: "service monitor has invalid label selector: key: Invalid value: \"-incorrect-labelselector-and-it-is-too-long-for-kubernetes-today-\": name part must be no more than 63 characters; name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')"}}, + }, + ExpectInstantiationError: false, + }, + }) +} + +func (s *DanglingServiceMonitorTestSuite) TestOneDanglingServiceMonitorIsDangling() { + s.AddServiceWithLabels(service2, &labelselector2) + s.AddServiceMonitorWithLabelSelector(servicemonitor1, labelselector1) + s.AddServiceMonitorWithLabelSelector(servicemonitor2, labelselector2) + label1, _ := metaV1.LabelSelectorAsSelector(&metaV1.LabelSelector{MatchLabels: labelselector1.MatchLabels}) + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: map[string][]diagnostic.Diagnostic{ + servicemonitor1: {{Message: fmt.Sprintf("no services found matching the service monitor's label selector (%v) and namespace selector ([])", label1)}}, + servicemonitor2: {}, + }, + ExpectInstantiationError: false, + }, + }) +} + +func (s *DanglingServiceMonitorTestSuite) TestNamespaceSelector() { + s.AddServiceWithNamespace(service1, namespace1) + s.AddServiceMonitorWithNamespaceSelector(servicemonitor1, namespaceselector1) + + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: map[string][]diagnostic.Diagnostic{ + servicemonitor1: {}, + }, + ExpectInstantiationError: false, + }, + }) +} + +func (s *DanglingServiceMonitorTestSuite) TestDanglingNamespaceSelector() { + s.AddServiceWithNamespace(service1, namespace2) + s.AddServiceMonitorWithNamespaceSelector(servicemonitor1, namespaceselector1) + + s.Validate(s.ctx, []templates.TestCase{ + { + Param: params.Params{}, + Diagnostics: map[string][]diagnostic.Diagnostic{ + servicemonitor1: {{Message: fmt.Sprintf("no services found matching the service monitor's label selector () and namespace selector (%v)", namespaceselector1.MatchNames)}}, + }, + ExpectInstantiationError: false, + }, + }) +} diff --git a/tests/checks/dangling-servicemonitor.yml b/tests/checks/dangling-servicemonitor.yml new file mode 100644 index 000000000..10cc25e40 --- /dev/null +++ b/tests/checks/dangling-servicemonitor.yml @@ -0,0 +1,102 @@ +apiVersion: v1 +kind: Service +metadata: + name: dont-fire + namespace: dontfire + labels: + app.kubernetes.io/name: dontfire +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: dont-fire +spec: + selector: + matchLabels: + app.kubernetes.io/name: dontfire +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: dont-fire2 +spec: + namespaceSelector: + matchNames: + - dontfire +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: dont-fire +spec: + selector: + matchLabels: + app.kubernetes.io/name: dontfire + namespaceSelector: + matchNames: + - dontfire +--- +apiVersion: v1 +kind: Service +metadata: + name: app1 + labels: + app.kubernetes.io/name: app1 +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: app1 +spec: + selector: + matchLabels: + app.kubernetes.io/name: app +--- +apiVersion: v1 +kind: Service +metadata: + name: app2 + labels: + app.kubernetes.io/name: app2 +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: app2 +spec: + selector: + matchLabels: + app.kubernetes.io/name: app +--- +apiVersion: v1 +kind: Service +metadata: + name: app1 + namespace: test1 +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: app1 +spec: + namespaceSelector: + matchNames: + - test2 +--- +apiVersion: v1 +kind: Service +metadata: + name: app1 + namespace: test1 +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: app1 +spec: + selector: + matchLabels: + app.kubernetes.io/name: app1 + namespaceSelector: + matchNames: + - test2 \ No newline at end of file