IPAddress validation
Validate IPAddress name is in canonical format Validate ParentRef is required, and Resource and Name. Validate IPAddress is inmutable on update.
This commit is contained in:
parent
036f57f3cb
commit
c36562dfd7
@ -18,6 +18,7 @@ package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@ -741,3 +742,77 @@ func validateClusterCIDRUpdateSpec(update, old *networking.ClusterCIDRSpec, fldP
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateIPAddressName validates that the name is the decimal representation of an IP address.
|
||||
// IPAddress does not support generating names, prefix is not considered.
|
||||
func ValidateIPAddressName(name string, prefix bool) []string {
|
||||
var errs []string
|
||||
ip, err := netip.ParseAddr(name)
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
} else if ip.String() != name {
|
||||
errs = append(errs, "not a valid ip in canonical format")
|
||||
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func ValidateIPAddress(ipAddress *networking.IPAddress) field.ErrorList {
|
||||
allErrs := apivalidation.ValidateObjectMeta(&ipAddress.ObjectMeta, false, ValidateIPAddressName, field.NewPath("metadata"))
|
||||
errs := validateIPAddressParentReference(ipAddress.Spec.ParentRef, field.NewPath("spec"))
|
||||
allErrs = append(allErrs, errs...)
|
||||
return allErrs
|
||||
|
||||
}
|
||||
|
||||
// validateIPAddressParentReference ensures that the IPAddress ParenteReference exists and is valid.
|
||||
func validateIPAddressParentReference(params *networking.ParentReference, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if params == nil {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("parentRef"), ""))
|
||||
return allErrs
|
||||
}
|
||||
|
||||
fldPath = fldPath.Child("parentRef")
|
||||
// group is required but the Core group used by Services is the empty value, so it can not be enforced
|
||||
if params.Group != "" {
|
||||
for _, msg := range validation.IsDNS1123Subdomain(params.Group) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), params.Group, msg))
|
||||
}
|
||||
}
|
||||
|
||||
// resource is required
|
||||
if params.Resource == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("resource"), ""))
|
||||
} else {
|
||||
for _, msg := range pathvalidation.IsValidPathSegmentName(params.Resource) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("resource"), params.Resource, msg))
|
||||
}
|
||||
}
|
||||
|
||||
// name is required
|
||||
if params.Name == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
} else {
|
||||
for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg))
|
||||
}
|
||||
}
|
||||
|
||||
// namespace is optional
|
||||
if params.Namespace != "" {
|
||||
for _, msg := range pathvalidation.IsValidPathSegmentName(params.Namespace) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), params.Namespace, msg))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateIPAddressUpdate tests if an update to an IPAddress is valid.
|
||||
func ValidateIPAddressUpdate(update, old *networking.IPAddress) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateImmutableField(update.Spec.ParentRef, old.Spec.ParentRef, field.NewPath("spec").Child("parentRef"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
@ -2195,3 +2195,215 @@ func TestValidateClusterConfigUpdate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateIPAddress(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
expectedErrors int
|
||||
ipAddress *networking.IPAddress
|
||||
}{
|
||||
"empty-ipaddress-bad-name": {
|
||||
expectedErrors: 1,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: networking.IPAddressSpec{
|
||||
ParentRef: &networking.ParentReference{
|
||||
Group: "",
|
||||
Resource: "services",
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"empty-ipaddress-bad-name-no-parent-reference": {
|
||||
expectedErrors: 2,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"good-ipaddress": {
|
||||
expectedErrors: 0,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "192.168.1.1",
|
||||
},
|
||||
Spec: networking.IPAddressSpec{
|
||||
ParentRef: &networking.ParentReference{
|
||||
Group: "",
|
||||
Resource: "services",
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"good-ipaddress-gateway": {
|
||||
expectedErrors: 0,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "192.168.1.1",
|
||||
},
|
||||
Spec: networking.IPAddressSpec{
|
||||
ParentRef: &networking.ParentReference{
|
||||
Group: "gateway.networking.k8s.io",
|
||||
Resource: "gateway",
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"good-ipv6address": {
|
||||
expectedErrors: 0,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "2001:4860:4860::8888",
|
||||
},
|
||||
Spec: networking.IPAddressSpec{
|
||||
ParentRef: &networking.ParentReference{
|
||||
Group: "",
|
||||
Resource: "services",
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"non-canonica-ipv6address": {
|
||||
expectedErrors: 1,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "2001:4860:4860:0::8888",
|
||||
},
|
||||
Spec: networking.IPAddressSpec{
|
||||
ParentRef: &networking.ParentReference{
|
||||
Group: "",
|
||||
Resource: "services",
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"missing-ipaddress-reference": {
|
||||
expectedErrors: 1,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "192.168.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"wrong-ipaddress-reference": {
|
||||
expectedErrors: 1,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "192.168.1.1",
|
||||
},
|
||||
Spec: networking.IPAddressSpec{
|
||||
ParentRef: &networking.ParentReference{
|
||||
Group: "custom.resource.com",
|
||||
Resource: "services",
|
||||
Name: "foo$%&",
|
||||
Namespace: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"wrong-ipaddress-reference-multiple-errors": {
|
||||
expectedErrors: 4,
|
||||
ipAddress: &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "192.168.1.1",
|
||||
},
|
||||
Spec: networking.IPAddressSpec{
|
||||
ParentRef: &networking.ParentReference{
|
||||
Group: ".cust@m.resource.com",
|
||||
Resource: "",
|
||||
Name: "",
|
||||
Namespace: "bar$$$$$%@",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
errs := ValidateIPAddress(testCase.ipAddress)
|
||||
if len(errs) != testCase.expectedErrors {
|
||||
t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateIPAddressUpdate(t *testing.T) {
|
||||
old := &networking.IPAddress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "192.168.1.1",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Spec: networking.IPAddressSpec{
|
||||
ParentRef: &networking.ParentReference{
|
||||
Group: "custom.resource.com",
|
||||
Resource: "services",
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
new func(svc *networking.IPAddress) *networking.IPAddress
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "Successful update, no changes",
|
||||
new: func(old *networking.IPAddress) *networking.IPAddress {
|
||||
out := old.DeepCopy()
|
||||
return out
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "Failed update, update spec.ParentRef",
|
||||
new: func(svc *networking.IPAddress) *networking.IPAddress {
|
||||
out := svc.DeepCopy()
|
||||
out.Spec.ParentRef = &networking.ParentReference{
|
||||
Group: "custom.resource.com",
|
||||
Resource: "Gateway",
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
}
|
||||
|
||||
return out
|
||||
}, expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Failed update, delete spec.ParentRef",
|
||||
new: func(svc *networking.IPAddress) *networking.IPAddress {
|
||||
out := svc.DeepCopy()
|
||||
out.Spec.ParentRef = nil
|
||||
return out
|
||||
}, expectErr: true,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err := ValidateIPAddressUpdate(testCase.new(old), old)
|
||||
if !testCase.expectErr && err != nil {
|
||||
t.Errorf("ValidateIPAddressUpdate must be successful for test '%s', got %v", testCase.name, err)
|
||||
}
|
||||
if testCase.expectErr && err == nil {
|
||||
t.Errorf("ValidateIPAddressUpdate must return error for test: %s, but got nil", testCase.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user