diff --git a/Makefile b/Makefile index 5a75ce1ea0..24d370d2b9 100644 --- a/Makefile +++ b/Makefile @@ -265,6 +265,19 @@ prepare-e2e: chainsaw set-image-controller add-image-targetallocator add-image-o .PHONY: prepare-e2e-with-featuregates prepare-e2e-with-featuregates: chainsaw enable-operator-featuregates prepare-e2e +.PHONY: enable-watch-namespace +enable-watch-namespace: PATCH = [{"op":"add","path":"/spec/template/spec/containers/0/env","value": [ {"name": "K8S_NAMESPACE", "valueFrom": {"fieldRef": { "fieldPath": "metadata.namespace" } } }, {"name": "WATCH_NAMESPACE", "value": "$$(K8S_NAMESPACE),watch-ns"} ] }] +enable-watch-namespace: manifests kustomize + cd config/manager && $(KUSTOMIZE) edit add patch --kind Deployment --patch '$(PATCH)' + +.PHONY: prepare-e2e-watch-namespace +prepare-e2e-watch-namespace: chainsaw enable-watch-namespace prepare-e2e + +# end-to-tests +.PHONY: e2e-watch-namespace +e2e-watch-namespace: chainsaw + $(CHAINSAW) test --test-dir ./tests/e2e-watch-namespace + .PHONY: scorecard-tests scorecard-tests: operator-sdk $(OPERATOR_SDK) scorecard -w=5m bundle || (echo "scorecard test failed" && exit 1) diff --git a/internal/config/main.go b/internal/config/main.go index 61e1512695..2cdebb5c3c 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -59,6 +59,7 @@ type Config struct { openshiftRoutesAvailability openshift.RoutesAvailability labelsFilter []string annotationsFilter []string + namespaces []string } // New constructs a new configuration based on the given options. @@ -101,6 +102,7 @@ func New(opts ...Option) Config { autoInstrumentationNginxImage: o.autoInstrumentationNginxImage, labelsFilter: o.labelsFilter, annotationsFilter: o.annotationsFilter, + namespaces: o.namespaces, } } @@ -225,3 +227,8 @@ func (c *Config) LabelsFilter() []string { func (c *Config) AnnotationsFilter() []string { return c.annotationsFilter } + +// Namespaces Returns the namespaces to be watched. +func (c *Config) Namespaces() []string { + return c.namespaces +} diff --git a/internal/config/options.go b/internal/config/options.go index 9d150dd247..4fd449ee1a 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -54,6 +54,7 @@ type options struct { openshiftRoutesAvailability openshift.RoutesAvailability labelsFilter []string annotationsFilter []string + namespaces []string } func WithAutoDetect(a autodetect.AutoDetect) Option { @@ -225,3 +226,9 @@ func WithAnnotationFilters(annotationFilters []string) Option { o.annotationsFilter = filters } } + +func WithNamespaces(namespaces []string) Option { + return func(o *options) { + o.namespaces = namespaces + } +} diff --git a/internal/webhook/podmutation/webhookhandler.go b/internal/webhook/podmutation/webhookhandler.go index 926c3d4512..43df78d024 100644 --- a/internal/webhook/podmutation/webhookhandler.go +++ b/internal/webhook/podmutation/webhookhandler.go @@ -19,9 +19,11 @@ import ( "context" "encoding/json" "net/http" + "slices" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -89,7 +91,12 @@ func (p *podMutationWebhook) Handle(ctx context.Context, req admission.Request) return res } - for _, m := range p.podMutators { + var mutators []PodMutator + // mutate only in case the namespace is marked to be watched + if slices.Contains(p.config.Namespaces(), ns.Name) || slices.Contains(p.config.Namespaces(), metav1.NamespaceAll) { + mutators = p.podMutators + } + for _, m := range mutators { pod, err = m.Mutate(ctx, ns, pod) if err != nil { res := admission.Errored(http.StatusInternalServerError, err) diff --git a/main.go b/main.go index 6c566efa43..d61b967a8e 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,8 @@ import ( "strings" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + routev1 "github.com/openshift/api/route/v1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "github.com/spf13/pflag" @@ -195,6 +197,21 @@ func main() { os.Exit(1) } + var watchNamespaces []string + var namespaces map[string]cache.Config + watchNamespace, found := os.LookupEnv("WATCH_NAMESPACE") + if found { + setupLog.Info("watching namespace(s)", "namespaces", watchNamespace) + namespaces = map[string]cache.Config{} + watchNamespaces = strings.Split(watchNamespace, ",") + for _, ns := range watchNamespaces { + namespaces[ns] = cache.Config{} + } + } else { + setupLog.Info("the env var WATCH_NAMESPACE isn't set, watching all namespaces") + watchNamespaces = []string{metav1.NamespaceAll} + } + cfg := config.New( config.WithLogger(ctrl.Log.WithName("config")), config.WithVersion(v), @@ -217,24 +234,13 @@ func main() { config.WithAutoDetect(ad), config.WithLabelFilters(labelsFilter), config.WithAnnotationFilters(annotationsFilter), + config.WithNamespaces(watchNamespaces), ) err = cfg.AutoDetect() if err != nil { setupLog.Error(err, "failed to autodetect config variables") } - var namespaces map[string]cache.Config - watchNamespace, found := os.LookupEnv("WATCH_NAMESPACE") - if found { - setupLog.Info("watching namespace(s)", "namespaces", watchNamespace) - namespaces = map[string]cache.Config{} - for _, ns := range strings.Split(watchNamespace, ",") { - namespaces[ns] = cache.Config{} - } - } else { - setupLog.Info("the env var WATCH_NAMESPACE isn't set, watching all namespaces") - } - // see https://github.com/openshift/library-go/blob/4362aa519714a4b62b00ab8318197ba2bba51cb7/pkg/config/leaderelection/leaderelection.go#L104 leaseDuration := time.Second * 137 renewDeadline := time.Second * 107 diff --git a/tests/e2e-watch-namespace/00-install-collector.yaml b/tests/e2e-watch-namespace/00-install-collector.yaml new file mode 100644 index 0000000000..49525d37b7 --- /dev/null +++ b/tests/e2e-watch-namespace/00-install-collector.yaml @@ -0,0 +1,24 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: deployment + namespace: opentelemetry-operator-system +spec: + config: | + receivers: + otlp: + protocols: + grpc: + http: + processors: + + exporters: + debug: + + service: + pipelines: + traces: + receivers: [otlp] + processors: [] + exporters: [debug] + mode: deployment diff --git a/tests/e2e-watch-namespace/00-install-instrumentation.yaml b/tests/e2e-watch-namespace/00-install-instrumentation.yaml new file mode 100644 index 0000000000..3948eccf0f --- /dev/null +++ b/tests/e2e-watch-namespace/00-install-instrumentation.yaml @@ -0,0 +1,8 @@ +apiVersion: opentelemetry.io/v1alpha1 +kind: Instrumentation +metadata: + name: deployment + namespace: opentelemetry-operator-system +spec: + exporter: + endpoint: http://deployment-collector.opentelemetry-operator-system:4317 \ No newline at end of file diff --git a/tests/e2e-watch-namespace/01-deployment.yaml b/tests/e2e-watch-namespace/01-deployment.yaml new file mode 100644 index 0000000000..e0f8630e88 --- /dev/null +++ b/tests/e2e-watch-namespace/01-deployment.yaml @@ -0,0 +1,19 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deploy +spec: + selector: + matchLabels: + app: my-deploy + replicas: 1 + template: + metadata: + labels: + app: my-deploy + annotations: + instrumentation.opentelemetry.io/inject-java: "opentelemetry-operator-system/deployment" + spec: + containers: + - name: myapp + image: ghcr.io/open-telemetry/opentelemetry-operator/e2e-test-app-java:main diff --git a/tests/e2e-watch-namespace/not-watch-ns/01-assert.yaml b/tests/e2e-watch-namespace/not-watch-ns/01-assert.yaml new file mode 100644 index 0000000000..34f42ff736 --- /dev/null +++ b/tests/e2e-watch-namespace/not-watch-ns/01-assert.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + instrumentation.opentelemetry.io/inject-java: "opentelemetry-operator-system/deployment" + labels: + app: my-deploy +spec: + (initContainers == null): true + (length(containers)): 1 + containers: + - name: myapp + (env == null): true + diff --git a/tests/e2e-watch-namespace/not-watch-ns/chainsaw-test.yaml b/tests/e2e-watch-namespace/not-watch-ns/chainsaw-test.yaml new file mode 100644 index 0000000000..9d92fa2b5c --- /dev/null +++ b/tests/e2e-watch-namespace/not-watch-ns/chainsaw-test.yaml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: not-watch-ns +spec: + namespace: not-watch-ns + steps: + - name: step-00 + skipDelete: true + try: + # when running in parallel it seems to conflict a bit on both tests creating the collector + # opentelemetrycollectors.opentelemetry.io "deployment" already exists + - sleep: + duration: 5s + + - apply: + file: ../00-install-collector.yaml + - apply: + file: ../00-install-instrumentation.yaml + # wait not working :-( + - sleep: + duration: 10s + # Deployment + - name: step-01 + try: + - apply: + file: ../01-deployment.yaml + - assert: + timeout: 60s + file: 01-assert.yaml + catch: + - podLogs: + selector: app=my-deploy diff --git a/tests/e2e-watch-namespace/watch-ns/01-assert.yaml b/tests/e2e-watch-namespace/watch-ns/01-assert.yaml new file mode 100644 index 0000000000..e29b44488a --- /dev/null +++ b/tests/e2e-watch-namespace/watch-ns/01-assert.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Pod +metadata: + annotations: + instrumentation.opentelemetry.io/inject-java: "opentelemetry-operator-system/deployment" + labels: + app: my-deploy +spec: + initContainers: + - name: opentelemetry-auto-instrumentation-java + (volumeMounts[?name == 'opentelemetry-auto-instrumentation-java']): + - name: opentelemetry-auto-instrumentation-java + mountPath: /otel-auto-instrumentation-java + (containers[?name == 'myapp']): + - name: myapp + env: + - name: OTEL_NODE_IP + - name: OTEL_POD_IP + - name: JAVA_TOOL_OPTIONS + - name: OTEL_SERVICE_NAME + value: my-deploy + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://deployment-collector.opentelemetry-operator-system:4317 + - name: OTEL_RESOURCE_ATTRIBUTES_POD_NAME + - name: OTEL_RESOURCE_ATTRIBUTES_NODE_NAME + - name: OTEL_RESOURCE_ATTRIBUTES + (volumeMounts[?name == 'opentelemetry-auto-instrumentation-java']): + - name: opentelemetry-auto-instrumentation-java + mountPath: /otel-auto-instrumentation-java + (volumes[?name == 'opentelemetry-auto-instrumentation-java']): + - name: opentelemetry-auto-instrumentation-java + emptyDir: {} diff --git a/tests/e2e-watch-namespace/watch-ns/chainsaw-test.yaml b/tests/e2e-watch-namespace/watch-ns/chainsaw-test.yaml new file mode 100644 index 0000000000..075e9d8c3b --- /dev/null +++ b/tests/e2e-watch-namespace/watch-ns/chainsaw-test.yaml @@ -0,0 +1,55 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: watch-ns +spec: + namespace: watch-ns + steps: + - name: step-00 + skipDelete: true + try: + - apply: + file: ../00-install-collector.yaml + - apply: + file: ../00-install-instrumentation.yaml + # wait not working :-( + - sleep: + duration: 10s +# - command: +# entrypoint: kubectl +# args: +# - wait +# - pods +# - --for +# - condition=Ready="true" +# - -l +# - app.kubernetes.io/name=deployment-collector +# - -n +# - opentelemetry-operator-system +# - --timeout +# - 10s +## - 1m +# - -v +# - '8' +# - wait: +# resource: pods +# selector: 'app.kubernetes.io/name=deployment-collector' +# timeout: 10s +# namespace: opentelemetry-operator-system +# for: +# condition: +# name: Ready +# value: 'true' + # Deployment + - name: step-01 + try: + - apply: + file: ../01-deployment.yaml + - assert: + timeout: 60s + file: 01-assert.yaml + catch: + - podLogs: + selector: app=my-deploy