@@ -24,10 +24,12 @@ import (
2424 "testing"
2525
2626 admissionv1 "k8s.io/api/admission/v1"
27+ "k8s.io/apimachinery/pkg/api/resource"
2728 authenticationv1 "k8s.io/api/authentication/v1"
2829 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930 "k8s.io/apimachinery/pkg/runtime"
3031 "k8s.io/apimachinery/pkg/types"
32+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
3133
3234 enterpriseApi "github.com/splunk/splunk-operator/api/v4"
3335 "github.com/splunk/splunk-operator/pkg/splunk/enterprise/validation"
@@ -557,3 +559,166 @@ func TestPostgresClusterClassPgHBAUpdateIntegration(t *testing.T) {
557559 assert .Contains (t , resp .Result .Message , "unknown auth method" )
558560 })
559561}
562+
563+ func ptrBool (b bool ) * bool { return & b }
564+ func ptrString (s string ) * string { return & s }
565+ func ptrInt32 (i int32 ) * int32 { return & i }
566+
567+ func ptrQuantity (s string ) * resource.Quantity {
568+ q := resource .MustParse (s )
569+ return & q
570+ }
571+
572+ func newFakeReader (objects ... runtime.Object ) * fake.ClientBuilder {
573+ s := runtime .NewScheme ()
574+ enterpriseApi .AddToScheme (s )
575+ b := fake .NewClientBuilder ().WithScheme (s )
576+ for _ , obj := range objects {
577+ b = b .WithRuntimeObjects (obj )
578+ }
579+ return b
580+ }
581+
582+ func TestCrossResourceValidationIntegration (t * testing.T ) {
583+ prodClass := & enterpriseApi.PostgresClusterClass {
584+ ObjectMeta : metav1.ObjectMeta {Name : "prod" },
585+ Spec : enterpriseApi.PostgresClusterClassSpec {
586+ Provisioner : "postgresql.cnpg.io" ,
587+ Config : & enterpriseApi.PostgresClusterClassConfig {
588+ Instances : ptrInt32 (3 ),
589+ Storage : ptrQuantity ("50Gi" ),
590+ PostgresVersion : ptrString ("17" ),
591+ ConnectionPoolerEnabled : ptrBool (false ),
592+ },
593+ },
594+ }
595+
596+ reader := newFakeReader (prodClass ).Build ()
597+
598+ server := validation .NewWebhookServer (validation.WebhookServerOptions {
599+ Port : 9443 ,
600+ Validators : validation .DefaultValidators ,
601+ Reader : reader ,
602+ })
603+
604+ tests := []struct {
605+ name string
606+ obj * enterpriseApi.PostgresCluster
607+ wantAllowed bool
608+ wantMessage string
609+ }{
610+ {
611+ name : "allowed - inherits all from class" ,
612+ obj : & enterpriseApi.PostgresCluster {
613+ TypeMeta : metav1.TypeMeta {APIVersion : "enterprise.splunk.com/v4" , Kind : "PostgresCluster" },
614+ ObjectMeta : metav1.ObjectMeta {Name : "test" , Namespace : "default" },
615+ Spec : enterpriseApi.PostgresClusterSpec {Class : "prod" },
616+ },
617+ wantAllowed : true ,
618+ },
619+ {
620+ name : "rejected - class not found" ,
621+ obj : & enterpriseApi.PostgresCluster {
622+ TypeMeta : metav1.TypeMeta {APIVersion : "enterprise.splunk.com/v4" , Kind : "PostgresCluster" },
623+ ObjectMeta : metav1.ObjectMeta {Name : "test" , Namespace : "default" },
624+ Spec : enterpriseApi.PostgresClusterSpec {Class : "nonexistent" },
625+ },
626+ wantAllowed : false ,
627+ wantMessage : "PostgresClusterClass not found" ,
628+ },
629+ {
630+ name : "rejected - storage below class floor" ,
631+ obj : & enterpriseApi.PostgresCluster {
632+ TypeMeta : metav1.TypeMeta {APIVersion : "enterprise.splunk.com/v4" , Kind : "PostgresCluster" },
633+ ObjectMeta : metav1.ObjectMeta {Name : "test" , Namespace : "default" },
634+ Spec : enterpriseApi.PostgresClusterSpec {
635+ Class : "prod" ,
636+ Storage : ptrQuantity ("10Gi" ),
637+ },
638+ },
639+ wantAllowed : false ,
640+ wantMessage : "storage cannot be lower than class default" ,
641+ },
642+ {
643+ name : "rejected - version below class floor" ,
644+ obj : & enterpriseApi.PostgresCluster {
645+ TypeMeta : metav1.TypeMeta {APIVersion : "enterprise.splunk.com/v4" , Kind : "PostgresCluster" },
646+ ObjectMeta : metav1.ObjectMeta {Name : "test" , Namespace : "default" },
647+ Spec : enterpriseApi.PostgresClusterSpec {
648+ Class : "prod" ,
649+ PostgresVersion : ptrString ("16" ),
650+ },
651+ },
652+ wantAllowed : false ,
653+ wantMessage : "postgresVersion cannot be lower than class default" ,
654+ },
655+ {
656+ name : "rejected - pooler enabled when class disables" ,
657+ obj : & enterpriseApi.PostgresCluster {
658+ TypeMeta : metav1.TypeMeta {APIVersion : "enterprise.splunk.com/v4" , Kind : "PostgresCluster" },
659+ ObjectMeta : metav1.ObjectMeta {Name : "test" , Namespace : "default" },
660+ Spec : enterpriseApi.PostgresClusterSpec {
661+ Class : "prod" ,
662+ ConnectionPoolerEnabled : ptrBool (true ),
663+ },
664+ },
665+ wantAllowed : false ,
666+ wantMessage : "connectionPoolerEnabled cannot be enabled" ,
667+ },
668+ {
669+ name : "allowed - storage equal to class" ,
670+ obj : & enterpriseApi.PostgresCluster {
671+ TypeMeta : metav1.TypeMeta {APIVersion : "enterprise.splunk.com/v4" , Kind : "PostgresCluster" },
672+ ObjectMeta : metav1.ObjectMeta {Name : "test" , Namespace : "default" },
673+ Spec : enterpriseApi.PostgresClusterSpec {
674+ Class : "prod" ,
675+ Storage : ptrQuantity ("50Gi" ),
676+ },
677+ },
678+ wantAllowed : true ,
679+ },
680+ {
681+ name : "allowed - higher version" ,
682+ obj : & enterpriseApi.PostgresCluster {
683+ TypeMeta : metav1.TypeMeta {APIVersion : "enterprise.splunk.com/v4" , Kind : "PostgresCluster" },
684+ ObjectMeta : metav1.ObjectMeta {Name : "test" , Namespace : "default" },
685+ Spec : enterpriseApi.PostgresClusterSpec {
686+ Class : "prod" ,
687+ PostgresVersion : ptrString ("18" ),
688+ },
689+ },
690+ wantAllowed : true ,
691+ },
692+ }
693+
694+ for _ , tt := range tests {
695+ t .Run (tt .name , func (t * testing.T ) {
696+ ar := newPostgresClusterAdmissionReview (t , "uid-xref-" + tt .name , admissionv1 .Create , tt .obj , nil )
697+ resp := sendAdmissionReview (t , server , ar )
698+
699+ assert .Equal (t , tt .wantAllowed , resp .Allowed , "unexpected admission result" )
700+ if tt .wantMessage != "" {
701+ require .NotNil (t , resp .Result )
702+ assert .Contains (t , resp .Result .Message , tt .wantMessage )
703+ }
704+ })
705+ }
706+ }
707+
708+ func TestCrossResourceValidationDisabledWithoutReader (t * testing.T ) {
709+ server := validation .NewWebhookServer (validation.WebhookServerOptions {
710+ Port : 9443 ,
711+ Validators : validation .DefaultValidators ,
712+ })
713+
714+ obj := & enterpriseApi.PostgresCluster {
715+ TypeMeta : metav1.TypeMeta {APIVersion : "enterprise.splunk.com/v4" , Kind : "PostgresCluster" },
716+ ObjectMeta : metav1.ObjectMeta {Name : "test" , Namespace : "default" },
717+ Spec : enterpriseApi.PostgresClusterSpec {Class : "nonexistent" },
718+ }
719+
720+ ar := newPostgresClusterAdmissionReview (t , "uid-no-reader" , admissionv1 .Create , obj , nil )
721+ resp := sendAdmissionReview (t , server , ar )
722+
723+ assert .True (t , resp .Allowed , "without a reader, cross-resource validation should be skipped" )
724+ }
0 commit comments