Repository: open-telemetry/opentelemetry-operator

Issue:
Use gotestsum for running unit tests
[gotestsum](https://github.com/gotestyourself/gotestsum) is widely used as a test runner in large Go projects, kubernetes and opentelemetry-collector-contrib included. Its benefits include:

* A nicer default report format, including a summary at the end. Currently, it's quite annoying to figure out which test failed when looking at logs.
* The ability to export test results to JUnit, allowing us to use Github Actions that can post reports and create Github annotations based on test failures. See https://github.com/marketplace/actions/junit-test-dashboard and https://github.com/marketplace/actions/junit-report-action.

@open-telemetry/operator-approvers let me know what you think.

Hints from issue comments:
@jaronoff97: i dig it

Solution patch:
diff --git a/.chloggen/fix_opampbridge.yaml b/.chloggen/fix_opampbridge.yaml
new file mode 100644
index 00000000..68e0ae95
--- /dev/null
+++ b/.chloggen/fix_opampbridge.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: opampbridge
+
+# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: Do not register the opampbridge webhook if the CRD is not present
+
+# One or more tracking issues related to the change
+issues: [4070]
+
+# (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/Makefile b/Makefile
index 2bb97f67..aeaadb32 100644
--- a/Makefile
+++ b/Makefile
@@ -252,8 +252,8 @@ manifests: controller-gen
 # Run tests
 # setup-envtest uses KUBEBUILDER_ASSETS which points to a directory with binaries (api-server, etcd and kubectl)
 .PHONY: test
-test: envtest
-	KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(KUBE_VERSION) -p path)" go test ${GOTEST_OPTS} ./...
+test: envtest gotestsum
+	KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(KUBE_VERSION) -p path)" $(GOTESTSUM) -- ${GOTEST_OPTS} ./...
 
 .PHONY: precommit
 precommit: fmt vet lint test ensure-update-is-noop
@@ -506,6 +506,7 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest
 CHLOGGEN ?= $(LOCALBIN)/chloggen
 GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
 CHAINSAW ?= $(LOCALBIN)/chainsaw
+GOTESTSUM ?= $(LOCALBIN)/gotestsum
 
 # renovate: datasource=go depName=sigs.k8s.io/kustomize/kustomize/v5
 KUSTOMIZE_VERSION ?= v5.6.0
@@ -517,6 +518,8 @@ GOLANGCI_LINT_VERSION ?= v2.1.6
 KIND_VERSION ?= v0.29.0
 # renovate: datasource=go depName=github.com/kyverno/chainsaw
 CHAINSAW_VERSION ?= v0.2.12
+# renovate: datasource=go depName=gotest.tools/gotestsum
+GOTESTSUM_VERSION ?= v1.12.2
 
 .PHONY: install-tools
 install-tools: kustomize golangci-lint kind controller-gen envtest crdoc kind operator-sdk chainsaw
@@ -552,6 +555,10 @@ crdoc: ## Download crdoc locally if necessary.
 chainsaw: ## Find or download chainsaw
 	$(call go-get-tool,$(CHAINSAW), github.com/kyverno/chainsaw,$(CHAINSAW_VERSION))
 
+.PHONY: gotestsum
+gotestsum: ## Find or download gotestsum
+	$(call go-get-tool,$(GOTESTSUM), gotest.tools/gotestsum,$(GOTESTSUM_VERSION))
+
 # go-get-tool will 'go get' any package $2 and install it to $1.
 PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
 define go-get-tool
