diff --git a/api/apps/v1alpha1/app_types.go b/api/apps/v1alpha1/app_types.go index 4bc31a0e9..e179e96b3 100644 --- a/api/apps/v1alpha1/app_types.go +++ b/api/apps/v1alpha1/app_types.go @@ -53,6 +53,7 @@ type AppSourceRef struct { type AppSpec struct { // +kubebuilder:validation:Required // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.appID is immutable" AppID string `json:"appID"` // +kubebuilder:validation:Required @@ -60,9 +61,11 @@ type AppSpec struct { Version string `json:"version"` // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.targetRef is immutable" TargetRef AppTargetRef `json:"targetRef"` // +kubebuilder:validation:Required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.sourceRef is immutable" SourceRef AppSourceRef `json:"sourceRef"` // +kubebuilder:validation:Required @@ -70,6 +73,7 @@ type AppSpec struct { // +kubebuilder:validation:Required // +kubebuilder:validation:Enum="local";"cluster" + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.scope is immutable" Scope string `json:"scope"` } diff --git a/cmd/main.go b/cmd/main.go index 468037185..fef385798 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -303,7 +303,7 @@ func main() { webhookServer := validation.NewWebhookServer(validation.WebhookServerOptions{ Port: 9443, CertDir: "/tmp/k8s-webhook-server/serving-certs", - Validators: validation.DefaultValidators, + Validators: validation.DefaultValidators(mgr.GetClient()), ReadTimeout: readTimeout, WriteTimeout: writeTimeout, }) diff --git a/config/crd/bases/apps.splunk.com_apps.yaml b/config/crd/bases/apps.splunk.com_apps.yaml index fe148c898..7a2dd7cad 100644 --- a/config/crd/bases/apps.splunk.com_apps.yaml +++ b/config/crd/bases/apps.splunk.com_apps.yaml @@ -55,6 +55,9 @@ spec: appID: minLength: 1 type: string + x-kubernetes-validations: + - message: spec.appID is immutable + rule: self == oldSelf package: description: AppPackageSpec defines the package location within the source. @@ -70,6 +73,9 @@ spec: - local - cluster type: string + x-kubernetes-validations: + - message: spec.scope is immutable + rule: self == oldSelf sourceRef: description: AppSourceRef defines the app source details. properties: @@ -79,6 +85,9 @@ spec: required: - name type: object + x-kubernetes-validations: + - message: spec.sourceRef is immutable + rule: self == oldSelf targetRef: description: AppTargetRef defines the target environment the app should bind to. @@ -102,6 +111,9 @@ spec: - kind - name type: object + x-kubernetes-validations: + - message: spec.targetRef is immutable + rule: self == oldSelf version: minLength: 1 type: string diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index f534bd66b..e6d02eaa5 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -28,4 +28,13 @@ webhooks: - clustermanagers - licensemanagers - monitoringconsoles + - apiGroups: + - apps.splunk.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - apps sideEffects: None diff --git a/pkg/splunk/enterprise/validation/app_validation.go b/pkg/splunk/enterprise/validation/app_validation.go new file mode 100644 index 000000000..a2e48e66a --- /dev/null +++ b/pkg/splunk/enterprise/validation/app_validation.go @@ -0,0 +1,190 @@ +/* +Copyright (c) 2018-2026 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 validation + +import ( + "context" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/controller-runtime/pkg/client" + + appsv1alpha1 "github.com/splunk/splunk-operator/api/apps/v1alpha1" +) + +// AppValidator validates App resources at admission time. +// This implements the Validator interface and can be used +// with the generic validation registry to route App validation +// requests to this struct. +type AppValidator struct { + k8sClient client.Client +} + +// NewAppValidator creates an App validator backed by a Kubernetes client. +func NewAppValidator(k8sClient client.Client) Validator { + return &AppValidator{k8sClient: k8sClient} +} + +// ValidateCreate validates an App on CREATE. +func (v *AppValidator) ValidateCreate(obj runtime.Object) field.ErrorList { + app, ok := obj.(*appsv1alpha1.App) + if !ok { + return field.ErrorList{field.InternalError(nil, + &TypeAssertionError{Expected: &appsv1alpha1.App{}, Actual: obj})} + } + + return ValidateAppCreate(v.k8sClient, app) +} + +// ValidateUpdate validates an App on UPDATE. +func (v *AppValidator) ValidateUpdate(obj, oldObj runtime.Object) field.ErrorList { + app, ok := obj.(*appsv1alpha1.App) + if !ok { + return field.ErrorList{field.InternalError(nil, + &TypeAssertionError{Expected: &appsv1alpha1.App{}, Actual: obj})} + } + + oldApp, ok := oldObj.(*appsv1alpha1.App) + if !ok { + return field.ErrorList{field.InternalError(nil, + &TypeAssertionError{Expected: &appsv1alpha1.App{}, Actual: oldObj})} + } + + return ValidateAppUpdate(v.k8sClient, app, oldApp) +} + +// GetGroupKind returns the GroupKind for App. +func (v *AppValidator) GetGroupKind(obj runtime.Object) schema.GroupKind { + return schema.GroupKind{Group: appsv1alpha1.GroupVersion.Group, Kind: "App"} +} + +// GetName returns the App name. +func (v *AppValidator) GetName(obj runtime.Object) string { + app, ok := obj.(*appsv1alpha1.App) + if !ok { + return "" + } + + return app.GetName() +} + +// GetWarningsOnCreate returns warnings for App CREATE. +func (v *AppValidator) GetWarningsOnCreate(obj runtime.Object) []string { + app, ok := obj.(*appsv1alpha1.App) + if !ok { + return nil + } + + return GetAppWarningsOnCreate(app) +} + +// GetWarningsOnUpdate returns warnings for App UPDATE. +func (v *AppValidator) GetWarningsOnUpdate(obj, oldObj runtime.Object) []string { + app, ok := obj.(*appsv1alpha1.App) + if !ok { + return nil + } + + oldApp, ok := oldObj.(*appsv1alpha1.App) + if !ok { + return nil + } + + return GetAppWarningsOnUpdate(app, oldApp) +} + +// ValidateAppCreate validates an App on CREATE. +func ValidateAppCreate(k8sClient client.Client, obj *appsv1alpha1.App) field.ErrorList { + return validateApp(context.Background(), k8sClient, obj) +} + +// ValidateAppUpdate validates an App on UPDATE. +func ValidateAppUpdate(k8sClient client.Client, obj, _ *appsv1alpha1.App) field.ErrorList { + return validateApp(context.Background(), k8sClient, obj) +} + +// GetAppWarningsOnCreate returns warnings for App CREATE. +func GetAppWarningsOnCreate(obj *appsv1alpha1.App) []string { + return nil +} + +// GetAppWarningsOnUpdate returns warnings for App UPDATE. +func GetAppWarningsOnUpdate(obj, oldObj *appsv1alpha1.App) []string { + return nil +} + +func validateApp(ctx context.Context, k8sClient client.Client, app *appsv1alpha1.App) field.ErrorList { + if k8sClient == nil { + return field.ErrorList{field.InternalError(field.NewPath("spec"), fmt.Errorf("kubernetes client is required for App validation"))} + } + + var allErrs field.ErrorList + allErrs = append(allErrs, validateAppSourceRef(ctx, k8sClient, app)...) + allErrs = append(allErrs, validateAppUniqueness(ctx, k8sClient, app)...) + + return allErrs +} + +func validateAppSourceRef(ctx context.Context, k8sClient client.Client, app *appsv1alpha1.App) field.ErrorList { + var allErrs field.ErrorList + + sourceRefPath := field.NewPath("spec").Child("sourceRef").Child("name") + key := client.ObjectKey{Name: app.Spec.SourceRef.Name, Namespace: app.Namespace} + + var source appsv1alpha1.AppSource + if err := k8sClient.Get(ctx, key, &source); err != nil { + if apierrors.IsNotFound(err) { + allErrs = append(allErrs, field.NotFound(sourceRefPath, app.Spec.SourceRef.Name)) + return allErrs + } + + allErrs = append(allErrs, field.InternalError(sourceRefPath, fmt.Errorf("failed to validate AppSource reference: %w", err))) + } + + return allErrs +} + +func validateAppUniqueness(ctx context.Context, k8sClient client.Client, app *appsv1alpha1.App) field.ErrorList { + var allErrs field.ErrorList + + var appList appsv1alpha1.AppList + if err := k8sClient.List(ctx, &appList, client.InNamespace(app.Namespace)); err != nil { + return field.ErrorList{field.InternalError(field.NewPath("spec"), fmt.Errorf("failed to validate App uniqueness: %w", err))} + } + + for i := range appList.Items { + other := &appList.Items[i] + if other.Name == app.Name { + continue + } + + if other.Spec.AppID == app.Spec.AppID && + other.Spec.Scope == app.Spec.Scope && + other.Spec.TargetRef == app.Spec.TargetRef { + allErrs = append(allErrs, field.Invalid( + field.NewPath("spec"), + fmt.Sprintf("%s/%s:%s:%s/%s", app.Namespace, app.Name, app.Spec.AppID, app.Spec.TargetRef.Kind, app.Spec.TargetRef.Name), + fmt.Sprintf("another App %q already exists in namespace %q with the same targetRef, appID, and scope", other.Name, app.Namespace))) + break + } + } + + return allErrs +} diff --git a/pkg/splunk/enterprise/validation/app_validation_test.go b/pkg/splunk/enterprise/validation/app_validation_test.go new file mode 100644 index 000000000..18d25c721 --- /dev/null +++ b/pkg/splunk/enterprise/validation/app_validation_test.go @@ -0,0 +1,217 @@ +/* +Copyright (c) 2018-2026 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 validation + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + appsv1alpha1 "github.com/splunk/splunk-operator/api/apps/v1alpha1" +) + +func TestValidateAppCreate(t *testing.T) { + tests := []struct { + name string + app *appsv1alpha1.App + objects []client.Object + wantErrCount int + wantErrFields []string + wantMessage string + }{ + { + name: "valid app", + app: newValidApp("app-one"), + objects: []client.Object{ + newAppSource("source-one"), + }, + wantErrCount: 0, + }, + { + name: "missing referenced AppSource", + app: newValidApp("app-one"), + wantErrCount: 1, + wantErrFields: []string{ + "spec.sourceRef.name", + }, + wantMessage: "Not found", + }, + { + name: "duplicate target appID scope tuple", + app: newValidApp("app-two"), + objects: []client.Object{ + newAppSource("source-one"), + newValidApp("app-existing"), + }, + wantErrCount: 1, + wantErrFields: []string{ + "spec", + }, + wantMessage: "same targetRef, appID, and scope", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := ValidateAppCreate(newValidationClient(t, tt.objects...), tt.app) + require.Len(t, errs, tt.wantErrCount) + + for i, wantField := range tt.wantErrFields { + assert.Equal(t, wantField, errs[i].Field) + } + + if tt.wantMessage != "" && len(errs) > 0 { + assert.Contains(t, errs[0].Error(), tt.wantMessage) + } + }) + } +} + +func TestValidateAppUpdate(t *testing.T) { + tests := []struct { + name string + app *appsv1alpha1.App + oldApp *appsv1alpha1.App + objects []client.Object + wantErrCount int + wantErrFields []string + }{ + { + name: "webhook allows mutable field changes", + app: func() *appsv1alpha1.App { + app := newValidApp("app-one") + app.Spec.Version = "2.0.0" + app.Spec.Package.Path = "apps/sample-app-v2.tgz" + return app + }(), + oldApp: func() *appsv1alpha1.App { + app := newValidApp("app-one") + app.Spec.Version = "1.0.0" + app.Spec.Package.Path = "apps/sample-app-v1.tgz" + return app + }(), + objects: []client.Object{ + newAppSource("source-one"), + }, + wantErrCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := ValidateAppUpdate(newValidationClient(t, tt.objects...), tt.app, tt.oldApp) + require.Len(t, errs, tt.wantErrCount) + + for i, wantField := range tt.wantErrFields { + assert.Equal(t, wantField, errs[i].Field) + } + }) + } +} + +func TestValidateAdmissionReviewForApp(t *testing.T) { + app := newValidApp("app-one") + validators := map[schema.GroupVersionResource]Validator{ + AppGVR: NewAppValidator(newValidationClient(t, newAppSource("source-one"))), + } + + raw, err := json.Marshal(app) + require.NoError(t, err) + + warnings, err := Validate(&admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + UID: "app-test-uid", + Operation: admissionv1.Create, + Resource: metav1.GroupVersionResource{ + Group: appsv1alpha1.GroupVersion.Group, + Version: appsv1alpha1.GroupVersion.Version, + Resource: "apps", + }, + Object: runtime.RawExtension{Raw: raw}, + }, + }, validators) + + assert.NoError(t, err) + assert.Empty(t, warnings) +} + +func newValidationClient(t *testing.T, objects ...client.Object) client.Client { + t.Helper() + + scheme := runtime.NewScheme() + require.NoError(t, appsv1alpha1.AddToScheme(scheme)) + + return fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(objects...). + Build() +} + +func newValidApp(name string) *appsv1alpha1.App { + return &appsv1alpha1.App{ + TypeMeta: metav1.TypeMeta{ + APIVersion: appsv1alpha1.GroupVersion.String(), + Kind: "App", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + }, + Spec: appsv1alpha1.AppSpec{ + AppID: "sample-app", + Version: "1.0.0", + TargetRef: appsv1alpha1.AppTargetRef{ + Kind: "Standalone", + Name: "standalone-sample", + }, + SourceRef: appsv1alpha1.AppSourceRef{ + Name: "source-one", + }, + Package: appsv1alpha1.AppPackageSpec{ + Path: "apps/sample-app.tgz", + }, + Scope: "local", + }, + } +} + +func newAppSource(name string) *appsv1alpha1.AppSource { + return &appsv1alpha1.AppSource{ + TypeMeta: metav1.TypeMeta{ + APIVersion: appsv1alpha1.GroupVersion.String(), + Kind: "AppSource", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + }, + Spec: appsv1alpha1.AppSourceSpec{ + Type: "s3", + S3: &appsv1alpha1.AppSourceS3Spec{ + Endpoint: "https://s3.amazonaws.com", + }, + }, + } +} diff --git a/pkg/splunk/enterprise/validation/registry.go b/pkg/splunk/enterprise/validation/registry.go index 695183a8f..c4cfee5db 100644 --- a/pkg/splunk/enterprise/validation/registry.go +++ b/pkg/splunk/enterprise/validation/registry.go @@ -18,6 +18,7 @@ package validation import ( "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" enterpriseApi "github.com/splunk/splunk-operator/api/enterprise/v4" ) @@ -71,97 +72,107 @@ var ( Version: "v4", Resource: "monitoringconsoles", } + + AppGVR = schema.GroupVersionResource{ + Group: "apps.splunk.com", + Version: "v1alpha1", + Resource: "apps", + } ) -// DefaultValidators is the registry of validators for all Splunk Enterprise CRDs -var DefaultValidators = map[schema.GroupVersionResource]Validator{ - StandaloneGVR: &GenericValidator[*enterpriseApi.Standalone]{ - ValidateCreateFunc: ValidateStandaloneCreate, - ValidateUpdateFunc: ValidateStandaloneUpdate, - WarningsOnCreateFunc: GetStandaloneWarningsOnCreate, - WarningsOnUpdateFunc: GetStandaloneWarningsOnUpdate, - GroupKind: schema.GroupKind{ - Group: "enterprise.splunk.com", - Kind: "Standalone", +// DefaultValidators returns the validator registry used by the admission webhook. +func DefaultValidators(k8sClient client.Client) map[schema.GroupVersionResource]Validator { + return map[schema.GroupVersionResource]Validator{ + StandaloneGVR: &GenericValidator[*enterpriseApi.Standalone]{ + ValidateCreateFunc: ValidateStandaloneCreate, + ValidateUpdateFunc: ValidateStandaloneUpdate, + WarningsOnCreateFunc: GetStandaloneWarningsOnCreate, + WarningsOnUpdateFunc: GetStandaloneWarningsOnUpdate, + GroupKind: schema.GroupKind{ + Group: "enterprise.splunk.com", + Kind: "Standalone", + }, }, - }, - - IndexerClusterGVR: &GenericValidator[*enterpriseApi.IndexerCluster]{ - ValidateCreateFunc: ValidateIndexerClusterCreate, - ValidateUpdateFunc: ValidateIndexerClusterUpdate, - WarningsOnCreateFunc: GetIndexerClusterWarningsOnCreate, - WarningsOnUpdateFunc: GetIndexerClusterWarningsOnUpdate, - GroupKind: schema.GroupKind{ - Group: "enterprise.splunk.com", - Kind: "IndexerCluster", + + IndexerClusterGVR: &GenericValidator[*enterpriseApi.IndexerCluster]{ + ValidateCreateFunc: ValidateIndexerClusterCreate, + ValidateUpdateFunc: ValidateIndexerClusterUpdate, + WarningsOnCreateFunc: GetIndexerClusterWarningsOnCreate, + WarningsOnUpdateFunc: GetIndexerClusterWarningsOnUpdate, + GroupKind: schema.GroupKind{ + Group: "enterprise.splunk.com", + Kind: "IndexerCluster", + }, }, - }, - - SearchHeadClusterGVR: &GenericValidator[*enterpriseApi.SearchHeadCluster]{ - ValidateCreateFunc: ValidateSearchHeadClusterCreate, - ValidateUpdateFunc: ValidateSearchHeadClusterUpdate, - WarningsOnCreateFunc: GetSearchHeadClusterWarningsOnCreate, - WarningsOnUpdateFunc: GetSearchHeadClusterWarningsOnUpdate, - GroupKind: schema.GroupKind{ - Group: "enterprise.splunk.com", - Kind: "SearchHeadCluster", + + SearchHeadClusterGVR: &GenericValidator[*enterpriseApi.SearchHeadCluster]{ + ValidateCreateFunc: ValidateSearchHeadClusterCreate, + ValidateUpdateFunc: ValidateSearchHeadClusterUpdate, + WarningsOnCreateFunc: GetSearchHeadClusterWarningsOnCreate, + WarningsOnUpdateFunc: GetSearchHeadClusterWarningsOnUpdate, + GroupKind: schema.GroupKind{ + Group: "enterprise.splunk.com", + Kind: "SearchHeadCluster", + }, }, - }, - - ClusterManagerGVR: &GenericValidator[*enterpriseApi.ClusterManager]{ - ValidateCreateFunc: ValidateClusterManagerCreate, - ValidateUpdateFunc: ValidateClusterManagerUpdate, - WarningsOnCreateFunc: GetClusterManagerWarningsOnCreate, - WarningsOnUpdateFunc: GetClusterManagerWarningsOnUpdate, - GroupKind: schema.GroupKind{ - Group: "enterprise.splunk.com", - Kind: "ClusterManager", + + ClusterManagerGVR: &GenericValidator[*enterpriseApi.ClusterManager]{ + ValidateCreateFunc: ValidateClusterManagerCreate, + ValidateUpdateFunc: ValidateClusterManagerUpdate, + WarningsOnCreateFunc: GetClusterManagerWarningsOnCreate, + WarningsOnUpdateFunc: GetClusterManagerWarningsOnUpdate, + GroupKind: schema.GroupKind{ + Group: "enterprise.splunk.com", + Kind: "ClusterManager", + }, }, - }, - - // ClusterMaster is an alias for ClusterManager (deprecated) - ClusterMasterGVR: &GenericValidator[*enterpriseApi.ClusterManager]{ - ValidateCreateFunc: ValidateClusterManagerCreate, - ValidateUpdateFunc: ValidateClusterManagerUpdate, - WarningsOnCreateFunc: GetClusterManagerWarningsOnCreate, - WarningsOnUpdateFunc: GetClusterManagerWarningsOnUpdate, - GroupKind: schema.GroupKind{ - Group: "enterprise.splunk.com", - Kind: "ClusterManager", + + // ClusterMaster is an alias for ClusterManager (deprecated) + ClusterMasterGVR: &GenericValidator[*enterpriseApi.ClusterManager]{ + ValidateCreateFunc: ValidateClusterManagerCreate, + ValidateUpdateFunc: ValidateClusterManagerUpdate, + WarningsOnCreateFunc: GetClusterManagerWarningsOnCreate, + WarningsOnUpdateFunc: GetClusterManagerWarningsOnUpdate, + GroupKind: schema.GroupKind{ + Group: "enterprise.splunk.com", + Kind: "ClusterManager", + }, }, - }, - - LicenseManagerGVR: &GenericValidator[*enterpriseApi.LicenseManager]{ - ValidateCreateFunc: ValidateLicenseManagerCreate, - ValidateUpdateFunc: ValidateLicenseManagerUpdate, - WarningsOnCreateFunc: GetLicenseManagerWarningsOnCreate, - WarningsOnUpdateFunc: GetLicenseManagerWarningsOnUpdate, - GroupKind: schema.GroupKind{ - Group: "enterprise.splunk.com", - Kind: "LicenseManager", + + LicenseManagerGVR: &GenericValidator[*enterpriseApi.LicenseManager]{ + ValidateCreateFunc: ValidateLicenseManagerCreate, + ValidateUpdateFunc: ValidateLicenseManagerUpdate, + WarningsOnCreateFunc: GetLicenseManagerWarningsOnCreate, + WarningsOnUpdateFunc: GetLicenseManagerWarningsOnUpdate, + GroupKind: schema.GroupKind{ + Group: "enterprise.splunk.com", + Kind: "LicenseManager", + }, }, - }, - - // LicenseMaster is an alias for LicenseManager (deprecated) - LicenseMasterGVR: &GenericValidator[*enterpriseApi.LicenseManager]{ - ValidateCreateFunc: ValidateLicenseManagerCreate, - ValidateUpdateFunc: ValidateLicenseManagerUpdate, - WarningsOnCreateFunc: GetLicenseManagerWarningsOnCreate, - WarningsOnUpdateFunc: GetLicenseManagerWarningsOnUpdate, - GroupKind: schema.GroupKind{ - Group: "enterprise.splunk.com", - Kind: "LicenseManager", + + // LicenseMaster is an alias for LicenseManager (deprecated) + LicenseMasterGVR: &GenericValidator[*enterpriseApi.LicenseManager]{ + ValidateCreateFunc: ValidateLicenseManagerCreate, + ValidateUpdateFunc: ValidateLicenseManagerUpdate, + WarningsOnCreateFunc: GetLicenseManagerWarningsOnCreate, + WarningsOnUpdateFunc: GetLicenseManagerWarningsOnUpdate, + GroupKind: schema.GroupKind{ + Group: "enterprise.splunk.com", + Kind: "LicenseManager", + }, }, - }, - - MonitoringConsoleGVR: &GenericValidator[*enterpriseApi.MonitoringConsole]{ - ValidateCreateFunc: ValidateMonitoringConsoleCreate, - ValidateUpdateFunc: ValidateMonitoringConsoleUpdate, - WarningsOnCreateFunc: GetMonitoringConsoleWarningsOnCreate, - WarningsOnUpdateFunc: GetMonitoringConsoleWarningsOnUpdate, - GroupKind: schema.GroupKind{ - Group: "enterprise.splunk.com", - Kind: "MonitoringConsole", + + MonitoringConsoleGVR: &GenericValidator[*enterpriseApi.MonitoringConsole]{ + ValidateCreateFunc: ValidateMonitoringConsoleCreate, + ValidateUpdateFunc: ValidateMonitoringConsoleUpdate, + WarningsOnCreateFunc: GetMonitoringConsoleWarningsOnCreate, + WarningsOnUpdateFunc: GetMonitoringConsoleWarningsOnUpdate, + GroupKind: schema.GroupKind{ + Group: "enterprise.splunk.com", + Kind: "MonitoringConsole", + }, }, - }, + + AppGVR: NewAppValidator(k8sClient), + } } diff --git a/pkg/splunk/enterprise/validation/validate.go b/pkg/splunk/enterprise/validation/validate.go index bed7aa6cc..6df1104a0 100644 --- a/pkg/splunk/enterprise/validation/validate.go +++ b/pkg/splunk/enterprise/validation/validate.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/validation/field" + appsv1alpha1 "github.com/splunk/splunk-operator/api/apps/v1alpha1" enterpriseApi "github.com/splunk/splunk-operator/api/enterprise/v4" ) @@ -35,6 +36,7 @@ var ( ) func init() { + _ = appsv1alpha1.AddToScheme(scheme) _ = enterpriseApi.AddToScheme(scheme) codecs = serializer.NewCodecFactory(scheme) }