Repository: open-telemetry/opentelemetry-operator
Pull request #3812 (2025-03-17 22:22:54)

Comment out autoinstrumentation-php in versions.txt
**Description:** <Describe what has changed.>
PHP was recently added to [versions.txt](https://github.com/open-telemetry/opentelemetry-operator/blob/main/versions.txt#L37), which is great for progressing towards expanding language support. However, I’d like to keep versions.txt focused on features that are currently available in the operator.

Our downstream project relies on versions.txt for CI/CD, ensuring that only functional and accessible Docker images are referenced. Since PHP support isn’t ready yet, this is causing CI/CD failures due to the lack of a defined PHP image and the operator not having support for PHP implemented.

To avoid breaking CI/CD, this PR comments out the PHP version until official support is available.


jvoravong: @SergeyKleyman would you see any issues with this? I don't want to break anything you might know about.

Patch:
diff --git a/.chloggen/allow-non-identifying.yaml b/.chloggen/allow-non-identifying.yaml
new file mode 100755
index 00000000..f87da39c
--- /dev/null
+++ b/.chloggen/allow-non-identifying.yaml
@@ -0,0 +1,16 @@
+# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
+change_type: enhancement
+
+# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
+component: opamp
+
+# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: Allows specifying non-identifying attributes for the Bridge, also follows OpAMP guidelines for reporting instance.service.id
+
+# One or more tracking issues related to the change
+issues: [2301]
+
+# (Optional) One or more lines of additional information to render under the primary note.
+# These lines will be padded with 2 spaces and then inserted directly into the document.
+# Use pipe (|) for multiline entries.
+subtext:
diff --git a/.chloggen/fix-extensions-panic.yaml b/.chloggen/fix-extensions-panic.yaml
new file mode 100755
index 00000000..1649e2a8
--- /dev/null
+++ b/.chloggen/fix-extensions-panic.yaml
@@ -0,0 +1,16 @@
+# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
+change_type: bug_fix
+
+# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
+component: collector
+
+# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: "Add nil check for Extensions in GetLivenessProbe and GetReadinessProbe functions"
+
+# One or more tracking issues related to the change
+issues: [3747]
+
+# (Optional) One or more lines of additional information to render under the primary note.
+# These lines will be padded with 2 spaces and then inserted directly into the document.
+# Use pipe (|) for multiline entries.
+subtext:
diff --git a/.chloggen/namespace-ta.yaml b/.chloggen/namespace-ta.yaml
new file mode 100644
index 00000000..e9150372
--- /dev/null
+++ b/.chloggen/namespace-ta.yaml
@@ -0,0 +1,19 @@
+# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
+change_type: enhancement
+
+# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
+component: target allocator
+
+# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: |
+  Add support for setting the allowNamespaces and denyNamespaces in the target allocator.
+
+# One or more tracking issues related to the change
+issues: [3086]
+
+# (Optional) One or more lines of additional information to render under the primary note.
+# These lines will be padded with 2 spaces and then inserted directly into the document.
+# Use pipe (|) for multiline entries.
+subtext: |
+  allowNamespaces can be set to an empty list to watch all namespaces (default) or to list of namespaces to watch.
+  denyNamespaces can be set to an empty list to deny watching any namespaces (default) or to a list of namespaces to deny watching.
diff --git a/Makefile b/Makefile
index ea6fdd94..763baa0d 100644
--- a/Makefile
+++ b/Makefile
@@ -497,7 +497,7 @@ KUSTOMIZE_VERSION ?= v5.6.0
 # renovate: datasource=go depName=sigs.k8s.io/controller-tools/cmd/controller-gen
 CONTROLLER_TOOLS_VERSION ?= v0.17.1
 # renovate: datasource=github-releases depName=golangci/golangci-lint
-GOLANGCI_LINT_VERSION ?= v1.64.7
+GOLANGCI_LINT_VERSION ?= v1.64.8
 # renovate: datasource=go depName=sigs.k8s.io/kind
 KIND_VERSION ?= v0.27.0
 # renovate: datasource=go depName=github.com/kyverno/chainsaw
diff --git a/apis/v1alpha1/opampbridge_types.go b/apis/v1alpha1/opampbridge_types.go
index 285e0749..81f91221 100644
--- a/apis/v1alpha1/opampbridge_types.go
+++ b/apis/v1alpha1/opampbridge_types.go
@@ -23,6 +23,9 @@ type OpAMPBridgeSpec struct {
 	// ComponentsAllowed is a list of allowed OpenTelemetry components for each pipeline type (receiver, processor, etc.)
 	// +optional
 	ComponentsAllowed map[string][]string `json:"componentsAllowed,omitempty"`
+	// Description allows the customization of the non identifying attributes for the OpAMP Bridge.
+	// +optional
+	Description *AgentDescription `json:"description,omitempty"`
 	// Resources to set on the OpAMPBridge pods.
 	// +optional
 	Resources v1.ResourceRequirements `json:"resources,omitempty"`
@@ -111,6 +114,12 @@ type OpAMPBridgeStatus struct {
 	Version string `json:"version,omitempty"`
 }
 
+type AgentDescription struct {
+	// NonIdentifyingAttributes are a map of key-value pairs that may be specified to provide
+	// extra information about the agent to the OpAMP server.
+	NonIdentifyingAttributes map[string]string `json:"non_identifying_attributes"`
+}
+
 // +kubebuilder:object:root=true
 // +kubebuilder:subresource:status
 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
diff --git a/apis/v1alpha1/targetallocator_webhook.go b/apis/v1alpha1/targetallocator_webhook.go
index 6e0ed989..68c0a656 100644
--- a/apis/v1alpha1/targetallocator_webhook.go
+++ b/apis/v1alpha1/targetallocator_webhook.go
@@ -116,6 +116,11 @@ func (w TargetAllocatorWebhook) validate(ctx context.Context, ta *TargetAllocato
 		if err != nil || len(warnings) > 0 {
 			return warnings, err
 		}
+
+		// Check to see that allowNamespaces and denyNamespaces are not both set at the same time
+		if len(ta.Spec.PrometheusCR.AllowNamespaces) > 0 && len(ta.Spec.PrometheusCR.DenyNamespaces) > 0 {
+			return warnings, fmt.Errorf("allowNamespaces and denyNamespaces are mutually exclusive")
+		}
 	}
 
 	return warnings, nil
diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go
index f4544a51..0caa0157 100644
--- a/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/v1alpha1/zz_generated.deepcopy.go
@@ -17,6 +17,28 @@ import (
 	"k8s.io/apimachinery/pkg/util/intstr"
 )
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AgentDescription) DeepCopyInto(out *AgentDescription) {
+	*out = *in
+	if in.NonIdentifyingAttributes != nil {
+		in, out := &in.NonIdentifyingAttributes, &out.NonIdentifyingAttributes
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentDescription.
+func (in *AgentDescription) DeepCopy() *AgentDescription {
+	if in == nil {
+		return nil
+	}
+	out := new(AgentDescription)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ApacheHttpd) DeepCopyInto(out *ApacheHttpd) {
 	*out = *in
@@ -612,6 +634,11 @@ func (in *OpAMPBridgeSpec) DeepCopyInto(out *OpAMPBridgeSpec) {
 			(*out)[key] = outVal
 		}
 	}
+	if in.Description != nil {
+		in, out := &in.Description, &out.Description
+		*out = new(AgentDescription)
+		(*in).DeepCopyInto(*out)
+	}
 	in.Resources.DeepCopyInto(&out.Resources)
 	if in.NodeSelector != nil {
 		in, out := &in.NodeSelector, &out.NodeSelector
diff --git a/apis/v1beta1/config.go b/apis/v1beta1/config.go
index b7a1078c..ad664b57 100644
--- a/apis/v1beta1/config.go
+++ b/apis/v1beta1/config.go
@@ -343,6 +343,10 @@ func (c *Config) ApplyDefaults(logger logr.Logger) error {
 // GetLivenessProbe gets the first enabled liveness probe. There should only ever be one extension enabled
 // that provides the hinting for the liveness probe.
 func (c *Config) GetLivenessProbe(logger logr.Logger) (*corev1.Probe, error) {
+	if c.Extensions == nil {
+		return nil, nil
+	}
+
 	enabledComponents := c.GetEnabledComponents()
 	for componentName := range enabledComponents[KindExtension] {
 		// TODO: Clean up the naming here and make it simpler to use a retriever.
@@ -359,6 +363,10 @@ func (c *Config) GetLivenessProbe(logger logr.Logger) (*corev1.Probe, error) {
 // GetReadinessProbe gets the first enabled readiness probe. There should only ever be one extension enabled
 // that provides the hinting for the readiness probe.
 func (c *Config) GetReadinessProbe(logger logr.Logger) (*corev1.Probe, error) {
+	if c.Extensions == nil {
+		return nil, nil
+	}
+
 	enabledComponents := c.GetEnabledComponents()
 	for componentName := range enabledComponents[KindExtension] {
 		// TODO: Clean up the naming here and make it simpler to use a retriever.
diff --git a/apis/v1beta1/targetallocator_types.go b/apis/v1beta1/targetallocator_types.go
index 6ee96beb..80e6a625 100644
--- a/apis/v1beta1/targetallocator_types.go
+++ b/apis/v1beta1/targetallocator_types.go
@@ -12,6 +12,12 @@ type TargetAllocatorPrometheusCR struct {
 	// Enabled indicates whether to use a PrometheusOperator custom resources as targets or not.
 	// +optional
 	Enabled bool `json:"enabled,omitempty"`
+	// AllowNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (allow list). This is mutually exclusive with DenyNamespaces.
+	// +optional
+	AllowNamespaces []string `json:"allowNamespaces,omitempty"`
+	// DenyNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (deny list). This is mutually exclusive with AllowNamespaces.
+	// +optional
+	DenyNamespaces []string `json:"denyNamespaces,omitempty"`
 	// Default interval between consecutive scrapes. Intervals set in ServiceMonitors and PodMonitors override it.
 	//Equivalent to the same setting on the Prometheus CR.
 	//
diff --git a/apis/v1beta1/zz_generated.deepcopy.go b/apis/v1beta1/zz_generated.deepcopy.go
index e50cee37..da29269f 100644
--- a/apis/v1beta1/zz_generated.deepcopy.go
+++ b/apis/v1beta1/zz_generated.deepcopy.go
@@ -761,6 +761,16 @@ func (in *TargetAllocatorEmbedded) DeepCopy() *TargetAllocatorEmbedded {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *TargetAllocatorPrometheusCR) DeepCopyInto(out *TargetAllocatorPrometheusCR) {
 	*out = *in
+	if in.AllowNamespaces != nil {
+		in, out := &in.AllowNamespaces, &out.AllowNamespaces
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.DenyNamespaces != nil {
+		in, out := &in.DenyNamespaces, &out.DenyNamespaces
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 	if in.ScrapeInterval != nil {
 		in, out := &in.ScrapeInterval, &out.ScrapeInterval
 		*out = new(metav1.Duration)
diff --git a/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml
index 4635bf6b..fbd456d6 100644
--- a/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml
+++ b/bundle/community/manifests/opentelemetry-operator.clusterserviceversion.yaml
@@ -99,7 +99,7 @@ metadata:
     categories: Logging & Tracing,Monitoring
     certified: "false"
     containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator
-    createdAt: "2025-03-05T15:50:41Z"
+    createdAt: "2025-03-12T19:10:16Z"
     description: Provides the OpenTelemetry components, including the Collector
     operators.operatorframework.io/builder: operator-sdk-v1.29.0
     operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
diff --git a/bundle/community/manifests/opentelemetry.io_opampbridges.yaml b/bundle/community/manifests/opentelemetry.io_opampbridges.yaml
index b73e5dba..cfb4bcdc 100644
--- a/bundle/community/manifests/opentelemetry.io_opampbridges.yaml
+++ b/bundle/community/manifests/opentelemetry.io_opampbridges.yaml
@@ -489,6 +489,15 @@ spec:
                     type: string
                   type: array
                 type: object
+              description:
+                properties:
+                  non_identifying_attributes:
+                    additionalProperties:
+                      type: string
+                    type: object
+                required:
+                - non_identifying_attributes
+                type: object
               endpoint:
                 type: string
               env:
diff --git a/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml
index 6edc5e5f..1ce98fc9 100644
--- a/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml
+++ b/bundle/community/manifests/opentelemetry.io_opentelemetrycollectors.yaml
@@ -7889,6 +7889,14 @@ spec:
                     type: object
                   prometheusCR:
                     properties:
+                      allowNamespaces:
+                        items:
+                          type: string
+                        type: array
+                      denyNamespaces:
+                        items:
+                          type: string
+                        type: array
                       enabled:
                         type: boolean
                       podMonitorSelector:
diff --git a/bundle/community/manifests/opentelemetry.io_targetallocators.yaml b/bundle/community/manifests/opentelemetry.io_targetallocators.yaml
index 30b62d7b..55f539a8 100644
--- a/bundle/community/manifests/opentelemetry.io_targetallocators.yaml
+++ b/bundle/community/manifests/opentelemetry.io_targetallocators.yaml
@@ -2257,6 +2257,14 @@ spec:
                 type: string
               prometheusCR:
                 properties:
+                  allowNamespaces:
+                    items:
+                      type: string
+                    type: array
+                  denyNamespaces:
+                    items:
+                      type: string
+                    type: array
                   enabled:
                     type: boolean
                   podMonitorSelector:
diff --git a/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml b/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml
index 030ad795..7e8e6e61 100644
--- a/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml
+++ b/bundle/openshift/manifests/opentelemetry-operator.clusterserviceversion.yaml
@@ -99,7 +99,7 @@ metadata:
     categories: Logging & Tracing,Monitoring
     certified: "false"
     containerImage: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator
-    createdAt: "2025-03-05T15:50:41Z"
+    createdAt: "2025-03-12T19:10:16Z"
     description: Provides the OpenTelemetry components, including the Collector
     operators.operatorframework.io/builder: operator-sdk-v1.29.0
     operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
diff --git a/bundle/openshift/manifests/opentelemetry.io_opampbridges.yaml b/bundle/openshift/manifests/opentelemetry.io_opampbridges.yaml
index b73e5dba..cfb4bcdc 100644
--- a/bundle/openshift/manifests/opentelemetry.io_opampbridges.yaml
+++ b/bundle/openshift/manifests/opentelemetry.io_opampbridges.yaml
@@ -489,6 +489,15 @@ spec:
                     type: string
                   type: array
                 type: object
+              description:
+                properties:
+                  non_identifying_attributes:
+                    additionalProperties:
+                      type: string
+                    type: object
+                required:
+                - non_identifying_attributes
+                type: object
               endpoint:
                 type: string
               env:
diff --git a/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml b/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml
index 6edc5e5f..1ce98fc9 100644
--- a/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml
+++ b/bundle/openshift/manifests/opentelemetry.io_opentelemetrycollectors.yaml
@@ -7889,6 +7889,14 @@ spec:
                     type: object
                   prometheusCR:
                     properties:
+                      allowNamespaces:
+                        items:
+                          type: string
+                        type: array
+                      denyNamespaces:
+                        items:
+                          type: string
+                        type: array
                       enabled:
                         type: boolean
                       podMonitorSelector:
diff --git a/bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml b/bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml
index 30b62d7b..55f539a8 100644
--- a/bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml
+++ b/bundle/openshift/manifests/opentelemetry.io_targetallocators.yaml
@@ -2257,6 +2257,14 @@ spec:
                 type: string
               prometheusCR:
                 properties:
+                  allowNamespaces:
+                    items:
+                      type: string
+                    type: array
+                  denyNamespaces:
+                    items:
+                      type: string
+                    type: array
                   enabled:
                     type: boolean
                   podMonitorSelector:
diff --git a/cmd/operator-opamp-bridge/agent/agent.go b/cmd/operator-opamp-bridge/agent/agent.go
index 97ad9a88..b566ede2 100644
--- a/cmd/operator-opamp-bridge/agent/agent.go
+++ b/cmd/operator-opamp-bridge/agent/agent.go
@@ -58,7 +58,7 @@ func NewAgent(logger logr.Logger, applier operator.ConfigApplier, config *config
 		applier:             applier,
 		logger:              logger,
 		appliedKeys:         map[kubeResourceKey]bool{},
-		instanceId:          config.GetNewInstanceId(),
+		instanceId:          config.GetInstanceId(),
 		agentDescription:    config.GetDescription(),
 		remoteConfigEnabled: config.RemoteConfigEnabled(),
 		opampClient:         opampClient,
diff --git a/cmd/operator-opamp-bridge/config/config.go b/cmd/operator-opamp-bridge/config/config.go
index 80f5665a..d41bff82 100644
--- a/cmd/operator-opamp-bridge/config/config.go
+++ b/cmd/operator-opamp-bridge/config/config.go
@@ -81,6 +81,7 @@ type Config struct {
 	ListenAddr         string       `yaml:"listenAddr,omitempty"`
 	ClusterConfig      *rest.Config `yaml:"-"`
 	RootLogger         logr.Logger  `yaml:"-"`
+	instanceId         uuid.UUID    `yaml:"-"`
 
 	// ComponentsAllowed is a list of allowed OpenTelemetry components for each pipeline type (receiver, processor, etc.)
 	ComponentsAllowed map[string][]string `yaml:"componentsAllowed,omitempty"`
@@ -89,10 +90,20 @@ type Config struct {
 	Capabilities      map[Capability]bool `yaml:"capabilities"`
 	HeartbeatInterval time.Duration       `yaml:"heartbeatInterval,omitempty"`
 	Name              string              `yaml:"name,omitempty"`
+	AgentDescription  AgentDescription    `yaml:"description,omitempty"`
+}
+
+// AgentDescription is copied from the OpAMP Extension in the collector.
+// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/ccc3e6ed6386d404eb4beddd257ff979d2a346f4/extension/opampextension/config.go#L48
+type AgentDescription struct {
+	// NonIdentifyingAttributes are a map of key-value pairs that may be specified to provide
+	// extra information about the agent to the OpAMP server.
+	NonIdentifyingAttributes map[string]string `yaml:"non_identifying_attributes"`
 }
 
 func NewConfig(logger logr.Logger) *Config {
 	return &Config{
+		instanceId: mustGetInstanceId(),
 		RootLogger: logger,
 	}
 }
@@ -150,17 +161,33 @@ func (c *Config) GetAgentVersion() string {
 	return agentVersion
 }
 
+func (c *Config) GetInstanceId() uuid.UUID {
+	return c.instanceId
+}
+
 func (c *Config) GetDescription() *protobufs.AgentDescription {
 	return &protobufs.AgentDescription{
 		IdentifyingAttributes: []*protobufs.KeyValue{
 			keyValuePair("service.name", c.GetAgentType()),
+			keyValuePair("service.instance.id", c.GetInstanceId().String()),
 			keyValuePair("service.version", c.GetAgentVersion()),
 		},
-		NonIdentifyingAttributes: []*protobufs.KeyValue{
+		NonIdentifyingAttributes: append(
+			c.AgentDescription.nonIdentifyingAttributes(),
 			keyValuePair("os.family", runtime.GOOS),
 			keyValuePair("host.name", hostname),
-		},
+		),
+	}
+}
+
+func (ad *AgentDescription) nonIdentifyingAttributes() []*protobufs.KeyValue {
+	toReturn := make([]*protobufs.KeyValue, len(ad.NonIdentifyingAttributes))
+	i := 0
+	for k, v := range ad.NonIdentifyingAttributes {
+		toReturn[i] = keyValuePair(k, v)
+		i++
 	}
+	return toReturn
 }
 
 func keyValuePair(key string, value string) *protobufs.KeyValue {
@@ -174,7 +201,7 @@ func keyValuePair(key string, value string) *protobufs.KeyValue {
 	}
 }
 
-func (c *Config) GetNewInstanceId() uuid.UUID {
+func mustGetInstanceId() uuid.UUID {
 	u, err := uuid.NewV7()
 	if err != nil {
 		// This really should never happen and if it does we should fail.
@@ -183,6 +210,11 @@ func (c *Config) GetNewInstanceId() uuid.UUID {
 	return u
 }
 
+func (c *Config) GetNewInstanceId() uuid.UUID {
+	c.instanceId = mustGetInstanceId()
+	return c.instanceId
+}
+
 func (c *Config) RemoteConfigEnabled() bool {
 	capabilities := c.GetCapabilities()
 	return capabilities&protobufs.AgentCapabilities_AgentCapabilities_AcceptsRemoteConfig != 0
diff --git a/cmd/otel-allocator/README.md b/cmd/otel-allocator/README.md
index 6db10581..1a564063 100644
--- a/cmd/otel-allocator/README.md
+++ b/cmd/otel-allocator/README.md
@@ -191,9 +191,11 @@ Upstream documentation here: [PrometheusReceiver](https://github.com/open-teleme
 
 ### RBAC
 
-Before the TargetAllocator can start scraping, you need to set up Kubernetes RBAC (role-based access controls) resources. This means that you need to have a `ServiceAccount` and corresponding cluster roles so that the TargetAllocator has access to all of the necessary resources to pull metrics from.
+Before the TargetAllocator can start scraping, you need to set up Kubernetes RBAC (role-based access controls) resources. This means that you need to have a `ServiceAccount` and corresponding ClusterRoles/Roles so that the TargetAllocator has access to all the necessary resources to pull metrics from.
 
-You can create your own `ServiceAccount`, and reference it in `spec.targetAllocator.serviceAccount` in your `OpenTelemetryCollector` CR. You’ll then need to configure the `ClusterRole` and `ClusterRoleBinding` for this `ServiceAccount`, as per below.
+You can create your own `ServiceAccount`, and reference it in `spec.targetAllocator.serviceAccount` in your `OpenTelemetryCollector` CR. You’ll then need to configure the `ClusterRole` and `ClusterRoleBinding` or `Role` and `RoleBinding` for this `ServiceAccount`, as per below.
+
+#### Cluster-scoped RBAC
 
 ```yaml
   targetAllocator:
@@ -204,11 +206,11 @@ You can create your own `ServiceAccount`, and reference it in `spec.targetAlloca
 ```
 
 > 🚨 **Note**: The Collector part of this same CR *also* has a serviceAccount key which only affects the collector and *not*
-the TargetAllocator.
+> the TargetAllocator.
 
-If you omit the `ServiceAccount` name, the TargetAllocator creates a `ServiceAccount` for you. The `ServiceAccount`’s default name is a concatenation of the Collector name and the `-targetallocator` suffix. By default, this `ServiceAccount` has no defined policy, so you’ll need to create your own `ClusterRole` and `ClusterRoleBinding` for it, as per below.
+If you omit the `ServiceAccount` name, the TargetAllocator creates a `ServiceAccount` for you. The `ServiceAccount`’s default name is a concatenation of the Collector name and the `-targetallocator` suffix. By default, this `ServiceAccount` has no defined policy, so you’ll need to create your own `ClusterRole` and `ClusterRoleBinding` or `Role` and `RoleBinding` for it, as per below.
 
-The role below will provide the minimum access required for the Target Allocator to query all the targets it needs based on any Prometheus configurations:
+The ClusterRole below will provide the minimum access required for the Target Allocator to query all the targets it needs based on any Prometheus configurations:
 
 ```yaml
 apiVersion: rbac.authorization.k8s.io/v1
@@ -242,7 +244,7 @@ rules:
   verbs: ["get"]
 ```
 
-If you enable the the `prometheusCR` (set `spec.targetAllocator.prometheusCR.enabled` to `true`) in the `OpenTelemetryCollector` CR, you will also need to define the following roles. These give the TargetAllocator access to the `PodMonitor` and `ServiceMonitor` CRs. It also gives namespace access to the `PodMonitor` and `ServiceMonitor`.
+If you enable the `prometheusCR` (set `spec.targetAllocator.prometheusCR.enabled` to `true`) in the `OpenTelemetryCollector` CR, you will also need to define the following ClusterRoles. These give the TargetAllocator access to the `PodMonitor` and `ServiceMonitor` CRs. It also gives namespace access to the `PodMonitor` and `ServiceMonitor`.
 
 ```yaml
 apiVersion: rbac.authorization.k8s.io/v1
@@ -263,8 +265,82 @@ rules:
   verbs: ["get", "list", "watch"]
 ```
 
-> ✨ The above roles can be combined into a single role.
+> ✨ The above ClusterRoles can be combined into a single ClusterRole.
+ 
+#### Namespace-scoped RBAC
+
+If you want to have the TargetAllocator watch a specific namespace, you can set the allowNamespaces field 
+in the TargetAllocator's prometheusCR configuration. This is useful if you want to restrict the TargetAllocator to only watch Prometheus
+CRs in a specific namespace, and not have cluster-wide access.
+
+```yaml
+  targetAllocator:
+    enabled: true
+    serviceAccount: opentelemetry-targetallocator-sa
+    prometheusCR:
+      enabled: true
+      allowNamespaces: 
+      - foo
+```
+
+In this case, you will need to create a Role and RoleBinding instead of a ClusterRole and ClusterRoleBinding. The Role
+and RoleBinding should be created in the namespace specified by the allowNamespaces field.
 
+```yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: opentelemetry-targetallocator-role
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - services
+      - endpoints
+      - configmaps
+      - secrets
+      - namespaces
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - apps
+    resources:
+      - statefulsets
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - discovery.k8s.io
+    resources:
+      - endpointslices
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - networking.k8s.io
+    resources:
+      - ingresses
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - monitoring.coreos.com
+    resources:
+      - servicemonitors
+      - podmonitors
+      - scrapeconfigs
+      - probes
+    verbs:
+      - get
+      - watch
+      - list
+```
 
 ### Service / Pod monitor endpoint credentials
 
@@ -420,4 +496,3 @@ Shards the received targets based on the discovered Collector instances
 
 ### Collector
 Client to watch for deployed Collector instances which will then provided to the Allocator. 
-
diff --git a/cmd/otel-allocator/internal/allocation/allocator.go b/cmd/otel-allocator/internal/allocation/allocator.go
index ac630d04..7dcbf87f 100644
--- a/cmd/otel-allocator/internal/allocation/allocator.go
+++ b/cmd/otel-allocator/internal/allocation/allocator.go
@@ -5,6 +5,8 @@ package allocation
 
 import (
 	"errors"
+	"runtime"
+	"slices"
 	"sync"
 
 	"github.com/go-logr/logr"
@@ -73,7 +75,7 @@ func (a *allocator) SetFallbackStrategy(strategy Strategy) {
 // SetTargets accepts a list of targets that will be used to make
 // load balancing decisions. This method should be called when there are
 // new targets discovered or existing targets are shutdown.
-func (a *allocator) SetTargets(targets map[string]*target.Item) {
+func (a *allocator) SetTargets(targets []*target.Item) {
 	timer := prometheus.NewTimer(TimeToAssign.WithLabelValues("SetTargets", a.strategy.GetName()))
 	defer timer.ObserveDuration()
 
@@ -81,12 +83,14 @@ func (a *allocator) SetTargets(targets map[string]*target.Item) {
 		targets = a.filter.Apply(targets)
 	}
 	RecordTargetsKept(targets)
+	concurrency := runtime.NumCPU() * 2 // determined experimentally
+	targetMap := buildTargetMap(targets, concurrency)
 
 	a.m.Lock()
 	defer a.m.Unlock()
 
 	// Check for target changes
-	targetsDiff := diff.Maps(a.targetItems, targets)
+	targetsDiff := diff.Maps(a.targetItems, targetMap)
 	// If there are any additions or removals
 	if len(targetsDiff.Additions()) != 0 || len(targetsDiff.Removals()) != 0 {
 		a.handleTargets(targetsDiff)
@@ -302,3 +306,30 @@ func (a *allocator) handleCollectors(diff diff.Changes[*Collector]) {
 		TargetsUnassigned.Set(float64(unassignedTargets))
 	}
 }
+
+const minChunkSize = 100 // for small target counts, it's not worth it to spawn a lot of goroutines
+
+// buildTargetMap builds a map of targets, using their hashes as keys. It does this concurrently, and the concurrency
+// is configurable via the concurrency parameter. We do this in parallel because target hashing is surprisingly
+// expensive.
+func buildTargetMap(targets []*target.Item, concurrency int) map[string]*target.Item {
+	// technically there may be duplicates, so this may overallocate, but in the majority of cases it will be exact
+	result := make(map[string]*target.Item, len(targets))
+	chunkSize := len(targets) / concurrency
+	chunkSize = max(chunkSize, minChunkSize)
+	wg := sync.WaitGroup{}
+	for chunk := range slices.Chunk(targets, chunkSize) {
+		wg.Add(1)
+		go func(ch []*target.Item) {
+			defer wg.Done()
+			for _, item := range ch {
+				item.Hash()
+			}
+		}(chunk)
+	}
+	wg.Wait()
+	for _, item := range targets {
+		result[item.Hash()] = item
+	}
+	return result
+}
diff --git a/cmd/otel-allocator/internal/allocation/strategy.go b/cmd/otel-allocator/internal/allocation/strategy.go
index f23e41ec..a66c1e88 100644
--- a/cmd/otel-allocator/internal/allocation/strategy.go
+++ b/cmd/otel-allocator/internal/allocation/strategy.go
@@ -50,7 +50,7 @@ var (
 type Option func(Allocator)
 
 type Filter interface {
-	Apply(map[string]*target.Item) map[string]*target.Item
+	Apply([]*target.Item) []*target.Item
 }
 
 func WithFilter(filter Filter) Option {
@@ -69,7 +69,7 @@ func WithFallbackStrategy(fallbackStrategy string) Option {
 	}
 }
 
-func RecordTargetsKept(targets map[string]*target.Item) {
+func RecordTargetsKept(targets []*target.Item) {
 	TargetsRemaining.Set(float64(len(targets)))
 }
 
@@ -90,7 +90,7 @@ func GetRegisteredAllocatorNames() []string {
 
 type Allocator interface {
 	SetCollectors(collectors map[string]*Collector)
-	SetTargets(targets map[string]*target.Item)
+	SetTargets(targets []*target.Item)
 	TargetItems() map[string]*target.Item
 	Collectors() map[string]*Collector
 	GetTargetsForCollectorAndJob(collector string, job string) []*target.Item
diff --git a/cmd/otel-allocator/internal/config/config.go b/cmd/otel-allocator/internal/config/config.go
index 5d7678b4..296ece16 100644
--- a/cmd/otel-allocator/internal/config/config.go
+++ b/cmd/otel-allocator/internal/config/config.go
@@ -20,6 +20,7 @@ import (
 	_ "github.com/prometheus/prometheus/discovery/install"
 	"github.com/spf13/pflag"
 	"gopkg.in/yaml.v2"
+	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
@@ -53,6 +54,8 @@ type Config struct {
 
 type PrometheusCRConfig struct {
 	Enabled                         bool                  `yaml:"enabled,omitempty"`
+	AllowNamespaces                 []string              `yaml:"allow_namespaces,omitempty"`
+	DenyNamespaces                  []string              `yaml:"deny_namespaces,omitempty"`
 	PodMonitorSelector              *metav1.LabelSelector `yaml:"pod_monitor_selector,omitempty"`
 	PodMonitorNamespaceSelector     *metav1.LabelSelector `yaml:"pod_monitor_namespace_selector,omitempty"`
 	ServiceMonitorSelector          *metav1.LabelSelector `yaml:"service_monitor_selector,omitempty"`
@@ -290,7 +293,11 @@ func CreateDefaultConfig() Config {
 		AllocationFallbackStrategy: "",
 		FilterStrategy:             DefaultFilterStrategy,
 		PrometheusCR: PrometheusCRConfig{
-			ScrapeInterval: DefaultCRScrapeInterval,
+			ScrapeInterval:                  DefaultCRScrapeInterval,
+			ServiceMonitorNamespaceSelector: &metav1.LabelSelector{},
+			PodMonitorNamespaceSelector:     &metav1.LabelSelector{},
+			ScrapeConfigNamespaceSelector:   &metav1.LabelSelector{},
+			ProbeNamespaceSelector:          &metav1.LabelSelector{},
 		},
 	}
 }
@@ -338,6 +345,9 @@ func ValidateConfig(config *Config) error {
 	if config.CollectorNamespace == "" {
 		return fmt.Errorf("collector namespace must be set")
 	}
+	if len(config.PrometheusCR.AllowNamespaces) != 0 && len(config.PrometheusCR.DenyNamespaces) != 0 {
+		return fmt.Errorf("only one of allowNamespaces or denyNamespaces can be set")
+	}
 	return nil
 }
 
@@ -363,3 +373,25 @@ func (c HTTPSServerConfig) NewTLSConfig() (*tls.Config, error) {
 	}
 	return tlsConfig, nil
 }
+
+// GetAllowDenyLists returns the allow and deny lists as maps. If the allow list is empty, it defaults to all namespaces.
+// If the deny list is empty, it defaults to an empty map.
+func (c PrometheusCRConfig) GetAllowDenyLists() (map[string]struct{}, map[string]struct{}) {
+	allowList := map[string]struct{}{}
+	if len(c.AllowNamespaces) != 0 {
+		for _, ns := range c.AllowNamespaces {
+			allowList[ns] = struct{}{}
+		}
+	} else {
+		allowList = map[string]struct{}{v1.NamespaceAll: {}}
+	}
+
+	denyList := map[string]struct{}{}
+	if len(c.DenyNamespaces) != 0 {
+		for _, ns := range c.DenyNamespaces {
+			denyList[ns] = struct{}{}
+		}
+	}
+
+	return allowList, denyList
+}
diff --git a/cmd/otel-allocator/internal/prehook/prehook.go b/cmd/otel-allocator/internal/prehook/prehook.go
index 720c8754..e3825698 100644
--- a/cmd/otel-allocator/internal/prehook/prehook.go
+++ b/cmd/otel-allocator/internal/prehook/prehook.go
@@ -11,7 +11,7 @@ import (
 )
 
 type Hook interface {
-	Apply(map[string]*target.Item) map[string]*target.Item
+	Apply([]*target.Item) []*target.Item
 	SetConfig(map[string][]*relabel.Config)
 	GetConfig() map[string][]*relabel.Config
 }
diff --git a/cmd/otel-allocator/internal/prehook/relabel.go b/cmd/otel-allocator/internal/prehook/relabel.go
index d5dba862..f6a6c084 100644
--- a/cmd/otel-allocator/internal/prehook/relabel.go
+++ b/cmd/otel-allocator/internal/prehook/relabel.go
@@ -4,6 +4,8 @@
 package prehook
 
 import (
+	"slices"
+
 	"github.com/go-logr/logr"
 	"github.com/prometheus/prometheus/model/relabel"
 
@@ -26,7 +28,7 @@ func newRelabelConfigTargetFilter(log logr.Logger) Hook {
 	}
 }
 
-func (tf *relabelConfigTargetFilter) Apply(targets map[string]*target.Item) map[string]*target.Item {
+func (tf *relabelConfigTargetFilter) Apply(targets []*target.Item) []*target.Item {
 	numTargets := len(targets)
 
 	// need to wait until relabelCfg is set
@@ -34,19 +36,25 @@ func (tf *relabelConfigTargetFilter) Apply(targets map[string]*target.Item) map[
 		return targets
 	}
 
-	// Note: jobNameKey != tItem.JobName (jobNameKey is hashed)
-	for jobNameKey, tItem := range targets {
-		var keepTarget bool
+	writeIndex := 0
+	for _, tItem := range targets {
+		keepTarget := true
 		lset := tItem.Labels
 		for _, cfg := range tf.relabelCfg[tItem.JobName] {
 			lset, keepTarget = relabel.Process(lset, cfg)
 			if !keepTarget {
-				delete(targets, jobNameKey)
 				break // inner loop
 			}
 		}
+
+		if keepTarget {
+			targets[writeIndex] = tItem
+			writeIndex++
+		}
 	}
 
+	targets = targets[:writeIndex]
+	targets = slices.Clip(targets)
 	tf.log.V(2).Info("Filtering complete", "seen", numTargets, "kept", len(targets))
 	return targets
 }
diff --git a/cmd/otel-allocator/internal/target/discovery.go b/cmd/otel-allocator/internal/target/discovery.go
index 9e236d35..132a7c86 100644
--- a/cmd/otel-allocator/internal/target/discovery.go
+++ b/cmd/otel-allocator/internal/target/discovery.go
@@ -54,8 +54,7 @@ type Discoverer struct {
 	scrapeConfigsUpdater   scrapeConfigsUpdater
 	targetSets             map[string][]*targetgroup.Group
 	triggerReload          chan struct{}
-	processTargetsCallBack func(targets map[string]*Item)
-	mtxTargets             sync.Mutex
+	processTargetsCallBack func(targets []*Item)
 }
 
 type discoveryHook interface {
@@ -66,7 +65,7 @@ type scrapeConfigsUpdater interface {
 	UpdateScrapeConfigResponse(map[string]*promconfig.ScrapeConfig) error
 }
 
-func NewDiscoverer(log logr.Logger, manager *discovery.Manager, hook discoveryHook, scrapeConfigsUpdater scrapeConfigsUpdater, setTargets func(targets map[string]*Item)) *Discoverer {
+func NewDiscoverer(log logr.Logger, manager *discovery.Manager, hook discoveryHook, scrapeConfigsUpdater scrapeConfigsUpdater, setTargets func(targets []*Item)) *Discoverer {
 	return &Discoverer{
 		log:                    log,
 		manager:                manager,
@@ -163,22 +162,29 @@ func (m *Discoverer) reloader() {
 func (m *Discoverer) Reload() {
 	m.mtxScrape.Lock()
 	var wg sync.WaitGroup
-	targets := map[string]*Item{}
 	timer := prometheus.NewTimer(processTargetsDuration)
 	defer timer.ObserveDuration()
 
+	// count targets and preallocate
+	targetCount := 0
+	for _, groups := range m.targetSets {
+		for _, group := range groups {
+			targetCount += len(group.Targets)
+		}
+	}
+	targets := make([]*Item, targetCount)
+
+	targetsAssigned := 0
 	for jobName, groups := range m.targetSets {
 		wg.Add(1)
 		// Run the sync in parallel as these take a while and at high load can't catch up.
-		go func(jobName string, groups []*targetgroup.Group) {
-			processedTargets := m.processTargetGroups(jobName, groups)
-			m.mtxTargets.Lock()
-			for k, v := range processedTargets {
-				targets[k] = v
-			}
-			m.mtxTargets.Unlock()
+		go func(jobName string, groups []*targetgroup.Group, intoTargets []*Item) {
+			m.processTargetGroups(jobName, groups, intoTargets)
 			wg.Done()
-		}(jobName, groups)
+		}(jobName, groups, targets[targetsAssigned:])
+		for _, group := range groups {
+			targetsAssigned += len(group.Targets)
+		}
 	}
 	m.mtxScrape.Unlock()
 	wg.Wait()
@@ -186,12 +192,13 @@ func (m *Discoverer) Reload() {
 }
 
 // processTargetGroups processes the target groups and returns a map of targets.
-func (m *Discoverer) processTargetGroups(jobName string, groups []*targetgroup.Group) map[string]*Item {
+func (m *Discoverer) processTargetGroups(jobName string, groups []*targetgroup.Group, intoTargets []*Item) {
 	builder := labels.NewBuilder(labels.Labels{})
 	timer := prometheus.NewTimer(processTargetGroupsDuration.WithLabelValues(jobName))
-	targets := map[string]*Item{}
+
 	defer timer.ObserveDuration()
 	var count float64 = 0
+	index := 0
 	for _, tg := range groups {
 		builder.Reset(labels.EmptyLabels())
 		for ln, lv := range tg.Labels {
@@ -205,11 +212,11 @@ func (m *Discoverer) processTargetGroups(jobName string, groups []*targetgroup.G
 				builder.Set(string(ln), string(lv))
 			}
 			item := NewItem(jobName, string(t[model.AddressLabel]), builder.Labels(), "")
-			targets[item.Hash()] = item
+			intoTargets[index] = item
+			index++
 		}
 	}
 	targetsDiscovered.WithLabelValues(jobName).Set(count)
-	return targets
 }
 
 // Run receives and saves target set updates and triggers the scraping loops reloading.
diff --git a/cmd/otel-allocator/internal/target/target.go b/cmd/otel-allocator/internal/target/target.go
index 2a177727..9aaed32a 100644
--- a/cmd/otel-allocator/internal/target/target.go
+++ b/cmd/otel-allocator/internal/target/target.go
@@ -32,6 +32,9 @@ type Item struct {
 }
 
 func (t *Item) Hash() string {
+	if t.hash == "" {
+		t.hash = t.JobName + t.TargetURL + strconv.FormatUint(t.Labels.Hash(), 10)
+	}
 	return t.hash
 }
 
@@ -53,11 +56,9 @@ func (t *Item) GetNodeName() string {
 // NewItem Creates a new target item.
 // INVARIANTS:
 // * Item fields must not be modified after creation.
-// * Item should only be made via its constructor, never directly.
 func NewItem(jobName string, targetURL string, labels labels.Labels, collectorName string) *Item {
 	return &Item{
 		JobName:       jobName,
-		hash:          jobName + targetURL + strconv.FormatUint(labels.Hash(), 10),
 		TargetURL:     targetURL,
 		Labels:        labels,
 		CollectorName: collectorName,
diff --git a/cmd/otel-allocator/internal/watcher/promOperator.go b/cmd/otel-allocator/internal/watcher/promOperator.go
index f484a90d..deaa9a85 100644
--- a/cmd/otel-allocator/internal/watcher/promOperator.go
+++ b/cmd/otel-allocator/internal/watcher/promOperator.go
@@ -53,7 +53,9 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
 		return nil, err
 	}
 
-	factory := informers.NewMonitoringInformerFactories(map[string]struct{}{v1.NamespaceAll: {}}, map[string]struct{}{}, mClient, allocatorconfig.DefaultResyncTime, nil) //TODO decide what strategy to use regarding namespaces
+	allowList, denyList := cfg.PrometheusCR.GetAllowDenyLists()
+
+	factory := informers.NewMonitoringInformerFactories(allowList, denyList, mClient, allocatorconfig.DefaultResyncTime, nil)
 
 	monitoringInformers, err := getInformers(factory)
 	if err != nil {
@@ -65,6 +67,9 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
 
 	// TODO: We should make these durations configurable
 	prom := &monitoringv1.Prometheus{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: cfg.CollectorNamespace,
+		},
 		Spec: monitoringv1.PrometheusSpec{
 			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
 				ScrapeInterval:                  monitoringv1.Duration(cfg.PrometheusCR.ScrapeInterval.String()),
@@ -78,6 +83,7 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
 				ProbeNamespaceSelector:          cfg.PrometheusCR.ProbeNamespaceSelector,
 				ServiceDiscoveryRole:            &serviceDiscoveryRole,
 			},
+			EvaluationInterval: monitoringv1.Duration("30s"),
 		},
 	}
 
@@ -99,7 +105,7 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
 			logger.Error(err, "Retrying namespace informer creation in promOperator CRD watcher")
 			return true
 		}, func() error {
-			nsMonInf, err = getNamespaceInformer(ctx, map[string]struct{}{v1.NamespaceAll: {}}, promLogger, clientset, operatorMetrics)
+			nsMonInf, err = getNamespaceInformer(ctx, allowList, denyList, promLogger, clientset, operatorMetrics)
 			return err
 		})
 	if getNamespaceInformerErr != nil {
@@ -128,6 +134,7 @@ func NewPrometheusCRWatcher(ctx context.Context, logger logr.Logger, cfg allocat
 		probeNamespaceSelector:          cfg.PrometheusCR.ProbeNamespaceSelector,
 		resourceSelector:                resourceSelector,
 		store:                           store,
+		prometheusCR:                    prom,
 	}, nil
 }
 
@@ -147,9 +154,10 @@ type PrometheusCRWatcher struct {
 	probeNamespaceSelector          *metav1.LabelSelector
 	resourceSelector                *prometheus.ResourceSelector
 	store                           *assets.StoreBuilder
+	prometheusCR                    *monitoringv1.Prometheus
 }
 
-func getNamespaceInformer(ctx context.Context, allowList map[string]struct{}, promOperatorLogger *slog.Logger, clientset kubernetes.Interface, operatorMetrics *operator.Metrics) (cache.SharedIndexInformer, error) {
+func getNamespaceInformer(ctx context.Context, allowList, denyList map[string]struct{}, promOperatorLogger *slog.Logger, clientset kubernetes.Interface, operatorMetrics *operator.Metrics) (cache.SharedIndexInformer, error) {
 	kubernetesVersion, err := clientset.Discovery().ServerVersion()
 	if err != nil {
 		return nil, err
@@ -165,7 +173,7 @@ func getNamespaceInformer(ctx context.Context, allowList map[string]struct{}, pr
 		clientset.CoreV1(),
 		clientset.AuthorizationV1().SelfSubjectAccessReviews(),
 		allowList,
-		map[string]struct{}{},
+		denyList,
 	)
 	if err != nil {
 		return nil, err
@@ -362,13 +370,7 @@ func (w *PrometheusCRWatcher) LoadConfig(ctx context.Context) (*promconfig.Confi
 		}
 
 		generatedConfig, err := w.configGenerator.GenerateServerConfiguration(
-			"30s",
-			"",
-			nil,
-			nil,
-			&monitoringv1.TSDBSpec{},
-			nil,
-			nil,
+			w.prometheusCR,
 			serviceMonitorInstances,
 			podMonitorInstances,
 			probeInstances,
diff --git a/config/crd/bases/opentelemetry.io_opampbridges.yaml b/config/crd/bases/opentelemetry.io_opampbridges.yaml
index 55e1b0ae..480bc6f5 100644
--- a/config/crd/bases/opentelemetry.io_opampbridges.yaml
+++ b/config/crd/bases/opentelemetry.io_opampbridges.yaml
@@ -486,6 +486,15 @@ spec:
                     type: string
                   type: array
                 type: object
+              description:
+                properties:
+                  non_identifying_attributes:
+                    additionalProperties:
+                      type: string
+                    type: object
+                required:
+                - non_identifying_attributes
+                type: object
               endpoint:
                 type: string
               env:
diff --git a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml
index 79ccfbd2..7f40cdd8 100644
--- a/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml
+++ b/config/crd/bases/opentelemetry.io_opentelemetrycollectors.yaml
@@ -7875,6 +7875,14 @@ spec:
                     type: object
                   prometheusCR:
                     properties:
+                      allowNamespaces:
+                        items:
+                          type: string
+                        type: array
+                      denyNamespaces:
+                        items:
+                          type: string
+                        type: array
                       enabled:
                         type: boolean
                       podMonitorSelector:
diff --git a/config/crd/bases/opentelemetry.io_targetallocators.yaml b/config/crd/bases/opentelemetry.io_targetallocators.yaml
index 00c4db7e..ce8432e9 100644
--- a/config/crd/bases/opentelemetry.io_targetallocators.yaml
+++ b/config/crd/bases/opentelemetry.io_targetallocators.yaml
@@ -2254,6 +2254,14 @@ spec:
                 type: string
               prometheusCR:
                 properties:
+                  allowNamespaces:
+                    items:
+                      type: string
+                    type: array
+                  denyNamespaces:
+                    items:
+                      type: string
+                    type: array
                   enabled:
                     type: boolean
                   podMonitorSelector:
diff --git a/docs/api/opampbridges.md b/docs/api/opampbridges.md
index ecb84bf8..9b90bad4 100644
--- a/docs/api/opampbridges.md
+++ b/docs/api/opampbridges.md
@@ -111,6 +111,13 @@ OpAMPBridgeSpec defines the desired state of OpAMPBridge.
           ComponentsAllowed is a list of allowed OpenTelemetry components for each pipeline type (receiver, processor, etc.)<br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b><a href="#opampbridgespecdescription">description</a></b></td>
+        <td>object</td>
+        <td>
+          Description allows the customization of the non identifying attributes for the OpAMP Bridge.<br/>
+        </td>
+        <td>false</td>
       </tr><tr>
         <td><b><a href="#opampbridgespecenvindex">env</a></b></td>
         <td>[]object</td>
@@ -1919,6 +1926,34 @@ merge patch.<br/>
 </table>
 
 
+### OpAMPBridge.spec.description
+<sup><sup>[↩ Parent](#opampbridgespec)</sup></sup>
+
+
+
+Description allows the customization of the non identifying attributes for the OpAMP Bridge.
+
+<table>
+    <thead>
+        <tr>
+            <th>Name</th>
+            <th>Type</th>
+            <th>Description</th>
+            <th>Required</th>
+        </tr>
+    </thead>
+    <tbody><tr>
+        <td><b>non_identifying_attributes</b></td>
+        <td>map[string]string</td>
+        <td>
+          NonIdentifyingAttributes are a map of key-value pairs that may be specified to provide
+extra information about the agent to the OpAMP server.<br/>
+        </td>
+        <td>true</td>
+      </tr></tbody>
+</table>
+
+
 ### OpAMPBridge.spec.env[index]
 <sup><sup>[↩ Parent](#opampbridgespec)</sup></sup>
 
diff --git a/docs/api/opentelemetrycollectors.md b/docs/api/opentelemetrycollectors.md
index 859e6e9d..f4a29a46 100644
--- a/docs/api/opentelemetrycollectors.md
+++ b/docs/api/opentelemetrycollectors.md
@@ -33283,6 +33283,20 @@ All CR instances which the ServiceAccount has access to will be retrieved. This
         </tr>
     </thead>
     <tbody><tr>
+        <td><b>allowNamespaces</b></td>
+        <td>[]string</td>
+        <td>
+          AllowNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (allow list). This is mutually exclusive with DenyNamespaces.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>denyNamespaces</b></td>
+        <td>[]string</td>
+        <td>
+          DenyNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (deny list). This is mutually exclusive with AllowNamespaces.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
         <td><b>enabled</b></td>
         <td>boolean</td>
         <td>
diff --git a/docs/api/targetallocators.md b/docs/api/targetallocators.md
index f9643bc0..99a7591e 100644
--- a/docs/api/targetallocators.md
+++ b/docs/api/targetallocators.md
@@ -9480,6 +9480,20 @@ PrometheusCR defines the configuration for the retrieval of PrometheusOperator C
         </tr>
     </thead>
     <tbody><tr>
+        <td><b>allowNamespaces</b></td>
+        <td>[]string</td>
+        <td>
+          AllowNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (allow list). This is mutually exclusive with DenyNamespaces.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
+        <td><b>denyNamespaces</b></td>
+        <td>[]string</td>
+        <td>
+          DenyNamespaces Namespaces to scope the interaction of the Target Allocator and the apiserver (deny list). This is mutually exclusive with AllowNamespaces.<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
         <td><b>enabled</b></td>
         <td>boolean</td>
         <td>
diff --git a/go.mod b/go.mod
index e0a01a82..2340fd45 100644
--- a/go.mod
+++ b/go.mod
@@ -25,9 +25,9 @@ require (
 	github.com/openshift/api v0.0.0-20240124164020-e2ce40831f2e
 	github.com/operator-framework/api v0.30.0
 	github.com/operator-framework/operator-lib v0.17.0
-	github.com/prometheus-operator/prometheus-operator v0.77.2
-	github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.2
-	github.com/prometheus-operator/prometheus-operator/pkg/client v0.77.2
+	github.com/prometheus-operator/prometheus-operator v0.78.2
+	github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.78.2
+	github.com/prometheus-operator/prometheus-operator/pkg/client v0.78.2
 	github.com/prometheus/client_golang v1.21.0
 	github.com/prometheus/common v0.63.0
 	github.com/prometheus/prometheus v0.55.1
@@ -205,15 +205,15 @@ require (
 	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
 	go.uber.org/atomic v1.11.0 // indirect
 	golang.org/x/arch v0.8.0 // indirect
-	golang.org/x/crypto v0.35.0 // indirect
+	golang.org/x/crypto v0.36.0 // indirect
 	golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
 	golang.org/x/mod v0.22.0 // indirect
-	golang.org/x/net v0.36.0 // indirect
+	golang.org/x/net v0.37.0 // indirect
 	golang.org/x/oauth2 v0.28.0 // indirect
-	golang.org/x/sync v0.11.0 // indirect
-	golang.org/x/sys v0.30.0 // indirect
-	golang.org/x/term v0.29.0 // indirect
-	golang.org/x/text v0.22.0 // indirect
+	golang.org/x/sync v0.12.0 // indirect
+	golang.org/x/sys v0.31.0 // indirect
+	golang.org/x/term v0.30.0 // indirect
+	golang.org/x/text v0.23.0 // indirect
 	golang.org/x/time v0.8.0 // indirect
 	golang.org/x/tools v0.29.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 14caba7b..e0dae942 100644
--- a/go.sum
+++ b/go.sum
@@ -537,12 +537,12 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
 github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
 github.com/prometheus-community/prom-label-proxy v0.11.0 h1:IO02WiiFMfcIqvjhwMbCYnDJiTNcSHBrkCGRQ/7KDd0=
 github.com/prometheus-community/prom-label-proxy v0.11.0/go.mod h1:lfvrG70XqsxWDrSh1843QXBG0fSg8EbIXmAo8xGsvw8=
-github.com/prometheus-operator/prometheus-operator v0.77.2 h1:HRewl8m49URAOfRTZql2YjjNGZ8+25srcGji5wx/tic=
-github.com/prometheus-operator/prometheus-operator v0.77.2/go.mod h1:y2XLK9xQ4ulaAtWmvKDM+hQNl7w2Wv/iE+tuF5Xnb8A=
-github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.2 h1:F/MALZ518KfI1zEg+Kg8/uTzoXKDyqw+LNC/5irJlJE=
-github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.2/go.mod h1:D0KY8md81DQKdaR/cXwnhoWB3MYYyc/UjvqE8GFkIvA=
-github.com/prometheus-operator/prometheus-operator/pkg/client v0.77.2 h1:rEBVlnqBSkNc6ufXnDu/GtSyS2jL1l6imtufzJUfOXI=
-github.com/prometheus-operator/prometheus-operator/pkg/client v0.77.2/go.mod h1:Q+R7L3DIlJ2a8R+W+7wjk0B25Ci/VTy7V07Zvq327x0=
+github.com/prometheus-operator/prometheus-operator v0.78.2 h1:ismQgRxQbfZf/iFpEXxpbciqmzpUKmQLKmdJl+2k7KE=
+github.com/prometheus-operator/prometheus-operator v0.78.2/go.mod h1:6Vv58mZOaqQ2TLLXskA1e7RnQUwmtLIpV5ggHx7rb08=
+github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.78.2 h1:SyoVBXD/r0PntR1rprb90ClI32FSUNOCWqqTatnipHM=
+github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.78.2/go.mod h1:SvsRXw4m1F2vk7HquU5h475bFpke27mIUswfyw9u3ug=
+github.com/prometheus-operator/prometheus-operator/pkg/client v0.78.2 h1:kiCXsZol0PCuSKA/ShGOzwSqEkAXSeQFLKLrra/vOSk=
+github.com/prometheus-operator/prometheus-operator/pkg/client v0.78.2/go.mod h1:gw/WDyY7l0PLuPHdQRuAaibYBBKlwZSQkO9MU6M2MPw=
 github.com/prometheus/alertmanager v0.27.0 h1:V6nTa2J5V4s8TG4C4HtrBP/WNSebCCTYGGv4qecA/+I=
 github.com/prometheus/alertmanager v0.27.0/go.mod h1:8Ia/R3urPmbzJ8OsdvmZvIprDwvwmYCmUbwBL+jlPOE=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -695,8 +695,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
-golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
-golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -773,8 +773,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
-golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
-golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
+golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
+golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -796,8 +796,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
-golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -858,16 +858,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
-golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -879,8 +879,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
-golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/internal/manifests/opampbridge/configmap.go b/internal/manifests/opampbridge/configmap.go
index e3afd6bc..bef00ef2 100644
--- a/internal/manifests/opampbridge/configmap.go
+++ b/internal/manifests/opampbridge/configmap.go
@@ -39,6 +39,12 @@ func ConfigMap(params manifests.Params) (*corev1.ConfigMap, error) {
 		config["componentsAllowed"] = params.OpAMPBridge.Spec.ComponentsAllowed
 	}
 
+	if params.OpAMPBridge.Spec.Description != nil {
+		config["description"] = map[string]any{
+			"non_identifying_attributes": params.OpAMPBridge.Spec.Description.NonIdentifyingAttributes,
+		}
+	}
+
 	configYAML, err := yaml.Marshal(config)
 	if err != nil {
 		return &corev1.ConfigMap{}, err
diff --git a/internal/manifests/targetallocator/configmap.go b/internal/manifests/targetallocator/configmap.go
index caf2256a..ed980d73 100644
--- a/internal/manifests/targetallocator/configmap.go
+++ b/internal/manifests/targetallocator/configmap.go
@@ -94,6 +94,14 @@ func ConfigMap(params Params) (*corev1.ConfigMap, error) {
 			prometheusCRConfig["scrape_interval"] = taSpec.PrometheusCR.ScrapeInterval.Duration
 		}
 
+		if taSpec.PrometheusCR.AllowNamespaces != nil {
+			prometheusCRConfig["allow_namespaces"] = taSpec.PrometheusCR.AllowNamespaces
+		}
+
+		if taSpec.PrometheusCR.DenyNamespaces != nil {
+			prometheusCRConfig["deny_namespaces"] = taSpec.PrometheusCR.DenyNamespaces
+		}
+
 		prometheusCRConfig["service_monitor_selector"] = taSpec.PrometheusCR.ServiceMonitorSelector
 
 		prometheusCRConfig["pod_monitor_selector"] = taSpec.PrometheusCR.PodMonitorSelector
diff --git a/versions.txt b/versions.txt
index 99880c8f..01dbf6b2 100644
--- a/versions.txt
+++ b/versions.txt
@@ -34,7 +34,7 @@ autoinstrumentation-go=v0.19.0-alpha
 
 # Represents the current release of PHP instrumentation.
 # Should match autoinstrumentation/php/version.txt
-autoinstrumentation-PHP=1.1.0
+# autoinstrumentation-PHP=1.1.0
 
 # Represents the current release of Apache HTTPD instrumentation.
 # Should match autoinstrumentation/apache-httpd/version.txt


Test patch:
diff --git a/apis/v1alpha1/targetallocator_webhook_test.go b/apis/v1alpha1/targetallocator_webhook_test.go
index b5e9fd5a..dbdfd2e2 100644
--- a/apis/v1alpha1/targetallocator_webhook_test.go
+++ b/apis/v1alpha1/targetallocator_webhook_test.go
@@ -301,6 +301,19 @@ func TestTargetAllocatorValidatingWebhook(t *testing.T) {
 			},
 			expectedErr: "the OpenTelemetry Spec Ports configuration is incorrect",
 		},
+		{
+			name: "allowNamespaces and denyNamespaces can't both be set",
+			targetallocator: TargetAllocator{
+				Spec: TargetAllocatorSpec{
+					PrometheusCR: v1beta1.TargetAllocatorPrometheusCR{
+						Enabled:         true,
+						AllowNamespaces: []string{"ns1"},
+						DenyNamespaces:  []string{"ns2"},
+					},
+				},
+			},
+			expectedErr: "allowNamespaces and denyNamespaces are mutually exclusive",
+		},
 	}
 
 	for _, test := range tests {
diff --git a/apis/v1beta1/config_test.go b/apis/v1beta1/config_test.go
index dae9bf98..573e6208 100644
--- a/apis/v1beta1/config_test.go
+++ b/apis/v1beta1/config_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 
 	"github.com/go-logr/logr"
+	"github.com/google/go-cmp/cmp"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	go_yaml "gopkg.in/yaml.v3"
@@ -758,3 +759,341 @@ func TestConfig_GetExporterPorts(t *testing.T) {
 		})
 	}
 }
+
+func TestConfig_GetLivenessProbe(t *testing.T) {
+	tests := []struct {
+		name      string
+		config    *Config
+		wantProbe *v1.Probe
+		wantErr   bool
+	}{
+		{
+			name: "nil extensions should return nil",
+			config: &Config{
+				Extensions: nil,
+				Service: Service{
+					Extensions: []string{},
+				},
+			},
+			wantProbe: nil,
+		},
+		{
+			name: "nil extensions with health_check in service extensions should return nil",
+			config: &Config{
+				Extensions: nil,
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: nil,
+		},
+		{
+			name: "empty extensions should return nil",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{},
+				},
+				Service: Service{
+					Extensions: []string{},
+				},
+			},
+			wantProbe: nil,
+		},
+		{
+			name: "empty extensions with health_check in service extensions should return probe",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: &v1.Probe{
+				ProbeHandler: v1.ProbeHandler{
+					HTTPGet: &v1.HTTPGetAction{
+						Path: "/",
+						Port: intstr.FromInt32(13133),
+					},
+				},
+			},
+		},
+		{
+			name: "health_check extension enabled should return probe",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"health_check": map[string]interface{}{},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: &v1.Probe{
+				ProbeHandler: v1.ProbeHandler{
+					HTTPGet: &v1.HTTPGetAction{
+						Path: "/",
+						Port: intstr.FromInt32(13133),
+					},
+				},
+			},
+		},
+		{
+			name: "health_check extension with custom path",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"health_check": map[string]interface{}{
+							"path": "/healthz",
+						},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: &v1.Probe{
+				ProbeHandler: v1.ProbeHandler{
+					HTTPGet: &v1.HTTPGetAction{
+						Path: "/healthz",
+						Port: intstr.FromInt32(13133),
+					},
+				},
+			},
+		},
+		{
+			name: "health_check extension with custom endpoint port",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"health_check": map[string]interface{}{
+							"endpoint": "0.0.0.0:8080",
+						},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: &v1.Probe{
+				ProbeHandler: v1.ProbeHandler{
+					HTTPGet: &v1.HTTPGetAction{
+						Path: "/",
+						Port: intstr.FromInt32(8080),
+					},
+				},
+			},
+		},
+		{
+			name: "extension without liveness probe should return nil",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"jaeger_query": map[string]interface{}{},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"jaeger_query"},
+				},
+			},
+			wantProbe: nil,
+		},
+		{
+			name: "invalid health_check config should return error",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"health_check": func() {},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.config.GetLivenessProbe(logr.Discard())
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Config.GetLivenessProbe() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if diff := cmp.Diff(tt.wantProbe, got); diff != "" {
+				t.Errorf("Config.GetLivenessProbe() mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestConfig_GetReadinessProbe(t *testing.T) {
+	tests := []struct {
+		name      string
+		config    *Config
+		wantProbe *v1.Probe
+		wantErr   bool
+	}{
+		{
+			name: "nil extensions should return nil",
+			config: &Config{
+				Extensions: nil,
+				Service: Service{
+					Extensions: []string{},
+				},
+			},
+			wantProbe: nil,
+		},
+		{
+			name: "nil extensions with health_check in service extensions should return nil",
+			config: &Config{
+				Extensions: nil,
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: nil,
+		},
+		{
+			name: "empty extensions should return nil",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{},
+				},
+				Service: Service{
+					Extensions: []string{},
+				},
+			},
+			wantProbe: nil,
+		},
+		{
+			name: "empty extensions with health_check in service extensions should return probe",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: &v1.Probe{
+				ProbeHandler: v1.ProbeHandler{
+					HTTPGet: &v1.HTTPGetAction{
+						Path: "/",
+						Port: intstr.FromInt32(13133),
+					},
+				},
+			},
+		},
+		{
+			name: "health_check extension enabled should return probe",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"health_check": map[string]interface{}{},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: &v1.Probe{
+				ProbeHandler: v1.ProbeHandler{
+					HTTPGet: &v1.HTTPGetAction{
+						Path: "/",
+						Port: intstr.FromInt32(13133),
+					},
+				},
+			},
+		},
+		{
+			name: "health_check extension with custom path",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"health_check": map[string]interface{}{
+							"path": "/healthz",
+						},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: &v1.Probe{
+				ProbeHandler: v1.ProbeHandler{
+					HTTPGet: &v1.HTTPGetAction{
+						Path: "/healthz",
+						Port: intstr.FromInt32(13133),
+					},
+				},
+			},
+		},
+		{
+			name: "health_check extension with custom endpoint port",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"health_check": map[string]interface{}{
+							"endpoint": "0.0.0.0:8080",
+						},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantProbe: &v1.Probe{
+				ProbeHandler: v1.ProbeHandler{
+					HTTPGet: &v1.HTTPGetAction{
+						Path: "/",
+						Port: intstr.FromInt32(8080),
+					},
+				},
+			},
+		},
+		{
+			name: "extension without readiness probe should return nil",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"jaeger_query": map[string]interface{}{},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"jaeger_query"},
+				},
+			},
+			wantProbe: nil,
+		},
+		{
+			name: "invalid health_check config should return error",
+			config: &Config{
+				Extensions: &AnyConfig{
+					Object: map[string]interface{}{
+						"health_check": func() {},
+					},
+				},
+				Service: Service{
+					Extensions: []string{"health_check"},
+				},
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.config.GetReadinessProbe(logr.Discard())
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Config.GetReadinessProbe() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if diff := cmp.Diff(tt.wantProbe, got); diff != "" {
+				t.Errorf("Config.GetReadinessProbe() mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
diff --git a/cmd/operator-opamp-bridge/config/config_test.go b/cmd/operator-opamp-bridge/config/config_test.go
index 99590337..96dfde40 100644
--- a/cmd/operator-opamp-bridge/config/config_test.go
+++ b/cmd/operator-opamp-bridge/config/config_test.go
@@ -10,7 +10,10 @@ import (
 	"time"
 
 	"github.com/go-logr/logr"
+	"github.com/google/uuid"
+	"github.com/open-telemetry/opamp-go/protobufs"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestLoad(t *testing.T) {
@@ -18,6 +21,7 @@ func TestLoad(t *testing.T) {
 		file         string
 		envVariables map[string]string
 	}
+	instanceId := uuid.New()
 	tests := []struct {
 		name    string
 		args    args
@@ -30,6 +34,7 @@ func TestLoad(t *testing.T) {
 				file: "./testdata/agent.yaml",
 			},
 			want: &Config{
+				instanceId: instanceId,
 				RootLogger: logr.Discard(),
 				Endpoint:   "ws://127.0.0.1:4320/v1/opamp",
 				Capabilities: map[Capability]bool{
@@ -55,6 +60,7 @@ func TestLoad(t *testing.T) {
 				file: "./testdata/agenthttpbasic.yaml",
 			},
 			want: &Config{
+				instanceId:        instanceId,
 				RootLogger:        logr.Discard(),
 				Endpoint:          "http://127.0.0.1:4320/v1/opamp",
 				HeartbeatInterval: 45 * time.Second,
@@ -82,6 +88,7 @@ func TestLoad(t *testing.T) {
 				file: "./testdata/agentbasiccomponentsallowed.yaml",
 			},
 			want: &Config{
+				instanceId: instanceId,
 				RootLogger: logr.Discard(),
 				Endpoint:   "ws://127.0.0.1:4320/v1/opamp",
 				Capabilities: map[Capability]bool{
@@ -133,6 +140,7 @@ func TestLoad(t *testing.T) {
 				},
 			},
 			want: &Config{
+				instanceId: instanceId,
 				RootLogger: logr.Discard(),
 				Endpoint:   "ws://127.0.0.1:4320/v1/opamp",
 				Headers: map[string]string{
@@ -158,6 +166,41 @@ func TestLoad(t *testing.T) {
 			},
 			wantErr: assert.NoError,
 		},
+		{
+			name: "base case with nonidentify attributes",
+			args: args{
+				file: "./testdata/agentwithdescription.yaml",
+				envVariables: map[string]string{
+					"MY_ENV_VAR_1": "my-env-variable-1-value",
+					"MY_ENV_VAR_2": "my-env-variable-2-value",
+				},
+			},
+			want: &Config{
+				instanceId: instanceId,
+				RootLogger: logr.Discard(),
+				Endpoint:   "ws://127.0.0.1:4320/v1/opamp",
+				AgentDescription: AgentDescription{
+					NonIdentifyingAttributes: map[string]string{
+						"custom.attribute": "custom-value",
+					},
+				},
+				Capabilities: map[Capability]bool{
+					AcceptsRemoteConfig:            true,
+					ReportsEffectiveConfig:         true,
+					ReportsOwnTraces:               true,
+					ReportsOwnMetrics:              true,
+					ReportsOwnLogs:                 true,
+					AcceptsOpAMPConnectionSettings: true,
+					AcceptsOtherConnectionSettings: true,
+					AcceptsRestartCommand:          true,
+					ReportsHealth:                  true,
+					ReportsRemoteConfig:            true,
+					AcceptsPackages:                false,
+					ReportsPackageStatuses:         false,
+				},
+			},
+			wantErr: assert.NoError,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -173,9 +216,41 @@ func TestLoad(t *testing.T) {
 				return
 			}
 			// there are some fields we don't care about, so we ignore them.
+			got.instanceId = tt.want.instanceId
 			got.ClusterConfig = tt.want.ClusterConfig
 			got.RootLogger = tt.want.RootLogger
 			assert.Equalf(t, tt.want, got, "Load(%v)", tt.args.file)
 		})
 	}
 }
+
+func TestGetDescription(t *testing.T) {
+	got := NewConfig(logr.Discard())
+	instanceId := uuid.New()
+	got.instanceId = instanceId
+	err := LoadFromFile(got, "./testdata/agentwithdescription.yaml")
+	require.NoError(t, err, fmt.Sprintf("Load(%v)", "./testdata/agentwithdescription.yaml"))
+	desc := got.GetDescription()
+	assert.Len(t, desc.IdentifyingAttributes, 3)
+	assert.Contains(t, desc.IdentifyingAttributes, &protobufs.KeyValue{Key: "service.instance.id", Value: &protobufs.AnyValue{
+		Value: &protobufs.AnyValue_StringValue{StringValue: instanceId.String()},
+	}})
+	assert.Len(t, desc.NonIdentifyingAttributes, 3)
+	assert.Contains(t, desc.NonIdentifyingAttributes, &protobufs.KeyValue{Key: "custom.attribute", Value: &protobufs.AnyValue{
+		Value: &protobufs.AnyValue_StringValue{StringValue: "custom-value"},
+	}})
+}
+
+func TestGetDescriptionNoneSet(t *testing.T) {
+	got := NewConfig(logr.Discard())
+	instanceId := uuid.New()
+	got.instanceId = instanceId
+	err := LoadFromFile(got, "./testdata/agent.yaml")
+	require.NoError(t, err, fmt.Sprintf("Load(%v)", "./testdata/agent.yaml"))
+	desc := got.GetDescription()
+	assert.Len(t, desc.IdentifyingAttributes, 3)
+	assert.Contains(t, desc.IdentifyingAttributes, &protobufs.KeyValue{Key: "service.instance.id", Value: &protobufs.AnyValue{
+		Value: &protobufs.AnyValue_StringValue{StringValue: instanceId.String()},
+	}})
+	assert.Len(t, desc.NonIdentifyingAttributes, 2)
+}
diff --git a/cmd/operator-opamp-bridge/config/testdata/agentwithdescription.yaml b/cmd/operator-opamp-bridge/config/testdata/agentwithdescription.yaml
new file mode 100644
index 00000000..b2b3e360
--- /dev/null
+++ b/cmd/operator-opamp-bridge/config/testdata/agentwithdescription.yaml
@@ -0,0 +1,17 @@
+endpoint: ws://127.0.0.1:4320/v1/opamp
+description:
+  non_identifying_attributes:
+    custom.attribute: "custom-value"
+capabilities:
+  AcceptsRemoteConfig: true
+  ReportsEffectiveConfig: true
+  AcceptsPackages: false
+  ReportsPackageStatuses: false
+  ReportsOwnTraces: true
+  ReportsOwnMetrics: true
+  ReportsOwnLogs: true
+  AcceptsOpAMPConnectionSettings: true
+  AcceptsOtherConnectionSettings: true
+  AcceptsRestartCommand: true
+  ReportsHealth: true
+  ReportsRemoteConfig: true
diff --git a/cmd/otel-allocator/benchmark_test.go b/cmd/otel-allocator/benchmark_test.go
index 09e2ffcc..175290d0 100644
--- a/cmd/otel-allocator/benchmark_test.go
+++ b/cmd/otel-allocator/benchmark_test.go
@@ -28,65 +28,70 @@ import (
 	"github.com/open-telemetry/opentelemetry-operator/cmd/otel-allocator/internal/target"
 )
 
+var targetCounts = []int{1000, 10000, 100000, 800000}
+
 // BenchmarkProcessTargets benchmarks the whole target allocation pipeline. It starts with data the prometheus
 // discovery manager would normally output, and pushes it all the way into the allocator. It notably doe *not* check
 // the HTTP server afterward. Test data is chosen to be reasonably representative of what the Prometheus service discovery
 // outputs in the real world.
 func BenchmarkProcessTargets(b *testing.B) {
-	numTargets := 800000
 	targetsPerGroup := 5
 	groupsPerJob := 20
-	tsets := prepareBenchmarkData(numTargets, targetsPerGroup, groupsPerJob)
-	for _, strategy := range allocation.GetRegisteredAllocatorNames() {
-		b.Run(strategy, func(b *testing.B) {
-			targetDiscoverer := createTestDiscoverer(strategy, map[string][]*relabel.Config{})
-			targetDiscoverer.UpdateTsets(tsets)
-			b.ResetTimer()
-			for i := 0; i < b.N; i++ {
-				targetDiscoverer.Reload()
-			}
-		})
+	for _, numTargets := range targetCounts {
+		tsets := prepareBenchmarkData(numTargets, targetsPerGroup, groupsPerJob)
+		for _, strategy := range allocation.GetRegisteredAllocatorNames() {
+			b.Run(fmt.Sprintf("%s/%d", strategy, numTargets), func(b *testing.B) {
+				targetDiscoverer := createTestDiscoverer(strategy, map[string][]*relabel.Config{})
+				targetDiscoverer.UpdateTsets(tsets)
+				b.ResetTimer()
+				for i := 0; i < b.N; i++ {
+					targetDiscoverer.Reload()
+				}
+			})
+		}
 	}
 }
 
 // BenchmarkProcessTargetsWithRelabelConfig is BenchmarkProcessTargets with a relabel config set. The relabel config
 // does not actually modify any records, but does force the prehook to perform any necessary conversions along the way.
 func BenchmarkProcessTargetsWithRelabelConfig(b *testing.B) {
-	numTargets := 800000
 	targetsPerGroup := 5
 	groupsPerJob := 20
-	tsets := prepareBenchmarkData(numTargets, targetsPerGroup, groupsPerJob)
-	prehookConfig := make(map[string][]*relabel.Config, len(tsets))
-	for jobName := range tsets {
-		// keep all targets in half the jobs, drop the rest
-		jobNrStr := strings.Split(jobName, "-")[1]
-		jobNr, err := strconv.Atoi(jobNrStr)
-		require.NoError(b, err)
-		var action relabel.Action
-		if jobNr%2 == 0 {
-			action = "keep"
-		} else {
-			action = "drop"
+	for _, numTargets := range targetCounts {
+		tsets := prepareBenchmarkData(numTargets, targetsPerGroup, groupsPerJob)
+		prehookConfig := make(map[string][]*relabel.Config, len(tsets))
+		for jobName := range tsets {
+			// keep all targets in half the jobs, drop the rest
+			jobNrStr := strings.Split(jobName, "-")[1]
+			jobNr, err := strconv.Atoi(jobNrStr)
+			require.NoError(b, err)
+			var action relabel.Action
+			if jobNr%2 == 0 {
+				action = "keep"
+			} else {
+				action = "drop"
+			}
+			prehookConfig[jobName] = []*relabel.Config{
+				{
+					Action:       action,
+					Regex:        relabel.MustNewRegexp(".*"),
+					SourceLabels: model.LabelNames{"__address__"},
+				},
+			}
 		}
-		prehookConfig[jobName] = []*relabel.Config{
-			{
-				Action:       action,
-				Regex:        relabel.MustNewRegexp(".*"),
-				SourceLabels: model.LabelNames{"__address__"},
-			},
+
+		for _, strategy := range allocation.GetRegisteredAllocatorNames() {
+			b.Run(fmt.Sprintf("%s/%d", strategy, numTargets), func(b *testing.B) {
+				targetDiscoverer := createTestDiscoverer(strategy, prehookConfig)
+				targetDiscoverer.UpdateTsets(tsets)
+				b.ResetTimer()
+				for i := 0; i < b.N; i++ {
+					targetDiscoverer.Reload()
+				}
+			})
 		}
 	}
 
-	for _, strategy := range allocation.GetRegisteredAllocatorNames() {
-		b.Run(strategy, func(b *testing.B) {
-			targetDiscoverer := createTestDiscoverer(strategy, prehookConfig)
-			targetDiscoverer.UpdateTsets(tsets)
-			b.ResetTimer()
-			for i := 0; i < b.N; i++ {
-				targetDiscoverer.Reload()
-			}
-		})
-	}
 }
 
 func prepareBenchmarkData(numTargets, targetsPerGroup, groupsPerJob int) map[string][]*targetgroup.Group {
@@ -140,7 +145,10 @@ func prepareBenchmarkData(numTargets, targetsPerGroup, groupsPerJob int) map[str
 	}
 	targets := []model.LabelSet{}
 	for i := 0; i < numTargets; i++ {
-		targets = append(targets, exampleTarget.Clone())
+		newTarget := exampleTarget.Clone()
+		// ensure each target has a unique label to avoid deduplication
+		newTarget["target_id"] = model.LabelValue(strconv.Itoa(i))
+		targets = append(targets, newTarget)
 	}
 	groups := make([]*targetgroup.Group, numGroups)
 	for i := 0; i < numGroups; i++ {
diff --git a/cmd/otel-allocator/internal/allocation/allocator_test.go b/cmd/otel-allocator/internal/allocation/allocator_test.go
index a18030e0..4a6f4e42 100644
--- a/cmd/otel-allocator/internal/allocation/allocator_test.go
+++ b/cmd/otel-allocator/internal/allocation/allocator_test.go
@@ -174,10 +174,7 @@ func TestAllocationCollision(t *testing.T) {
 		firstTarget := target.NewItem("sample-name", "0.0.0.0:8000", firstLabels, "")
 		secondTarget := target.NewItem("sample-name", "0.0.0.0:8000", secondLabels, "")
 
-		targetList := map[string]*target.Item{
-			firstTarget.Hash():  firstTarget,
-			secondTarget.Hash(): secondTarget,
-		}
+		targetList := []*target.Item{firstTarget, secondTarget}
 
 		// test that targets and collectors are added properly
 		allocator.SetTargets(targetList)
diff --git a/cmd/otel-allocator/internal/allocation/least_weighted_test.go b/cmd/otel-allocator/internal/allocation/least_weighted_test.go
index 100de2d9..9f4c2880 100644
--- a/cmd/otel-allocator/internal/allocation/least_weighted_test.go
+++ b/cmd/otel-allocator/internal/allocation/least_weighted_test.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"math"
 	"math/rand"
+	"slices"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -128,7 +129,7 @@ func TestCollectorBalanceWhenAddingAndRemovingAtRandom(t *testing.T) {
 	for index := range targets {
 		shouldDelete := rand.Intn(toDelete) //nolint:gosec
 		if counter < shouldDelete {
-			delete(targets, index)
+			targets = slices.Delete(targets, index, index)
 		}
 		counter++
 	}
@@ -144,9 +145,7 @@ func TestCollectorBalanceWhenAddingAndRemovingAtRandom(t *testing.T) {
 		assert.InDelta(t, i.NumTargets, count, math.Round(percent))
 	}
 	// adding targets at 'random'
-	for _, item := range MakeNNewTargets(13, 3, 100) {
-		targets[item.Hash()] = item
-	}
+	targets = append(targets, MakeNNewTargets(13, 3, 100)...)
 	s.SetTargets(targets)
 
 	targetItemLen = len(s.TargetItems())
diff --git a/cmd/otel-allocator/internal/allocation/per_node_test.go b/cmd/otel-allocator/internal/allocation/per_node_test.go
index d610d21c..2e36395a 100644
--- a/cmd/otel-allocator/internal/allocation/per_node_test.go
+++ b/cmd/otel-allocator/internal/allocation/per_node_test.go
@@ -56,11 +56,11 @@ func TestAllocationPerNode(t *testing.T) {
 	thirdTarget := target.NewItem("sample-name", "0.0.0.0:8000", thirdLabels, "")
 	fourthTarget := target.NewItem("sample-name", "0.0.0.0:8000", fourthLabels, "")
 
-	targetList := map[string]*target.Item{
-		firstTarget.Hash():  firstTarget,
-		secondTarget.Hash(): secondTarget,
-		thirdTarget.Hash():  thirdTarget,
-		fourthTarget.Hash(): fourthTarget,
+	targetList := []*target.Item{
+		firstTarget,
+		secondTarget,
+		thirdTarget,
+		fourthTarget,
 	}
 
 	// test that targets and collectors are added properly
@@ -74,8 +74,8 @@ func TestAllocationPerNode(t *testing.T) {
 	assert.Len(t, actualItems, expectedTargetLen)
 
 	// verify allocation to nodes
-	for targetHash, item := range targetList {
-		actualItem, found := actualItems[targetHash]
+	for _, item := range targetList {
+		actualItem, found := actualItems[item.Hash()]
 		// if third target, should be skipped
 		assert.True(t, found, "target with hash %s not found", item.Hash())
 
@@ -83,7 +83,7 @@ func TestAllocationPerNode(t *testing.T) {
 		itemsForCollector := s.GetTargetsForCollectorAndJob(actualItem.CollectorName, actualItem.JobName)
 
 		// first two should be assigned one to each collector; if third target, should not be assigned
-		if targetHash == thirdTarget.Hash() {
+		if item.Hash() == thirdTarget.Hash() {
 			assert.Len(t, itemsForCollector, 0)
 			continue
 		}
@@ -123,11 +123,11 @@ func TestAllocationPerNodeUsingFallback(t *testing.T) {
 	thirdTarget := target.NewItem("sample-name", "0.0.0.0:8000", thirdLabels, "")
 	fourthTarget := target.NewItem("sample-name", "0.0.0.0:8000", fourthLabels, "")
 
-	targetList := map[string]*target.Item{
-		firstTarget.Hash():  firstTarget,
-		secondTarget.Hash(): secondTarget,
-		thirdTarget.Hash():  thirdTarget,
-		fourthTarget.Hash(): fourthTarget,
+	targetList := []*target.Item{
+		firstTarget,
+		secondTarget,
+		thirdTarget,
+		fourthTarget,
 	}
 
 	// test that targets and collectors are added properly
@@ -141,8 +141,8 @@ func TestAllocationPerNodeUsingFallback(t *testing.T) {
 	assert.Len(t, actualItems, expectedTargetLen)
 
 	// verify allocation to nodes
-	for targetHash, item := range targetList {
-		actualItem, found := actualItems[targetHash]
+	for _, item := range targetList {
+		actualItem, found := actualItems[item.Hash()]
 
 		assert.True(t, found, "target with hash %s not found", item.Hash())
 
@@ -151,7 +151,7 @@ func TestAllocationPerNodeUsingFallback(t *testing.T) {
 		// first two should be assigned one to each collector; if third target, it should be assigned
 		// according to the fallback strategy which may assign it to the otherwise empty collector or
 		// one of the others, depending on the strategy and collector loop order
-		if targetHash == thirdTarget.Hash() {
+		if item.Hash() == thirdTarget.Hash() {
 			assert.Empty(t, item.GetNodeName())
 			assert.NotZero(t, len(itemsForCollector))
 			continue
diff --git a/cmd/otel-allocator/internal/allocation/testutils.go b/cmd/otel-allocator/internal/allocation/testutils.go
index 93a9fc08..91e8762e 100644
--- a/cmd/otel-allocator/internal/allocation/testutils.go
+++ b/cmd/otel-allocator/internal/allocation/testutils.go
@@ -24,8 +24,8 @@ func colIndex(index, numCols int) int {
 	return index % numCols
 }
 
-func MakeNNewTargets(n int, numCollectors int, startingIndex int) map[string]*target.Item {
-	toReturn := map[string]*target.Item{}
+func MakeNNewTargets(n int, numCollectors int, startingIndex int) []*target.Item {
+	toReturn := []*target.Item{}
 	for i := startingIndex; i < n+startingIndex; i++ {
 		collector := fmt.Sprintf("collector-%d", colIndex(i, numCollectors))
 		label := labels.Labels{
@@ -33,7 +33,7 @@ func MakeNNewTargets(n int, numCollectors int, startingIndex int) map[string]*ta
 			{Name: "total", Value: strconv.Itoa(n + startingIndex)},
 		}
 		newTarget := target.NewItem(fmt.Sprintf("test-job-%d", i), fmt.Sprintf("test-url-%d", i), label, collector)
-		toReturn[newTarget.Hash()] = newTarget
+		toReturn = append(toReturn, newTarget)
 	}
 	return toReturn
 }
@@ -51,8 +51,8 @@ func MakeNCollectors(n int, startingIndex int) map[string]*Collector {
 	return toReturn
 }
 
-func MakeNNewTargetsWithEmptyCollectors(n int, startingIndex int) map[string]*target.Item {
-	toReturn := map[string]*target.Item{}
+func MakeNNewTargetsWithEmptyCollectors(n int, startingIndex int) []*target.Item {
+	toReturn := []*target.Item{}
 	for i := startingIndex; i < n+startingIndex; i++ {
 		label := labels.Labels{
 			{Name: "i", Value: strconv.Itoa(i)},
@@ -60,7 +60,7 @@ func MakeNNewTargetsWithEmptyCollectors(n int, startingIndex int) map[string]*ta
 			{Name: "__meta_kubernetes_pod_node_name", Value: "node-0"},
 		}
 		newTarget := target.NewItem(fmt.Sprintf("test-job-%d", i), fmt.Sprintf("test-url-%d", i), label, "")
-		toReturn[newTarget.Hash()] = newTarget
+		toReturn = append(toReturn, newTarget)
 	}
 	return toReturn
 }
diff --git a/cmd/otel-allocator/internal/config/config_test.go b/cmd/otel-allocator/internal/config/config_test.go
index 394ac583..78040ea1 100644
--- a/cmd/otel-allocator/internal/config/config_test.go
+++ b/cmd/otel-allocator/internal/config/config_test.go
@@ -16,6 +16,7 @@ import (
 	"github.com/prometheus/prometheus/discovery/file"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -51,8 +52,12 @@ func TestLoad(t *testing.T) {
 				},
 				FilterStrategy: DefaultFilterStrategy,
 				PrometheusCR: PrometheusCRConfig{
-					Enabled:        true,
-					ScrapeInterval: model.Duration(time.Second * 60),
+					Enabled:                         true,
+					ScrapeInterval:                  model.Duration(time.Second * 60),
+					ServiceMonitorNamespaceSelector: &metav1.LabelSelector{},
+					PodMonitorNamespaceSelector:     &metav1.LabelSelector{},
+					ScrapeConfigNamespaceSelector:   &metav1.LabelSelector{},
+					ProbeNamespaceSelector:          &metav1.LabelSelector{},
 				},
 				HTTPS: HTTPSServerConfig{
 					Enabled:         true,
@@ -142,7 +147,11 @@ func TestLoad(t *testing.T) {
 							"release": "test",
 						},
 					},
-					ScrapeInterval: DefaultCRScrapeInterval,
+					ServiceMonitorNamespaceSelector: &metav1.LabelSelector{},
+					PodMonitorNamespaceSelector:     &metav1.LabelSelector{},
+					ScrapeConfigNamespaceSelector:   &metav1.LabelSelector{},
+					ProbeNamespaceSelector:          &metav1.LabelSelector{},
+					ScrapeInterval:                  DefaultCRScrapeInterval,
 				},
 				PromConfig: &promconfig.Config{
 					GlobalConfig: promconfig.GlobalConfig{
@@ -213,7 +222,11 @@ func TestLoad(t *testing.T) {
 							"release": "test",
 						},
 					},
-					ScrapeInterval: DefaultCRScrapeInterval,
+					ServiceMonitorNamespaceSelector: &metav1.LabelSelector{},
+					PodMonitorNamespaceSelector:     &metav1.LabelSelector{},
+					ScrapeConfigNamespaceSelector:   &metav1.LabelSelector{},
+					ProbeNamespaceSelector:          &metav1.LabelSelector{},
+					ScrapeInterval:                  DefaultCRScrapeInterval,
 				},
 				PromConfig: &promconfig.Config{
 					GlobalConfig: promconfig.GlobalConfig{
@@ -308,7 +321,11 @@ func TestLoad(t *testing.T) {
 							},
 						},
 					},
-					ScrapeInterval: DefaultCRScrapeInterval,
+					ServiceMonitorNamespaceSelector: &metav1.LabelSelector{},
+					PodMonitorNamespaceSelector:     &metav1.LabelSelector{},
+					ScrapeConfigNamespaceSelector:   &metav1.LabelSelector{},
+					ProbeNamespaceSelector:          &metav1.LabelSelector{},
+					ScrapeInterval:                  DefaultCRScrapeInterval,
 				},
 				PromConfig: &promconfig.Config{
 					GlobalConfig: promconfig.GlobalConfig{
@@ -403,7 +420,11 @@ func TestLoad(t *testing.T) {
 							},
 						},
 					},
-					ScrapeInterval: DefaultCRScrapeInterval,
+					ServiceMonitorNamespaceSelector: &metav1.LabelSelector{},
+					PodMonitorNamespaceSelector:     &metav1.LabelSelector{},
+					ScrapeConfigNamespaceSelector:   &metav1.LabelSelector{},
+					ProbeNamespaceSelector:          &metav1.LabelSelector{},
+					ScrapeInterval:                  DefaultCRScrapeInterval,
 				},
 				PromConfig: &promconfig.Config{
 					GlobalConfig: promconfig.GlobalConfig{
@@ -518,6 +539,18 @@ func TestValidateConfig(t *testing.T) {
 			},
 			expectedErr: nil,
 		},
+		{
+			name: "both allowNamespaces and denyNamespaces set",
+			fileConfig: Config{
+				PrometheusCR: PrometheusCRConfig{
+					Enabled:         true,
+					AllowNamespaces: []string{"ns1"},
+					DenyNamespaces:  []string{"ns2"},
+				},
+				CollectorNamespace: "default",
+			},
+			expectedErr: fmt.Errorf("only one of allowNamespaces or denyNamespaces can be set"),
+		},
 	}
 
 	for _, tc := range testCases {
@@ -528,3 +561,46 @@ func TestValidateConfig(t *testing.T) {
 		})
 	}
 }
+
+func TestGetAllowDenyLists(t *testing.T) {
+	testCases := []struct {
+		name              string
+		promCRConfig      PrometheusCRConfig
+		expectedAllowList map[string]struct{}
+		expectedDenyList  map[string]struct{}
+	}{
+		{
+			name:              "no allow or deny namespaces",
+			promCRConfig:      PrometheusCRConfig{Enabled: true},
+			expectedAllowList: map[string]struct{}{v1.NamespaceAll: {}},
+			expectedDenyList:  map[string]struct{}{},
+		},
+		{
+			name:              "allow namespaces",
+			promCRConfig:      PrometheusCRConfig{Enabled: true, AllowNamespaces: []string{"ns1"}},
+			expectedAllowList: map[string]struct{}{"ns1": {}},
+			expectedDenyList:  map[string]struct{}{},
+		},
+		{
+			name:              "deny namespaces",
+			promCRConfig:      PrometheusCRConfig{Enabled: true, DenyNamespaces: []string{"ns2"}},
+			expectedAllowList: map[string]struct{}{v1.NamespaceAll: {}},
+			expectedDenyList:  map[string]struct{}{"ns2": {}},
+		},
+		{
+			name:              "both allow and deny namespaces",
+			promCRConfig:      PrometheusCRConfig{Enabled: true, AllowNamespaces: []string{"ns1"}, DenyNamespaces: []string{"ns2"}},
+			expectedAllowList: map[string]struct{}{"ns1": {}},
+			expectedDenyList:  map[string]struct{}{"ns2": {}},
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			allowList, denyList := tc.promCRConfig.GetAllowDenyLists()
+			assert.Equal(t, tc.expectedAllowList, allowList)
+			assert.Equal(t, tc.expectedDenyList, denyList)
+		})
+	}
+}
diff --git a/cmd/otel-allocator/internal/prehook/relabel_test.go b/cmd/otel-allocator/internal/prehook/relabel_test.go
index 792b3b3a..51debfc3 100644
--- a/cmd/otel-allocator/internal/prehook/relabel_test.go
+++ b/cmd/otel-allocator/internal/prehook/relabel_test.go
@@ -167,9 +167,9 @@ func colIndex(index, numCols int) int {
 	return index % numCols
 }
 
-func makeNNewTargets(rCfgs []relabelConfigObj, n int, numCollectors int, startingIndex int) (map[string]*target.Item, int, map[string]*target.Item, map[string][]*relabel.Config) {
-	toReturn := map[string]*target.Item{}
-	expectedMap := make(map[string]*target.Item)
+func makeNNewTargets(rCfgs []relabelConfigObj, n int, numCollectors int, startingIndex int) ([]*target.Item, int, []*target.Item, map[string][]*relabel.Config) {
+	toReturn := []*target.Item{}
+	expected := []*target.Item{}
 	numItemsRemaining := n
 	relabelConfig := make(map[string][]*relabel.Config)
 	for i := startingIndex; i < n+startingIndex; i++ {
@@ -189,15 +189,14 @@ func makeNNewTargets(rCfgs []relabelConfigObj, n int, numCollectors int, startin
 
 		relabelConfig[jobName] = rCfgs[index].cfg
 
-		targetKey := newTarget.Hash()
 		if relabelConfigs[index].isDrop {
 			numItemsRemaining--
 		} else {
-			expectedMap[targetKey] = newTarget
+			expected = append(expected, newTarget)
 		}
-		toReturn[targetKey] = newTarget
+		toReturn = append(toReturn, newTarget)
 	}
-	return toReturn, numItemsRemaining, expectedMap, relabelConfig
+	return toReturn, numItemsRemaining, expected, relabelConfig
 }
 
 func TestApply(t *testing.T) {
diff --git a/cmd/otel-allocator/internal/server/mocks_test.go b/cmd/otel-allocator/internal/server/mocks_test.go
index ec607bdf..75d08bd1 100644
--- a/cmd/otel-allocator/internal/server/mocks_test.go
+++ b/cmd/otel-allocator/internal/server/mocks_test.go
@@ -17,7 +17,7 @@ type mockAllocator struct {
 }
 
 func (m *mockAllocator) SetCollectors(_ map[string]*allocation.Collector)               {}
-func (m *mockAllocator) SetTargets(_ map[string]*target.Item)                           {}
+func (m *mockAllocator) SetTargets(_ []*target.Item)                                    {}
 func (m *mockAllocator) Collectors() map[string]*allocation.Collector                   { return nil }
 func (m *mockAllocator) GetTargetsForCollectorAndJob(_ string, _ string) []*target.Item { return nil }
 func (m *mockAllocator) SetFilter(_ allocation.Filter)                                  {}
diff --git a/cmd/otel-allocator/internal/server/server_test.go b/cmd/otel-allocator/internal/server/server_test.go
index fce2ef0c..aabe1075 100644
--- a/cmd/otel-allocator/internal/server/server_test.go
+++ b/cmd/otel-allocator/internal/server/server_test.go
@@ -160,8 +160,12 @@ func TestServer_TargetsHandler(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			listenAddr := ":8080"
 			s := NewServer(logger, tt.args.allocator, listenAddr)
+			targets := []*target.Item{}
+			for _, item := range tt.args.cMap {
+				targets = append(targets, item)
+			}
 			tt.args.allocator.SetCollectors(map[string]*allocation.Collector{"test-collector": {Name: "test-collector"}})
-			tt.args.allocator.SetTargets(tt.args.cMap)
+			tt.args.allocator.SetTargets(targets)
 			request := httptest.NewRequest("GET", fmt.Sprintf("/jobs/%s/targets?collector_id=%s", tt.args.job, tt.args.collector), nil)
 			w := httptest.NewRecorder()
 
diff --git a/cmd/otel-allocator/internal/target/discovery_test.go b/cmd/otel-allocator/internal/target/discovery_test.go
index c05d2a13..38a08a74 100644
--- a/cmd/otel-allocator/internal/target/discovery_test.go
+++ b/cmd/otel-allocator/internal/target/discovery_test.go
@@ -63,7 +63,7 @@ func TestDiscovery(t *testing.T) {
 	require.NoError(t, err)
 	d := discovery.NewManager(ctx, gokitlog.NewNopLogger(), registry, sdMetrics)
 	results := make(chan []string)
-	manager := NewDiscoverer(ctrl.Log.WithName("test"), d, nil, scu, func(targets map[string]*Item) {
+	manager := NewDiscoverer(ctrl.Log.WithName("test"), d, nil, scu, func(targets []*Item) {
 		var result []string
 		for _, t := range targets {
 			result = append(result, t.TargetURL)
diff --git a/cmd/otel-allocator/internal/watcher/promOperator_test.go b/cmd/otel-allocator/internal/watcher/promOperator_test.go
index 771a8d3d..2914d2d8 100644
--- a/cmd/otel-allocator/internal/watcher/promOperator_test.go
+++ b/cmd/otel-allocator/internal/watcher/promOperator_test.go
@@ -43,7 +43,7 @@ var defaultScrapeProtocols = []promconfig.ScrapeProtocol{
 }
 
 func TestLoadConfig(t *testing.T) {
-
+	namespace := "test"
 	tests := []struct {
 		name            string
 		serviceMonitors []*monitoringv1.ServiceMonitor
@@ -60,7 +60,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "simple",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.ServiceMonitorSpec{
 						JobLabel: "test",
@@ -76,7 +76,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "simple",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.PodMonitorSpec{
 						JobLabel: "test",
@@ -109,7 +109,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "endpointslice",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -131,7 +131,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "pod",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -149,7 +149,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "auth",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.ServiceMonitorSpec{
 						JobLabel: "auth",
@@ -202,7 +202,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "endpointslice",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -227,7 +227,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "bearer",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.PodMonitorSpec{
 						JobLabel: "bearer",
@@ -270,7 +270,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "pod",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -295,7 +295,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "valid-sm",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.ServiceMonitorSpec{
 						JobLabel: "test",
@@ -311,7 +311,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "valid-pm",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.PodMonitorSpec{
 						JobLabel: "test",
@@ -325,7 +325,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "invalid-pm",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.PodMonitorSpec{
 						JobLabel: "test",
@@ -366,7 +366,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "endpointslice",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -388,7 +388,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "pod",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -406,7 +406,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "valid-sm",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.ServiceMonitorSpec{
 						JobLabel: "test",
@@ -420,7 +420,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "invalid-sm",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.ServiceMonitorSpec{
 						JobLabel: "test",
@@ -444,7 +444,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "valid-pm",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.PodMonitorSpec{
 						JobLabel: "test",
@@ -477,7 +477,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "endpointslice",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -499,7 +499,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "pod",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -517,7 +517,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "sm-1",
-						Namespace: "test",
+						Namespace: namespace,
 						Labels: map[string]string{
 							"testsvc": "testsvc",
 						},
@@ -537,7 +537,7 @@ func TestLoadConfig(t *testing.T) {
 						Namespace: "test",
 					},
 					Spec: monitoringv1.ServiceMonitorSpec{
-						JobLabel: "test",
+						JobLabel: namespace,
 						Endpoints: []monitoringv1.Endpoint{
 							{
 								Port: "web",
@@ -570,7 +570,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "endpointslice",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -588,7 +588,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "pm-1",
-						Namespace: "test",
+						Namespace: namespace,
 						Labels: map[string]string{
 							"testpod": "testpod",
 						},
@@ -605,7 +605,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "pm-2",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.PodMonitorSpec{
 						JobLabel: "test",
@@ -641,7 +641,7 @@ func TestLoadConfig(t *testing.T) {
 							&kubeDiscovery.SDConfig{
 								Role: "pod",
 								NamespaceDiscovery: kubeDiscovery.NamespaceDiscovery{
-									Names:               []string{"test"},
+									Names:               []string{namespace},
 									IncludeOwnNamespace: false,
 								},
 								HTTPClientConfig: config.DefaultHTTPClientConfig,
@@ -659,7 +659,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "scrapeconfig-test-1",
-						Namespace: "test",
+						Namespace: namespace,
 						Labels: map[string]string{
 							"testpod": "testpod",
 						},
@@ -723,7 +723,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "probe-test-1",
-						Namespace: "test",
+						Namespace: namespace,
 						Labels: map[string]string{
 							"testpod": "testpod",
 						},
@@ -771,7 +771,7 @@ func TestLoadConfig(t *testing.T) {
 										},
 									},
 									Labels: map[model.LabelName]model.LabelValue{
-										"namespace": "test",
+										"namespace": model.LabelValue(namespace),
 									},
 									Source: "0",
 								},
@@ -803,7 +803,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "sm-2",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.ServiceMonitorSpec{
 						JobLabel: "test",
@@ -873,7 +873,7 @@ func TestLoadConfig(t *testing.T) {
 				{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "pm-2",
-						Namespace: "test",
+						Namespace: namespace,
 					},
 					Spec: monitoringv1.PodMonitorSpec{
 						JobLabel: "test",
@@ -926,7 +926,7 @@ func TestLoadConfig(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			w, _ := getTestPrometheusCRWatcher(t, tt.serviceMonitors, tt.podMonitors, tt.probes, tt.scrapeConfigs, tt.cfg)
+			w, _ := getTestPrometheusCRWatcher(t, namespace, tt.serviceMonitors, tt.podMonitors, tt.probes, tt.scrapeConfigs, tt.cfg)
 
 			// Start namespace informers in order to populate cache.
 			go w.nsInformer.Run(w.stopChannel)
@@ -957,6 +957,7 @@ func TestLoadConfig(t *testing.T) {
 
 func TestNamespaceLabelUpdate(t *testing.T) {
 	var err error
+	namespace := "test"
 	podMonitors := []*monitoringv1.PodMonitor{
 		{
 			ObjectMeta: metav1.ObjectMeta{
@@ -975,7 +976,7 @@ func TestNamespaceLabelUpdate(t *testing.T) {
 		{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      "pm-2",
-				Namespace: "test",
+				Namespace: namespace,
 			},
 			Spec: monitoringv1.PodMonitorSpec{
 				JobLabel: "test",
@@ -1031,7 +1032,7 @@ func TestNamespaceLabelUpdate(t *testing.T) {
 		ScrapeConfigs: []*promconfig.ScrapeConfig{},
 	}
 
-	w, source := getTestPrometheusCRWatcher(t, nil, podMonitors, nil, nil, cfg)
+	w, source := getTestPrometheusCRWatcher(t, namespace, nil, podMonitors, nil, nil, cfg)
 	events := make(chan Event, 1)
 	eventInterval := 5 * time.Millisecond
 
@@ -1079,10 +1080,11 @@ func TestNamespaceLabelUpdate(t *testing.T) {
 
 func TestRateLimit(t *testing.T) {
 	var err error
+	namespace := "test"
 	serviceMonitor := &monitoringv1.ServiceMonitor{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "simple",
-			Namespace: "test",
+			Namespace: namespace,
 		},
 		Spec: monitoringv1.ServiceMonitorSpec{
 			JobLabel: "test",
@@ -1097,7 +1099,7 @@ func TestRateLimit(t *testing.T) {
 	eventInterval := 500 * time.Millisecond
 	cfg := allocatorconfig.Config{}
 
-	w, _ := getTestPrometheusCRWatcher(t, nil, nil, nil, nil, cfg)
+	w, _ := getTestPrometheusCRWatcher(t, namespace, nil, nil, nil, nil, cfg)
 	defer w.Close()
 	w.eventInterval = eventInterval
 
@@ -1158,7 +1160,15 @@ func TestRateLimit(t *testing.T) {
 
 // getTestPrometheusCRWatcher creates a test instance of PrometheusCRWatcher with fake clients
 // and test secrets.
-func getTestPrometheusCRWatcher(t *testing.T, svcMonitors []*monitoringv1.ServiceMonitor, podMonitors []*monitoringv1.PodMonitor, probes []*monitoringv1.Probe, scrapeConfigs []*promv1alpha1.ScrapeConfig, cfg allocatorconfig.Config) (*PrometheusCRWatcher, *fcache.FakeControllerSource) {
+func getTestPrometheusCRWatcher(
+	t *testing.T,
+	namespace string,
+	svcMonitors []*monitoringv1.ServiceMonitor,
+	podMonitors []*monitoringv1.PodMonitor,
+	probes []*monitoringv1.Probe,
+	scrapeConfigs []*promv1alpha1.ScrapeConfig,
+	cfg allocatorconfig.Config,
+) (*PrometheusCRWatcher, *fcache.FakeControllerSource) {
 	mClient := fakemonitoringclient.NewSimpleClientset()
 	for _, sm := range svcMonitors {
 		if sm != nil {
@@ -1225,6 +1235,9 @@ func getTestPrometheusCRWatcher(t *testing.T, svcMonitors []*monitoringv1.Servic
 	serviceDiscoveryRole := monitoringv1.ServiceDiscoveryRole("EndpointSlice")
 
 	prom := &monitoringv1.Prometheus{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+		},
 		Spec: monitoringv1.PrometheusSpec{
 			CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
 				ScrapeInterval:                  monitoringv1.Duration("30s"),
@@ -1238,6 +1251,7 @@ func getTestPrometheusCRWatcher(t *testing.T, svcMonitors []*monitoringv1.Servic
 				ScrapeConfigNamespaceSelector:   cfg.PrometheusCR.ScrapeConfigNamespaceSelector,
 				ServiceDiscoveryRole:            &serviceDiscoveryRole,
 			},
+			EvaluationInterval: monitoringv1.Duration("30s"),
 		},
 	}
 
@@ -1282,6 +1296,7 @@ func getTestPrometheusCRWatcher(t *testing.T, svcMonitors []*monitoringv1.Servic
 		scrapeConfigNamespaceSelector:   cfg.PrometheusCR.ScrapeConfigNamespaceSelector,
 		resourceSelector:                resourceSelector,
 		store:                           store,
+		prometheusCR:                    prom,
 	}, source
 
 }
diff --git a/internal/manifests/opampbridge/configmap_test.go b/internal/manifests/opampbridge/configmap_test.go
index 6001ef7d..da1d6c2e 100644
--- a/internal/manifests/opampbridge/configmap_test.go
+++ b/internal/manifests/opampbridge/configmap_test.go
@@ -46,6 +46,9 @@ componentsAllowed:
   - memory_limiter
   receivers:
   - otlp
+description:
+  non_identifying_attributes:
+    hello: world
 endpoint: ws://opamp-server:4320/v1/opamp
 headers:
   authorization: access-12345-token
@@ -85,6 +88,11 @@ headers:
 					Image:    tc.image,
 					Endpoint: "ws://opamp-server:4320/v1/opamp",
 					Headers:  map[string]string{"authorization": "access-12345-token"},
+					Description: &v1alpha1.AgentDescription{
+						NonIdentifyingAttributes: map[string]string{
+							"hello": "world",
+						},
+					},
 					Capabilities: map[v1alpha1.OpAMPBridgeCapability]bool{
 						v1alpha1.OpAMPBridgeCapabilityReportsStatus:                  true,
 						v1alpha1.OpAMPBridgeCapabilityAcceptsRemoteConfig:            true,
diff --git a/tests/e2e-targetallocator-cr/00-assert.yaml b/tests/e2e-targetallocator-cr/00-assert.yaml
index cc345158..45476aa1 100644
--- a/tests/e2e-targetallocator-cr/00-assert.yaml
+++ b/tests/e2e-targetallocator-cr/00-assert.yaml
@@ -165,4 +165,4 @@ spec:
       app.kubernetes.io/managed-by: opentelemetry-operator
       app.kubernetes.io/name: cr-targetallocator
       app.kubernetes.io/part-of: opentelemetry
-  maxUnavailable: 1
\ No newline at end of file
+  maxUnavailable: 1
diff --git a/tests/e2e-targetallocator-cr/00-install.yaml b/tests/e2e-targetallocator-cr/00-install.yaml
index 0ab5658f..7145e7cb 100644
--- a/tests/e2e-targetallocator-cr/00-install.yaml
+++ b/tests/e2e-targetallocator-cr/00-install.yaml
@@ -1,3 +1,39 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: (join('-', ['cr-targetallocator', $namespace]))
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - namespaces
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups:
+      - monitoring.coreos.com
+    resources:
+      - servicemonitors
+    verbs:
+      - get
+      - list
+      - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: (join('-', ['cr-targetallocator', $namespace]))
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: (join('-', ['cr-targetallocator', $namespace]))
+subjects:
+  - kind: ServiceAccount
+    name: cr-targetallocator
+    namespace: ($namespace)
+---
 apiVersion: opentelemetry.io/v1alpha1
 kind: TargetAllocator
 metadata:
@@ -6,4 +42,4 @@ spec:
   scrapeConfigs:
     - job_name: prometheus
       static_configs:
-      - targets: [ "localhost:9090" ]
\ No newline at end of file
+      - targets: [ "localhost:9090" ]
diff --git a/tests/e2e-targetallocator-cr/01-assert.yaml b/tests/e2e-targetallocator-cr/01-assert.yaml
index 0c0873c5..546e3eaa 100644
--- a/tests/e2e-targetallocator-cr/01-assert.yaml
+++ b/tests/e2e-targetallocator-cr/01-assert.yaml
@@ -76,6 +76,7 @@ status:
 ---
 apiVersion: v1
 data:
+  # Note: The content of this file must match exactly (careful about the order of keys) the content of the file in the ConfigMap
   targetallocator.yaml: |
     allocation_strategy: consistent-hashing
     collector_selector: null
@@ -136,4 +137,4 @@ spec:
       app.kubernetes.io/managed-by: opentelemetry-operator
       app.kubernetes.io/name: cr-targetallocator
       app.kubernetes.io/part-of: opentelemetry
-  maxUnavailable: 1
\ No newline at end of file
+  maxUnavailable: 1
diff --git a/tests/e2e-targetallocator-cr/01-install.yaml b/tests/e2e-targetallocator-cr/01-install.yaml
index 543cccb9..d7b04211 100644
--- a/tests/e2e-targetallocator-cr/01-install.yaml
+++ b/tests/e2e-targetallocator-cr/01-install.yaml
@@ -1,38 +1,4 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: (join('-', ['cr-targetallocator', $namespace]))
-rules:
-  - apiGroups:
-      - ""
-    resources:
-      - pods
-      - namespaces
-    verbs:
-      - get
-      - list
-      - watch
-  - apiGroups:
-      - monitoring.coreos.com
-    resources:
-      - servicemonitors
-    verbs:
-      - get
-      - list
-      - watch
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: (join('-', ['cr-targetallocator', $namespace]))
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: (join('-', ['cr-targetallocator', $namespace]))
-subjects:
-  - kind: ServiceAccount
-    name: cr-targetallocator
-    namespace: ($namespace)
+
 ---
 apiVersion: opentelemetry.io/v1alpha1
 kind: TargetAllocator
@@ -72,4 +38,4 @@ spec:
     - mountPath: /usr/share/testvolume
       name: testvolume
   volumes:
-    - name: testvolume
\ No newline at end of file
+    - name: testvolume
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/assert-job-failed.yaml b/tests/e2e-targetallocator/targetallocator-namespace/assert-job-failed.yaml
new file mode 100644
index 00000000..e0bae174
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/assert-job-failed.yaml
@@ -0,0 +1,7 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: check-metrics
+  namespace: ($namespace)
+status:
+  failed: 1
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/assert-job-succeeded.yaml b/tests/e2e-targetallocator/targetallocator-namespace/assert-job-succeeded.yaml
new file mode 100644
index 00000000..e0044e72
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/assert-job-succeeded.yaml
@@ -0,0 +1,7 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: check-metrics
+  namespace: ($namespace)
+status:
+  succeeded: 1
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/assert-workloads-ready.yaml b/tests/e2e-targetallocator/targetallocator-namespace/assert-workloads-ready.yaml
new file mode 100644
index 00000000..34ebca5b
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/assert-workloads-ready.yaml
@@ -0,0 +1,17 @@
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: prometheus-cr-collector
+  namespace: ($namespace)
+status:
+  readyReplicas: 1
+  replicas: 1
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: cr-targetallocator
+  namespace: ($namespace)
+status:
+  readyReplicas: 1
+  replicas: 1
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/chainsaw-test.yaml b/tests/e2e-targetallocator/targetallocator-namespace/chainsaw-test.yaml
new file mode 100755
index 00000000..72363a49
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/chainsaw-test.yaml
@@ -0,0 +1,108 @@
+# 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:
+  name: targetallocator-namespace
+spec:
+  bindings:
+    - name: denyNamespaces
+      value: (join('-', [$namespace, 'top-secret']))
+    - name: endpoint
+      value: "http://cr-targetallocator/scrape_configs"
+    - name: pattern
+      value: ""
+    - name: retries
+      value: "30"
+  catch:
+    - podLogs:
+        selector: app.kubernetes.io/managed-by=opentelemetry-operator
+  steps:
+    # Sets up resources in a namespace that we shouldn't be able to read from
+    - name: install-into-deny-namespace
+      bindings:
+        - name: namespace
+          value: ($denyNamespaces)
+      try:
+        - create:
+            resource:
+              apiVersion: v1
+              kind: Namespace
+              metadata:
+                name: ($denyNamespaces)
+        - apply:
+            file: resources/serviceaccounts.yaml
+        - apply:
+            file: resources/rbac.yaml
+        - apply:
+            file: resources/ta-allow-namespaces.yaml
+        - apply:
+            file: resources/otelcol.yaml
+    - name: wait-until-top-secret-install-ready
+      bindings:
+        - name: namespace
+          value: ($denyNamespaces)
+      try:
+        - assert:
+            file: assert-workloads-ready.yaml
+    # The TA should be able to discover targets from its own namespace
+    - name: check-top-secret-ta-can-discover-its-own-service-monitor
+      bindings:
+        - name: namespace
+          value: ($denyNamespaces)
+        - name: pattern
+          value: (join('/', ['serviceMonitor', $denyNamespaces, 'cr-targetallocator']))
+      try:
+        - apply:
+            file: resources/job-check-metrics.yaml
+    - name: assert-top-secret-job-succeeded
+      bindings:
+        - name: namespace
+          value: ($denyNamespaces)
+      try:
+        - assert:
+            file: assert-job-succeeded.yaml
+    # Sets up resources in the original namespace and then makes sure that we can't discover the denied namespace
+    - name: install-with-deny-namespace-set-to-top-secret-configured-earlier
+      try:
+        - apply:
+            file: resources/serviceaccounts.yaml
+        - apply:
+            file: resources/clusterrbac.yaml
+        - apply:
+            file: resources/ta-deny-namespaces.yaml
+        - apply:
+            file: resources/otelcol.yaml
+    - name: wait-until-install-with-deny-namespace-set-to-top-secret-configured-earlier-is-ready
+      try:
+        - assert:
+            file: assert-workloads-ready.yaml
+    # Make sure TA can still discover its own namespace first
+    - name: check-ta-can-discover-its-own-service-monitor
+      bindings:
+        - name: pattern
+          value: (join('/', ['serviceMonitor', $namespace, 'cr-targetallocator']))
+      try:
+        - apply:
+            file: resources/job-check-metrics.yaml
+    - name: assert-job-succeeded
+      try:
+        - assert:
+            file: assert-job-succeeded.yaml
+    - name: delete-job
+      try:
+        - delete:
+            file: resources/job-check-metrics.yaml
+    # Make sure TA can't discover the denied namespace
+    - name: check-ta-cant-discover-denied-namespace
+      bindings:
+        - name: pattern
+          value: (join('/', ['serviceMonitor', $denyNamespaces, 'cr-targetallocator']))
+        - name: retries
+          value: "5"
+      try:
+        - apply:
+            file: resources/job-check-metrics.yaml
+    - name: assert-job-failed-since-ta-cant-discover-denied-namespace
+      try:
+        - assert:
+            file: assert-job-failed.yaml
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/clusterrbac.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/clusterrbac.yaml
new file mode 100644
index 00000000..3fffb25d
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/clusterrbac.yaml
@@ -0,0 +1,103 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: (join('-', ['ta', $namespace]))
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - services
+      - endpoints
+      - configmaps
+      - secrets
+      - namespaces
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - apps
+    resources:
+      - statefulsets
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - discovery.k8s.io
+    resources:
+      - endpointslices
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - networking.k8s.io
+    resources:
+      - ingresses
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - monitoring.coreos.com
+    resources:
+      - servicemonitors
+      - podmonitors
+      - scrapeconfigs
+      - probes
+    verbs:
+      - get
+      - watch
+      - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: (join('-', ['collector', $namespace]))
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - pods
+      - services
+      - endpoints
+    verbs:
+      - get
+      - watch
+      - list
+  - apiGroups:
+      - networking.k8s.io
+    resources:
+      - ingresses
+    verbs:
+      - get
+      - watch
+      - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: (join('-', ['ta', $namespace]))
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: (join('-', ['ta', $namespace]))
+subjects:
+  - kind: ServiceAccount
+    name: ta
+    namespace: ($namespace)
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: (join('-', ['collector', $namespace]))
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: (join('-', ['collector', $namespace]))
+subjects:
+  - kind: ServiceAccount
+    name: collector
+    namespace: ($namespace)
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/job-check-metrics.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/job-check-metrics.yaml
new file mode 100644
index 00000000..c5fa2b33
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/job-check-metrics.yaml
@@ -0,0 +1,33 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: check-metrics
+  namespace: ($namespace)
+spec:
+  template:
+    spec:
+      restartPolicy: Never
+      containers:
+        - name: check-metrics
+          image: curlimages/curl
+          env:
+            - name: endpoint
+              value: ($endpoint)
+            - name: pattern
+              value: ($pattern)
+            - name: retries
+              value: ($retries)
+          args:
+            - /bin/sh
+            - -c
+            - |
+              for i in $(seq $retries); do
+                if curl -m 1 -s $endpoint | grep "$pattern"; then
+                  echo "Found $pattern in $endpoint" 
+                  exit 0
+                fi
+                echo "Waiting for $pattern in $endpoint"
+                sleep 5
+              done
+              echo "Failed to find $pattern in $endpoint"
+              exit 1
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/otelcol.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/otelcol.yaml
new file mode 100644
index 00000000..8d2cba4c
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/otelcol.yaml
@@ -0,0 +1,35 @@
+apiVersion: opentelemetry.io/v1beta1
+kind: OpenTelemetryCollector
+metadata:
+  name: prometheus-cr
+  namespace: ($namespace)
+  labels:
+    opentelemetry.io/target-allocator: cr
+spec:
+  observability:
+    metrics:
+      disablePrometheusAnnotations: true
+      enableMetrics: true
+  config:
+    receivers:
+      prometheus:
+        config:
+          scrape_configs: []
+
+    processors:
+
+    exporters:
+      prometheus:
+        endpoint: 0.0.0.0:9090
+    service:
+      pipelines:
+        metrics:
+          receivers: [prometheus]
+          exporters: [prometheus]
+      telemetry:
+        logs:
+          level: "DEBUG"
+          development: true
+          encoding: "json"
+  mode: statefulset
+  serviceAccount: collector
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/rbac.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/rbac.yaml
new file mode 100644
index 00000000..1f45fe12
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/rbac.yaml
@@ -0,0 +1,107 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: ta
+  namespace: ($namespace)
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  - services
+  - endpoints
+  - configmaps
+  - secrets
+  - namespaces
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - apps
+  resources:
+  - statefulsets
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - discovery.k8s.io
+  resources:
+  - endpointslices
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - ingresses
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - monitoring.coreos.com
+  resources:
+  - servicemonitors
+  - podmonitors
+  - scrapeconfigs
+  - probes
+  verbs:
+  - get
+  - watch
+  - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: collector
+  namespace: ($namespace)
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  - services
+  - endpoints
+  verbs:
+  - get
+  - watch
+  - list
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - ingresses
+  verbs:
+  - get
+  - watch
+  - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: ta
+  namespace: ($namespace)
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: ta
+subjects:
+- kind: ServiceAccount
+  name: ta
+  namespace: ($namespace)
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: collector
+  namespace: ($namespace)
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: collector
+subjects:
+- kind: ServiceAccount
+  name: collector
+  namespace: ($namespace)
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/serviceaccounts.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/serviceaccounts.yaml
new file mode 100644
index 00000000..19c24ed3
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/serviceaccounts.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+automountServiceAccountToken: true
+kind: ServiceAccount
+metadata:
+  name: ta
+  namespace: ($namespace)
+---
+apiVersion: v1
+automountServiceAccountToken: true
+kind: ServiceAccount
+metadata:
+  name: collector
+  namespace: ($namespace)
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-allow-namespaces.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-allow-namespaces.yaml
new file mode 100644
index 00000000..410d3c48
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-allow-namespaces.yaml
@@ -0,0 +1,22 @@
+apiVersion: opentelemetry.io/v1alpha1
+kind: TargetAllocator
+metadata:
+  name: cr
+  namespace: ($namespace)
+spec:
+  args:
+    "zap-log-level": "debug"
+  prometheusCR:
+    enabled: true
+    allowNamespaces:
+    - ($namespace)
+    scrapeInterval: 1s
+    scrapeConfigSelector: {}
+    probeSelector: {}
+    serviceMonitorSelector: {}
+    podMonitorSelector: {}
+  observability:
+    metrics:
+      disablePrometheusAnnotations: true
+      enableMetrics: true
+  serviceAccount: ta
diff --git a/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-deny-namespaces.yaml b/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-deny-namespaces.yaml
new file mode 100644
index 00000000..02a426a0
--- /dev/null
+++ b/tests/e2e-targetallocator/targetallocator-namespace/resources/ta-deny-namespaces.yaml
@@ -0,0 +1,22 @@
+apiVersion: opentelemetry.io/v1alpha1
+kind: TargetAllocator
+metadata:
+  name: cr
+  namespace: ($namespace)
+spec:
+  args:
+    "zap-log-level": "debug"
+  prometheusCR:
+    enabled: true
+    denyNamespaces:
+    - ($denyNamespaces)
+    scrapeInterval: 1s
+    scrapeConfigSelector: {}
+    probeSelector: {}
+    serviceMonitorSelector: {}
+    podMonitorSelector: {}
+  observability:
+    metrics:
+      disablePrometheusAnnotations: true
+      enableMetrics: true
+  serviceAccount: ta