diff --git a/internal/autodetect/main.go b/internal/autodetect/main.go
index adc5a20a..b7d350f4 100644
--- a/internal/autodetect/main.go
+++ b/internal/autodetect/main.go
@@ -17,6 +17,7 @@ import (
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/collector"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/fips"
+	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/opampbridge"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus"
 	autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac"
@@ -35,6 +36,7 @@ type AutoDetect interface {
 	CertManagerAvailability(ctx context.Context) (certmanager.Availability, error)
 	TargetAllocatorAvailability() (targetallocator.Availability, error)
 	CollectorAvailability() (collector.Availability, error)
+	OpAmpBridgeAvailablity() (opampbridge.Availability, error)
 	FIPSEnabled(ctx context.Context) bool
 }
 
@@ -218,6 +220,38 @@ func (a *autoDetect) CollectorAvailability() (collector.Availability, error) {
 	return collector.NotAvailable, nil
 }
 
+// OpAmpBridgeAvailablity checks if OpAMPBridge CR are available.
+func (a *autoDetect) OpAmpBridgeAvailablity() (opampbridge.Availability, error) {
+	apiList, err := a.dcl.ServerGroups()
+	if err != nil {
+		return opampbridge.NotAvailable, err
+	}
+
+	apiGroups := apiList.Groups
+	otelGroupIndex := slices.IndexFunc(apiGroups, func(group metav1.APIGroup) bool {
+		return group.Name == "opentelemetry.io"
+	})
+	if otelGroupIndex == -1 {
+		return opampbridge.NotAvailable, nil
+	}
+
+	otelGroup := apiGroups[otelGroupIndex]
+	for _, groupVersion := range otelGroup.Versions {
+		resourceList, err := a.dcl.ServerResourcesForGroupVersion(groupVersion.GroupVersion)
+		if err != nil {
+			return opampbridge.NotAvailable, err
+		}
+		index := slices.IndexFunc(resourceList.APIResources, func(group metav1.APIResource) bool {
+			return group.Kind == "OpAMPBridge"
+		})
+		if index >= 0 {
+			return opampbridge.Available, nil
+		}
+	}
+
+	return opampbridge.NotAvailable, nil
+}
+
 func (a *autoDetect) FIPSEnabled(_ context.Context) bool {
 	return fips.IsFipsEnabled()
 }
@@ -268,5 +302,12 @@ func ApplyAutoDetect(autoDetect AutoDetect, c *config.Config, logger logr.Logger
 	c.CollectorAvailability = coAvl
 	logger.V(2).Info("determined Collector CRD availability", "availability", coAvl)
 
+	opAvl, err := autoDetect.OpAmpBridgeAvailablity()
+	if err != nil {
+		return err
+	}
+	c.OpAmpBridgeAvailability = opAvl
+	logger.V(2).Info("determined OpAmpBridge CRD availability", "availability", coAvl)
+
 	return nil
 }
diff --git a/internal/autodetect/opampbridge/operator.go b/internal/autodetect/opampbridge/operator.go
new file mode 100644
index 00000000..5b491a0b
--- /dev/null
+++ b/internal/autodetect/opampbridge/operator.go
@@ -0,0 +1,19 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package opampbridge
+
+// Availability represents that the OpAmpBridge CR is available in the cluster.
+type Availability int
+
+const (
+	// NotAvailable OpAmpBridge CR is not available in the cluster.
+	NotAvailable Availability = iota
+
+	// Available OpAmpBridge CR is available in the cluster.
+	Available
+)
+
+func (p Availability) String() string {
+	return [...]string{"NotAvailable", "Available"}[p]
+}
diff --git a/internal/config/config.go b/internal/config/config.go
index 3a670461..b546cbec 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/collector"
+	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/opampbridge"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus"
 	autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac"
@@ -81,6 +82,8 @@ type Config struct {
 	TargetAllocatorAvailability targetallocator.Availability
 	// CollectorAvailability represents the availability of the OpenTelemetryCollector CRD.
 	CollectorAvailability collector.Availability
+	// OpAmpBridgeAvailability represents the availability of the OpAmpBridge CRD.
+	OpAmpBridgeAvailability opampbridge.Availability
 	// IgnoreMissingCollectorCRDs is true if the operator can ignore missing OpenTelemetryCollector CRDs.
 	IgnoreMissingCollectorCRDs bool
 	// LabelsFilter Returns the filters converted to regex strings used to filter out unwanted labels from propagations.
@@ -99,6 +102,7 @@ func New(opts ...Option) Config {
 		certManagerAvailability:           certmanager.NotAvailable,
 		targetAllocatorAvailability:       targetallocator.NotAvailable,
 		collectorAvailability:             collector.NotAvailable,
+		opAmpBridgeAvailability:           opampbridge.NotAvailable,
 		collectorConfigMapEntry:           defaultCollectorConfigMapEntry,
 		targetAllocatorConfigMapEntry:     defaultTargetAllocatorConfigMapEntry,
 		operatorOpAMPBridgeConfigMapEntry: defaultOperatorOpAMPBridgeConfigMapEntry,
@@ -129,6 +133,7 @@ func New(opts ...Option) Config {
 		OperatorOpAMPBridgeConfigMapEntry:   o.operatorOpAMPBridgeConfigMapEntry,
 		logger:                              o.logger,
 		OpenShiftRoutesAvailability:         o.openshiftRoutesAvailability,
+		OpAmpBridgeAvailability:             o.opAmpBridgeAvailability,
 		PrometheusCRAvailability:            o.prometheusCRAvailability,
 		CertManagerAvailability:             o.certManagerAvailability,
 		TargetAllocatorAvailability:         o.targetAllocatorAvailability,
diff --git a/internal/config/options.go b/internal/config/options.go
index 32c768b4..0cf7d338 100644
--- a/internal/config/options.go
+++ b/internal/config/options.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/collector"
+	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/opampbridge"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus"
 	autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac"
@@ -49,6 +50,7 @@ type options struct {
 	certManagerAvailability             certmanager.Availability
 	targetAllocatorAvailability         targetallocator.Availability
 	collectorAvailability               collector.Availability
+	opAmpBridgeAvailability             opampbridge.Availability
 	ignoreMissingCollectorCRDs          bool
 	labelsFilter                        []string
 	annotationsFilter                   []string
diff --git a/main.go b/main.go
index 4ea1076d..feb5894a 100644
--- a/main.go
+++ b/main.go
@@ -41,6 +41,7 @@ import (
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/collector"
+	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/opampbridge"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/targetallocator"
@@ -417,15 +418,17 @@ func main() {
 		}
 	}
 
-	if err = controllers.NewOpAMPBridgeReconciler(controllers.OpAMPBridgeReconcilerParams{
-		Client:   mgr.GetClient(),
-		Log:      ctrl.Log.WithName("controllers").WithName("OpAMPBridge"),
-		Scheme:   mgr.GetScheme(),
-		Config:   cfg,
-		Recorder: mgr.GetEventRecorderFor("opamp-bridge"),
-	}).SetupWithManager(mgr); err != nil {
-		setupLog.Error(err, "unable to create controller", "controller", "OpAMPBridge")
-		os.Exit(1)
+	if cfg.OpAmpBridgeAvailability == opampbridge.Available {
+		if err = controllers.NewOpAMPBridgeReconciler(controllers.OpAMPBridgeReconcilerParams{
+			Client:   mgr.GetClient(),
+			Log:      ctrl.Log.WithName("controllers").WithName("OpAMPBridge"),
+			Scheme:   mgr.GetScheme(),
+			Config:   cfg,
+			Recorder: mgr.GetEventRecorderFor("opamp-bridge"),
+		}).SetupWithManager(mgr); err != nil {
+			setupLog.Error(err, "unable to create controller", "controller", "OpAMPBridge")
+			os.Exit(1)
+		}
 	}
 
 	if cfg.PrometheusCRAvailability == prometheus.Available && createSMOperatorMetrics {
@@ -502,9 +505,11 @@ func main() {
 				}),
 		})
 
-		if err = otelv1alpha1.SetupOpAMPBridgeWebhook(mgr, cfg); err != nil {
-			setupLog.Error(err, "unable to create webhook", "webhook", "OpAMPBridge")
-			os.Exit(1)
+		if cfg.OpAmpBridgeAvailability == opampbridge.Available {
+			if err = otelv1alpha1.SetupOpAMPBridgeWebhook(mgr, cfg); err != nil {
+				setupLog.Error(err, "unable to create webhook", "webhook", "OpAMPBridge")
+				os.Exit(1)
+			}
 		}
 	} else {
 		ctrl.Log.Info("Webhooks are disabled, operator is running an unsupported mode", "ENABLE_WEBHOOKS", "false")



Test patch:
diff --git a/internal/autodetect/main_test.go b/internal/autodetect/main_test.go
index 435a98b7..f3788372 100644
--- a/internal/autodetect/main_test.go
+++ b/internal/autodetect/main_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/autodetectutils"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/collector"
+	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/opampbridge"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus"
 	autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac"
@@ -372,6 +373,9 @@ func TestConfigChangesOnAutoDetect(t *testing.T) {
 		TargetAllocatorAvailabilityFunc: func() (targetallocator.Availability, error) {
 			return targetallocator.Available, nil
 		},
+		OpAmpBridgeAvailabilityFunc: func() (opampbridge.Availability, error) {
+			return opampbridge.Available, nil
+		},
 	}
 	cfg := config.New()
 
@@ -381,6 +385,7 @@ func TestConfigChangesOnAutoDetect(t *testing.T) {
 	require.Equal(t, autoRBAC.NotAvailable, cfg.CreateRBACPermissions)
 	require.Equal(t, certmanager.NotAvailable, cfg.CertManagerAvailability)
 	require.Equal(t, targetallocator.NotAvailable, cfg.TargetAllocatorAvailability)
+	require.Equal(t, opampbridge.NotAvailable, cfg.OpAmpBridgeAvailability)
 
 	// test
 	err := autodetect.ApplyAutoDetect(mock, &cfg, ctrl.Log.WithName("test"))
@@ -392,6 +397,7 @@ func TestConfigChangesOnAutoDetect(t *testing.T) {
 	require.Equal(t, autoRBAC.Available, cfg.CreateRBACPermissions)
 	require.Equal(t, certmanager.Available, cfg.CertManagerAvailability)
 	require.Equal(t, targetallocator.Available, cfg.TargetAllocatorAvailability)
+	require.Equal(t, opampbridge.Available, cfg.OpAmpBridgeAvailability)
 }
 
 var _ autodetect.AutoDetect = (*mockAutoDetect)(nil)
@@ -403,6 +409,14 @@ type mockAutoDetect struct {
 	CertManagerAvailabilityFunc     func(ctx context.Context) (certmanager.Availability, error)
 	TargetAllocatorAvailabilityFunc func() (targetallocator.Availability, error)
 	CollectorAvailabilityFunc       func() (collector.Availability, error)
+	OpAmpBridgeAvailabilityFunc     func() (opampbridge.Availability, error)
+}
+
+func (m *mockAutoDetect) OpAmpBridgeAvailablity() (opampbridge.Availability, error) {
+	if m.OpAmpBridgeAvailabilityFunc != nil {
+		return m.OpAmpBridgeAvailabilityFunc()
+	}
+	return opampbridge.NotAvailable, nil
 }
 
 func (m *mockAutoDetect) CollectorAvailability() (collector.Availability, error) {
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 93bbb7e1..7928bb54 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -14,6 +14,7 @@ import (
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/collector"
+	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/opampbridge"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac"
@@ -58,6 +59,9 @@ func TestConfigChangesOnAutoDetect(t *testing.T) {
 		CollectorAvailabilityFunc: func() (collector.Availability, error) {
 			return collector.Available, nil
 		},
+		OpAmpBridgeAvailabilityFunc: func() (opampbridge.Availability, error) {
+			return opampbridge.Available, nil
+		},
 	}
 	cfg := config.New()
 
@@ -68,6 +72,7 @@ func TestConfigChangesOnAutoDetect(t *testing.T) {
 	require.Equal(t, certmanager.NotAvailable, cfg.CertManagerAvailability)
 	require.Equal(t, targetallocator.NotAvailable, cfg.TargetAllocatorAvailability)
 	require.Equal(t, collector.NotAvailable, cfg.CollectorAvailability)
+	require.Equal(t, opampbridge.NotAvailable, cfg.OpAmpBridgeAvailability)
 
 	// test
 	require.NoError(t, autodetect.ApplyAutoDetect(mock, &cfg, logr.Discard()))
@@ -78,6 +83,7 @@ func TestConfigChangesOnAutoDetect(t *testing.T) {
 	require.Equal(t, rbac.Available, cfg.CreateRBACPermissions)
 	require.Equal(t, certmanager.Available, cfg.CertManagerAvailability)
 	require.Equal(t, targetallocator.Available, cfg.TargetAllocatorAvailability)
+	require.Equal(t, opampbridge.Available, cfg.OpAmpBridgeAvailability)
 }
 
 var _ autodetect.AutoDetect = (*mockAutoDetect)(nil)
@@ -89,6 +95,14 @@ type mockAutoDetect struct {
 	CertManagerAvailabilityFunc     func(ctx context.Context) (certmanager.Availability, error)
 	TargetAllocatorAvailabilityFunc func() (targetallocator.Availability, error)
 	CollectorAvailabilityFunc       func() (collector.Availability, error)
+	OpAmpBridgeAvailabilityFunc     func() (opampbridge.Availability, error)
+}
+
+func (m *mockAutoDetect) OpAmpBridgeAvailablity() (opampbridge.Availability, error) {
+	if m.OpAmpBridgeAvailabilityFunc != nil {
+		return m.OpAmpBridgeAvailabilityFunc()
+	}
+	return opampbridge.NotAvailable, nil
 }
 
 func (m *mockAutoDetect) FIPSEnabled(_ context.Context) bool {
diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go
index 2a1ef83d..67e60768 100644
--- a/internal/controllers/suite_test.go
+++ b/internal/controllers/suite_test.go
@@ -47,6 +47,7 @@ import (
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/collector"
+	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/opampbridge"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift"
 	"github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus"
 	autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac"
@@ -94,6 +95,7 @@ type mockAutoDetect struct {
 	CertManagerAvailabilityFunc     func(ctx context.Context) (certmanager.Availability, error)
 	TargetAllocatorAvailabilityFunc func() (targetallocator.Availability, error)
 	CollectorCRDAvailabilityFunc    func() (collector.Availability, error)
+	OpAmpBridgeAvailabilityFunc     func() (opampbridge.Availability, error)
 }
 
 func (m *mockAutoDetect) FIPSEnabled(_ context.Context) bool {
@@ -142,6 +144,13 @@ func (m *mockAutoDetect) CollectorAvailability() (collector.Availability, error)
 	return collector.Available, nil
 }
 
+func (m *mockAutoDetect) OpAmpBridgeAvailablity() (opampbridge.Availability, error) {
+	if m.OpAmpBridgeAvailabilityFunc != nil {
+		return m.OpAmpBridgeAvailabilityFunc()
+	}
+	return opampbridge.NotAvailable, nil
+}
+
 func TestMain(m *testing.M) {
 	var err error
 	ctx, cancel = context.WithCancel(context.TODO())