diff --git a/internal/config/main.go b/internal/config/main.go index dae27bb721..bed73be4da 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -51,6 +51,7 @@ type Config struct { autoInstrumentationJavaImage string autoInstrumentationNodeJSImage string autoInstrumentationPythonImage string + labelsFilter []string } // New constructs a new configuration based on the given options. @@ -81,6 +82,7 @@ func New(opts ...Option) Config { autoInstrumentationJavaImage: o.autoInstrumentationJavaImage, autoInstrumentationNodeJSImage: o.autoInstrumentationNodeJSImage, autoInstrumentationPythonImage: o.autoInstrumentationPythonImage, + labelsFilter: o.labelsFilter, } } @@ -174,3 +176,8 @@ func (c *Config) AutoInstrumentationNodeJSImage() string { func (c *Config) AutoInstrumentationPythonImage() string { return c.autoInstrumentationPythonImage } + +// Returns the filters converted to regex strings used to filter out unwanted labels from propagations. +func (c *Config) LabelsFilter() []string { + return c.labelsFilter +} diff --git a/internal/config/options.go b/internal/config/options.go index 727c880948..37fe630a37 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -15,6 +15,8 @@ package config import ( + "regexp" + "strings" "time" "github.com/go-logr/logr" @@ -41,6 +43,7 @@ type options struct { onChange []func() error platform platform.Platform version version.Version + labelsFilter []string } func WithAutoDetect(a autodetect.AutoDetect) Option { @@ -115,3 +118,28 @@ func WithAutoInstrumentationPythonImage(s string) Option { o.autoInstrumentationPythonImage = s } } + +func WithLabelFilters(labelFilters []string) Option { + return func(o *options) { + + filters := []string{} + for _, pattern := range labelFilters { + var result strings.Builder + + for i, literal := range strings.Split(pattern, "*") { + + // Replace * with .* + if i > 0 { + result.WriteString(".*") + } + + // Quote any regular expression meta characters in the + // literal text. + result.WriteString(regexp.QuoteMeta(literal)) + } + filters = append(filters, result.String()) + } + + o.labelsFilter = filters + } +} diff --git a/main.go b/main.go index bafbeb76b7..a2c892752f 100644 --- a/main.go +++ b/main.go @@ -80,7 +80,9 @@ func main() { autoInstrumentationJava string autoInstrumentationNodeJS string autoInstrumentationPython string + labelsFilter []string ) + pflag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-addr", ":8081", "The address the probe endpoint binds to.") pflag.BoolVar(&enableLeaderElection, "enable-leader-election", false, @@ -91,11 +93,14 @@ func main() { pflag.StringVar(&autoInstrumentationJava, "auto-instrumentation-java-image", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:%s", v.AutoInstrumentationJava), "The default OpenTelemetry Java instrumentation image. This image is used when no image is specified in the CustomResource.") pflag.StringVar(&autoInstrumentationNodeJS, "auto-instrumentation-nodejs-image", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:%s", v.AutoInstrumentationNodeJS), "The default OpenTelemetry NodeJS instrumentation image. This image is used when no image is specified in the CustomResource.") pflag.StringVar(&autoInstrumentationPython, "auto-instrumentation-python-image", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:%s", v.AutoInstrumentationPython), "The default OpenTelemetry Python instrumentation image. This image is used when no image is specified in the CustomResource.") + pflag.StringArrayVar(&labelsFilter, "labels", []string{}, "Labels to filter away from propagating onto deploys") pflag.Parse() logger := zap.New(zap.UseFlagOptions(&opts)) ctrl.SetLogger(logger) + pflag.Parse() + logger.Info("Starting the OpenTelemetry Operator", "opentelemetry-operator", v.Operator, "opentelemetry-collector", collectorImage, @@ -107,6 +112,7 @@ func main() { "go-version", v.Go, "go-arch", runtime.GOARCH, "go-os", runtime.GOOS, + "labels-filter", labelsFilter, ) restConfig := ctrl.GetConfigOrDie() @@ -127,6 +133,7 @@ func main() { config.WithAutoInstrumentationNodeJSImage(autoInstrumentationNodeJS), config.WithAutoInstrumentationPythonImage(autoInstrumentationPython), config.WithAutoDetect(ad), + config.WithLabelFilters(labelsFilter), ) watchNamespace, found := os.LookupEnv("WATCH_NAMESPACE") diff --git a/pkg/collector/daemonset.go b/pkg/collector/daemonset.go index 4da5b04092..9bbb9472b3 100644 --- a/pkg/collector/daemonset.go +++ b/pkg/collector/daemonset.go @@ -27,7 +27,7 @@ import ( // DaemonSet builds the deployment for the given instance. func DaemonSet(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) appsv1.DaemonSet { - labels := Labels(otelcol) + labels := Labels(otelcol, cfg.LabelsFilter()) labels["app.kubernetes.io/name"] = naming.Collector(otelcol) annotations := Annotations(otelcol) diff --git a/pkg/collector/daemonset_test.go b/pkg/collector/daemonset_test.go index 71fec012ac..910d9a34b1 100644 --- a/pkg/collector/daemonset_test.go +++ b/pkg/collector/daemonset_test.go @@ -129,3 +129,27 @@ func TestDaemonstPodSecurityContext(t *testing.T) { assert.Equal(t, &runAsUser, d.Spec.Template.Spec.SecurityContext.RunAsUser) assert.Equal(t, &runasGroup, d.Spec.Template.Spec.SecurityContext.RunAsGroup) } + +func TestDaemonsetFilterLabels(t *testing.T) { + excludedLabels := map[string]string{ + "foo": "1", + "app.foo.bar": "1", + } + + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + Labels: excludedLabels, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{}, + } + + cfg := config.New(config.WithLabelFilters([]string{"foo*", "app.*.bar"})) + + d := DaemonSet(cfg, logger, otelcol) + + assert.Len(t, d.ObjectMeta.Labels, 5) + for k := range excludedLabels { + assert.NotContains(t, d.ObjectMeta.Labels, k) + } +} diff --git a/pkg/collector/deployment.go b/pkg/collector/deployment.go index 8a4abd212c..08da24b761 100644 --- a/pkg/collector/deployment.go +++ b/pkg/collector/deployment.go @@ -27,7 +27,7 @@ import ( // Deployment builds the deployment for the given instance. func Deployment(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) appsv1.Deployment { - labels := Labels(otelcol) + labels := Labels(otelcol, cfg.LabelsFilter()) labels["app.kubernetes.io/name"] = naming.Collector(otelcol) annotations := Annotations(otelcol) diff --git a/pkg/collector/deployment_test.go b/pkg/collector/deployment_test.go index 7ed3d959b1..9555a454b4 100644 --- a/pkg/collector/deployment_test.go +++ b/pkg/collector/deployment_test.go @@ -151,3 +151,27 @@ func TestDeploymentHostNetwork(t *testing.T) { assert.Equal(t, d2.Spec.Template.Spec.HostNetwork, true) assert.Equal(t, d2.Spec.Template.Spec.DNSPolicy, v1.DNSClusterFirstWithHostNet) } + +func TestDeploymentFilterLabels(t *testing.T) { + excludedLabels := map[string]string{ + "foo": "1", + "app.foo.bar": "1", + } + + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + Labels: excludedLabels, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{}, + } + + cfg := config.New(config.WithLabelFilters([]string{"foo*", "app.*.bar"})) + + d := Deployment(cfg, logger, otelcol) + + assert.Len(t, d.ObjectMeta.Labels, 5) + for k := range excludedLabels { + assert.NotContains(t, d.ObjectMeta.Labels, k) + } +} diff --git a/pkg/collector/horizontalpodautoscaler.go b/pkg/collector/horizontalpodautoscaler.go index e559d7da24..334ae87bd6 100644 --- a/pkg/collector/horizontalpodautoscaler.go +++ b/pkg/collector/horizontalpodautoscaler.go @@ -27,7 +27,7 @@ import ( const defaultCPUTarget int32 = 90 func HorizontalPodAutoscaler(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) autoscalingv1.HorizontalPodAutoscaler { - labels := Labels(otelcol) + labels := Labels(otelcol, cfg.LabelsFilter()) labels["app.kubernetes.io/name"] = naming.Collector(otelcol) annotations := Annotations(otelcol) diff --git a/pkg/collector/labels.go b/pkg/collector/labels.go index 06fc02bb7c..071efb5654 100644 --- a/pkg/collector/labels.go +++ b/pkg/collector/labels.go @@ -15,17 +15,30 @@ package collector import ( + "regexp" + "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" "github.com/open-telemetry/opentelemetry-operator/pkg/naming" ) +func isFilteredLabel(label string, filterLabels []string) bool { + for _, pattern := range filterLabels { + match, _ := regexp.MatchString(pattern, label) + return match + } + + return false +} + // Labels return the common labels to all objects that are part of a managed OpenTelemetryCollector. -func Labels(instance v1alpha1.OpenTelemetryCollector) map[string]string { +func Labels(instance v1alpha1.OpenTelemetryCollector, filterLabels []string) map[string]string { // new map every time, so that we don't touch the instance's label base := map[string]string{} if nil != instance.Labels { for k, v := range instance.Labels { - base[k] = v + if !isFilteredLabel(k, filterLabels) { + base[k] = v + } } } diff --git a/pkg/collector/labels_test.go b/pkg/collector/labels_test.go index 38585950d3..d9a5b71858 100644 --- a/pkg/collector/labels_test.go +++ b/pkg/collector/labels_test.go @@ -34,7 +34,7 @@ func TestLabelsCommonSet(t *testing.T) { } // test - labels := Labels(otelcol) + labels := Labels(otelcol, []string{}) assert.Equal(t, "opentelemetry-operator", labels["app.kubernetes.io/managed-by"]) assert.Equal(t, "my-ns.my-instance", labels["app.kubernetes.io/instance"]) assert.Equal(t, "opentelemetry", labels["app.kubernetes.io/part-of"]) @@ -50,9 +50,25 @@ func TestLabelsPropagateDown(t *testing.T) { } // test - labels := Labels(otelcol) + labels := Labels(otelcol, []string{}) // verify assert.Len(t, labels, 5) assert.Equal(t, "mycomponent", labels["myapp"]) } + +func TestLabelsFilter(t *testing.T) { + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"test.bar.io": "foo", "test.foo.io": "bar"}, + }, + } + + // This requires the filter to be in regex match form and not the other simpler wildcard one. + labels := Labels(otelcol, []string{".*.bar.io"}) + + // verify + assert.Len(t, labels, 5) + assert.NotContains(t, labels, "test.bar.io") + assert.Equal(t, "bar", labels["test.foo.io"]) +} diff --git a/pkg/collector/reconcile/configmap.go b/pkg/collector/reconcile/configmap.go index d3d8010f17..85ceaf2512 100644 --- a/pkg/collector/reconcile/configmap.go +++ b/pkg/collector/reconcile/configmap.go @@ -64,7 +64,7 @@ func ConfigMaps(ctx context.Context, params Params) error { func desiredConfigMap(_ context.Context, params Params) corev1.ConfigMap { name := naming.ConfigMap(params.Instance) - labels := collector.Labels(params.Instance) + labels := collector.Labels(params.Instance, []string{}) labels["app.kubernetes.io/name"] = name config, err := ReplaceConfig(params) if err != nil { diff --git a/pkg/collector/reconcile/service.go b/pkg/collector/reconcile/service.go index 2143e77df2..694321ca00 100644 --- a/pkg/collector/reconcile/service.go +++ b/pkg/collector/reconcile/service.go @@ -68,7 +68,7 @@ func Services(ctx context.Context, params Params) error { } func desiredService(ctx context.Context, params Params) *corev1.Service { - labels := collector.Labels(params.Instance) + labels := collector.Labels(params.Instance, []string{}) labels["app.kubernetes.io/name"] = naming.Service(params.Instance) // by coincidence, the selector is the same as the label, but note that the selector points to the deployment @@ -163,10 +163,10 @@ func headless(ctx context.Context, params Params) *corev1.Service { } func monitoringService(ctx context.Context, params Params) *corev1.Service { - labels := collector.Labels(params.Instance) + labels := collector.Labels(params.Instance, []string{}) labels["app.kubernetes.io/name"] = naming.MonitoringService(params.Instance) - selector := collector.Labels(params.Instance) + selector := collector.Labels(params.Instance, []string{}) selector["app.kubernetes.io/name"] = fmt.Sprintf("%s-collector", params.Instance.Name) return &corev1.Service{ diff --git a/pkg/collector/reconcile/service_test.go b/pkg/collector/reconcile/service_test.go index d400560d69..19a76a495d 100644 --- a/pkg/collector/reconcile/service_test.go +++ b/pkg/collector/reconcile/service_test.go @@ -213,7 +213,7 @@ func TestMonitoringService(t *testing.T) { } func service(name string, ports []v1.ServicePort) v1.Service { - labels := collector.Labels(params().Instance) + labels := collector.Labels(params().Instance, []string{}) labels["app.kubernetes.io/name"] = name selector := labels diff --git a/pkg/collector/serviceaccount.go b/pkg/collector/serviceaccount.go index fa1efea863..ac7e61ca4f 100644 --- a/pkg/collector/serviceaccount.go +++ b/pkg/collector/serviceaccount.go @@ -33,7 +33,7 @@ func ServiceAccountName(instance v1alpha1.OpenTelemetryCollector) string { //ServiceAccount returns the service account for the given instance. func ServiceAccount(otelcol v1alpha1.OpenTelemetryCollector) corev1.ServiceAccount { - labels := Labels(otelcol) + labels := Labels(otelcol, []string{}) labels["app.kubernetes.io/name"] = naming.ServiceAccount(otelcol) return corev1.ServiceAccount{ diff --git a/pkg/collector/statefulset.go b/pkg/collector/statefulset.go index 6427633c0a..9d14e52218 100644 --- a/pkg/collector/statefulset.go +++ b/pkg/collector/statefulset.go @@ -27,7 +27,7 @@ import ( // StatefulSet builds the statefulset for the given instance. func StatefulSet(cfg config.Config, logger logr.Logger, otelcol v1alpha1.OpenTelemetryCollector) appsv1.StatefulSet { - labels := Labels(otelcol) + labels := Labels(otelcol, cfg.LabelsFilter()) labels["app.kubernetes.io/name"] = naming.Collector(otelcol) annotations := Annotations(otelcol) diff --git a/pkg/collector/statefulset_test.go b/pkg/collector/statefulset_test.go index f4f5141b03..a016b83ce2 100644 --- a/pkg/collector/statefulset_test.go +++ b/pkg/collector/statefulset_test.go @@ -210,3 +210,27 @@ func TestStatefulSetHostNetwork(t *testing.T) { assert.Equal(t, d2.Spec.Template.Spec.HostNetwork, true) assert.Equal(t, d2.Spec.Template.Spec.DNSPolicy, v1.DNSClusterFirstWithHostNet) } + +func TestStatefulSetFilterLabels(t *testing.T) { + excludedLabels := map[string]string{ + "foo": "1", + "app.foo.bar": "1", + } + + otelcol := v1alpha1.OpenTelemetryCollector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance", + Labels: excludedLabels, + }, + Spec: v1alpha1.OpenTelemetryCollectorSpec{}, + } + + cfg := config.New(config.WithLabelFilters([]string{"foo*", "app.*.bar"})) + + d := StatefulSet(cfg, logger, otelcol) + + assert.Len(t, d.ObjectMeta.Labels, 5) + for k := range excludedLabels { + assert.NotContains(t, d.ObjectMeta.Labels, k) + } +}