diff --git a/go.mod b/go.mod index 74ef0e3d..1f2aa74b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/dash0hq/dash0-operator go 1.23.2 require ( + github.com/Masterminds/sprig/v3 v3.3.0 github.com/cisco-open/k8s-objectmatcher v1.10.0 github.com/dash0hq/dash0-operator/images/pkg/common v0.0.0-00010101000000-000000000000 github.com/go-logr/logr v1.4.2 @@ -29,7 +30,10 @@ require ( ) require ( + dario.cat/mergo v1.0.1 // indirect emperror.dev/errors v0.8.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/brunoga/deep v1.2.4 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -56,6 +60,7 @@ require ( github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/compress v1.17.11 // indirect @@ -64,6 +69,8 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/muhlemmer/gu v0.3.1 // indirect @@ -77,7 +84,9 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.61.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/go.sum b/go.sum index e30b5248..4907981f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,13 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/brunoga/deep v1.2.4 h1:Aj9E9oUbE+ccbyh35VC/NHlzzjfIVU69BXu2mt2LmL8= @@ -20,6 +28,8 @@ github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lSh github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -65,6 +75,8 @@ github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -94,6 +106,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -136,8 +152,12 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/test/e2e/dash0_monitoring_resource.go b/test/e2e/dash0_monitoring_resource.go index acc32986..1bbaf9a0 100644 --- a/test/e2e/dash0_monitoring_resource.go +++ b/test/e2e/dash0_monitoring_resource.go @@ -12,6 +12,7 @@ import ( "text/template" "time" + "github.com/Masterminds/sprig/v3" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -26,6 +27,7 @@ type dash0MonitoringValues struct { InstrumentWorkloads dash0v1alpha1.InstrumentWorkloadsMode Endpoint string Token string + Filter string } const ( @@ -52,7 +54,11 @@ func renderDash0MonitoringResourceTemplate(dash0MonitoringValues dash0Monitoring By("render Dash0Monitoring resource template") if dash0MonitoringResourceTemplate == nil { dash0MonitoringResourceTemplate = - template.Must(template.New("dash0monitoring").Parse(dash0MonitoringResourceSource)) + template.Must( + template. + New("dash0monitoring"). + Funcs(sprig.FuncMap()). + Parse(dash0MonitoringResourceSource)) } var dash0MonitoringResource bytes.Buffer diff --git a/test/e2e/dash0monitoring.e2e.yaml.template b/test/e2e/dash0monitoring.e2e.yaml.template index 7c491233..9f3e81ca 100644 --- a/test/e2e/dash0monitoring.e2e.yaml.template +++ b/test/e2e/dash0monitoring.e2e.yaml.template @@ -10,4 +10,8 @@ spec: endpoint: {{ .Endpoint }} authorization: token: {{ .Token }} +{{- end }} +{{- if .Filter }} + filter: + {{- .Filter | indent 4 -}} {{- end }} \ No newline at end of file diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 65917d57..76acd687 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -699,6 +699,117 @@ var _ = Describe("Dash0 Operator", Ordered, func() { }) }) + Describe("telemetry filtering", func() { + + // Note: This test case deliberately works without an operator configuration resource, instead only using a + // monitoring resource to configure the namespace telemetry filter _and_ the export. If we deployed an + // operator configuration resource first, that would create a collector config map without any custom + // filters, and start the collector. Then, when we deploy the monitoring resource, the config map would be + // updated, but then it takes a bit until the collector is restarted via the configuration reloader. We + // can avoid that config change and the config reloading wait time by skipping the operator configuration + // resource. + + BeforeEach(func() { + deployOperatorWithoutAutoOperationConfiguration( + operatorNamespace, + operatorHelmChart, + operatorHelmChartUrl, + images, + ) + }) + + AfterEach(func() { + undeployDash0MonitoringResource(applicationUnderTestNamespace) + undeployOperator(operatorNamespace) + }) + + It("emits health check spans without filter", func() { + deployDash0MonitoringResource( + applicationUnderTestNamespace, + dash0MonitoringValuesWithExport, + operatorNamespace, + ) + By("installing the Node.js deployment") + Expect(installNodeJsDeployment(applicationUnderTestNamespace)).To(Succeed()) + + testId := uuid.New().String() + timestampLowerBound := time.Now() + By("verifying that the Node.js deployment emits spans") + Eventually(func(g Gomega) { + verifySpans( + g, + runtimeTypeNodeJs, + workloadTypeDeployment, + "/dash0-k8s-operator-test", + fmt.Sprintf("id=%s", testId), + timestampLowerBound, + false, + ) + }, verifyTelemetryTimeout, pollingInterval).Should(Succeed()) + By("Node.js deployment: matching spans have been received") + By("Now searching collected spans for health checks...") + matchResults := fileHasMatchingSpan( + Default, + nil, + matchHttpServerSpanWithHttpTarget("/ready", ""), + timestampLowerBound, + ) + matchResults.expectAtLeastOneMatch( + Default, "Node.js deployment: expected to find /ready check spans") + }) + + It("does not emit health check spans when filter is active", func() { + filter := + ` +traces: + span: + - 'attributes["http.route"] == "/ready"' +` + deployDash0MonitoringResource( + applicationUnderTestNamespace, + dash0MonitoringValues{ + InstrumentWorkloads: dash0v1alpha1.All, + Endpoint: defaultEndpoint, + Token: defaultToken, + Filter: filter, + }, + operatorNamespace, + ) + verifyConfigMapContainsString( + operatorNamespace, + // nolint:lll + `'resource.attributes[\"k8s.namespace.name\"] == \"e2e-application-under-test-namespace\" and (attributes[\"http.route\"] == \"/ready\")'`, + ) + By("installing the Node.js deployment") + Expect(installNodeJsDeployment(applicationUnderTestNamespace)).To(Succeed()) + + testId := uuid.New().String() + timestampLowerBound := time.Now() + By("verifying that the Node.js deployment emits spans") + Eventually(func(g Gomega) { + verifySpans( + g, + runtimeTypeNodeJs, + workloadTypeDeployment, + "/dash0-k8s-operator-test", + fmt.Sprintf("id=%s", testId), + timestampLowerBound, + false, + ) + }, verifyTelemetryTimeout, pollingInterval).Should(Succeed()) + By("Node.js deployment: matching spans have been received") + By("Now searching collected spans for health checks...") + matchResults := fileHasMatchingSpan( + Default, + nil, + matchHttpServerSpanWithHttpTarget("/ready", ""), + timestampLowerBound, + ) + matchResults.expectZeroMatches( + Default, "Node.js deployment: expected to find no /ready check spans") + }) + }) + Describe("operator startup", func() { AfterAll(func() { undeployOperator(operatorNamespace) diff --git a/test/e2e/match_results.go b/test/e2e/match_results.go index 3eee6559..49847ceb 100644 --- a/test/e2e/match_results.go +++ b/test/e2e/match_results.go @@ -87,7 +87,7 @@ func (mrl *MatchResultList[R, O]) expectZeroMatches(g Gomega, message string) { g.Expect(len(matchingResults)).To( BeZero(), fmt.Sprintf( - "%s -- expected no matching objects but found %d matches, here is an arbitrary matching result: %s", + "%s -- expected no matching objects but found %d matches, here is an arbitrary matching result:\n%s", message, len(matchingResults), matchingResults[0].String(), diff --git a/test/e2e/run_command.go b/test/e2e/run_command.go index b66843b7..a591cc7b 100644 --- a/test/e2e/run_command.go +++ b/test/e2e/run_command.go @@ -82,7 +82,7 @@ func getProjectDir() (string, error) { func verifyCommandOutputContainsStrings(command *exec.Cmd, timeout time.Duration, needles ...string) { Eventually(func(g Gomega) { - // We cannot run the same exec.Command multiple times, thus we create a new instance each time instead. + // We cannot run the same exec.Command multiple times, thus we create a new instance on each attempt instead. haystack, err := run(exec.Command(command.Args[0], command.Args[1:]...), false) g.Expect(err).ToNot(HaveOccurred()) for _, needle := range needles { diff --git a/test/e2e/spans.go b/test/e2e/spans.go index a830c9ce..ea2d89d5 100644 --- a/test/e2e/spans.go +++ b/test/e2e/spans.go @@ -272,7 +272,13 @@ func matchHttpServerSpanWithHttpTarget(expectedRoute string, expectedQuery strin ) } - expectedTarget := fmt.Sprintf("%s?%s", expectedRoute, expectedQuery) + var expectedTarget string + if expectedQuery != "" { + expectedTarget = fmt.Sprintf("%s?%s", expectedRoute, expectedQuery) + } else { + expectedTarget = expectedRoute + } + target, hasTarget := span.Attributes().Get(httpTargetAttrib) route, hasRoute := span.Attributes().Get(httpRouteAttrib) query, hasQuery := span.Attributes().Get(urlQueryAttrib)