Skip to content

Commit 21145a6

Browse files
committed
validate cross-referenced parameters
1 parent 4831349 commit 21145a6

4 files changed

Lines changed: 409 additions & 8 deletions

File tree

cmd/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"crypto/tls"
2222
"flag"
2323
"fmt"
24+
"maps"
2425
"os"
2526
"path/filepath"
2627
"time"
@@ -42,6 +43,7 @@ import (
4243

4344
"go.uber.org/zap/zapcore"
4445
"k8s.io/apimachinery/pkg/runtime"
46+
"k8s.io/apimachinery/pkg/runtime/schema"
4547
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
4648
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
4749
_ "k8s.io/client-go/plugin/pkg/client/auth"
@@ -329,10 +331,14 @@ func main() {
329331
}
330332
}
331333

334+
validators := make(map[schema.GroupVersionResource]validation.Validator, len(validation.DefaultValidators))
335+
maps.Copy(validators, validation.DefaultValidators)
336+
validation.RegisterPostgresClusterCrossValidation(validators, mgr.GetAPIReader())
337+
332338
webhookServer := validation.NewWebhookServer(validation.WebhookServerOptions{
333339
Port: 9443,
334340
CertDir: "/tmp/k8s-webhook-server/serving-certs",
335-
Validators: validation.DefaultValidators,
341+
Validators: validators,
336342
ReadTimeout: readTimeout,
337343
WriteTimeout: writeTimeout,
338344
})

pkg/splunk/enterprise/validation/postgrescluster_validation.go

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@ limitations under the License.
1717
package validation
1818

1919
import (
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
5361
func 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

Comments
 (0)