@@ -17,14 +17,18 @@ limitations under the License.
1717package validation
1818
1919import (
20+ "context"
21+ "strconv"
22+
2023 "k8s.io/apimachinery/pkg/util/validation/field"
24+ "sigs.k8s.io/controller-runtime/pkg/client"
2125
2226 enterpriseApi "github.com/splunk/splunk-operator/api/v4"
2327 hba "github.com/splunk/splunk-operator/pkg/postgresql/cluster/core"
2428)
2529
2630// ValidatePostgresClusterCreate validates a PostgresCluster on CREATE.
27- func ValidatePostgresClusterCreate (obj * enterpriseApi.PostgresCluster ) field.ErrorList {
31+ func ValidatePostgresClusterCreate (obj * enterpriseApi.PostgresCluster , reader client. Reader ) field.ErrorList {
2832 var allErrs field.ErrorList
2933
3034 if len (obj .Spec .PgHBA ) > 0 {
@@ -36,12 +40,16 @@ func ValidatePostgresClusterCreate(obj *enterpriseApi.PostgresCluster) field.Err
3640 }
3741 }
3842
43+ if reader != nil {
44+ allErrs = append (allErrs , validateAgainstClass (obj , reader )... )
45+ }
46+
3947 return allErrs
4048}
4149
4250// ValidatePostgresClusterUpdate validates a PostgresCluster on UPDATE.
43- func ValidatePostgresClusterUpdate (obj , oldObj * enterpriseApi.PostgresCluster ) field.ErrorList {
44- return ValidatePostgresClusterCreate (obj )
51+ func ValidatePostgresClusterUpdate (obj , oldObj * enterpriseApi.PostgresCluster , reader client. Reader ) field.ErrorList {
52+ return ValidatePostgresClusterCreate (obj , reader )
4553}
4654
4755// GetPostgresClusterWarningsOnCreate returns warnings for PostgresCluster CREATE.
@@ -53,3 +61,97 @@ func GetPostgresClusterWarningsOnCreate(obj *enterpriseApi.PostgresCluster) []st
5361func GetPostgresClusterWarningsOnUpdate (obj , oldObj * enterpriseApi.PostgresCluster ) []string {
5462 return nil
5563}
64+
65+ func validateAgainstClass (obj * enterpriseApi.PostgresCluster , reader client.Reader ) field.ErrorList {
66+ var allErrs field.ErrorList
67+
68+ class := & enterpriseApi.PostgresClusterClass {}
69+ if err := reader .Get (context .Background (), client.ObjectKey {Name : obj .Spec .Class }, class ); err != nil {
70+ allErrs = append (allErrs , field .Invalid (
71+ field .NewPath ("spec" ).Child ("class" ),
72+ obj .Spec .Class ,
73+ "referenced PostgresClusterClass not found" ))
74+ return allErrs
75+ }
76+
77+ classConfig := class .Spec .Config
78+
79+ // Merge completeness: after overlaying cluster on class, instances/postgresVersion/storage must be resolved
80+ mergedInstances := obj .Spec .Instances
81+ mergedVersion := obj .Spec .PostgresVersion
82+ mergedStorage := obj .Spec .Storage
83+ if classConfig != nil {
84+ if mergedInstances == nil {
85+ mergedInstances = classConfig .Instances
86+ }
87+ if mergedVersion == nil {
88+ mergedVersion = classConfig .PostgresVersion
89+ }
90+ if mergedStorage == nil {
91+ mergedStorage = classConfig .Storage
92+ }
93+ }
94+ specPath := field .NewPath ("spec" )
95+ if mergedInstances == nil {
96+ allErrs = append (allErrs , field .Required (specPath .Child ("instances" ),
97+ "must be set in PostgresCluster or PostgresClusterClass" ))
98+ }
99+ if mergedVersion == nil {
100+ allErrs = append (allErrs , field .Required (specPath .Child ("postgresVersion" ),
101+ "must be set in PostgresCluster or PostgresClusterClass" ))
102+ }
103+ if mergedStorage == nil {
104+ allErrs = append (allErrs , field .Required (specPath .Child ("storage" ),
105+ "must be set in PostgresCluster or PostgresClusterClass" ))
106+ }
107+
108+ if classConfig == nil {
109+ return allErrs
110+ }
111+
112+ // Storage: cluster cannot be lower than class
113+ if obj .Spec .Storage != nil && classConfig .Storage != nil {
114+ if obj .Spec .Storage .Cmp (* classConfig .Storage ) < 0 {
115+ allErrs = append (allErrs , field .Invalid (
116+ field .NewPath ("spec" ).Child ("storage" ),
117+ obj .Spec .Storage .String (),
118+ "storage cannot be lower than class default (" + classConfig .Storage .String ()+ ")" ))
119+ }
120+ }
121+
122+ // PostgresVersion: cluster major version cannot be lower than class
123+ if obj .Spec .PostgresVersion != nil && classConfig .PostgresVersion != nil {
124+ clusterMajor := parseMajorVersion (* obj .Spec .PostgresVersion )
125+ classMajor := parseMajorVersion (* classConfig .PostgresVersion )
126+ if clusterMajor > 0 && classMajor > 0 && clusterMajor < classMajor {
127+ allErrs = append (allErrs , field .Invalid (
128+ field .NewPath ("spec" ).Child ("postgresVersion" ),
129+ * obj .Spec .PostgresVersion ,
130+ "postgresVersion cannot be lower than class default (" + * classConfig .PostgresVersion + ")" ))
131+ }
132+ }
133+
134+ // ConnectionPoolerEnabled: cannot enable if class disables
135+ if obj .Spec .ConnectionPoolerEnabled != nil && * obj .Spec .ConnectionPoolerEnabled {
136+ classDisabled := classConfig .ConnectionPoolerEnabled == nil || ! * classConfig .ConnectionPoolerEnabled
137+ if classDisabled {
138+ allErrs = append (allErrs , field .Invalid (
139+ field .NewPath ("spec" ).Child ("connectionPoolerEnabled" ),
140+ true ,
141+ "connectionPoolerEnabled cannot be enabled when disabled in PostgresClusterClass" ))
142+ }
143+ }
144+
145+ return allErrs
146+ }
147+
148+ func parseMajorVersion (version string ) int {
149+ for i , ch := range version {
150+ if ch == '.' {
151+ v , _ := strconv .Atoi (version [:i ])
152+ return v
153+ }
154+ }
155+ v , _ := strconv .Atoi (version )
156+ return v
157+ }
0 commit comments