diff --git a/apis/cloud.redhat.com/v1alpha1/clowdapp_webhook.go b/apis/cloud.redhat.com/v1alpha1/clowdapp_webhook.go index 55a5a596f..ea5159248 100644 --- a/apis/cloud.redhat.com/v1alpha1/clowdapp_webhook.go +++ b/apis/cloud.redhat.com/v1alpha1/clowdapp_webhook.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + "context" "fmt" apps "k8s.io/api/apps/v1" @@ -25,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -32,55 +34,85 @@ import ( // log is for logging in this package. var clowdapplog = logf.Log.WithName("clowdapp-resource") -// SetupWebhookWithManager configures the webhook for this ClowdApp resource -func (i *ClowdApp) SetupWebhookWithManager(mgr ctrl.Manager) error { +// clowdAppValidator is a webhook that validates ClowdApp resources +type clowdAppValidator struct { + client.Client +} + +func (r *ClowdApp) SetupWebhookWithManager(mgr ctrl.Manager) error { + // Add index for spec.envName field for webhook queries + if err := mgr.GetFieldIndexer().IndexField( + context.TODO(), &ClowdApp{}, "spec.envName", func(o client.Object) []string { + return []string{o.(*ClowdApp).Spec.EnvName} + }); err != nil { + return err + } + return ctrl.NewWebhookManagedBy(mgr). - For(i). + For(r). + WithValidator(&clowdAppValidator{Client: mgr.GetClient()}). Complete() } //+kubebuilder:webhook:path=/validate-cloud-redhat-com-v1alpha1-clowdapp,mutating=false,failurePolicy=fail,sideEffects=None,groups=cloud.redhat.com,resources=clowdapps,verbs=create;update,versions=v1alpha1,name=vclowdapp.kb.io,admissionReviewVersions={v1} //+kubebuilder:webhook:path=/mutate-pod,mutating=true,failurePolicy=ignore,sideEffects=None,groups="",resources=pods,verbs=create;update,versions=v1,name=vclowdmutatepod.kb.io,admissionReviewVersions={v1} +// Define default validations that should always run +var defaultValidations = []appValidationFunc{ + validateDatabase, + validateSidecars, + validateInit, + validateDeploymentStrategy, +} + // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (i *ClowdApp) ValidateCreate() (admission.Warnings, error) { - clowdapplog.Info("validate create", "name", i.Name) - - return []string{}, i.processValidations(i, - validateDatabase, - validateSidecars, - validateInit, - validateDeploymentStrategy, - ) +func (v *clowdAppValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + clowdapp := obj.(*ClowdApp) + clowdapplog.Info("validate create", "name", clowdapp.Name) + + // Create validations list with default validations plus duplicate name check + validations := append([]appValidationFunc{v.validateDuplicateName}, defaultValidations...) + + return []string{}, v.processValidations(ctx, clowdapp, validations...) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (i *ClowdApp) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) { - clowdapplog.Info("validate update", "name", i.Name) - - return []string{}, i.processValidations(i, - validateDatabase, - validateSidecars, - validateInit, - validateDeploymentStrategy, - ) +func (v *clowdAppValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + clowdapp := newObj.(*ClowdApp) + oldClowdApp := oldObj.(*ClowdApp) + clowdapplog.Info("validate update", "name", clowdapp.Name) + + // Start with default validations + validations := make([]appValidationFunc, len(defaultValidations)) + copy(validations, defaultValidations) + + // Append duplicate name validation if names differ + if oldClowdApp.Name != clowdapp.Name { + validations = append(validations, v.validateDuplicateName) + } + + return []string{}, v.processValidations(ctx, clowdapp, validations...) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (i *ClowdApp) ValidateDelete() (admission.Warnings, error) { - clowdapplog.Info("validate delete", "name", i.Name) +func (v *clowdAppValidator) ValidateDelete(_ context.Context, obj runtime.Object) (admission.Warnings, error) { + clowdapp := obj.(*ClowdApp) + clowdapplog.Info("validate delete", "name", clowdapp.Name) + return []string{}, nil } -type appValidationFunc func(*ClowdApp) field.ErrorList +type appValidationFunc func(context.Context, client.Client, *ClowdApp) field.ErrorList -func (i *ClowdApp) processValidations(o *ClowdApp, vfns ...appValidationFunc) error { +func (v *clowdAppValidator) processValidations(ctx context.Context, o *ClowdApp, vfns ...appValidationFunc) error { var allErrs field.ErrorList for _, validation := range vfns { - fieldList := validation(o) - if fieldList != nil { - allErrs = append(allErrs, fieldList...) + if validation != nil { + fieldList := validation(ctx, v.Client, o) + if fieldList != nil { + allErrs = append(allErrs, fieldList...) + } } } @@ -90,11 +122,11 @@ func (i *ClowdApp) processValidations(o *ClowdApp, vfns ...appValidationFunc) er return apierrors.NewInvalid( schema.GroupKind{Group: "cloud.redhat.com", Kind: "ClowdApp"}, - i.Name, allErrs, + o.Name, allErrs, ) } -func validateDatabase(i *ClowdApp) field.ErrorList { +func validateDatabase(_ context.Context, _ client.Client, r *ClowdApp) field.ErrorList { allErrs := field.ErrorList{} if i.Spec.Database.Name != "" && i.Spec.Database.SharedDBAppName != "" { @@ -112,7 +144,7 @@ func validateDatabase(i *ClowdApp) field.ErrorList { return allErrs } -func validateInit(i *ClowdApp) field.ErrorList { +func validateInit(_ context.Context, _ client.Client, r *ClowdApp) field.ErrorList { allErrs := field.ErrorList{} for depIdx, deployment := range i.Spec.Deployments { @@ -132,7 +164,7 @@ func validateInit(i *ClowdApp) field.ErrorList { return allErrs } -func validateSidecars(i *ClowdApp) field.ErrorList { +func validateSidecars(_ context.Context, _ client.Client, r *ClowdApp) field.ErrorList { allErrs := field.ErrorList{} for depIndx, deployment := range i.Spec.Deployments { for carIndx, sidecar := range deployment.PodSpec.Sidecars { @@ -166,7 +198,7 @@ func validateSidecars(i *ClowdApp) field.ErrorList { return allErrs } -func validateDeploymentStrategy(i *ClowdApp) field.ErrorList { +func validateDeploymentStrategy(_ context.Context, _ client.Client, r *ClowdApp) field.ErrorList { allErrs := field.ErrorList{} for depIndex, deployment := range i.Spec.Deployments { if deployment.DeploymentStrategy != nil && deployment.WebServices.Public.Enabled && deployment.DeploymentStrategy.PrivateStrategy == apps.RecreateDeploymentStrategyType { @@ -181,3 +213,33 @@ func validateDeploymentStrategy(i *ClowdApp) field.ErrorList { } return allErrs } + +func (v *clowdAppValidator) validateDuplicateName(ctx context.Context, c client.Client, r *ClowdApp) field.ErrorList { + allErrs := field.ErrorList{} + + // Check if another ClowdApp with the same name already exists in the same ClowdEnvironment + existingClowdApps := &ClowdAppList{} + err := c.List(ctx, existingClowdApps, client.MatchingFields{ + "spec.envName": r.Spec.EnvName, + }) + + if err != nil { + // If we got an error, log it but don't fail validation + // This allows the webhook to continue functioning even if there are temporary + // API server issues + clowdapplog.Error(err, "Error checking for duplicate ClowdApp name", "name", r.Name) + return allErrs + } + + // Iterate through existing ClowdApps to check for duplicates with same name in different namespaces + for _, existingApp := range existingClowdApps.Items { + if existingApp.Name == r.Name && existingApp.Namespace != r.Namespace { + allErrs = append(allErrs, field.Duplicate( + field.NewPath("metadata").Child("name"), + fmt.Sprintf("ClowdApp with name '%s' already exists in ClowdEnvironment '%s' in namespace '%s'", r.Name, r.Spec.EnvName, existingApp.Namespace)), + ) + } + } + + return allErrs +} diff --git a/apis/cloud.redhat.com/v1alpha1/webhook_suite_test.go b/apis/cloud.redhat.com/v1alpha1/webhook_suite_test.go index 89bf468e4..a60d6a299 100644 --- a/apis/cloud.redhat.com/v1alpha1/webhook_suite_test.go +++ b/apis/cloud.redhat.com/v1alpha1/webhook_suite_test.go @@ -29,7 +29,9 @@ import ( g "github.com/onsi/gomega" admissionv1beta1 "k8s.io/api/admission/v1beta1" - //+kubebuilder:scaffold:imports + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -37,6 +39,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/webhook" + //+kubebuilder:scaffold:imports ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -80,6 +83,9 @@ var _ = ginkgo.BeforeSuite(func() { err = admissionv1beta1.AddToScheme(scheme) g.Expect(err).NotTo(g.HaveOccurred()) + err = v1.AddToScheme(scheme) + g.Expect(err).NotTo(g.HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) @@ -134,3 +140,328 @@ var _ = ginkgo.AfterSuite(func() { err := testEnv.Stop() g.Expect(err).NotTo(g.HaveOccurred()) }) + +var _ = ginkgo.Describe("ClowdApp webhook", func() { + ginkgo.Context("When creating ClowdApp", func() { + var testNamespace1 *v1.Namespace + var testNamespace2 *v1.Namespace + var testEnv1 *ClowdEnvironment + var testEnv2 *ClowdEnvironment + + ginkgo.BeforeEach(func() { + // Create two test namespaces for cross-namespace testing + testNamespace1 = &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-webhook-1-" + fmt.Sprintf("%d", time.Now().UnixNano()), + }, + } + err := k8sClient.Create(ctx, testNamespace1) + g.Expect(err).NotTo(g.HaveOccurred()) + + testNamespace2 = &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-webhook-2-" + fmt.Sprintf("%d", time.Now().UnixNano()), + }, + } + err = k8sClient.Create(ctx, testNamespace2) + g.Expect(err).NotTo(g.HaveOccurred()) + + // Create test environments in both namespaces + testEnv1 = &ClowdEnvironment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-env-1", + Namespace: testNamespace1.Name, + }, + Spec: ClowdEnvironmentSpec{ + TargetNamespace: testNamespace1.Name, + Providers: ProvidersConfig{ + Web: WebConfig{ + Port: 8000, + Mode: "operator", + }, + Metrics: MetricsConfig{ + Port: 9000, + Mode: "operator", + Path: "/metrics", + }, + Kafka: KafkaConfig{ + Mode: "none", + }, + Database: DatabaseConfig{ + Mode: "none", + }, + Logging: LoggingConfig{ + Mode: "none", + }, + ObjectStore: ObjectStoreConfig{ + Mode: "none", + }, + InMemoryDB: InMemoryDBConfig{ + Mode: "none", + }, + }, + }, + } + err = k8sClient.Create(ctx, testEnv1) + g.Expect(err).NotTo(g.HaveOccurred()) + + testEnv2 = &ClowdEnvironment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-env-2", + Namespace: testNamespace2.Name, + }, + Spec: ClowdEnvironmentSpec{ + TargetNamespace: testNamespace2.Name, + Providers: ProvidersConfig{ + Web: WebConfig{ + Port: 8000, + Mode: "operator", + }, + Metrics: MetricsConfig{ + Port: 9000, + Mode: "operator", + Path: "/metrics", + }, + Kafka: KafkaConfig{ + Mode: "none", + }, + Database: DatabaseConfig{ + Mode: "none", + }, + Logging: LoggingConfig{ + Mode: "none", + }, + ObjectStore: ObjectStoreConfig{ + Mode: "none", + }, + InMemoryDB: InMemoryDBConfig{ + Mode: "none", + }, + }, + }, + } + err = k8sClient.Create(ctx, testEnv2) + g.Expect(err).NotTo(g.HaveOccurred()) + }) + + ginkgo.AfterEach(func() { + // Clean up test resources + err := k8sClient.Delete(ctx, testEnv1) + g.Expect(err).NotTo(g.HaveOccurred()) + err = k8sClient.Delete(ctx, testEnv2) + g.Expect(err).NotTo(g.HaveOccurred()) + err = k8sClient.Delete(ctx, testNamespace1) + g.Expect(err).NotTo(g.HaveOccurred()) + err = k8sClient.Delete(ctx, testNamespace2) + g.Expect(err).NotTo(g.HaveOccurred()) + }) + + ginkgo.It("should allow creating a ClowdApp with unique name", func() { + clowdApp := &ClowdApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unique-app", + Namespace: testNamespace1.Name, + }, + Spec: ClowdAppSpec{ + EnvName: "test-env-1", + Deployments: []Deployment{ + { + Name: "processor", + PodSpec: PodSpec{ + Image: "quay.io/psav/clowder-hello", + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, clowdApp) + g.Expect(err).NotTo(g.HaveOccurred()) + }) + + ginkgo.It("should allow creating ClowdApps with same name in different ClowdEnvironments", func() { + // First, create a ClowdApp in namespace 1 with env-1 + firstApp := &ClowdApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "same-name-app", + Namespace: testNamespace1.Name, + }, + Spec: ClowdAppSpec{ + EnvName: "test-env-1", + Deployments: []Deployment{ + { + Name: "processor", + PodSpec: PodSpec{ + Image: "quay.io/psav/clowder-hello", + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, firstApp) + g.Expect(err).NotTo(g.HaveOccurred()) + + // Now create another ClowdApp with the same name in namespace 2 with env-2 + secondApp := &ClowdApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "same-name-app", + Namespace: testNamespace2.Name, + }, + Spec: ClowdAppSpec{ + EnvName: "test-env-2", + Deployments: []Deployment{ + { + Name: "different-processor", + PodSpec: PodSpec{ + Image: "quay.io/psav/clowder-hello", + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, secondApp) + g.Expect(err).NotTo(g.HaveOccurred()) + }) + + ginkgo.It("should reject creating ClowdApps with duplicate name in same ClowdEnvironment", func() { + // First, create a ClowdApp in namespace 1 with env-1 + firstApp := &ClowdApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "duplicate-app", + Namespace: testNamespace1.Name, + }, + Spec: ClowdAppSpec{ + EnvName: "test-env-1", + Deployments: []Deployment{ + { + Name: "processor", + PodSpec: PodSpec{ + Image: "quay.io/psav/clowder-hello", + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, firstApp) + g.Expect(err).NotTo(g.HaveOccurred()) + + // Now try to create another ClowdApp with the same name in namespace 2 but same env + secondApp := &ClowdApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "duplicate-app", + Namespace: testNamespace2.Name, + }, + Spec: ClowdAppSpec{ + EnvName: "test-env-1", // Same environment as first app + Deployments: []Deployment{ + { + Name: "different-processor", + PodSpec: PodSpec{ + Image: "quay.io/psav/clowder-hello", + }, + }, + }, + }, + } + + err = k8sClient.Create(ctx, secondApp) + g.Expect(err).To(g.HaveOccurred()) + g.Expect(errors.IsInvalid(err)).To(g.BeTrue()) + g.Expect(err.Error()).To(g.ContainSubstring("already exists")) + }) + + ginkgo.It("should allow updating a ClowdApp without changing name", func() { + // Create a ClowdApp + clowdApp := &ClowdApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "update-app", + Namespace: testNamespace1.Name, + }, + Spec: ClowdAppSpec{ + EnvName: "test-env-1", + Deployments: []Deployment{ + { + Name: "processor", + PodSpec: PodSpec{ + Image: "quay.io/psav/clowder-hello", + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, clowdApp) + g.Expect(err).NotTo(g.HaveOccurred()) + + // Update the ClowdApp (change image) + clowdApp.Spec.Deployments[0].PodSpec.Image = "quay.io/psav/clowder-hello:v2" + err = k8sClient.Update(ctx, clowdApp) + g.Expect(err).NotTo(g.HaveOccurred()) + }) + + ginkgo.It("should validate database configuration", func() { + // Try to create a ClowdApp with invalid database configuration + invalidApp := &ClowdApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-db-app", + Namespace: testNamespace1.Name, + }, + Spec: ClowdAppSpec{ + EnvName: "test-env-1", + Database: DatabaseSpec{ + Name: "my-db", + SharedDBAppName: "shared-db-app", + }, + Deployments: []Deployment{ + { + Name: "processor", + PodSpec: PodSpec{ + Image: "quay.io/psav/clowder-hello", + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, invalidApp) + g.Expect(err).To(g.HaveOccurred()) + g.Expect(errors.IsInvalid(err)).To(g.BeTrue()) + g.Expect(err.Error()).To(g.ContainSubstring("cannot set db name and sharedDbApp Name together")) + }) + + ginkgo.It("should validate sidecar configuration", func() { + // Try to create a ClowdApp with invalid sidecar configuration + invalidApp := &ClowdApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-sidecar-app", + Namespace: testNamespace1.Name, + }, + Spec: ClowdAppSpec{ + EnvName: "test-env-1", + Deployments: []Deployment{ + { + Name: "processor", + PodSpec: PodSpec{ + Image: "quay.io/psav/clowder-hello", + Sidecars: []Sidecar{ + { + Name: "invalid-sidecar", + Enabled: true, + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(ctx, invalidApp) + g.Expect(err).To(g.HaveOccurred()) + g.Expect(errors.IsInvalid(err)).To(g.BeTrue()) + g.Expect(err.Error()).To(g.ContainSubstring("Sidecar is of unknown type")) + }) + }) +}) diff --git a/build/build_api_docs.sh b/build/build_api_docs.sh index 69fc1fa25..04ce00ecf 100755 --- a/build/build_api_docs.sh +++ b/build/build_api_docs.sh @@ -8,6 +8,7 @@ if [ ! -d docs/build/crd-ref-docs ]; then mkdir -p docs/build/crd-ref-docs git clone https://github.com/elastic/crd-ref-docs.git docs/build/crd-ref-docs cd docs/build/crd-ref-docs + git checkout v0.0.12 go build -o crd-ref-docs main.go cd - fi diff --git a/docs/api_reference.md b/docs/api_reference.md index 3913dc921..f7ac87445 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -1013,11 +1013,6 @@ JobConditionState describes the state a job is in _Appears in:_ - [ClowdJobInvocationStatus](#clowdjobinvocationstatus) -| Field | Description | -| --- | --- | -| `Invoked` | JobInvoked represents a job that has been invoked
| -| `Complete` | JobComplete represents a job that has completed successfully
| -| `Failed` | JobFailed represents a job that has failed
| #### JobTestingSpec @@ -1459,9 +1454,6 @@ PrometheusStatus provides info on how to connect to Prometheus _Appears in:_ - [ClowdEnvironmentStatus](#clowdenvironmentstatus) -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `serverAddress` _string_ | | | | #### ProvidersConfig @@ -1774,7 +1766,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `mocktitlements` _string_ | Mock entitlements image -- if not defined, value from operator config is used if set, otherwise a hard-coded default is used. | | | -| `keycloak` _string_ | Keycloak image -- default is 'quay.io/keycloak/keycloak:\{KeycloakVersion\}' unless overridden here | | | +| `keycloak` _string_ | Keycloak image -- default is 'quay.io/keycloak/keycloak:{KeycloakVersion}' unless overridden here | | | | `caddy` _string_ | Caddy image -- if not defined, value from operator config is used if set, otherwise a hard-coded default is used. | | | | `caddyGateway` _string_ | Caddy Gateway image -- if not defined, value from operator config is used if set, otherwise a hard-coded default is used. | | | | `caddyProxy` _string_ | Caddy Reverse Proxy image -- if not defined, value from operator config is used if set, otherwise a hard-coded default is used. | | | diff --git a/docs/clowder-design.md b/docs/clowder-design.md index 634218bc4..b294d3a9d 100644 --- a/docs/clowder-design.md +++ b/docs/clowder-design.md @@ -95,3 +95,26 @@ Common configuration interface: The design proposed in this document is intentionally opinionated and makes choices on behalf of apps because more often than not dev teams do not have strong preferences on many operational aspects of their application. + +## Validation + +Clowder includes validating webhooks to ensure ClowdApp resources are properly configured before they are applied to the cluster. The following validations are performed: + +### Duplicate Name Validation +ClowdApp names must be unique within each ClowdEnvironment. When creating a new ClowdApp, the validating webhook will check if another ClowdApp with the same name already exists in the same ClowdEnvironment and reject the request if a duplicate is found. This ensures uniqueness of ClowdApp names within each environment while allowing the same name to be used across different ClowdEnvironments. + +### Database Configuration Validation +- Cannot set both `database.name` and `database.sharedDbAppName` simultaneously +- Cannot use Cyndi with a shared database + +### Sidecar Validation +- Sidecar names must be one of the supported types: `token-refresher` or `otel-collector` +- This validation applies to both deployment and job sidecars + +### Init Container Validation +- When multiple init containers are defined, each must have a unique name + +### Deployment Strategy Validation +- Private strategy cannot be set to `Recreate` for deployments with public web services enabled + +These validations help ensure proper configuration and prevent common misconfigurations that could cause deployment issues. diff --git a/tests/kuttl/test-duplicate-name-validation/00-install.yaml b/tests/kuttl/test-duplicate-name-validation/00-install.yaml new file mode 100644 index 000000000..3693fa36f --- /dev/null +++ b/tests/kuttl/test-duplicate-name-validation/00-install.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: test-duplicate-name-validation +spec: + finalizers: + - kubernetes \ No newline at end of file diff --git a/tests/kuttl/test-duplicate-name-validation/01-setup.yaml b/tests/kuttl/test-duplicate-name-validation/01-setup.yaml new file mode 100644 index 000000000..a3d651930 --- /dev/null +++ b/tests/kuttl/test-duplicate-name-validation/01-setup.yaml @@ -0,0 +1,53 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: test-duplicate-name-validation-2 +spec: + finalizers: + - kubernetes +--- +apiVersion: cloud.redhat.com/v1alpha1 +kind: ClowdEnvironment +metadata: + name: test-duplicate-name-validation +spec: + targetNamespace: test-duplicate-name-validation + providers: + web: + port: 8000 + mode: operator + metrics: + port: 9000 + mode: operator + path: "/metrics" + kafka: + mode: none + db: + mode: none + logging: + mode: none + objectStore: + mode: none + inMemoryDb: + mode: none + resourceDefaults: + limits: + cpu: 400m + memory: 1024Mi + requests: + cpu: 30m + memory: 512Mi + +--- +apiVersion: cloud.redhat.com/v1alpha1 +kind: ClowdApp +metadata: + name: test-app + namespace: test-duplicate-name-validation +spec: + envName: test-duplicate-name-validation + deployments: + - name: processor + podSpec: + image: quay.io/psav/clowder-hello \ No newline at end of file diff --git a/tests/kuttl/test-duplicate-name-validation/02-errors.yaml b/tests/kuttl/test-duplicate-name-validation/02-errors.yaml new file mode 100644 index 000000000..5b4448ff6 --- /dev/null +++ b/tests/kuttl/test-duplicate-name-validation/02-errors.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: cloud.redhat.com/v1alpha1 +kind: ClowdApp +metadata: + name: test-app + namespace: test-duplicate-name-validation-2 +spec: + envName: test-duplicate-name-validation + deployments: + - name: different-processor + podSpec: + image: quay.io/psav/clowder-hello \ No newline at end of file diff --git a/tests/kuttl/test-duplicate-name-validation/02-test-duplicate.yaml b/tests/kuttl/test-duplicate-name-validation/02-test-duplicate.yaml new file mode 100644 index 000000000..5b4448ff6 --- /dev/null +++ b/tests/kuttl/test-duplicate-name-validation/02-test-duplicate.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: cloud.redhat.com/v1alpha1 +kind: ClowdApp +metadata: + name: test-app + namespace: test-duplicate-name-validation-2 +spec: + envName: test-duplicate-name-validation + deployments: + - name: different-processor + podSpec: + image: quay.io/psav/clowder-hello \ No newline at end of file