diff --git a/api/v1beta2/common.go b/api/v1beta2/common.go index faa793d4f..fd338bc26 100644 --- a/api/v1beta2/common.go +++ b/api/v1beta2/common.go @@ -17,6 +17,7 @@ limitations under the License. package v1beta2 import ( + "regexp" "strconv" "k8s.io/apimachinery/pkg/util/intstr" @@ -42,16 +43,24 @@ func validateIBMPowerVSResourceReference(res IBMPowerVSResourceReference, resTyp if res.ID != nil && res.Name != nil { return false, field.Invalid(field.NewPath("spec", resType), res, "Only one of "+resType+" - ID or Name may be specified") } + return true, nil } func validateIBMPowerVSNetworkReference(res IBMPowerVSResourceReference) (bool, *field.Error) { + // Ensure only one of ID, Name, or RegEx is specified if (res.ID != nil && res.Name != nil) || (res.ID != nil && res.RegEx != nil) || (res.Name != nil && res.RegEx != nil) { return false, field.Invalid(field.NewPath("spec", "Network"), res, "Only one of Network - ID, Name or RegEx can be specified") } return true, nil } +// regexMatches validates if a given regex matches the target string. +func regexMatches(pattern, target string) bool { + matched, err := regexp.MatchString(pattern, target) + return err == nil && matched +} + func validateIBMPowerVSMemoryValues(resValue int32) bool { if val := float64(resValue); val < 2 { return false diff --git a/api/v1beta2/ibmpowervscluster_webhook.go b/api/v1beta2/ibmpowervscluster_webhook.go index aa548a800..40bd6591b 100644 --- a/api/v1beta2/ibmpowervscluster_webhook.go +++ b/api/v1beta2/ibmpowervscluster_webhook.go @@ -95,10 +95,48 @@ func (r *IBMPowerVSCluster) validateIBMPowerVSCluster() (admission.Warnings, err r.Name, allErrs) } +func (r *IBMPowerVSCluster) validateNetworkRegex() (bool, *field.Error) { + if r.Spec.Network.RegEx == nil { + return true, nil + } + var targetName string + var validationMessage string + + // If only spec.Network.RegEx is set and no other network resources are specified, + // the controller will create a DHCP server and the corresponding network in the format: + // DHCPSERVER_Private. + if r.Spec.DHCPServer != nil && *r.Spec.DHCPServer.Name != "" { + targetName = *r.Spec.DHCPServer.Name + validationMessage = "The RegEx should match the DHCP server name when the DHCP server is set" + } else { + if r.GetObjectMeta().GetName() == "" { + return false, field.Required( + field.NewPath("metadata", "name"), + "Cluster name must be set when Network.RegEx is provided and DHCP server name is not set", + ) + } + targetName = r.GetObjectMeta().GetName() + validationMessage = "The RegEx should match the cluster name when the DHCP server is not set" + } + + if !regexMatches(*r.Spec.Network.RegEx, targetName) { + return false, field.Invalid( + field.NewPath("spec", "Network", "RegEx"), + r.Spec.Network.RegEx, + validationMessage, + ) + } + + return true, nil +} + func (r *IBMPowerVSCluster) validateIBMPowerVSClusterNetwork() *field.Error { if res, err := validateIBMPowerVSNetworkReference(r.Spec.Network); !res { return err } + if res, err := r.validateNetworkRegex(); !res { + return err + } return nil } diff --git a/api/v1beta2/ibmpowervscluster_webhook_test.go b/api/v1beta2/ibmpowervscluster_webhook_test.go index 67f3cd6ce..4b860989e 100644 --- a/api/v1beta2/ibmpowervscluster_webhook_test.go +++ b/api/v1beta2/ibmpowervscluster_webhook_test.go @@ -68,6 +68,63 @@ func TestIBMPowerVSCluster_create(t *testing.T) { }, wantErr: true, }, + { + name: "Should error network regex and dhcp server name is set but does not match dhcp server name", + powervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + DHCPServer: &DHCPServer{ + Name: ptr.To("test"), + }, + }, + }, + wantErr: true, + }, + { + name: "Should allow if network regex, dhcp server name is set and matches dhcp server name", + powervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + DHCPServer: &DHCPServer{ + Name: ptr.To("capi"), + }, + }, + }, + wantErr: false, + }, + { + name: "Should error if only network regex is set, dhcp server name is not set and does not match cluster name", + powervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + }, + wantErr: true, + }, + { + name: "Should allow if network regex is set, dhcp server name is not set and matches cluster name", + powervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi-cluster-.*"), + }, + }, + }, + wantErr: false, + }, } for _, tc := range tests { @@ -141,6 +198,9 @@ func TestIBMPowerVSCluster_update(t *testing.T) { Network: IBMPowerVSResourceReference{ RegEx: ptr.To("^capi-net-id$"), }, + DHCPServer: &DHCPServer{ + Name: ptr.To("capi-net-id"), + }, }, }, newPowervsCluster: &IBMPowerVSCluster{ @@ -149,6 +209,9 @@ func TestIBMPowerVSCluster_update(t *testing.T) { Network: IBMPowerVSResourceReference{ RegEx: ptr.To("^capi-net-id$"), }, + DHCPServer: &DHCPServer{ + Name: ptr.To("capi-net-id"), + }, }, }, wantErr: false, @@ -175,6 +238,107 @@ func TestIBMPowerVSCluster_update(t *testing.T) { }, wantErr: true, }, + { + name: "Should error if network regex and dhcp server name is set but network regex does not match dhcp server name", + oldPowervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + DHCPServer: &DHCPServer{ + Name: ptr.To("capi"), + }, + }, + }, + newPowervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + DHCPServer: &DHCPServer{ + Name: ptr.To("test"), + }, + }, + }, + wantErr: true, + }, + { + name: "Should allow if network regex, dhcp server name is set and network regex matches dhcp server name", + oldPowervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + DHCPServer: &DHCPServer{ + Name: ptr.To("capi"), + }, + }, + }, + newPowervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + DHCPServer: &DHCPServer{ + Name: ptr.To("capi"), + }, + }, + }, + wantErr: false, + }, + { + name: "Should error if network regex is set, dhcp server name is not set and network regex does not match cluster name", + oldPowervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + DHCPServer: &DHCPServer{ + Name: ptr.To("capi"), + }, + }, + }, + newPowervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "capi", + }, + }, + wantErr: true, + }, + { + name: "Should allow if network regex is set, dhcp server name is not set and network regex matches cluster name", + oldPowervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi$"), + }, + DHCPServer: &DHCPServer{ + Name: ptr.To("capi"), + }, + }, + }, + newPowervsCluster: &IBMPowerVSCluster{ + Spec: IBMPowerVSClusterSpec{ + ServiceInstanceID: "capi-si-id", + Network: IBMPowerVSResourceReference{ + RegEx: ptr.To("^capi-cluster-.*"), + }, + }, + }, + wantErr: false, + }, } for _, tc := range tests {