diff --git a/deploy/crds/enterprise.splunk.com_monitoringconsoles_crd.yaml b/deploy/crds/enterprise.splunk.com_monitoringconsoles_crd.yaml index 22b990218..19f233706 100644 --- a/deploy/crds/enterprise.splunk.com_monitoringconsoles_crd.yaml +++ b/deploy/crds/enterprise.splunk.com_monitoringconsoles_crd.yaml @@ -633,6 +633,84 @@ spec: type: array type: object type: object + appRepo: + description: Splunk Enterprise App repository. Specifies remote App + location and scope for Splunk App management + properties: + appSources: + description: List of App sources on remote storage + items: + description: AppSourceSpec defines list of App package (*.spl, + *.tgz) locations on remote volumes + properties: + location: + description: Location relative to the volume path + type: string + name: + description: Logical name for the set of apps placed in + this location. Logical name must be unique to the appRepo + type: string + scope: + description: 'Scope of the App deployment: cluster, clusterWithPreConfig, + local. Scope determines whether the App(s) is/are installed + locally or cluster-wide' + type: string + volumeName: + description: Remote Storage Volume name + type: string + type: object + type: array + appsRepoPollIntervalSeconds: + description: Interval in seconds to check the Remote Storage for + App changes. The default value for this config is 1 hour(3600 + sec), minimum value is 1 minute(60sec) and maximum value is + 1 day(86400 sec). We assign the value based on following conditions + - 1. If no value or 0 is specified then it will be defaulted + to 1 hour. 2. If anything less than min is specified then + we set it to 1 min. 3. If anything more than the max value + is specified then we set it to 1 day. + format: int64 + type: integer + defaults: + description: Defines the default configuration settings for App + sources + properties: + scope: + description: 'Scope of the App deployment: cluster, clusterWithPreConfig, + local. Scope determines whether the App(s) is/are installed + locally or cluster-wide' + type: string + volumeName: + description: Remote Storage Volume name + type: string + type: object + volumes: + description: List of remote storage volumes + items: + description: VolumeSpec defines remote volume config + properties: + endpoint: + description: Remote volume URI + type: string + name: + description: Remote volume name + type: string + path: + description: Remote volume path + type: string + provider: + description: 'App Package Remote Store provider. Supported + values: aws, minio' + type: string + secretRef: + description: Secret object name + type: string + storageType: + description: 'Remote Storage type. Supported values: s3' + type: string + type: object + type: array + type: object clusterMasterRef: description: ClusterMasterRef refers to a Splunk Enterprise indexer cluster managed by the operator within Kubernetes @@ -2481,6 +2559,142 @@ spec: status: description: MonitoringConsoleStatus defines the observed state of MonitoringConsole properties: + appContext: + description: App Framework status + properties: + appRepo: + description: List of App package (*.spl, *.tgz) locations on remote + volume + properties: + appSources: + description: List of App sources on remote storage + items: + description: AppSourceSpec defines list of App package (*.spl, + *.tgz) locations on remote volumes + properties: + location: + description: Location relative to the volume path + type: string + name: + description: Logical name for the set of apps placed + in this location. Logical name must be unique to the + appRepo + type: string + scope: + description: 'Scope of the App deployment: cluster, + clusterWithPreConfig, local. Scope determines whether + the App(s) is/are installed locally or cluster-wide' + type: string + volumeName: + description: Remote Storage Volume name + type: string + type: object + type: array + appsRepoPollIntervalSeconds: + description: Interval in seconds to check the Remote Storage + for App changes. The default value for this config is 1 + hour(3600 sec), minimum value is 1 minute(60sec) and maximum + value is 1 day(86400 sec). We assign the value based on + following conditions - 1. If no value or 0 is specified + then it will be defaulted to 1 hour. 2. If anything less + than min is specified then we set it to 1 min. 3. If + anything more than the max value is specified then we set + it to 1 day. + format: int64 + type: integer + defaults: + description: Defines the default configuration settings for + App sources + properties: + scope: + description: 'Scope of the App deployment: cluster, clusterWithPreConfig, + local. Scope determines whether the App(s) is/are installed + locally or cluster-wide' + type: string + volumeName: + description: Remote Storage Volume name + type: string + type: object + volumes: + description: List of remote storage volumes + items: + description: VolumeSpec defines remote volume config + properties: + endpoint: + description: Remote volume URI + type: string + name: + description: Remote volume name + type: string + path: + description: Remote volume path + type: string + provider: + description: 'App Package Remote Store provider. Supported + values: aws, minio' + type: string + secretRef: + description: Secret object name + type: string + storageType: + description: 'Remote Storage type. Supported values: + s3' + type: string + type: object + type: array + type: object + appSrcDeployStatus: + additionalProperties: + description: AppSrcDeployInfo represents deployment info for + list of Apps + properties: + appDeploymentInfo: + items: + description: AppDeploymentInfo represents a single App + deployment information + properties: + Size: + format: int64 + type: integer + appName: + type: string + deployStatus: + description: AppDeploymentStatus represents the status + of an App on the Pod + type: integer + lastModifiedTime: + type: string + objectHash: + type: string + repoState: + description: AppRepoState represent the App state + on remote store + type: integer + type: object + type: array + type: object + description: Represents the Apps deployment status + type: object + appsRepoStatusPollIntervalSeconds: + description: Interval in seconds to check the Remote Storage for + App changes This is introduced here so that we dont do spec + validation in every reconcile just because the spec and status + are different. + format: int64 + type: integer + isDeploymentInProgress: + description: IsDeploymentInProgress indicates if the Apps deployment + is in progress + type: boolean + lastAppInfoCheckTime: + description: This is set to the time when we get the list of apps + from remote storage. + format: int64 + type: integer + version: + description: App Framework version info for future use + type: integer + type: object bundlePushInfo: description: Bundle push status tracker properties: diff --git a/pkg/apis/enterprise/v2/monitoringconsole_types.go b/pkg/apis/enterprise/v2/monitoringconsole_types.go index f5e7ede51..198b4e922 100644 --- a/pkg/apis/enterprise/v2/monitoringconsole_types.go +++ b/pkg/apis/enterprise/v2/monitoringconsole_types.go @@ -30,6 +30,9 @@ import ( // MonitoringConsoleSpec defines the desired state of MonitoringConsole type MonitoringConsoleSpec struct { CommonSplunkSpec `json:",inline"` + + // Splunk Enterprise App repository. Specifies remote App location and scope for Splunk App management + AppFrameworkConfig AppFrameworkSpec `json:"appRepo,omitempty"` } // MonitoringConsoleStatus defines the observed state of MonitoringConsole @@ -45,6 +48,9 @@ type MonitoringConsoleStatus struct { // Resource Revision tracker ResourceRevMap map[string]string `json:"resourceRevMap"` + + // App Framework status + AppContext AppDeploymentContext `json:"appContext,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/enterprise/v2/zz_generated.deepcopy.go b/pkg/apis/enterprise/v2/zz_generated.deepcopy.go index 5820572e4..d6f5dcf3c 100644 --- a/pkg/apis/enterprise/v2/zz_generated.deepcopy.go +++ b/pkg/apis/enterprise/v2/zz_generated.deepcopy.go @@ -660,6 +660,7 @@ func (in *MonitoringConsoleList) DeepCopyObject() runtime.Object { func (in *MonitoringConsoleSpec) DeepCopyInto(out *MonitoringConsoleSpec) { *out = *in in.CommonSplunkSpec.DeepCopyInto(&out.CommonSplunkSpec) + in.AppFrameworkConfig.DeepCopyInto(&out.AppFrameworkConfig) return } @@ -684,6 +685,7 @@ func (in *MonitoringConsoleStatus) DeepCopyInto(out *MonitoringConsoleStatus) { (*out)[key] = val } } + in.AppContext.DeepCopyInto(&out.AppContext) return } diff --git a/pkg/splunk/enterprise/configuration.go b/pkg/splunk/enterprise/configuration.go index ad9f1bb2b..dc1338c3b 100644 --- a/pkg/splunk/enterprise/configuration.go +++ b/pkg/splunk/enterprise/configuration.go @@ -462,8 +462,7 @@ func getSplunkStatefulSet(client splcommon.ControllerClient, cr splcommon.MetaOb func getAppListingConfigMap(client splcommon.ControllerClient, cr splcommon.MetaObject, instanceType InstanceType) *corev1.ConfigMap { var configMap *corev1.ConfigMap - // ToDo: Exclude MC, once it's own CR is available - if instanceType != SplunkIndexer && instanceType != SplunkSearchHead && instanceType != SplunkMonitoringConsole { + if instanceType != SplunkIndexer && instanceType != SplunkSearchHead { appsConfigMapName := GetSplunkAppsConfigMapName(cr.GetName(), cr.GetObjectKind().GroupVersionKind().Kind) namespacedName := types.NamespacedName{Namespace: cr.GetNamespace(), Name: appsConfigMapName} configMap, _ = splctrl.GetConfigMap(client, namespacedName) @@ -610,7 +609,7 @@ func updateSplunkPodTemplateWithConfig(client splcommon.ControllerClient, podTem // Always sort the slice, so that map entries are ordered, to avoid pod resets sort.Strings(appListingFiles) - if instanceType == SplunkDeployer || instanceType == SplunkClusterMaster || instanceType == SplunkStandalone || instanceType == SplunkLicenseMaster { + if instanceType != SplunkIndexer && instanceType != SplunkSearchHead { additionalDelayForAppInstallation = int32(maxSplunkAppsInstallationDelaySecs) } } @@ -620,12 +619,8 @@ func updateSplunkPodTemplateWithConfig(client splcommon.ControllerClient, podTem // prepare defaults variable splunkDefaults := "/mnt/splunk-secrets/default.yml" - // Check for apps defaults and add it to only the standalone or deployer/cm instances - if spec.DefaultsURLApps != "" && - (instanceType == SplunkDeployer || - instanceType == SplunkStandalone || - instanceType == SplunkClusterMaster || - instanceType == SplunkLicenseMaster) { + // Check for apps defaults and add it to only the standalone or deployer/cm/mc instances + if spec.DefaultsURLApps != "" && instanceType != SplunkIndexer && instanceType != SplunkSearchHead { splunkDefaults = fmt.Sprintf("%s,%s", spec.DefaultsURLApps, splunkDefaults) } if spec.DefaultsURL != "" { diff --git a/pkg/splunk/enterprise/monitoringconsole.go b/pkg/splunk/enterprise/monitoringconsole.go index 4d7098315..0796bc9fa 100644 --- a/pkg/splunk/enterprise/monitoringconsole.go +++ b/pkg/splunk/enterprise/monitoringconsole.go @@ -41,13 +41,13 @@ func ApplyMonitoringConsole(client splcommon.ControllerClient, cr *enterpriseApi Requeue: true, RequeueAfter: time.Second * 5, } - + scopedLog := log.WithName("ApplyMonitoringConsole").WithValues("name", cr.GetName(), "namespace", cr.GetNamespace()) if cr.Status.ResourceRevMap == nil { cr.Status.ResourceRevMap = make(map[string]string) } // validate and updates defaults for CR - err := validateMonitoringConsoleSpec(&cr.Spec) + err := validateMonitoringConsoleSpec(cr) if err != nil { return result, err } @@ -55,9 +55,22 @@ func ApplyMonitoringConsole(client splcommon.ControllerClient, cr *enterpriseApi // updates status after function completes cr.Status.Phase = splcommon.PhaseError + // If the app framework is configured then do following things - + // 1. Initialize the S3Clients based on providers + // 2. Check the status of apps on remote storage. + if len(cr.Spec.AppFrameworkConfig.AppSources) != 0 { + err := initAndCheckAppInfoStatus(client, cr, &cr.Spec.AppFrameworkConfig, &cr.Status.AppContext) + if err != nil { + return result, err + } + } + cr.Status.Selector = fmt.Sprintf("app.kubernetes.io/instance=splunk-%s-monitoring-console", cr.GetName()) defer func() { client.Status().Update(context.TODO(), cr) + if err != nil { + scopedLog.Error(err, "Status update failed") + } }() // create or update general config resources @@ -104,6 +117,16 @@ func ApplyMonitoringConsole(client splcommon.ControllerClient, cr *enterpriseApi // no need to requeue if everything is ready if cr.Status.Phase == splcommon.PhaseReady { + if cr.Status.AppContext.AppsSrcDeployStatus != nil { + markAppsStatusToComplete(client, cr, &cr.Spec.AppFrameworkConfig, cr.Status.AppContext.AppsSrcDeployStatus) + } + + // Requeue the reconcile after polling interval if we had set the lastAppInfoCheckTime. + if cr.Status.AppContext.LastAppInfoCheckTime != 0 { + result.RequeueAfter = GetNextRequeueTime(cr.Status.AppContext.AppsRepoStatusPollInterval, cr.Status.AppContext.LastAppInfoCheckTime) + } else { + result.Requeue = false + } result.Requeue = false } return result, nil @@ -136,12 +159,21 @@ func getMonitoringConsoleStatefulSet(client splcommon.ControllerClient, cr *ente return nil, err } ss.Spec.Template.ObjectMeta.Annotations[monitoringConsoleConfigRev] = monitoringConsoleConfigMap.ResourceVersion + + // Setup App framework init containers + setupAppInitContainers(client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig) return ss, nil } -// validateMonitoringConsoleSpec checks validity and makes default updates to a MonitoringConsoleSpec, and returns error if something is wrong. -func validateMonitoringConsoleSpec(spec *enterpriseApi.MonitoringConsoleSpec) error { - return validateCommonSplunkSpec(&spec.CommonSplunkSpec) +// validateMonitoringConsoleSpec checks validity and makes default updates to a MonitoringConsole, and returns error if something is wrong. +func validateMonitoringConsoleSpec(cr *enterpriseApi.MonitoringConsole) error { + if !reflect.DeepEqual(cr.Status.AppContext.AppFrameworkConfig, cr.Spec.AppFrameworkConfig) { + err := ValidateAppFrameworkSpec(&cr.Spec.AppFrameworkConfig, &cr.Status.AppContext, true) + if err != nil { + return err + } + } + return validateCommonSplunkSpec(&cr.Spec.CommonSplunkSpec) } //ApplyMonitoringConsoleEnvConfigMap creates or updates a Kubernetes ConfigMap for extra env for monitoring console pod diff --git a/pkg/splunk/enterprise/monitoringconsole_test.go b/pkg/splunk/enterprise/monitoringconsole_test.go index 7727665fd..c95e383e8 100644 --- a/pkg/splunk/enterprise/monitoringconsole_test.go +++ b/pkg/splunk/enterprise/monitoringconsole_test.go @@ -23,8 +23,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" enterpriseApi "github.com/splunk/splunk-operator/pkg/apis/enterprise/v2" + splclient "github.com/splunk/splunk-operator/pkg/splunk/client" splcommon "github.com/splunk/splunk-operator/pkg/splunk/common" spltest "github.com/splunk/splunk-operator/pkg/splunk/test" + splutil "github.com/splunk/splunk-operator/pkg/splunk/util" ) func TestApplyMonitoringConsole(t *testing.T) { @@ -35,6 +37,7 @@ func TestApplyMonitoringConsole(t *testing.T) { {MetaName: "*v1.Service-test-splunk-stack1-monitoring-console-service"}, {MetaName: "*v1.Secret-test-splunk-test-secret"}, {MetaName: "*v1.Secret-test-splunk-stack1-monitoring-console-secret-v1"}, + {MetaName: "*v1.ConfigMap-test-splunk-stack1-monitoringconsole-app-list"}, {MetaName: "*v1.ConfigMap-test-splunk-stack1-monitoring-console"}, {MetaName: "*v1.ConfigMap-test-splunk-stack1-monitoring-console"}, {MetaName: "*v1.StatefulSet-test-splunk-stack1-monitoring-console"}, @@ -50,8 +53,8 @@ func TestApplyMonitoringConsole(t *testing.T) { listmockCall := []spltest.MockFuncCall{ {ListOpts: listOpts}} - createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[2], funcCalls[3], funcCalls[5], funcCalls[6], funcCalls[8]}, "Update": {funcCalls[0], funcCalls[7]}, "List": {listmockCall[0]}} - updateCalls := map[string][]spltest.MockFuncCall{"Get": {funcCalls[0], funcCalls[1], funcCalls[2], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[6], funcCalls[7], funcCalls[8]}, "Update": {funcCalls[8]}, "List": {listmockCall[0]}} + createCalls := map[string][]spltest.MockFuncCall{"Get": funcCalls, "Create": {funcCalls[0], funcCalls[2], funcCalls[3], funcCalls[5], funcCalls[7], funcCalls[9]}, "Update": {funcCalls[0], funcCalls[7]}, "List": {listmockCall[0]}} + updateCalls := map[string][]spltest.MockFuncCall{"Get": {funcCalls[0], funcCalls[1], funcCalls[2], funcCalls[3], funcCalls[4], funcCalls[5], funcCalls[6], funcCalls[7], funcCalls[8], funcCalls[9]}, "Update": {funcCalls[9]}, "List": {listmockCall[0]}} current := enterpriseApi.MonitoringConsole{ TypeMeta: metav1.TypeMeta{ Kind: "MonitoringConsole", @@ -192,3 +195,447 @@ func TestApplyMonitoringConsoleEnvConfigMap(t *testing.T) { spltest.ReconcileTester(t, "TestApplyMonitoringConsoleEnvConfigMap", "test", "test", createCalls, updateCalls, reconcile, false, ¤t) } + +func TestGetMonitoringConsoleStatefulSet(t *testing.T) { + cr := enterpriseApi.MonitoringConsole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "stack1", + Namespace: "test", + }, + } + + c := spltest.NewMockClient() + _, err := splutil.ApplyNamespaceScopedSecretObject(c, "test") + if err != nil { + t.Errorf("Failed to create namespace scoped object") + } + + test := func(want string) { + f := func() (interface{}, error) { + if err := validateMonitoringConsoleSpec(&cr); err != nil { + t.Errorf("validateMonitoringConsoleSpec() returned error: %v", err) + } + return getMonitoringConsoleStatefulSet(c, &cr) + } + configTester(t, "getMonitoringConsoleStatefulSet()", f, want) + } + + test(`{"kind":"StatefulSet","apiVersion":"apps/v1","metadata":{"name":"splunk-stack1-monitoring-console","namespace":"test","creationTimestamp":null,"ownerReferences":[{"apiVersion":"","kind":"","name":"stack1","uid":"","controller":true}]},"spec":{"replicas":1,"selector":{"matchLabels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"},"annotations":{"monitoringConsoleConfigRev":"","traffic.sidecar.istio.io/excludeOutboundPorts":"8089,8191,9997","traffic.sidecar.istio.io/includeInboundPorts":"8000,8088"}},"spec":{"volumes":[{"name":"mnt-splunk-secrets","secret":{"secretName":"splunk-stack1-monitoring-console-secret-v1","defaultMode":420}}],"containers":[{"name":"splunk","image":"splunk/splunk","ports":[{"name":"http-splunkweb","containerPort":8000,"protocol":"TCP"},{"name":"http-hec","containerPort":8088,"protocol":"TCP"},{"name":"https-splunkd","containerPort":8089,"protocol":"TCP"},{"name":"tcp-s2s","containerPort":9997,"protocol":"TCP"}],"envFrom":[{"configMapRef":{"name":"splunk-stack1-monitoring-console"}}],"env":[{"name":"SPLUNK_HOME","value":"/opt/splunk"},{"name":"SPLUNK_START_ARGS","value":"--accept-license"},{"name":"SPLUNK_DEFAULTS_URL","value":"/mnt/splunk-secrets/default.yml"},{"name":"SPLUNK_HOME_OWNERSHIP_ENFORCEMENT","value":"false"},{"name":"SPLUNK_ROLE","value":"splunk_monitor"},{"name":"SPLUNK_DECLARATIVE_ADMIN_PASSWORD","value":"true"}],"resources":{"limits":{"cpu":"4","memory":"8Gi"},"requests":{"cpu":"100m","memory":"512Mi"}},"volumeMounts":[{"name":"pvc-etc","mountPath":"/opt/splunk/etc"},{"name":"pvc-var","mountPath":"/opt/splunk/var"},{"name":"mnt-splunk-secrets","mountPath":"/mnt/splunk-secrets"}],"livenessProbe":{"exec":{"command":["/sbin/checkstate.sh"]},"initialDelaySeconds":300,"timeoutSeconds":30,"periodSeconds":30},"readinessProbe":{"exec":{"command":["/bin/grep","started","/opt/container_artifact/splunk-container.state"]},"initialDelaySeconds":10,"timeoutSeconds":5,"periodSeconds":5},"imagePullPolicy":"IfNotPresent"}],"securityContext":{"runAsUser":41812,"fsGroup":41812},"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"weight":100,"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/instance","operator":"In","values":["splunk-stack1-monitoring-console"]}]},"topologyKey":"kubernetes.io/hostname"}}]}},"schedulerName":"default-scheduler"}},"volumeClaimTemplates":[{"metadata":{"name":"pvc-etc","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}}},"status":{}},{"metadata":{"name":"pvc-var","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"100Gi"}}},"status":{}}],"serviceName":"splunk-stack1-monitoring-console-headless","podManagementPolicy":"Parallel","updateStrategy":{"type":"OnDelete"}},"status":{"replicas":0}}`) + + cr.Spec.EtcVolumeStorageConfig.EphemeralStorage = true + cr.Spec.VarVolumeStorageConfig.EphemeralStorage = true + test(`{"kind":"StatefulSet","apiVersion":"apps/v1","metadata":{"name":"splunk-stack1-monitoring-console","namespace":"test","creationTimestamp":null,"ownerReferences":[{"apiVersion":"","kind":"","name":"stack1","uid":"","controller":true}]},"spec":{"replicas":1,"selector":{"matchLabels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"},"annotations":{"monitoringConsoleConfigRev":"","traffic.sidecar.istio.io/excludeOutboundPorts":"8089,8191,9997","traffic.sidecar.istio.io/includeInboundPorts":"8000,8088"}},"spec":{"volumes":[{"name":"mnt-splunk-etc","emptyDir":{}},{"name":"mnt-splunk-var","emptyDir":{}},{"name":"mnt-splunk-secrets","secret":{"secretName":"splunk-stack1-monitoring-console-secret-v1","defaultMode":420}}],"containers":[{"name":"splunk","image":"splunk/splunk","ports":[{"name":"http-splunkweb","containerPort":8000,"protocol":"TCP"},{"name":"http-hec","containerPort":8088,"protocol":"TCP"},{"name":"https-splunkd","containerPort":8089,"protocol":"TCP"},{"name":"tcp-s2s","containerPort":9997,"protocol":"TCP"}],"envFrom":[{"configMapRef":{"name":"splunk-stack1-monitoring-console"}}],"env":[{"name":"SPLUNK_HOME","value":"/opt/splunk"},{"name":"SPLUNK_START_ARGS","value":"--accept-license"},{"name":"SPLUNK_DEFAULTS_URL","value":"/mnt/splunk-secrets/default.yml"},{"name":"SPLUNK_HOME_OWNERSHIP_ENFORCEMENT","value":"false"},{"name":"SPLUNK_ROLE","value":"splunk_monitor"},{"name":"SPLUNK_DECLARATIVE_ADMIN_PASSWORD","value":"true"}],"resources":{"limits":{"cpu":"4","memory":"8Gi"},"requests":{"cpu":"100m","memory":"512Mi"}},"volumeMounts":[{"name":"mnt-splunk-etc","mountPath":"/opt/splunk/etc"},{"name":"mnt-splunk-var","mountPath":"/opt/splunk/var"},{"name":"mnt-splunk-secrets","mountPath":"/mnt/splunk-secrets"}],"livenessProbe":{"exec":{"command":["/sbin/checkstate.sh"]},"initialDelaySeconds":300,"timeoutSeconds":30,"periodSeconds":30},"readinessProbe":{"exec":{"command":["/bin/grep","started","/opt/container_artifact/splunk-container.state"]},"initialDelaySeconds":10,"timeoutSeconds":5,"periodSeconds":5},"imagePullPolicy":"IfNotPresent"}],"securityContext":{"runAsUser":41812,"fsGroup":41812},"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"weight":100,"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/instance","operator":"In","values":["splunk-stack1-monitoring-console"]}]},"topologyKey":"kubernetes.io/hostname"}}]}},"schedulerName":"default-scheduler"}},"serviceName":"splunk-stack1-monitoring-console-headless","podManagementPolicy":"Parallel","updateStrategy":{"type":"OnDelete"}},"status":{"replicas":0}}`) + + cr.Spec.EtcVolumeStorageConfig.EphemeralStorage = false + cr.Spec.VarVolumeStorageConfig.EphemeralStorage = false + + cr.Spec.ClusterMasterRef.Name = "stack2" + cr.Spec.EtcVolumeStorageConfig.StorageClassName = "gp2" + cr.Spec.VarVolumeStorageConfig.StorageClassName = "gp2" + cr.Spec.SchedulerName = "custom-scheduler" + cr.Spec.Defaults = "defaults-string" + cr.Spec.DefaultsURL = "/mnt/defaults/defaults.yml" + cr.Spec.Volumes = []corev1.Volume{ + {Name: "defaults"}, + } + test(`{"kind":"StatefulSet","apiVersion":"apps/v1","metadata":{"name":"splunk-stack1-monitoring-console","namespace":"test","creationTimestamp":null,"ownerReferences":[{"apiVersion":"","kind":"","name":"stack1","uid":"","controller":true}]},"spec":{"replicas":1,"selector":{"matchLabels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"},"annotations":{"monitoringConsoleConfigRev":"","traffic.sidecar.istio.io/excludeOutboundPorts":"8089,8191,9997","traffic.sidecar.istio.io/includeInboundPorts":"8000,8088"}},"spec":{"volumes":[{"name":"defaults"},{"name":"mnt-splunk-secrets","secret":{"secretName":"splunk-stack1-monitoring-console-secret-v1","defaultMode":420}},{"name":"mnt-splunk-defaults","configMap":{"name":"splunk-stack1-monitoring-console-defaults","defaultMode":420}}],"containers":[{"name":"splunk","image":"splunk/splunk","ports":[{"name":"http-splunkweb","containerPort":8000,"protocol":"TCP"},{"name":"http-hec","containerPort":8088,"protocol":"TCP"},{"name":"https-splunkd","containerPort":8089,"protocol":"TCP"},{"name":"tcp-s2s","containerPort":9997,"protocol":"TCP"}],"envFrom":[{"configMapRef":{"name":"splunk-stack1-monitoring-console"}}],"env":[{"name":"SPLUNK_HOME","value":"/opt/splunk"},{"name":"SPLUNK_START_ARGS","value":"--accept-license"},{"name":"SPLUNK_DEFAULTS_URL","value":"/mnt/splunk-defaults/default.yml,/mnt/defaults/defaults.yml,/mnt/splunk-secrets/default.yml"},{"name":"SPLUNK_HOME_OWNERSHIP_ENFORCEMENT","value":"false"},{"name":"SPLUNK_ROLE","value":"splunk_monitor"},{"name":"SPLUNK_DECLARATIVE_ADMIN_PASSWORD","value":"true"},{"name":"SPLUNK_CLUSTER_MASTER_URL","value":"splunk-stack2-cluster-master-service"}],"resources":{"limits":{"cpu":"4","memory":"8Gi"},"requests":{"cpu":"100m","memory":"512Mi"}},"volumeMounts":[{"name":"pvc-etc","mountPath":"/opt/splunk/etc"},{"name":"pvc-var","mountPath":"/opt/splunk/var"},{"name":"defaults","mountPath":"/mnt/defaults"},{"name":"mnt-splunk-secrets","mountPath":"/mnt/splunk-secrets"},{"name":"mnt-splunk-defaults","mountPath":"/mnt/splunk-defaults"}],"livenessProbe":{"exec":{"command":["/sbin/checkstate.sh"]},"initialDelaySeconds":300,"timeoutSeconds":30,"periodSeconds":30},"readinessProbe":{"exec":{"command":["/bin/grep","started","/opt/container_artifact/splunk-container.state"]},"initialDelaySeconds":10,"timeoutSeconds":5,"periodSeconds":5},"imagePullPolicy":"IfNotPresent"}],"securityContext":{"runAsUser":41812,"fsGroup":41812},"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"weight":100,"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/instance","operator":"In","values":["splunk-stack1-monitoring-console"]}]},"topologyKey":"kubernetes.io/hostname"}}]}},"schedulerName":"custom-scheduler"}},"volumeClaimTemplates":[{"metadata":{"name":"pvc-etc","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"gp2"},"status":{}},{"metadata":{"name":"pvc-var","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"100Gi"}},"storageClassName":"gp2"},"status":{}}],"serviceName":"splunk-stack1-monitoring-console-headless","podManagementPolicy":"Parallel","updateStrategy":{"type":"OnDelete"}},"status":{"replicas":0}}`) + + cr.Spec.DefaultsURLApps = "/mnt/apps/apps.yml" + test(`{"kind":"StatefulSet","apiVersion":"apps/v1","metadata":{"name":"splunk-stack1-monitoring-console","namespace":"test","creationTimestamp":null,"ownerReferences":[{"apiVersion":"","kind":"","name":"stack1","uid":"","controller":true}]},"spec":{"replicas":1,"selector":{"matchLabels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"},"annotations":{"monitoringConsoleConfigRev":"","traffic.sidecar.istio.io/excludeOutboundPorts":"8089,8191,9997","traffic.sidecar.istio.io/includeInboundPorts":"8000,8088"}},"spec":{"volumes":[{"name":"defaults"},{"name":"mnt-splunk-secrets","secret":{"secretName":"splunk-stack1-monitoring-console-secret-v1","defaultMode":420}},{"name":"mnt-splunk-defaults","configMap":{"name":"splunk-stack1-monitoring-console-defaults","defaultMode":420}}],"containers":[{"name":"splunk","image":"splunk/splunk","ports":[{"name":"http-splunkweb","containerPort":8000,"protocol":"TCP"},{"name":"http-hec","containerPort":8088,"protocol":"TCP"},{"name":"https-splunkd","containerPort":8089,"protocol":"TCP"},{"name":"tcp-s2s","containerPort":9997,"protocol":"TCP"}],"envFrom":[{"configMapRef":{"name":"splunk-stack1-monitoring-console"}}],"env":[{"name":"SPLUNK_HOME","value":"/opt/splunk"},{"name":"SPLUNK_START_ARGS","value":"--accept-license"},{"name":"SPLUNK_DEFAULTS_URL","value":"/mnt/splunk-defaults/default.yml,/mnt/defaults/defaults.yml,/mnt/apps/apps.yml,/mnt/splunk-secrets/default.yml"},{"name":"SPLUNK_HOME_OWNERSHIP_ENFORCEMENT","value":"false"},{"name":"SPLUNK_ROLE","value":"splunk_monitor"},{"name":"SPLUNK_DECLARATIVE_ADMIN_PASSWORD","value":"true"},{"name":"SPLUNK_CLUSTER_MASTER_URL","value":"splunk-stack2-cluster-master-service"}],"resources":{"limits":{"cpu":"4","memory":"8Gi"},"requests":{"cpu":"100m","memory":"512Mi"}},"volumeMounts":[{"name":"pvc-etc","mountPath":"/opt/splunk/etc"},{"name":"pvc-var","mountPath":"/opt/splunk/var"},{"name":"defaults","mountPath":"/mnt/defaults"},{"name":"mnt-splunk-secrets","mountPath":"/mnt/splunk-secrets"},{"name":"mnt-splunk-defaults","mountPath":"/mnt/splunk-defaults"}],"livenessProbe":{"exec":{"command":["/sbin/checkstate.sh"]},"initialDelaySeconds":300,"timeoutSeconds":30,"periodSeconds":30},"readinessProbe":{"exec":{"command":["/bin/grep","started","/opt/container_artifact/splunk-container.state"]},"initialDelaySeconds":10,"timeoutSeconds":5,"periodSeconds":5},"imagePullPolicy":"IfNotPresent"}],"securityContext":{"runAsUser":41812,"fsGroup":41812},"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"weight":100,"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/instance","operator":"In","values":["splunk-stack1-monitoring-console"]}]},"topologyKey":"kubernetes.io/hostname"}}]}},"schedulerName":"custom-scheduler"}},"volumeClaimTemplates":[{"metadata":{"name":"pvc-etc","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"gp2"},"status":{}},{"metadata":{"name":"pvc-var","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"100Gi"}},"storageClassName":"gp2"},"status":{}}],"serviceName":"splunk-stack1-monitoring-console-headless","podManagementPolicy":"Parallel","updateStrategy":{"type":"OnDelete"}},"status":{"replicas":0}}`) + + // Create a serviceaccount + current := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "defaults", + Namespace: "test", + }, + } + _ = splutil.CreateResource(c, ¤t) + cr.Spec.ServiceAccount = "defaults" + test(`{"kind":"StatefulSet","apiVersion":"apps/v1","metadata":{"name":"splunk-stack1-monitoring-console","namespace":"test","creationTimestamp":null,"ownerReferences":[{"apiVersion":"","kind":"","name":"stack1","uid":"","controller":true}]},"spec":{"replicas":1,"selector":{"matchLabels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"},"annotations":{"monitoringConsoleConfigRev":"","traffic.sidecar.istio.io/excludeOutboundPorts":"8089,8191,9997","traffic.sidecar.istio.io/includeInboundPorts":"8000,8088"}},"spec":{"volumes":[{"name":"defaults"},{"name":"mnt-splunk-secrets","secret":{"secretName":"splunk-stack1-monitoring-console-secret-v1","defaultMode":420}},{"name":"mnt-splunk-defaults","configMap":{"name":"splunk-stack1-monitoring-console-defaults","defaultMode":420}}],"containers":[{"name":"splunk","image":"splunk/splunk","ports":[{"name":"http-splunkweb","containerPort":8000,"protocol":"TCP"},{"name":"http-hec","containerPort":8088,"protocol":"TCP"},{"name":"https-splunkd","containerPort":8089,"protocol":"TCP"},{"name":"tcp-s2s","containerPort":9997,"protocol":"TCP"}],"envFrom":[{"configMapRef":{"name":"splunk-stack1-monitoring-console"}}],"env":[{"name":"SPLUNK_HOME","value":"/opt/splunk"},{"name":"SPLUNK_START_ARGS","value":"--accept-license"},{"name":"SPLUNK_DEFAULTS_URL","value":"/mnt/splunk-defaults/default.yml,/mnt/defaults/defaults.yml,/mnt/apps/apps.yml,/mnt/splunk-secrets/default.yml"},{"name":"SPLUNK_HOME_OWNERSHIP_ENFORCEMENT","value":"false"},{"name":"SPLUNK_ROLE","value":"splunk_monitor"},{"name":"SPLUNK_DECLARATIVE_ADMIN_PASSWORD","value":"true"},{"name":"SPLUNK_CLUSTER_MASTER_URL","value":"splunk-stack2-cluster-master-service"}],"resources":{"limits":{"cpu":"4","memory":"8Gi"},"requests":{"cpu":"100m","memory":"512Mi"}},"volumeMounts":[{"name":"pvc-etc","mountPath":"/opt/splunk/etc"},{"name":"pvc-var","mountPath":"/opt/splunk/var"},{"name":"defaults","mountPath":"/mnt/defaults"},{"name":"mnt-splunk-secrets","mountPath":"/mnt/splunk-secrets"},{"name":"mnt-splunk-defaults","mountPath":"/mnt/splunk-defaults"}],"livenessProbe":{"exec":{"command":["/sbin/checkstate.sh"]},"initialDelaySeconds":300,"timeoutSeconds":30,"periodSeconds":30},"readinessProbe":{"exec":{"command":["/bin/grep","started","/opt/container_artifact/splunk-container.state"]},"initialDelaySeconds":10,"timeoutSeconds":5,"periodSeconds":5},"imagePullPolicy":"IfNotPresent"}],"serviceAccountName":"defaults","securityContext":{"runAsUser":41812,"fsGroup":41812},"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"weight":100,"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/instance","operator":"In","values":["splunk-stack1-monitoring-console"]}]},"topologyKey":"kubernetes.io/hostname"}}]}},"schedulerName":"custom-scheduler"}},"volumeClaimTemplates":[{"metadata":{"name":"pvc-etc","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"gp2"},"status":{}},{"metadata":{"name":"pvc-var","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"100Gi"}},"storageClassName":"gp2"},"status":{}}],"serviceName":"splunk-stack1-monitoring-console-headless","podManagementPolicy":"Parallel","updateStrategy":{"type":"OnDelete"}},"status":{"replicas":0}}`) + + // Add extraEnv + cr.Spec.CommonSplunkSpec.ExtraEnv = []corev1.EnvVar{ + { + Name: "TEST_ENV_VAR", + Value: "test_value", + }, + } + test(`{"kind":"StatefulSet","apiVersion":"apps/v1","metadata":{"name":"splunk-stack1-monitoring-console","namespace":"test","creationTimestamp":null,"ownerReferences":[{"apiVersion":"","kind":"","name":"stack1","uid":"","controller":true}]},"spec":{"replicas":1,"selector":{"matchLabels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"},"annotations":{"monitoringConsoleConfigRev":"","traffic.sidecar.istio.io/excludeOutboundPorts":"8089,8191,9997","traffic.sidecar.istio.io/includeInboundPorts":"8000,8088"}},"spec":{"volumes":[{"name":"defaults"},{"name":"mnt-splunk-secrets","secret":{"secretName":"splunk-stack1-monitoring-console-secret-v1","defaultMode":420}},{"name":"mnt-splunk-defaults","configMap":{"name":"splunk-stack1-monitoring-console-defaults","defaultMode":420}}],"containers":[{"name":"splunk","image":"splunk/splunk","ports":[{"name":"http-splunkweb","containerPort":8000,"protocol":"TCP"},{"name":"http-hec","containerPort":8088,"protocol":"TCP"},{"name":"https-splunkd","containerPort":8089,"protocol":"TCP"},{"name":"tcp-s2s","containerPort":9997,"protocol":"TCP"}],"envFrom":[{"configMapRef":{"name":"splunk-stack1-monitoring-console"}}],"env":[{"name":"SPLUNK_HOME","value":"/opt/splunk"},{"name":"SPLUNK_START_ARGS","value":"--accept-license"},{"name":"SPLUNK_DEFAULTS_URL","value":"/mnt/splunk-defaults/default.yml,/mnt/defaults/defaults.yml,/mnt/apps/apps.yml,/mnt/splunk-secrets/default.yml"},{"name":"SPLUNK_HOME_OWNERSHIP_ENFORCEMENT","value":"false"},{"name":"SPLUNK_ROLE","value":"splunk_monitor"},{"name":"SPLUNK_DECLARATIVE_ADMIN_PASSWORD","value":"true"},{"name":"SPLUNK_CLUSTER_MASTER_URL","value":"splunk-stack2-cluster-master-service"},{"name":"TEST_ENV_VAR","value":"test_value"}],"resources":{"limits":{"cpu":"4","memory":"8Gi"},"requests":{"cpu":"100m","memory":"512Mi"}},"volumeMounts":[{"name":"pvc-etc","mountPath":"/opt/splunk/etc"},{"name":"pvc-var","mountPath":"/opt/splunk/var"},{"name":"defaults","mountPath":"/mnt/defaults"},{"name":"mnt-splunk-secrets","mountPath":"/mnt/splunk-secrets"},{"name":"mnt-splunk-defaults","mountPath":"/mnt/splunk-defaults"}],"livenessProbe":{"exec":{"command":["/sbin/checkstate.sh"]},"initialDelaySeconds":300,"timeoutSeconds":30,"periodSeconds":30},"readinessProbe":{"exec":{"command":["/bin/grep","started","/opt/container_artifact/splunk-container.state"]},"initialDelaySeconds":10,"timeoutSeconds":5,"periodSeconds":5},"imagePullPolicy":"IfNotPresent"}],"serviceAccountName":"defaults","securityContext":{"runAsUser":41812,"fsGroup":41812},"affinity":{"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"weight":100,"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/instance","operator":"In","values":["splunk-stack1-monitoring-console"]}]},"topologyKey":"kubernetes.io/hostname"}}]}},"schedulerName":"custom-scheduler"}},"volumeClaimTemplates":[{"metadata":{"name":"pvc-etc","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"gp2"},"status":{}},{"metadata":{"name":"pvc-var","namespace":"test","creationTimestamp":null,"labels":{"app.kubernetes.io/component":"monitoring-console","app.kubernetes.io/instance":"splunk-stack1-monitoring-console","app.kubernetes.io/managed-by":"splunk-operator","app.kubernetes.io/name":"monitoring-console","app.kubernetes.io/part-of":"splunk-stack1-monitoring-console"}},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"100Gi"}},"storageClassName":"gp2"},"status":{}}],"serviceName":"splunk-stack1-monitoring-console-headless","podManagementPolicy":"Parallel","updateStrategy":{"type":"OnDelete"}},"status":{"replicas":0}}`) + +} +func TestAppFrameworkApplyMonitoringConsoleShouldNotFail(t *testing.T) { + cr := enterpriseApi.MonitoringConsole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "monitoringConsole", + Namespace: "test", + }, + Spec: enterpriseApi.MonitoringConsoleSpec{ + AppFrameworkConfig: enterpriseApi.AppFrameworkSpec{ + VolList: []enterpriseApi.VolumeSpec{ + {Name: "msos_s2s3_vol", Endpoint: "https://s3-eu-west-2.amazonaws.com", Path: "testbucket-rs-london", SecretRef: "s3-secret", Type: "s3", Provider: "aws"}, + }, + AppSources: []enterpriseApi.AppSourceSpec{ + {Name: "adminApps", + Location: "adminAppsRepo", + AppSourceDefaultSpec: enterpriseApi.AppSourceDefaultSpec{ + VolName: "msos_s2s3_vol", + Scope: enterpriseApi.ScopeLocal}, + }, + {Name: "securityApps", + Location: "securityAppsRepo", + AppSourceDefaultSpec: enterpriseApi.AppSourceDefaultSpec{ + VolName: "msos_s2s3_vol", + Scope: enterpriseApi.ScopeLocal}, + }, + {Name: "authenticationApps", + Location: "authenticationAppsRepo", + AppSourceDefaultSpec: enterpriseApi.AppSourceDefaultSpec{ + VolName: "msos_s2s3_vol", + Scope: enterpriseApi.ScopeLocal}, + }, + }, + }, + }, + } + + client := spltest.NewMockClient() + + // Create namespace scoped secret + _, err := splutil.ApplyNamespaceScopedSecretObject(client, "test") + if err != nil { + t.Errorf(err.Error()) + } + + // Create S3 secret + s3Secret := spltest.GetMockS3SecretKeys("s3-secret") + + client.AddObject(&s3Secret) + + _, err = ApplyMonitoringConsole(client, &cr) + if err != nil { + t.Errorf("ApplyMonitoringConsole should be successful") + } +} + +func TestMonitoringConsoleGetAppsListForAWSS3ClientShouldNotFail(t *testing.T) { + cr := enterpriseApi.MonitoringConsole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "monitoringConsole", + Namespace: "test", + }, + Spec: enterpriseApi.MonitoringConsoleSpec{ + AppFrameworkConfig: enterpriseApi.AppFrameworkSpec{ + Defaults: enterpriseApi.AppSourceDefaultSpec{ + VolName: "msos_s2s3_vol2", + Scope: enterpriseApi.ScopeLocal, + }, + VolList: []enterpriseApi.VolumeSpec{ + { + Name: "msos_s2s3_vol", + Endpoint: "https://s3-eu-west-2.amazonaws.com", + Path: "testbucket-rs-london", + SecretRef: "s3-secret", + Type: "s3", + Provider: "aws", + }, + { + Name: "msos_s2s3_vol2", + Endpoint: "https://s3-eu-west-2.amazonaws.com", + Path: "testbucket-rs-london2", + SecretRef: "s3-secret", + Type: "s3", + Provider: "aws", + }, + }, + AppSources: []enterpriseApi.AppSourceSpec{ + {Name: "adminApps", + Location: "adminAppsRepo", + AppSourceDefaultSpec: enterpriseApi.AppSourceDefaultSpec{ + VolName: "msos_s2s3_vol", + Scope: enterpriseApi.ScopeLocal}, + }, + {Name: "securityApps", + Location: "securityAppsRepo", + AppSourceDefaultSpec: enterpriseApi.AppSourceDefaultSpec{ + VolName: "msos_s2s3_vol", + Scope: enterpriseApi.ScopeLocal}, + }, + {Name: "authenticationApps", + Location: "authenticationAppsRepo", + }, + }, + }, + }, + } + + client := spltest.NewMockClient() + + // Create S3 secret + s3Secret := spltest.GetMockS3SecretKeys("s3-secret") + + client.AddObject(&s3Secret) + + // Create namespace scoped secret + _, err := splutil.ApplyNamespaceScopedSecretObject(client, "test") + if err != nil { + t.Errorf(err.Error()) + } + + splclient.RegisterS3Client("aws") + + Etags := []string{"cc707187b036405f095a8ebb43a782c1", "5055a61b3d1b667a4c3279a381a2e7ae", "19779168370b97d8654424e6c9446dd9"} + Keys := []string{"admin_app.tgz", "security_app.tgz", "authentication_app.tgz"} + Sizes := []int64{10, 20, 30} + StorageClass := "STANDARD" + randomTime := time.Date(2021, time.May, 1, 23, 23, 0, 0, time.UTC) + + mockAwsHandler := spltest.MockAWSS3Handler{} + + mockAwsObjects := []spltest.MockAWSS3Client{ + { + Objects: []*spltest.MockAWSS3Object{ + { + Etag: &Etags[0], + Key: &Keys[0], + LastModified: &randomTime, + Size: &Sizes[0], + StorageClass: &StorageClass, + }, + }, + }, + { + Objects: []*spltest.MockAWSS3Object{ + { + Etag: &Etags[1], + Key: &Keys[1], + LastModified: &randomTime, + Size: &Sizes[1], + StorageClass: &StorageClass, + }, + }, + }, + { + Objects: []*spltest.MockAWSS3Object{ + { + Etag: &Etags[2], + Key: &Keys[2], + LastModified: &randomTime, + Size: &Sizes[2], + StorageClass: &StorageClass, + }, + }, + }, + } + + appFrameworkRef := cr.Spec.AppFrameworkConfig + + mockAwsHandler.AddObjects(appFrameworkRef, mockAwsObjects...) + + var vol enterpriseApi.VolumeSpec + var allSuccess bool = true + for index, appSource := range appFrameworkRef.AppSources { + + vol, err = splclient.GetAppSrcVolume(appSource, &appFrameworkRef) + if err != nil { + allSuccess = false + continue + } + + // Update the GetS3Client with our mock call which initializes mock AWS client + getClientWrapper := splclient.S3Clients[vol.Provider] + getClientWrapper.SetS3ClientFuncPtr(vol.Provider, splclient.NewMockAWSS3Client) + + s3ClientMgr := &S3ClientManager{client: client, + cr: &cr, appFrameworkRef: &cr.Spec.AppFrameworkConfig, + vol: &vol, + location: appSource.Location, + initFn: func(region, accessKeyID, secretAccessKey string) interface{} { + cl := spltest.MockAWSS3Client{} + cl.Objects = mockAwsObjects[index].Objects + return cl + }, + getS3Client: func(client splcommon.ControllerClient, cr splcommon.MetaObject, appFrameworkRef *enterpriseApi.AppFrameworkSpec, vol *enterpriseApi.VolumeSpec, location string, fn splclient.GetInitFunc) (splclient.SplunkS3Client, error) { + c, err := GetRemoteStorageClient(client, cr, appFrameworkRef, vol, location, fn) + return c, err + }, + } + + s3Response, err := s3ClientMgr.GetAppsList() + if err != nil { + allSuccess = false + continue + } + + var mockResponse spltest.MockAWSS3Client + mockResponse, err = splclient.ConvertS3Response(s3Response) + if err != nil { + allSuccess = false + continue + } + + if mockAwsHandler.GotSourceAppListResponseMap == nil { + mockAwsHandler.GotSourceAppListResponseMap = make(map[string]spltest.MockAWSS3Client) + } + + mockAwsHandler.GotSourceAppListResponseMap[appSource.Name] = mockResponse + } + + if allSuccess == false { + t.Errorf("Unable to get apps list for all the app sources") + } + method := "GetAppsList" + mockAwsHandler.CheckAWSS3Response(t, method) +} + +func TestMonitoringConsoleGetAppsListForAWSS3ClientShouldFail(t *testing.T) { + cr := enterpriseApi.MonitoringConsole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "stack1", + Namespace: "test", + }, + Spec: enterpriseApi.MonitoringConsoleSpec{ + AppFrameworkConfig: enterpriseApi.AppFrameworkSpec{ + VolList: []enterpriseApi.VolumeSpec{ + {Name: "msos_s2s3_vol", + Endpoint: "https://s3-eu-west-2.amazonaws.com", + Path: "testbucket-rs-london", + SecretRef: "s3-secret", + Type: "s3", + Provider: "aws"}, + }, + AppSources: []enterpriseApi.AppSourceSpec{ + {Name: "adminApps", + Location: "adminAppsRepo", + AppSourceDefaultSpec: enterpriseApi.AppSourceDefaultSpec{ + VolName: "msos_s2s3_vol", + Scope: enterpriseApi.ScopeLocal}, + }, + }, + }, + }, + } + + client := spltest.NewMockClient() + + // Create namespace scoped secret + _, err := splutil.ApplyNamespaceScopedSecretObject(client, "test") + if err != nil { + t.Errorf(err.Error()) + } + + splclient.RegisterS3Client("aws") + + Etags := []string{"cc707187b036405f095a8ebb43a782c1"} + Keys := []string{"admin_app.tgz"} + Sizes := []int64{10} + StorageClass := "STANDARD" + randomTime := time.Date(2021, time.May, 1, 23, 23, 0, 0, time.UTC) + + mockAwsHandler := spltest.MockAWSS3Handler{} + + mockAwsObjects := []spltest.MockAWSS3Client{ + { + Objects: []*spltest.MockAWSS3Object{ + { + Etag: &Etags[0], + Key: &Keys[0], + LastModified: &randomTime, + Size: &Sizes[0], + StorageClass: &StorageClass, + }, + }, + }, + } + + appFrameworkRef := cr.Spec.AppFrameworkConfig + + mockAwsHandler.AddObjects(appFrameworkRef, mockAwsObjects...) + + var vol enterpriseApi.VolumeSpec + + appSource := appFrameworkRef.AppSources[0] + vol, err = splclient.GetAppSrcVolume(appSource, &appFrameworkRef) + if err != nil { + t.Errorf("Unable to get Volume due to error=%s", err) + } + + // Update the GetS3Client with our mock call which initializes mock AWS client + getClientWrapper := splclient.S3Clients[vol.Provider] + getClientWrapper.SetS3ClientFuncPtr(vol.Provider, splclient.NewMockAWSS3Client) + + s3ClientMgr := &S3ClientManager{ + client: client, + cr: &cr, + appFrameworkRef: &cr.Spec.AppFrameworkConfig, + vol: &vol, + location: appSource.Location, + initFn: func(region, accessKeyID, secretAccessKey string) interface{} { + // Purposefully return nil here so that we test the error scenario + return nil + }, + getS3Client: func(client splcommon.ControllerClient, cr splcommon.MetaObject, + appFrameworkRef *enterpriseApi.AppFrameworkSpec, vol *enterpriseApi.VolumeSpec, + location string, fn splclient.GetInitFunc) (splclient.SplunkS3Client, error) { + // Get the mock client + c, err := GetRemoteStorageClient(client, cr, appFrameworkRef, vol, location, fn) + return c, err + }, + } + + _, err = s3ClientMgr.GetAppsList() + if err == nil { + t.Errorf("GetAppsList should have returned error as there is no S3 secret provided") + } + + // Create empty S3 secret + s3Secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "s3-secret", + Namespace: "test", + }, + Data: map[string][]byte{}, + } + + client.AddObject(&s3Secret) + + _, err = s3ClientMgr.GetAppsList() + if err == nil { + t.Errorf("GetAppsList should have returned error as S3 secret has empty keys") + } + + s3AccessKey := []byte{'1'} + s3Secret.Data = map[string][]byte{"s3_access_key": s3AccessKey} + _, err = s3ClientMgr.GetAppsList() + if err == nil { + t.Errorf("GetAppsList should have returned error as S3 secret has empty s3_secret_key") + } + + s3SecretKey := []byte{'2'} + s3Secret.Data = map[string][]byte{"s3_secret_key": s3SecretKey} + _, err = s3ClientMgr.GetAppsList() + if err == nil { + t.Errorf("GetAppsList should have returned error as S3 secret has empty s3_access_key") + } + + // Create S3 secret + s3Secret = spltest.GetMockS3SecretKeys("s3-secret") + + // This should return an error as we have initialized initFn for s3ClientMgr + // to return a nil client. + _, err = s3ClientMgr.GetAppsList() + if err == nil { + t.Errorf("GetAppsList should have returned error as we could not get the S3 client") + } + + s3ClientMgr.initFn = func(region, accessKeyID, secretAccessKey string) interface{} { + // To test the error scenario, do no set the Objects member yet + cl := spltest.MockAWSS3Client{} + return cl + } + + _, err = s3ClientMgr.GetAppsList() + if err == nil { + t.Errorf("GetAppsList should have returned error as we have empty objects in MockAWSS3Client") + } +}