From dfb0713de8021901b63239d621d66041b314898b Mon Sep 17 00:00:00 2001 From: Rafal Lal Date: Thu, 2 Apr 2026 16:25:10 +0200 Subject: [PATCH 1/2] Inject app runtime supervisor address as env var Signed-off-by: Rafal Lal --- pkg/splunk/enterprise/configuration.go | 4 ++++ pkg/splunk/enterprise/names.go | 11 +++++++++++ pkg/splunk/enterprise/names_test.go | 21 +++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/pkg/splunk/enterprise/configuration.go b/pkg/splunk/enterprise/configuration.go index 7f75bebe7..e1b446ec4 100644 --- a/pkg/splunk/enterprise/configuration.go +++ b/pkg/splunk/enterprise/configuration.go @@ -951,6 +951,10 @@ func updateSplunkPodTemplateWithConfig(ctx context.Context, client splcommon.Con {Name: livenessProbeDriverPathEnv, Value: GetLivenessDriverFilePath()}, {Name: "SPLUNK_GENERAL_TERMS", Value: os.Getenv("SPLUNK_GENERAL_TERMS")}, {Name: "SPLUNK_SKIP_CLUSTER_BUNDLE_PUSH", Value: "true"}, + { + Name: "SPLUNK_APPRUNTIME_HEADLESS_SERVICE_FQDN", + Value: GetSplunkAppRuntimeServiceFQDN(cr.GetNamespace(), instanceType, cr.GetName()), + }, } // update variables for licensing, if configured diff --git a/pkg/splunk/enterprise/names.go b/pkg/splunk/enterprise/names.go index e49782f59..21b95a9b1 100644 --- a/pkg/splunk/enterprise/names.go +++ b/pkg/splunk/enterprise/names.go @@ -253,6 +253,17 @@ func GetSplunkServiceName(instanceType InstanceType, identifier string, isHeadle return result } +// GetSplunkAppRuntimeServiceName uses a template to name the headless Kubernetes Service for Splunk App Runtime instances. +func GetSplunkAppRuntimeServiceName(instanceType InstanceType, identifier string) string { + appRuntimeInstanceType := fmt.Sprintf("%s-appruntime", instanceType) + return fmt.Sprintf(serviceTemplateStr, identifier, appRuntimeInstanceType, "headless") +} + +// GetSplunkAppRuntimeServiceFQDN returns the fully qualified domain name for the headless Kubernetes service for Splunk App Runtime instances. +func GetSplunkAppRuntimeServiceFQDN(namespace string, instanceType InstanceType, identifier string) string { + return splcommon.GetServiceFQDN(namespace, GetSplunkAppRuntimeServiceName(instanceType, identifier)) +} + // GetSplunkDefaultsName uses a template to name a Kubernetes ConfigMap for a SplunkEnterprise resource. func GetSplunkDefaultsName(identifier string, instanceType InstanceType) string { return fmt.Sprintf(defaultsTemplateStr, identifier, instanceType.ToKind()) diff --git a/pkg/splunk/enterprise/names_test.go b/pkg/splunk/enterprise/names_test.go index 6449de59b..2ae708853 100644 --- a/pkg/splunk/enterprise/names_test.go +++ b/pkg/splunk/enterprise/names_test.go @@ -70,6 +70,27 @@ func TestGetSplunkServiceName(t *testing.T) { test("splunk-stack1-license-manager-service", SplunkLicenseManager, LicenseManagerRefName, false) } +func TestGetSplunkAppRuntimeServiceName(t *testing.T) { + test := func(want string, instanceType InstanceType, identifier string) { + got := GetSplunkAppRuntimeServiceName(instanceType, identifier) + if got != want { + t.Errorf("GetSplunkAppRuntimeServiceName(\"%s\",\"%s\") = %s; want %s", + instanceType.ToString(), identifier, got, want) + } + } + + test("splunk-t1-standalone-appruntime-headless", SplunkStandalone, "t1") + test("splunk-t2-cluster-manager-appruntime-headless", SplunkClusterManager, "t2") +} + +func TestGetSplunkAppRuntimeServiceFQDN(t *testing.T) { + want := "splunk-t1-search-head-appruntime-headless.splunktest.svc.cluster.local" + got := GetSplunkAppRuntimeServiceFQDN("splunktest", SplunkSearchHead, "t1") + if got != want { + t.Errorf("GetSplunkAppRuntimeServiceFQDN() = %s; want %s", got, want) + } +} + func TestGetSplunkDefaultsName(t *testing.T) { got := GetSplunkDefaultsName("t1", SplunkSearchHead) want := "splunk-t1-search-head-defaults" From 5fc2b38dcc32f65f32c626a28d1925cbd479fe6e Mon Sep 17 00:00:00 2001 From: Rafal Lal Date: Thu, 2 Apr 2026 16:25:49 +0200 Subject: [PATCH 2/2] Fixes Signed-off-by: Rafal Lal --- .../splunk-operator/templates/deployment.yaml | 4 +- .../templates/rbac/clusterrole.yaml | 28 ++++++- .../splunk-operator/templates/rbac/role.yaml | 28 ++++++- internal/controller/appruntime_controller.go | 16 ++++ .../controller/appruntime_controller_test.go | 82 +++++++++++++++++++ 5 files changed, 154 insertions(+), 4 deletions(-) diff --git a/helm-chart/splunk-operator/templates/deployment.yaml b/helm-chart/splunk-operator/templates/deployment.yaml index 81b8afc33..73bf399a5 100644 --- a/helm-chart/splunk-operator/templates/deployment.yaml +++ b/helm-chart/splunk-operator/templates/deployment.yaml @@ -78,7 +78,7 @@ spec: - name: SPLUNK_GENERAL_TERMS value: {{ .Values.splunkOperator.splunkGeneralTerms | default "" }} {{- with .Values.extraEnvs }} -{{- toYaml . | trim | nindent 10 }} +{{- toYaml . | trim | nindent 12 }} {{- end }} ports: {{- range .Values.splunkOperator.service.ports }} @@ -109,4 +109,4 @@ spec: {{- with .Values.splunkOperator.volumes }} volumes: {{- toYaml .| nindent 8 }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/helm-chart/splunk-operator/templates/rbac/clusterrole.yaml b/helm-chart/splunk-operator/templates/rbac/clusterrole.yaml index a952b174c..eeb1a93d9 100644 --- a/helm-chart/splunk-operator/templates/rbac/clusterrole.yaml +++ b/helm-chart/splunk-operator/templates/rbac/clusterrole.yaml @@ -144,6 +144,32 @@ rules: - patch - update - watch +- apiGroups: + - enterprise.splunk.com + resources: + - appruntimes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - enterprise.splunk.com + resources: + - appruntimes/status + verbs: + - get + - patch + - update +- apiGroups: + - enterprise.splunk.com + resources: + - appruntimes/finalizers + verbs: + - update - apiGroups: - enterprise.splunk.com resources: @@ -430,4 +456,4 @@ rules: - get - patch - update -{{- end }} \ No newline at end of file +{{- end }} diff --git a/helm-chart/splunk-operator/templates/rbac/role.yaml b/helm-chart/splunk-operator/templates/rbac/role.yaml index 77be54727..a123f2837 100644 --- a/helm-chart/splunk-operator/templates/rbac/role.yaml +++ b/helm-chart/splunk-operator/templates/rbac/role.yaml @@ -144,6 +144,32 @@ rules: - patch - update - watch +- apiGroups: + - enterprise.splunk.com + resources: + - appruntimes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - enterprise.splunk.com + resources: + - appruntimes/status + verbs: + - get + - patch + - update +- apiGroups: + - enterprise.splunk.com + resources: + - appruntimes/finalizers + verbs: + - update - apiGroups: - enterprise.splunk.com resources: @@ -430,4 +456,4 @@ rules: - get - patch - update -{{- end }} \ No newline at end of file +{{- end }} diff --git a/internal/controller/appruntime_controller.go b/internal/controller/appruntime_controller.go index 881fa4bd3..6b461cd85 100644 --- a/internal/controller/appruntime_controller.go +++ b/internal/controller/appruntime_controller.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -271,6 +272,14 @@ func (r *AppRuntimeReconciler) createHeadlessService(ctx context.Context, ar *en svc.Labels = getCommonLabels(ar.Name) svc.Spec.Selector = svc.Labels svc.Spec.ClusterIP = corev1.ClusterIPNone + svc.Spec.Ports = []corev1.ServicePort{ + { + Name: "appruntime", + Port: 9000, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(9000), + }, + } err = r.Create(ctx, svc) if err != nil { return nil, err @@ -316,6 +325,13 @@ func (r *AppRuntimeReconciler) createPod(ctx context.Context, appRuntime *enterp Command: []string{ "/usr/bin/splunk-eps", }, + Ports: []corev1.ContainerPort{ + { + Name: "appruntime", + ContainerPort: 9000, + Protocol: corev1.ProtocolTCP, + }, + }, VolumeMounts: []corev1.VolumeMount{ { Name: "pvc-etc", diff --git a/internal/controller/appruntime_controller_test.go b/internal/controller/appruntime_controller_test.go index b0b429f89..424b4711b 100644 --- a/internal/controller/appruntime_controller_test.go +++ b/internal/controller/appruntime_controller_test.go @@ -1 +1,83 @@ package controller + +import ( + "context" + + enterpriseApi "github.com/splunk/splunk-operator/api/v4" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var _ = Describe("AppRuntime Controller", func() { + BeforeEach(func() { + Expect(enterpriseApi.AddToScheme(scheme.Scheme)).To(Succeed()) + }) + + It("creates a headless service that exposes the appruntime port", func() { + ctx := context.Background() + ar := &enterpriseApi.AppRuntime{ + ObjectMeta: metav1.ObjectMeta{ + Name: "stack1-standalone-appruntime", + Namespace: "test", + }, + } + + reconciler := AppRuntimeReconciler{ + Client: fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(ar).Build(), + Scheme: scheme.Scheme, + } + + svc, err := reconciler.createHeadlessService(ctx, ar, types.NamespacedName{ + Name: getHeadlessName(ar.Name), + Namespace: ar.Namespace, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(svc.Spec.ClusterIP).To(Equal(corev1.ClusterIPNone)) + Expect(svc.Spec.Ports).To(HaveLen(1)) + Expect(svc.Spec.Ports[0].Name).To(Equal("appruntime")) + Expect(svc.Spec.Ports[0].Port).To(Equal(int32(9000))) + Expect(svc.Spec.Ports[0].TargetPort.IntValue()).To(Equal(9000)) + }) + + It("creates appruntime pods with container port 9000", func() { + ctx := context.Background() + ar := &enterpriseApi.AppRuntime{ + ObjectMeta: metav1.ObjectMeta{ + Name: "stack1-standalone-appruntime", + Namespace: "test", + }, + Spec: enterpriseApi.AppRuntimeSpec{ + Image: "supervisor:0.0.1", + Replicas: 1, + }, + } + + reconciler := AppRuntimeReconciler{ + Client: fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(ar).Build(), + Scheme: scheme.Scheme, + } + + err := reconciler.createPod(ctx, ar, types.NamespacedName{ + Name: getPodName(ar.Name, 0), + Namespace: ar.Namespace, + }, "splunk-stack1-standalone", 0) + Expect(err).NotTo(HaveOccurred()) + + pod := &corev1.Pod{} + err = reconciler.Get(ctx, types.NamespacedName{ + Name: getPodName(ar.Name, 0), + Namespace: ar.Namespace, + }, pod) + Expect(err).NotTo(HaveOccurred()) + Expect(pod.Spec.Containers).NotTo(BeEmpty()) + Expect(pod.Spec.Containers[0].Ports).To(HaveLen(1)) + Expect(pod.Spec.Containers[0].Ports[0].Name).To(Equal("appruntime")) + Expect(pod.Spec.Containers[0].Ports[0].ContainerPort).To(Equal(int32(9000))) + }) +})