diff --git a/cmd/main.go b/cmd/main.go index 6a152ce16..ec286826d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -107,11 +107,11 @@ func main() { // as certificates issued by a trusted Certificate Authority (CA). The primary risk is potentially allowing // unauthorized access to sensitive metrics data. Consider replacing with CertDir, CertName, and KeyName // to provide certificates, ensuring the server communicates using trusted and secure certificates. - TLSOpts: tlsOpts, + TLSOpts: tlsOpts, FilterProvider: filters.WithAuthenticationAndAuthorization, } - // TODO: enable https for /metrics endpoint by default + // TODO: enable https for /metrics endpoint by default // if secureMetrics { // // FilterProvider is used to protect the metrics endpoint with authn/authz. // // These configurations ensure that only authorized users and service accounts @@ -164,8 +164,9 @@ func main() { } if err = (&intController.ClusterManagerReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("clustermanager-controller"), }).SetupWithManager(mgr); err != nil { fmt.Printf(" error - %v", err) setupLog.Error(err, "unable to create controller", "controller", "ClusterManager ") @@ -173,50 +174,57 @@ func main() { } fmt.Printf("%v", err) if err = (&intController.ClusterMasterReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("clustermaster-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterMaster") os.Exit(1) } if err = (&intController.IndexerClusterReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("indexercluster-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "IndexerCluster") os.Exit(1) } if err = (&intController.LicenseMasterReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("licensemaster-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "LicenseMaster") os.Exit(1) } if err = (&intController.LicenseManagerReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("licensemanager-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "LicenseManager") os.Exit(1) } if err = (&intController.MonitoringConsoleReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("monitoringconsole-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "MonitoringConsole") os.Exit(1) } if err = (&intController.SearchHeadClusterReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("searchheadcluster-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "SearchHeadCluster") os.Exit(1) } if err = (&intController.StandaloneReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("standalone-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Standalone") os.Exit(1) diff --git a/internal/controller/clustermanager_controller.go b/internal/controller/clustermanager_controller.go index f149bf129..b9d8f6eaf 100644 --- a/internal/controller/clustermanager_controller.go +++ b/internal/controller/clustermanager_controller.go @@ -22,6 +22,7 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" "github.com/splunk/splunk-operator/internal/controller/common" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" "github.com/pkg/errors" metrics "github.com/splunk/splunk-operator/pkg/splunk/client/metrics" @@ -30,6 +31,7 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -42,7 +44,8 @@ import ( // ClusterManagerReconciler reconciles a ClusterManager object type ClusterManagerReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=enterprise.splunk.com,resources=clustermanagers,verbs=get;list;watch;create;update;patch;delete @@ -103,6 +106,9 @@ func (r *ClusterManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reque reqLogger.Info("start", "CR version", instance.GetResourceVersion()) + // Pass event recorder through context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder) + result, err := ApplyClusterManager(ctx, r.Client, instance) if result.Requeue && result.RequeueAfter != 0 { reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second)) diff --git a/internal/controller/clustermaster_controller.go b/internal/controller/clustermaster_controller.go index 9f261f85b..7f0df1087 100644 --- a/internal/controller/clustermaster_controller.go +++ b/internal/controller/clustermaster_controller.go @@ -22,6 +22,7 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" "github.com/splunk/splunk-operator/internal/controller/common" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" "github.com/pkg/errors" enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" @@ -31,6 +32,7 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -43,7 +45,8 @@ import ( // ClusterMasterReconciler reconciles a ClusterMaster object type ClusterMasterReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=enterprise.splunk.com,resources=clustermasters,verbs=get;list;watch;create;update;patch;delete @@ -104,6 +107,9 @@ func (r *ClusterMasterReconciler) Reconcile(ctx context.Context, req ctrl.Reques reqLogger.Info("start", "CR version", instance.GetResourceVersion()) + // Pass event recorder through context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder) + result, err := ApplyClusterMaster(ctx, r.Client, instance) if result.Requeue && result.RequeueAfter != 0 { reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second)) diff --git a/internal/controller/indexercluster_controller.go b/internal/controller/indexercluster_controller.go index bc9a6c9f5..e36e82a6e 100644 --- a/internal/controller/indexercluster_controller.go +++ b/internal/controller/indexercluster_controller.go @@ -26,11 +26,13 @@ import ( "github.com/pkg/errors" enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" metrics "github.com/splunk/splunk-operator/pkg/splunk/client/metrics" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" enterprise "github.com/splunk/splunk-operator/pkg/splunk/enterprise" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -43,7 +45,8 @@ import ( // IndexerClusterReconciler reconciles a IndexerCluster object type IndexerClusterReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=enterprise.splunk.com,resources=indexerclusters,verbs=get;list;watch;create;update;patch;delete @@ -103,6 +106,9 @@ func (r *IndexerClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque reqLogger.Info("start", "CR version", instance.GetResourceVersion()) + // Pass event recorder through context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder) + result, err := ApplyIndexerCluster(ctx, r.Client, instance) if result.Requeue && result.RequeueAfter != 0 { reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second)) diff --git a/internal/controller/licensemanager_controller.go b/internal/controller/licensemanager_controller.go index cb749a736..195ae0cf7 100644 --- a/internal/controller/licensemanager_controller.go +++ b/internal/controller/licensemanager_controller.go @@ -22,6 +22,7 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" "github.com/splunk/splunk-operator/internal/controller/common" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" "github.com/pkg/errors" metrics "github.com/splunk/splunk-operator/pkg/splunk/client/metrics" @@ -30,6 +31,7 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -42,7 +44,8 @@ import ( // LicenseManagerReconciler reconciles a LicenseManager object type LicenseManagerReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=enterprise.splunk.com,resources=licensemanagers,verbs=get;list;watch;create;update;patch;delete @@ -102,6 +105,9 @@ func (r *LicenseManagerReconciler) Reconcile(ctx context.Context, req ctrl.Reque reqLogger.Info("start", "CR version", instance.GetResourceVersion()) + // Pass event recorder through context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder) + result, err := ApplyLicenseManager(ctx, r.Client, instance) if result.Requeue && result.RequeueAfter != 0 { reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second)) diff --git a/internal/controller/licensemaster_controller.go b/internal/controller/licensemaster_controller.go index 717632e33..044c90adf 100644 --- a/internal/controller/licensemaster_controller.go +++ b/internal/controller/licensemaster_controller.go @@ -21,6 +21,7 @@ import ( "time" "github.com/splunk/splunk-operator/internal/controller/common" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" "github.com/pkg/errors" enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" @@ -31,6 +32,7 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -43,7 +45,8 @@ import ( // LicenseMasterReconciler reconciles a LicenseMaster object type LicenseMasterReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=enterprise.splunk.com,resources=licensemasters,verbs=get;list;watch;create;update;patch;delete @@ -103,6 +106,9 @@ func (r *LicenseMasterReconciler) Reconcile(ctx context.Context, req ctrl.Reques reqLogger.Info("start", "CR version", instance.GetResourceVersion()) + // Pass event recorder through context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder) + result, err := ApplyLicenseMaster(ctx, r.Client, instance) if result.Requeue && result.RequeueAfter != 0 { reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second)) diff --git a/internal/controller/monitoringconsole_controller.go b/internal/controller/monitoringconsole_controller.go index 3767fcac2..4b6b3cf55 100644 --- a/internal/controller/monitoringconsole_controller.go +++ b/internal/controller/monitoringconsole_controller.go @@ -22,6 +22,7 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" "github.com/splunk/splunk-operator/internal/controller/common" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" "github.com/pkg/errors" enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" @@ -31,6 +32,7 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -43,7 +45,8 @@ import ( // MonitoringConsoleReconciler reconciles a MonitoringConsole object type MonitoringConsoleReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=enterprise.splunk.com,resources=monitoringconsoles,verbs=get;list;watch;create;update;patch;delete @@ -102,6 +105,9 @@ func (r *MonitoringConsoleReconciler) Reconcile(ctx context.Context, req ctrl.Re reqLogger.Info("start", "CR version", instance.GetResourceVersion()) + // Pass event recorder through context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder) + result, err := ApplyMonitoringConsole(ctx, r.Client, instance) if result.Requeue && result.RequeueAfter != 0 { reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second)) diff --git a/internal/controller/searchheadcluster_controller.go b/internal/controller/searchheadcluster_controller.go index 53d50fab9..cb14ee001 100644 --- a/internal/controller/searchheadcluster_controller.go +++ b/internal/controller/searchheadcluster_controller.go @@ -22,6 +22,7 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" "github.com/splunk/splunk-operator/internal/controller/common" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" "github.com/pkg/errors" metrics "github.com/splunk/splunk-operator/pkg/splunk/client/metrics" @@ -30,6 +31,7 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -42,7 +44,8 @@ import ( // SearchHeadClusterReconciler reconciles a SearchHeadCluster object type SearchHeadClusterReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=enterprise.splunk.com,resources=searchheadclusters,verbs=get;list;watch;create;update;patch;delete @@ -102,6 +105,9 @@ func (r *SearchHeadClusterReconciler) Reconcile(ctx context.Context, req ctrl.Re reqLogger.Info("start", "CR version", instance.GetResourceVersion()) + // Pass event recorder through context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder) + result, err := ApplySearchHeadCluster(ctx, r.Client, instance) if result.Requeue && result.RequeueAfter != 0 { reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second)) diff --git a/internal/controller/standalone_controller.go b/internal/controller/standalone_controller.go index 93e85b7f0..55d8b9430 100644 --- a/internal/controller/standalone_controller.go +++ b/internal/controller/standalone_controller.go @@ -22,9 +22,11 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" "github.com/splunk/splunk-operator/internal/controller/common" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -47,7 +49,8 @@ const ( // StandaloneReconciler reconciles a Standalone object type StandaloneReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=enterprise.splunk.com,resources=standalones,verbs=get;list;watch;create;update;patch;delete @@ -107,6 +110,9 @@ func (r *StandaloneReconciler) Reconcile(ctx context.Context, req ctrl.Request) reqLogger.Info("start", "CR version", instance.GetResourceVersion()) + // Pass event recorder through context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, r.Recorder) + result, err := ApplyStandalone(ctx, r.Client, instance) if result.Requeue && result.RequeueAfter != 0 { reqLogger.Info("Requeued", "period(seconds)", int(result.RequeueAfter/time.Second)) diff --git a/pkg/splunk/common/names.go b/pkg/splunk/common/names.go index 32e892b96..b0f7e94ca 100644 --- a/pkg/splunk/common/names.go +++ b/pkg/splunk/common/names.go @@ -117,6 +117,7 @@ const ( var AppDownloadVolume string = "/opt/splunk/appframework/" var EventPublisherKey contextKey = "eventPublisher" +var EventRecorderKey contextKey = "eventRecorder" // GetVersionedSecretName returns a versioned secret name func GetVersionedSecretName(versionedSecretIdentifier string, version string) string { diff --git a/pkg/splunk/enterprise/afwscheduler_test.go b/pkg/splunk/enterprise/afwscheduler_test.go index e3b1f336c..38668da69 100644 --- a/pkg/splunk/enterprise/afwscheduler_test.go +++ b/pkg/splunk/enterprise/afwscheduler_test.go @@ -3064,7 +3064,7 @@ func TestRunLocalScopedPlaybook(t *testing.T) { // Test3: get installed app name passes but isAppAlreadyInstalled fails with real error (not "Could not find object") mockPodExecReturnContexts[1].StdErr = "" mockPodExecReturnContexts[2].StdErr = "Some other real error message" // Real error, not "Could not find object" - mockPodExecReturnContexts[2].Err = fmt.Errorf("exit status 2") // Real error, not grep exit code 1 + mockPodExecReturnContexts[2].Err = fmt.Errorf("exit status 2") // Real error, not grep exit code 1 localInstallCtxt.sem <- struct{}{} waiter.Add(1) err = localInstallCtxt.runPlaybook(ctx) @@ -3073,10 +3073,10 @@ func TestRunLocalScopedPlaybook(t *testing.T) { } // Test4: isAppAlreadyInstalled returns app not enabled (grep exit code 1), then install fails - mockPodExecReturnContexts[2].StdOut = "" // No stdout means grep didn't find ENABLED - mockPodExecReturnContexts[2].StdErr = "" // No stderr - mockPodExecReturnContexts[2].Err = fmt.Errorf("exit status 1") // grep exit code 1 = pattern not found - mockPodExecReturnContexts[3].StdErr = "real installation error" // This is just logged now + mockPodExecReturnContexts[2].StdOut = "" // No stdout means grep didn't find ENABLED + mockPodExecReturnContexts[2].StdErr = "" // No stderr + mockPodExecReturnContexts[2].Err = fmt.Errorf("exit status 1") // grep exit code 1 = pattern not found + mockPodExecReturnContexts[3].StdErr = "real installation error" // This is just logged now mockPodExecReturnContexts[3].Err = fmt.Errorf("install command failed") // This causes the actual failure localInstallCtxt.sem <- struct{}{} @@ -3103,7 +3103,7 @@ func TestRunLocalScopedPlaybook(t *testing.T) { // Test6: Install succeeds with stderr content (should be ignored), but cleanup fails mockPodExecReturnContexts[3].StdErr = "Some informational message in stderr" // Stderr content should be ignored - mockPodExecReturnContexts[3].Err = nil // No actual error for install + mockPodExecReturnContexts[3].Err = nil // No actual error for install // Keep cleanup failure from previous test setup to make overall test fail // mockPodExecReturnContexts[4] still has error from earlier @@ -3326,10 +3326,10 @@ func TestPremiumAppScopedPlaybook(t *testing.T) { // Test4: isAppAlreadyInstalled returns app is not enabled (grep exit code 1) // so app install will run and it should fail with real error - mockPodExecReturnContexts[2].StdOut = "" // No stdout means grep didn't find ENABLED - mockPodExecReturnContexts[2].StdErr = "" // No stderr - mockPodExecReturnContexts[2].Err = fmt.Errorf("exit status 1") // grep exit code 1 = pattern not found - mockPodExecReturnContexts[3].StdErr = "real installation error" // This is just logged now + mockPodExecReturnContexts[2].StdOut = "" // No stdout means grep didn't find ENABLED + mockPodExecReturnContexts[2].StdErr = "" // No stderr + mockPodExecReturnContexts[2].Err = fmt.Errorf("exit status 1") // grep exit code 1 = pattern not found + mockPodExecReturnContexts[3].StdErr = "real installation error" // This is just logged now mockPodExecReturnContexts[3].Err = fmt.Errorf("install command failed") // This causes the actual failure localInstallCtxt.sem <- struct{}{} @@ -3341,7 +3341,7 @@ func TestPremiumAppScopedPlaybook(t *testing.T) { // Test5: Install succeeds with stderr content (should be ignored), but post install fails mockPodExecReturnContexts[3].StdErr = "Some informational message in stderr" // Stderr content should be ignored - mockPodExecReturnContexts[3].Err = nil // No actual error for install + mockPodExecReturnContexts[3].Err = nil // No actual error for install localInstallCtxt.sem <- struct{}{} waiter.Add(1) diff --git a/pkg/splunk/enterprise/clustermanager.go b/pkg/splunk/enterprise/clustermanager.go index 269753c5c..21cf44cf4 100644 --- a/pkg/splunk/enterprise/clustermanager.go +++ b/pkg/splunk/enterprise/clustermanager.go @@ -33,6 +33,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/record" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/log" @@ -49,7 +50,15 @@ func ApplyClusterManager(ctx context.Context, client splcommon.ControllerClient, } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("ApplyClusterManager") - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } + ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) cr.Kind = "ClusterManager" @@ -322,7 +331,14 @@ func getClusterManagerStatefulSet(ctx context.Context, client splcommon.Controll func CheckIfsmartstoreConfigMapUpdatedToPod(ctx context.Context, c splcommon.ControllerClient, cr *enterpriseApi.ClusterManager, podExecClient splutil.PodExecClientImpl) error { reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("CheckIfsmartstoreConfigMapUpdatedToPod").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) - eventPublisher, _ := newK8EventPublisher(c, cr) + + // Get event publisher from context + var eventPublisher *K8EventPublisher + if pub := ctx.Value(splcommon.EventPublisherKey); pub != nil { + if p, ok := pub.(*K8EventPublisher); ok { + eventPublisher = p + } + } command := fmt.Sprintf("cat /mnt/splunk-operator/local/%s", configToken) streamOptions := splutil.NewStreamOptionsObject(command) @@ -401,7 +417,14 @@ func PerformCmBundlePush(ctx context.Context, c splcommon.ControllerClient, cr * func PushManagerAppsBundle(ctx context.Context, c splcommon.ControllerClient, cr *enterpriseApi.ClusterManager) error { reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("PushManagerApps").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) - eventPublisher, _ := newK8EventPublisher(c, cr) + + // Get event publisher from context + var eventPublisher *K8EventPublisher + if pub := ctx.Value(splcommon.EventPublisherKey); pub != nil { + if p, ok := pub.(*K8EventPublisher); ok { + eventPublisher = p + } + } defaultSecretObjName := splcommon.GetNamespaceScopedSecretName(cr.GetNamespace()) defaultSecret, err := splutil.GetSecretByName(ctx, c, cr.GetNamespace(), cr.GetName(), defaultSecretObjName) @@ -477,7 +500,14 @@ var GetCMMultisiteEnvVarsCall = func(ctx context.Context, cr *enterpriseApi.Clus func changeClusterManagerAnnotations(ctx context.Context, c splcommon.ControllerClient, cr *enterpriseApi.LicenseManager) error { reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("changeClusterManagerAnnotations").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) - eventPublisher, _ := newK8EventPublisher(c, cr) + + // Get event publisher from context + var eventPublisher *K8EventPublisher + if pub := ctx.Value(splcommon.EventPublisherKey); pub != nil { + if p, ok := pub.(*K8EventPublisher); ok { + eventPublisher = p + } + } clusterManagerInstance := &enterpriseApi.ClusterManager{} if len(cr.Spec.ClusterManagerRef.Name) > 0 { diff --git a/pkg/splunk/enterprise/clustermaster.go b/pkg/splunk/enterprise/clustermaster.go index 85fcb70cc..ee21c7a3d 100644 --- a/pkg/splunk/enterprise/clustermaster.go +++ b/pkg/splunk/enterprise/clustermaster.go @@ -32,6 +32,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -47,7 +48,15 @@ func ApplyClusterMaster(ctx context.Context, client splcommon.ControllerClient, } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("ApplyClusterMaster") - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } + cr.Kind = "ClusterMaster" if cr.Status.ResourceRevMap == nil { @@ -303,7 +312,14 @@ func getClusterMasterStatefulSet(ctx context.Context, client splcommon.Controlle func CheckIfMastersmartstoreConfigMapUpdatedToPod(ctx context.Context, c splcommon.ControllerClient, cr *enterpriseApiV3.ClusterMaster, podExecClient splutil.PodExecClientImpl) error { reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("CheckIfMastersmartstoreConfigMapUpdatedToPod").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) - eventPublisher, _ := newK8EventPublisher(c, cr) + + // Get event publisher from context + var eventPublisher *K8EventPublisher + if pub := ctx.Value(splcommon.EventPublisherKey); pub != nil { + if p, ok := pub.(*K8EventPublisher); ok { + eventPublisher = p + } + } command := fmt.Sprintf("cat /mnt/splunk-operator/local/%s", configToken) streamOptions := splutil.NewStreamOptionsObject(command) @@ -382,7 +398,14 @@ func PerformCmasterBundlePush(ctx context.Context, c splcommon.ControllerClient, func PushMasterAppsBundle(ctx context.Context, c splcommon.ControllerClient, cr *enterpriseApiV3.ClusterMaster) error { reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("PushMasterApps").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) - eventPublisher, _ := newK8EventPublisher(c, cr) + + // Get event publisher from context + var eventPublisher *K8EventPublisher + if pub := ctx.Value(splcommon.EventPublisherKey); pub != nil { + if p, ok := pub.(*K8EventPublisher); ok { + eventPublisher = p + } + } defaultSecretObjName := splcommon.GetNamespaceScopedSecretName(cr.GetNamespace()) defaultSecret, err := splutil.GetSecretByName(ctx, c, cr.GetNamespace(), cr.GetName(), defaultSecretObjName) diff --git a/pkg/splunk/enterprise/controller_events_test.go b/pkg/splunk/enterprise/controller_events_test.go new file mode 100644 index 000000000..c4febfdda --- /dev/null +++ b/pkg/splunk/enterprise/controller_events_test.go @@ -0,0 +1,287 @@ +// Copyright (c) 2018-2022 Splunk Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enterprise + +import ( + "context" + "strings" + "testing" + "time" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" + spltest "github.com/splunk/splunk-operator/pkg/splunk/test" + "k8s.io/client-go/tools/record" +) + +// TestApplyFunctionsWithEventRecorder verifies Apply functions emit events correctly +func TestApplyFunctionsWithEventRecorder(t *testing.T) { + ctx := context.TODO() + recorder := record.NewFakeRecorder(100) + + // Add recorder to context (simulating controller behavior) + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, recorder) + + // Create mock client + client := spltest.NewMockClient() + + t.Run("ApplyStandalone emits events on validation failure", func(t *testing.T) { + cr := &enterpriseApi.Standalone{} + cr.Name = "test-standalone" + cr.Namespace = "default" + cr.Spec.Replicas = -1 // Invalid, should trigger validation event + + // Call Apply function + _, err := ApplyStandalone(ctx, client, cr) + + // Should have validation error + if err == nil { + t.Error("Expected validation error") + } + + // Check for validation warning event + select { + case event := <-recorder.Events: + if !strings.Contains(event, "Warning") { + t.Errorf("Expected Warning event, got: %s", event) + } + t.Logf("Received validation event: %s", event) + case <-time.After(2 * time.Second): + // Some validation errors may not emit events, that's ok + t.Log("No event received for validation (may be expected)") + } + }) +} + +// TestEventPublisherCreationFromContext verifies event publisher is created correctly from context +func TestEventPublisherCreationFromContext(t *testing.T) { + ctx := context.TODO() + recorder := record.NewFakeRecorder(10) + + // Add recorder to context + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, recorder) + + // Verify recorder can be retrieved + retrievedRecorder := ctx.Value(splcommon.EventRecorderKey) + if retrievedRecorder == nil { + t.Fatal("Failed to retrieve recorder from context") + } + + // Verify it's the correct type + if rec, ok := retrievedRecorder.(record.EventRecorder); !ok { + t.Error("Retrieved value is not an EventRecorder") + } else { + // Create event publisher + cm := &enterpriseApi.ClusterManager{} + eventPublisher, err := newK8EventPublisher(rec, cm) + if err != nil { + t.Fatalf("Failed to create event publisher: %v", err) + } + + // Emit an event + eventPublisher.Warning(ctx, "TestReason", "Test message") + + // Verify event was recorded + select { + case event := <-recorder.Events: + if !strings.Contains(event, "TestReason") { + t.Errorf("Expected event with TestReason, got: %s", event) + } + t.Logf("Event successfully emitted: %s", event) + case <-time.After(time.Second): + t.Error("No event received") + } + } +} + +// TestEventPublisherNilRecorderInContext verifies graceful handling when recorder is nil +func TestEventPublisherNilRecorderInContext(t *testing.T) { + ctx := context.TODO() + + // Don't add recorder to context (simulating test environment) + // Verify no panic occurs + + cm := &enterpriseApi.ClusterManager{} + cm.Name = "test-cm" + + // This should not panic even without recorder in context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cm) + } + } + + // eventPublisher should be nil + if eventPublisher != nil { + // Try to use it + eventPublisher.Warning(ctx, "TestReason", "Test message") + } + + t.Log("Nil event publisher handled gracefully") +} + +// TestEventEmissionInReconcileFlow simulates full reconcile flow with events +func TestEventEmissionInReconcileFlow(t *testing.T) { + ctx := context.TODO() + recorder := record.NewFakeRecorder(100) + + // Setup context with recorder + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, recorder) + + // Create test CR + cm := &enterpriseApi.ClusterManager{} + cm.Name = "production-cm" + cm.Namespace = "splunk" + + // Simulate creating event publisher (as done in Apply functions) + var eventPublisher *K8EventPublisher + if rec := ctx.Value(splcommon.EventRecorderKey); rec != nil { + if recorder, ok := rec.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(recorder, cm) + } + } + + if eventPublisher == nil { + t.Fatal("Failed to create event publisher from context") + } + + // Store in context for downstream use + ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) + + // Simulate various reconcile stages emitting events + scenarios := []struct { + reason string + message string + }{ + {"validateClusterManagerSpec", "validation started"}, + {"ApplySplunkConfig", "applying configuration"}, + {"ApplyService", "creating services"}, + } + + for _, scenario := range scenarios { + eventPublisher.Warning(ctx, scenario.reason, scenario.message) + } + + // Verify events were emitted + eventCount := 0 + timeout := time.After(3 * time.Second) + + for i := 0; i < len(scenarios); i++ { + select { + case event := <-recorder.Events: + eventCount++ + t.Logf("Received event %d: %s", eventCount, event) + case <-timeout: + if eventCount < len(scenarios) { + t.Logf("Received %d/%d events before timeout", eventCount, len(scenarios)) + } + goto done + } + } + +done: + if eventCount == 0 { + t.Error("No events received in reconcile flow") + } else { + t.Logf("Successfully received %d events in reconcile flow", eventCount) + } +} + +// TestEventDeduplication verifies EventRecorder deduplicates repeated events +func TestEventDeduplication(t *testing.T) { + recorder := record.NewFakeRecorder(100) + ctx := context.TODO() + + cm := &enterpriseApi.ClusterManager{} + cm.Name = "test-cm" + + eventPublisher, _ := newK8EventPublisher(recorder, cm) + + // Emit same event multiple times + for i := 0; i < 5; i++ { + eventPublisher.Warning(ctx, "RepeatedError", "This error repeats") + } + + // FakeRecorder doesn't actually deduplicate, but in real usage EventRecorder does + // This test documents the expected behavior + t.Log("In production, EventRecorder will deduplicate these events and increment count") + + // Drain events + receivedCount := 0 + timeout := time.After(2 * time.Second) + +drainLoop: + for { + select { + case event := <-recorder.Events: + receivedCount++ + t.Logf("Event %d: %s", receivedCount, event) + case <-timeout: + break drainLoop + default: + if receivedCount >= 5 { + break drainLoop + } + time.Sleep(50 * time.Millisecond) + } + } + + t.Logf("Received %d events (FakeRecorder doesn't deduplicate)", receivedCount) +} + +// TestMultipleCRsEmitEvents verifies multiple CRs can emit events simultaneously +func TestMultipleCRsEmitEvents(t *testing.T) { + recorder := record.NewFakeRecorder(100) + ctx := context.TODO() + + // Create multiple CR publishers + publishers := []struct { + name string + publisher *K8EventPublisher + }{ + {"cm-1", func() *K8EventPublisher { + p, _ := newK8EventPublisher(recorder, &enterpriseApi.ClusterManager{}) + return p + }()}, + {"cm-2", func() *K8EventPublisher { + p, _ := newK8EventPublisher(recorder, &enterpriseApi.ClusterManager{}) + return p + }()}, + {"standalone-1", func() *K8EventPublisher { + p, _ := newK8EventPublisher(recorder, &enterpriseApi.Standalone{}) + return p + }()}, + } + + expectedEvents := len(publishers) + + for i, pub := range publishers { + pub.publisher.Warning(ctx, "MultiCRTest", "Event from "+pub.name) + + // Verify event + select { + case event := <-recorder.Events: + if !strings.Contains(event, "MultiCRTest") { + t.Errorf("Expected MultiCRTest event, got: %s", event) + } + t.Logf("Event %d received: %s", i+1, event) + case <-time.After(time.Second): + t.Errorf("Timeout waiting for event from %s", pub.name) + } + } + + t.Logf("All %d CRs successfully emitted events", expectedEvents) +} diff --git a/pkg/splunk/enterprise/events.go b/pkg/splunk/enterprise/events.go index ebdc13d62..be44c8554 100644 --- a/pkg/splunk/enterprise/events.go +++ b/pkg/splunk/enterprise/events.go @@ -18,79 +18,53 @@ package enterprise import ( "context" - enterpriseApi "github.com/splunk/splunk-operator/api/v4" - - enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" - splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" - corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/log" ) // K8EventPublisher structure used to publish k8s event type K8EventPublisher struct { - client splcommon.ControllerClient - instance interface{} + recorder record.EventRecorder + instance runtime.Object } -// private function to get new k8s event publisher -func newK8EventPublisher(client splcommon.ControllerClient, instance interface{}) (*K8EventPublisher, error) { +// newK8EventPublisher creates a new k8s event publisher +func newK8EventPublisher(recorder record.EventRecorder, instance runtime.Object) (*K8EventPublisher, error) { eventPublisher := &K8EventPublisher{ - client: client, + recorder: recorder, instance: instance, } return eventPublisher, nil } -// publishEvents adds events to k8s -func (k *K8EventPublisher) publishEvent(ctx context.Context, eventType, reason, message string) { - - var event corev1.Event - - // in the case of testing, client is not passed - if k.client == nil { - return - } +// NewK8EventPublisherWithRecorder creates a new k8s event publisher with recorder (exported for controller use) +func NewK8EventPublisherWithRecorder(recorder record.EventRecorder, instance runtime.Object) (*K8EventPublisher, error) { + return newK8EventPublisher(recorder, instance) +} - // based on the custom resource instance type find name, type and create new event - switch v := k.instance.(type) { - case *enterpriseApi.Standalone: - event = v.NewEvent(eventType, reason, message) - case *enterpriseApiV3.LicenseMaster: - event = v.NewEvent(eventType, reason, message) - case *enterpriseApi.LicenseManager: - event = v.NewEvent(eventType, reason, message) - case *enterpriseApi.IndexerCluster: - event = v.NewEvent(eventType, reason, message) - case *enterpriseApi.ClusterManager: - event = v.NewEvent(eventType, reason, message) - case *enterpriseApiV3.ClusterMaster: - event = v.NewEvent(eventType, reason, message) - case *enterpriseApi.MonitoringConsole: - event = v.NewEvent(eventType, reason, message) - case *enterpriseApi.SearchHeadCluster: - event = v.NewEvent(eventType, reason, message) - default: +// publishEvent adds events to k8s using event recorder +func (k *K8EventPublisher) publishEvent(ctx context.Context, eventType, reason, message string) { + // in the case of testing, recorder is not passed + if k.recorder == nil { return } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("PublishEvent") - scopedLog.Info("publishing event", "reason", event.Reason, "message", event.Message) + scopedLog.Info("publishing event", "eventType", eventType, "reason", reason, "message", message) - err := k.client.Create(ctx, &event) - if err != nil { - scopedLog.Error(err, "failed to record event, ignoring", - "reason", event.Reason, "message", event.Message, "error", err) - } + // Use the EventRecorder to emit the event + k.recorder.Event(k.instance, eventType, reason, message) } // Normal publish normal events to k8s func (k *K8EventPublisher) Normal(ctx context.Context, reason, message string) { - k.publishEvent(ctx, corev1.EventTypeNormal, reason, message) + k.publishEvent(ctx, "Normal", reason, message) } // Warning publish warning events to k8s func (k *K8EventPublisher) Warning(ctx context.Context, reason, message string) { - k.publishEvent(ctx, corev1.EventTypeWarning, reason, message) + k.publishEvent(ctx, "Warning", reason, message) } diff --git a/pkg/splunk/enterprise/events_integration_test.go b/pkg/splunk/enterprise/events_integration_test.go new file mode 100644 index 000000000..fe4040b38 --- /dev/null +++ b/pkg/splunk/enterprise/events_integration_test.go @@ -0,0 +1,347 @@ +// Copyright (c) 2018-2022 Splunk Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enterprise + +import ( + "context" + "strings" + "testing" + "time" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" + "k8s.io/client-go/tools/record" +) + +// TestEventRecorderIntegration verifies that events are properly recorded +func TestEventRecorderIntegration(t *testing.T) { + tests := []struct { + name string + eventType string + reason string + message string + expectInEvent []string + }{ + { + name: "Warning event for validation failure", + eventType: "Warning", + reason: "ValidationFailed", + message: "Spec validation failed: invalid replicas", + expectInEvent: []string{"Warning", "ValidationFailed", "invalid replicas"}, + }, + { + name: "Warning event for config error", + eventType: "Warning", + reason: "ConfigError", + message: "Failed to apply configuration", + expectInEvent: []string{"Warning", "ConfigError", "Failed to apply"}, + }, + { + name: "Normal event for successful operation", + eventType: "Normal", + reason: "ConfigApplied", + message: "Configuration successfully applied", + expectInEvent: []string{"Normal", "ConfigApplied", "successfully applied"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a fake recorder with buffer + recorder := record.NewFakeRecorder(10) + + // Create a test instance + cm := &enterpriseApi.ClusterManager{} + cm.Name = "test-cm" + cm.Namespace = "default" + + // Create event publisher + eventPublisher, err := newK8EventPublisher(recorder, cm) + if err != nil { + t.Fatalf("Failed to create event publisher: %v", err) + } + + ctx := context.TODO() + + // Publish event based on type + if tt.eventType == "Warning" { + eventPublisher.Warning(ctx, tt.reason, tt.message) + } else { + eventPublisher.Normal(ctx, tt.reason, tt.message) + } + + // Verify event was recorded + select { + case event := <-recorder.Events: + t.Logf("Received event: %s", event) + for _, expected := range tt.expectInEvent { + if !strings.Contains(event, expected) { + t.Errorf("Expected event to contain '%s', got: %s", expected, event) + } + } + case <-time.After(time.Second): + t.Error("Timeout waiting for event") + } + }) + } +} + +// TestEventRecorderNilHandling verifies that nil recorder is handled gracefully +func TestEventRecorderNilHandling(t *testing.T) { + cm := &enterpriseApi.ClusterManager{} + cm.Name = "test-cm" + + // Create event publisher with nil recorder (simulating test environment) + eventPublisher, err := newK8EventPublisher(nil, cm) + if err != nil { + t.Fatalf("Failed to create event publisher with nil recorder: %v", err) + } + + ctx := context.TODO() + + // These should not panic + eventPublisher.Warning(ctx, "TestReason", "Test message") + eventPublisher.Normal(ctx, "TestReason", "Test message") + + t.Log("Nil recorder handled gracefully without panic") +} + +// TestEventRecorderMultipleEvents verifies multiple events are recorded +func TestEventRecorderMultipleEvents(t *testing.T) { + recorder := record.NewFakeRecorder(100) + + cm := &enterpriseApi.ClusterManager{} + cm.Name = "test-cm" + + eventPublisher, err := newK8EventPublisher(recorder, cm) + if err != nil { + t.Fatalf("Failed to create event publisher: %v", err) + } + + ctx := context.TODO() + + // Emit multiple events + events := []struct { + eventType string + reason string + message string + }{ + {"Warning", "ValidationFailed", "First error"}, + {"Warning", "ConfigError", "Second error"}, + {"Normal", "ConfigApplied", "Success message"}, + } + + for _, e := range events { + if e.eventType == "Warning" { + eventPublisher.Warning(ctx, e.reason, e.message) + } else { + eventPublisher.Normal(ctx, e.reason, e.message) + } + } + + // Verify all events were recorded + receivedCount := 0 + timeout := time.After(2 * time.Second) + + for i := 0; i < len(events); i++ { + select { + case event := <-recorder.Events: + receivedCount++ + t.Logf("Received event %d: %s", receivedCount, event) + case <-timeout: + t.Errorf("Only received %d events, expected %d", receivedCount, len(events)) + return + } + } + + if receivedCount != len(events) { + t.Errorf("Expected %d events, received %d", len(events), receivedCount) + } +} + +// TestEventRecorderContextPassing verifies event recorder passes through context +func TestEventRecorderContextPassing(t *testing.T) { + recorder := record.NewFakeRecorder(10) + + // Simulate controller setup + ctx := context.Background() + ctx = context.WithValue(ctx, splcommon.EventRecorderKey, recorder) + + // Verify recorder can be retrieved from context + retrievedRecorder := ctx.Value(splcommon.EventRecorderKey) + if retrievedRecorder == nil { + t.Fatal("Failed to retrieve event recorder from context") + } + + if rec, ok := retrievedRecorder.(record.EventRecorder); !ok { + t.Error("Retrieved value is not an EventRecorder") + } else { + cm := &enterpriseApi.ClusterManager{} + eventPublisher, err := newK8EventPublisher(rec, cm) + if err != nil { + t.Fatalf("Failed to create event publisher from context recorder: %v", err) + } + + // Emit event + eventPublisher.Warning(ctx, "TestReason", "Test from context") + + // Verify + select { + case event := <-recorder.Events: + if !strings.Contains(event, "TestReason") { + t.Errorf("Expected event with TestReason, got: %s", event) + } + case <-time.After(time.Second): + t.Error("No event received") + } + } +} + +// TestEventRecorderDifferentCRTypes verifies events work for all CR types +func TestEventRecorderDifferentCRTypes(t *testing.T) { + testCases := []struct { + name string + cr func() (*K8EventPublisher, *record.FakeRecorder) + }{ + { + name: "Standalone", + cr: func() (*K8EventPublisher, *record.FakeRecorder) { + recorder := record.NewFakeRecorder(10) + pub, _ := newK8EventPublisher(recorder, &enterpriseApi.Standalone{}) + return pub, recorder + }, + }, + { + name: "ClusterManager", + cr: func() (*K8EventPublisher, *record.FakeRecorder) { + recorder := record.NewFakeRecorder(10) + pub, _ := newK8EventPublisher(recorder, &enterpriseApi.ClusterManager{}) + return pub, recorder + }, + }, + { + name: "IndexerCluster", + cr: func() (*K8EventPublisher, *record.FakeRecorder) { + recorder := record.NewFakeRecorder(10) + pub, _ := newK8EventPublisher(recorder, &enterpriseApi.IndexerCluster{}) + return pub, recorder + }, + }, + { + name: "SearchHeadCluster", + cr: func() (*K8EventPublisher, *record.FakeRecorder) { + recorder := record.NewFakeRecorder(10) + pub, _ := newK8EventPublisher(recorder, &enterpriseApi.SearchHeadCluster{}) + return pub, recorder + }, + }, + { + name: "LicenseManager", + cr: func() (*K8EventPublisher, *record.FakeRecorder) { + recorder := record.NewFakeRecorder(10) + pub, _ := newK8EventPublisher(recorder, &enterpriseApi.LicenseManager{}) + return pub, recorder + }, + }, + { + name: "MonitoringConsole", + cr: func() (*K8EventPublisher, *record.FakeRecorder) { + recorder := record.NewFakeRecorder(10) + pub, _ := newK8EventPublisher(recorder, &enterpriseApi.MonitoringConsole{}) + return pub, recorder + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + eventPublisher, recorder := tt.cr() + if eventPublisher == nil { + t.Fatalf("Failed to create event publisher for %s", tt.name) + } + + ctx := context.TODO() + eventPublisher.Warning(ctx, "TestEvent", "Testing "+tt.name) + + select { + case event := <-recorder.Events: + if !strings.Contains(event, "TestEvent") { + t.Logf("Event for %s: %s", tt.name, event) + } + case <-time.After(time.Second): + t.Errorf("No event received for %s", tt.name) + } + }) + } +} + +// TestEventRecorderRealWorldScenario simulates real reconciliation scenario +func TestEventRecorderRealWorldScenario(t *testing.T) { + recorder := record.NewFakeRecorder(50) + + cm := &enterpriseApi.ClusterManager{} + cm.Name = "production-cm" + cm.Namespace = "splunk" + + // Create event publisher + eventPublisher, err := newK8EventPublisher(recorder, cm) + if err != nil { + t.Fatalf("Failed to create event publisher: %v", err) + } + + ctx := context.TODO() + ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) + + // Simulate reconciliation flow with various events + scenarios := []struct { + stage string + success bool + reason string + message string + }{ + {"Validation", false, "validateClusterManagerSpec", "validate clustermanager spec failed"}, + {"Config", false, "ApplySplunkConfig", "create or update general config failed"}, + {"Service", false, "ApplyService", "create or update service failed"}, + {"StatefulSet", false, "getStatefulSet", "failed to create statefulset"}, + } + + for _, scenario := range scenarios { + t.Logf("Testing scenario: %s", scenario.stage) + eventPublisher.Warning(ctx, scenario.reason, scenario.message) + + select { + case event := <-recorder.Events: + if !strings.Contains(event, scenario.reason) { + t.Errorf("Expected event with reason '%s', got: %s", scenario.reason, event) + } + t.Logf("✓ Event recorded for %s: %s", scenario.stage, event) + case <-time.After(time.Second): + t.Errorf("No event received for scenario: %s", scenario.stage) + } + } +} + +// BenchmarkEventEmission benchmarks event emission performance +func BenchmarkEventEmission(b *testing.B) { + recorder := record.NewFakeRecorder(10000) + cm := &enterpriseApi.ClusterManager{} + eventPublisher, _ := newK8EventPublisher(recorder, cm) + ctx := context.TODO() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + eventPublisher.Warning(ctx, "BenchmarkReason", "Benchmark message") + } +} diff --git a/pkg/splunk/enterprise/events_test.go b/pkg/splunk/enterprise/events_test.go index 18811bdd2..5e62e7c6a 100644 --- a/pkg/splunk/enterprise/events_test.go +++ b/pkg/splunk/enterprise/events_test.go @@ -19,11 +19,9 @@ import ( "context" "testing" - "github.com/pkg/errors" enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" enterpriseApi "github.com/splunk/splunk-operator/api/v4" - splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" - spltest "github.com/splunk/splunk-operator/pkg/splunk/test" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client/fake" ) @@ -35,9 +33,10 @@ func TestClusterManagerEventPublisher(t *testing.T) { builder := fake.NewClientBuilder() c := builder.Build() + recorder := record.NewFakeRecorder(10) cm := enterpriseApi.ClusterManager{} - k8sevent, err := newK8EventPublisher(c, &cm) + k8sevent, err := newK8EventPublisher(recorder, &cm) if err != nil { t.Errorf("Unexpected error while creating new event publisher %v", err) } @@ -49,60 +48,76 @@ func TestClusterManagerEventPublisher(t *testing.T) { cmaster := enterpriseApiV3.ClusterMaster{} k8sevent.instance = &cmaster k8sevent.Normal(ctx, "", "") + + // Use client to avoid unused variable warning + _ = c } func TestIndexerClusterEventPublisher(t *testing.T) { builder := fake.NewClientBuilder() c := builder.Build() + recorder := record.NewFakeRecorder(10) cm := enterpriseApi.IndexerCluster{} - k8sevent, err := newK8EventPublisher(c, &cm) + k8sevent, err := newK8EventPublisher(recorder, &cm) if err != nil { t.Errorf("Unexpected error while creating new event publisher %v", err) } k8sevent.Normal(context.TODO(), "testing", "normal message") k8sevent.Warning(context.TODO(), "testing", "warning message") + + // Use client to avoid unused variable warning + _ = c } func TestMonitoringConsoleEventPublisher(t *testing.T) { builder := fake.NewClientBuilder() c := builder.Build() + recorder := record.NewFakeRecorder(10) cm := enterpriseApi.MonitoringConsole{} - k8sevent, err := newK8EventPublisher(c, &cm) + k8sevent, err := newK8EventPublisher(recorder, &cm) if err != nil { t.Errorf("Unexpected error while creating new event publisher %v", err) } k8sevent.Normal(context.TODO(), "testing", "normal message") k8sevent.Warning(context.TODO(), "testing", "warning message") + + // Use client to avoid unused variable warning + _ = c } func TestSearchHeadClusterEventPublisher(t *testing.T) { builder := fake.NewClientBuilder() c := builder.Build() + recorder := record.NewFakeRecorder(10) cm := enterpriseApi.SearchHeadCluster{} - k8sevent, err := newK8EventPublisher(c, &cm) + k8sevent, err := newK8EventPublisher(recorder, &cm) if err != nil { t.Errorf("Unexpected error while creating new event publisher %v", err) } k8sevent.Normal(context.TODO(), "testing", "normal message") k8sevent.Warning(context.TODO(), "testing", "warning message") + + // Use client to avoid unused variable warning + _ = c } func TestStandaloneEventPublisher(t *testing.T) { builder := fake.NewClientBuilder() c := builder.Build() + recorder := record.NewFakeRecorder(10) cm := enterpriseApi.Standalone{} - k8sevent, err := newK8EventPublisher(c, &cm) + k8sevent, err := newK8EventPublisher(recorder, &cm) if err != nil { t.Errorf("Unexpected error while creating new event publisher %v", err) } @@ -112,25 +127,26 @@ func TestStandaloneEventPublisher(t *testing.T) { // Negative testing ctx := context.TODO() - k8sevent.client = nil + k8sevent.recorder = nil k8sevent.publishEvent(ctx, "", "", "") - mockClient := spltest.NewMockClient() - mockClient.InduceErrorKind[splcommon.MockClientInduceErrorCreate] = errors.New(splcommon.Rerr) - k8sevent.client = mockClient - k8sevent.publishEvent(ctx, "", "", "") + // Test with different instance type (this should work with EventRecorder) + k8sevent.recorder = recorder + k8sevent.instance = &cm + k8sevent.publishEvent(ctx, "Normal", "TestReason", "Test message") - k8sevent.instance = "randomString" - k8sevent.publishEvent(ctx, "", "", "") + // Use client to avoid unused variable warning + _ = c } func TestLicenseManagerEventPublisher(t *testing.T) { builder := fake.NewClientBuilder() c := builder.Build() + recorder := record.NewFakeRecorder(10) lmanager := enterpriseApi.LicenseManager{} - k8sevent, err := newK8EventPublisher(c, &lmanager) + k8sevent, err := newK8EventPublisher(recorder, &lmanager) if err != nil { t.Errorf("Unexpected error while creating new event publisher %v", err) } @@ -142,4 +158,7 @@ func TestLicenseManagerEventPublisher(t *testing.T) { lmaster := enterpriseApiV3.LicenseMaster{} k8sevent.instance = &lmaster k8sevent.Normal(ctx, "", "") + + // Use client to avoid unused variable warning + _ = c } diff --git a/pkg/splunk/enterprise/indexercluster.go b/pkg/splunk/enterprise/indexercluster.go index 2d135d84f..5a193dd7c 100644 --- a/pkg/splunk/enterprise/indexercluster.go +++ b/pkg/splunk/enterprise/indexercluster.go @@ -36,6 +36,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" rclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -55,7 +56,15 @@ func ApplyIndexerClusterManager(ctx context.Context, client splcommon.Controller } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("ApplyIndexerClusterManager").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } + ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) cr.Kind = "IndexerCluster" @@ -317,7 +326,15 @@ func ApplyIndexerCluster(ctx context.Context, client splcommon.ControllerClient, } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("ApplyIndexerCluster") - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } + cr.Kind = "IndexerCluster" // validate and updates defaults for CR diff --git a/pkg/splunk/enterprise/licensemanager.go b/pkg/splunk/enterprise/licensemanager.go index d603cbc9f..40844d35b 100644 --- a/pkg/splunk/enterprise/licensemanager.go +++ b/pkg/splunk/enterprise/licensemanager.go @@ -27,6 +27,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -45,7 +46,15 @@ func ApplyLicenseManager(ctx context.Context, client splcommon.ControllerClient, } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("ApplyLicenseManager") - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } + ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) cr.Kind = "LicenseManager" diff --git a/pkg/splunk/enterprise/licensemaster.go b/pkg/splunk/enterprise/licensemaster.go index 30ff75046..cd0c66222 100644 --- a/pkg/splunk/enterprise/licensemaster.go +++ b/pkg/splunk/enterprise/licensemaster.go @@ -26,6 +26,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -46,7 +47,15 @@ func ApplyLicenseMaster(ctx context.Context, client splcommon.ControllerClient, } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("ApplyLicenseMaster") - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } + ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) var err error diff --git a/pkg/splunk/enterprise/monitoringconsole.go b/pkg/splunk/enterprise/monitoringconsole.go index 64de4a2de..36eea6aa2 100644 --- a/pkg/splunk/enterprise/monitoringconsole.go +++ b/pkg/splunk/enterprise/monitoringconsole.go @@ -33,6 +33,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" rclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -49,7 +50,15 @@ func ApplyMonitoringConsole(ctx context.Context, client splcommon.ControllerClie } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("ApplyMonitoringConsole") - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } + ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) cr.Kind = "MonitoringConsole" @@ -377,7 +386,14 @@ func DeleteURLsConfigMap(revised *corev1.ConfigMap, crName string, newURLs []cor func changeMonitoringConsoleAnnotations(ctx context.Context, client splcommon.ControllerClient, cr *enterpriseApi.ClusterManager) error { reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("changeMonitoringConsoleAnnotations").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event publisher from context + var eventPublisher *K8EventPublisher + if pub := ctx.Value(splcommon.EventPublisherKey); pub != nil { + if p, ok := pub.(*K8EventPublisher); ok { + eventPublisher = p + } + } monitoringConsoleInstance := &enterpriseApi.MonitoringConsole{} if len(cr.Spec.MonitoringConsoleRef.Name) > 0 { diff --git a/pkg/splunk/enterprise/searchheadcluster.go b/pkg/splunk/enterprise/searchheadcluster.go index d5b4fd12f..df8e14126 100644 --- a/pkg/splunk/enterprise/searchheadcluster.go +++ b/pkg/splunk/enterprise/searchheadcluster.go @@ -32,6 +32,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "k8s.io/client-go/tools/remotecommand" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -47,7 +48,14 @@ func ApplySearchHeadCluster(ctx context.Context, client splcommon.ControllerClie } reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("ApplySearchHeadCluster") - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) cr.Kind = "SearchHeadCluster" diff --git a/pkg/splunk/enterprise/standalone.go b/pkg/splunk/enterprise/standalone.go index dbfa17051..fc4fa9149 100644 --- a/pkg/splunk/enterprise/standalone.go +++ b/pkg/splunk/enterprise/standalone.go @@ -29,6 +29,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -48,7 +49,15 @@ func ApplyStandalone(ctx context.Context, client splcommon.ControllerClient, cr if cr.Status.ResourceRevMap == nil { cr.Status.ResourceRevMap = make(map[string]string) } - eventPublisher, _ := newK8EventPublisher(client, cr) + + // Get event recorder from context + var eventPublisher *K8EventPublisher + if recorder := ctx.Value(splcommon.EventRecorderKey); recorder != nil { + if rec, ok := recorder.(record.EventRecorder); ok { + eventPublisher, _ = newK8EventPublisher(rec, cr) + } + } + ctx = context.WithValue(ctx, splcommon.EventPublisherKey, eventPublisher) cr.Kind = "Standalone" diff --git a/pkg/splunk/enterprise/upgrade.go b/pkg/splunk/enterprise/upgrade.go index 5d50e8cec..a688e61fe 100644 --- a/pkg/splunk/enterprise/upgrade.go +++ b/pkg/splunk/enterprise/upgrade.go @@ -36,7 +36,14 @@ var GetClusterInfoCall = func(ctx context.Context, mgr *indexerClusterPodManager func UpgradePathValidation(ctx context.Context, c splcommon.ControllerClient, cr splcommon.MetaObject, spec enterpriseApi.CommonSplunkSpec, mgr *indexerClusterPodManager) (bool, error) { reqLogger := log.FromContext(ctx) scopedLog := reqLogger.WithName("isClusterManagerReadyForUpgrade").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) - eventPublisher, _ := newK8EventPublisher(c, cr) + + // Get event publisher from context + var eventPublisher *K8EventPublisher + if pub := ctx.Value(splcommon.EventPublisherKey); pub != nil { + if p, ok := pub.(*K8EventPublisher); ok { + eventPublisher = p + } + } kind := cr.GroupVersionKind().Kind scopedLog.Info("kind is set to ", "kind", kind) // start from standalone first diff --git a/pkg/splunk/enterprise/util.go b/pkg/splunk/enterprise/util.go index 70b9b8f8a..abd96482a 100644 --- a/pkg/splunk/enterprise/util.go +++ b/pkg/splunk/enterprise/util.go @@ -2503,7 +2503,7 @@ func loadFixture(t *testing.T, filename string) string { if err != nil { t.Fatalf("Failed to load fixture %s: %v", filename, err) } - + // Compact the JSON to match the output from json.Marshal var compactJSON bytes.Buffer if err := json.Compact(&compactJSON, data); err != nil { diff --git a/test/secret/manager_secret_m4_test.go b/test/secret/manager_secret_m4_test.go index 526af6d31..fdf2d2a31 100644 --- a/test/secret/manager_secret_m4_test.go +++ b/test/secret/manager_secret_m4_test.go @@ -17,8 +17,8 @@ import ( "context" "fmt" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" enterpriseApi "github.com/splunk/splunk-operator/api/v4" diff --git a/test/secret/manager_secret_s1_test.go b/test/secret/manager_secret_s1_test.go index d51e004fd..123538317 100644 --- a/test/secret/manager_secret_s1_test.go +++ b/test/secret/manager_secret_s1_test.go @@ -19,8 +19,8 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" diff --git a/test/secret/secret_c3_test.go b/test/secret/secret_c3_test.go index 90bb9fe9c..698c84786 100644 --- a/test/secret/secret_c3_test.go +++ b/test/secret/secret_c3_test.go @@ -19,8 +19,8 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/splunk/splunk-operator/test/testenv" diff --git a/test/secret/secret_m4_test.go b/test/secret/secret_m4_test.go index f257e70ce..e40d94cfd 100644 --- a/test/secret/secret_m4_test.go +++ b/test/secret/secret_m4_test.go @@ -19,8 +19,8 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/splunk/splunk-operator/test/testenv" diff --git a/test/secret/secret_s1_test.go b/test/secret/secret_s1_test.go index 11c621815..fc7a0e47d 100644 --- a/test/secret/secret_s1_test.go +++ b/test/secret/secret_s1_test.go @@ -19,8 +19,8 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" diff --git a/test/smartstore/manager_smartstore_test.go b/test/smartstore/manager_smartstore_test.go index 45db78875..b90a68337 100644 --- a/test/smartstore/manager_smartstore_test.go +++ b/test/smartstore/manager_smartstore_test.go @@ -7,8 +7,8 @@ import ( enterpriseApi "github.com/splunk/splunk-operator/api/v4" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/splunk/splunk-operator/test/testenv" diff --git a/test/smartstore/smartstore_test.go b/test/smartstore/smartstore_test.go index f1c330a66..c2d550411 100644 --- a/test/smartstore/smartstore_test.go +++ b/test/smartstore/smartstore_test.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" enterpriseApiV3 "github.com/splunk/splunk-operator/api/v3" diff --git a/test/smoke/smoke_test.go b/test/smoke/smoke_test.go index 9c0a609e6..de4d26e88 100644 --- a/test/smoke/smoke_test.go +++ b/test/smoke/smoke_test.go @@ -17,8 +17,8 @@ import ( "context" "fmt" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/splunk/splunk-operator/test/testenv"