Merge pull request #3516 from XiShanYongYe-Chang/add-validation-for-mci
add validation for MultiClusterIngress
This commit is contained in:
commit
479b073dc4
|
@ -167,3 +167,17 @@ webhooks:
|
|||
sideEffects: None
|
||||
admissionReviewVersions: [ "v1" ]
|
||||
timeoutSeconds: 10
|
||||
- name: multiclusteringress.karmada.io
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
apiGroups: ["networking.karmada.io"]
|
||||
apiVersions: ["*"]
|
||||
resources: ["multiclusteringresses"]
|
||||
scope: "Namespaced"
|
||||
clientConfig:
|
||||
url: https://karmada-webhook.karmada-system.svc:443/validate-multiclusteringress
|
||||
caBundle: {{caBundle}}
|
||||
failurePolicy: Fail
|
||||
sideEffects: None
|
||||
admissionReviewVersions: [ "v1" ]
|
||||
timeoutSeconds: 10
|
||||
|
|
|
@ -171,4 +171,18 @@ webhooks:
|
|||
sideEffects: None
|
||||
admissionReviewVersions: [ "v1" ]
|
||||
timeoutSeconds: 3
|
||||
- name: multiclusteringress.karmada.io
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
apiGroups: ["networking.karmada.io"]
|
||||
apiVersions: ["*"]
|
||||
resources: ["multiclusteringresses"]
|
||||
scope: "Namespaced"
|
||||
clientConfig:
|
||||
url: https://{{ $name }}-webhook.{{ $namespace }}.svc:443/validate-multiclusteringress
|
||||
{{- include "karmada.webhook.caBundle" . | nindent 6 }}
|
||||
failurePolicy: Fail
|
||||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 3
|
||||
{{- end -}}
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/karmada-io/karmada/pkg/webhook/clusterpropagationpolicy"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/configuration"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/federatedresourcequota"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/multiclusteringress"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/overridepolicy"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/propagationpolicy"
|
||||
"github.com/karmada-io/karmada/pkg/webhook/resourceinterpretercustomization"
|
||||
|
@ -127,6 +128,7 @@ func Run(ctx context.Context, opts *options.Options) error {
|
|||
hookServer.Register("/validate-resourceinterpreterwebhookconfiguration", &webhook.Admission{Handler: &configuration.ValidatingAdmission{}})
|
||||
hookServer.Register("/validate-federatedresourcequota", &webhook.Admission{Handler: &federatedresourcequota.ValidatingAdmission{}})
|
||||
hookServer.Register("/validate-resourceinterpretercustomization", &webhook.Admission{Handler: &resourceinterpretercustomization.ValidatingAdmission{Client: hookManager.GetClient()}})
|
||||
hookServer.Register("/validate-multiclusteringress", &webhook.Admission{Handler: &multiclusteringress.ValidatingAdmission{}})
|
||||
hookServer.WebhookMux.Handle("/readyz/", http.StripPrefix("/readyz/", &healthz.Handler{}))
|
||||
|
||||
// blocks until the context is done.
|
||||
|
|
|
@ -175,5 +175,19 @@ webhooks:
|
|||
sideEffects: None
|
||||
admissionReviewVersions: [ "v1" ]
|
||||
timeoutSeconds: 3
|
||||
- name: multiclusteringress.karmada.io
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
apiGroups: ["networking.karmada.io"]
|
||||
apiVersions: ["*"]
|
||||
resources: ["multiclusteringresses"]
|
||||
scope: "Namespaced"
|
||||
clientConfig:
|
||||
url: https://{{ .Service }}.{{ .Namespace }}.svc:443/validate-multiclusteringress
|
||||
caBundle: {{ .CaBundle }}
|
||||
failurePolicy: Fail
|
||||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 3
|
||||
`
|
||||
)
|
||||
|
|
|
@ -186,6 +186,20 @@ webhooks:
|
|||
sideEffects: None
|
||||
admissionReviewVersions: [ "v1" ]
|
||||
timeoutSeconds: 3
|
||||
- name: multiclusteringress.karmada.io
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
apiGroups: ["networking.karmada.io"]
|
||||
apiVersions: ["*"]
|
||||
resources: ["multiclusteringresses"]
|
||||
scope: "Namespaced"
|
||||
clientConfig:
|
||||
url: https://karmada-webhook.%[1]s.svc:443/validate-multiclusteringress
|
||||
caBundle: %[2]s
|
||||
failurePolicy: Fail
|
||||
sideEffects: None
|
||||
admissionReviewVersions: ["v1"]
|
||||
timeoutSeconds: 3
|
||||
`, systemNamespace, caBundle)
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,20 @@ package lifted
|
|||
| taint.go | https://github.com/kubernetes/kubernetes/blob/release-1.23/staging/src/k8s.io/kubectl/pkg/cmd/taint/utils.go#L120-L126 | func validateTaintEffect | N |
|
||||
| validateclustertaints.go | https://github.com/kubernetes/kubernetes/blob/release-1.23/pkg/apis/core/validation/validation.go#L5001-L5033 | func ValidateClusterTaints | Y |
|
||||
| validateclustertaints.go | https://github.com/kubernetes/kubernetes/blob/release-1.23/pkg/apis/core/validation/validation.go#L3305-L3326 | func validateClusterTaintEffect | Y |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L326-L348 | func ValidateIngressSpec | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L468C1-L509 | func validateIngressBackend | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L547C1-L578 | func validateIngressTypedLocalObjectReference | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L379-L409 | func validateIngressRules | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L411C1-L417 | func validateIngressRuleValue | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L419-L428 | func validateHTTPIngressRuleValue | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L430-L466 | func validateHTTPIngressPath | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L299-L324 | func validateIngressTLS | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L641-L646 | func validateTLSSecretName | N |
|
||||
| validatingmci.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L357-L377 | func ValidateIngressLoadBalancerStatus | N |
|
||||
| validatingmci_test.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation_test.go | func TestValidateIngress | Y |
|
||||
| validatingmci_test.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation_test.go | func TestValidateIngressTLS | N |
|
||||
| validatingmci_test.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation_test.go | func TestValidateEmptyIngressTLS | N |
|
||||
| validatingmci_test.go | https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation_test.go | func TestValidateIngressStatusUpdate | Y |
|
||||
| visitpod.go | https://github.com/kubernetes/kubernetes/blob/release-1.23/pkg/api/v1/pod/util.go#L53-L63 | type ContainerType | N |
|
||||
| visitpod.go | https://github.com/kubernetes/kubernetes/blob/release-1.23/pkg/api/v1/pod/util.go#L65-L66 | const AllContainers | N |
|
||||
| visitpod.go | https://github.com/kubernetes/kubernetes/blob/release-1.23/pkg/api/v1/pod/util.go#L78-L80 | type ContainerVisitor | N |
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This code is directly lifted from the Kubernetes codebase.
|
||||
// For reference:
|
||||
// https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/networking/validation/validation.go
|
||||
|
||||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
pathvalidation "k8s.io/apimachinery/pkg/api/validation/path"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
netutils "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
var validateServiceName = apimachineryvalidation.NameIsDNS1035Label
|
||||
var validateSecretName = apimachineryvalidation.NameIsDNSSubdomain
|
||||
|
||||
var validateIngressClassName = apimachineryvalidation.NameIsDNSSubdomain
|
||||
|
||||
var (
|
||||
supportedPathTypes = sets.NewString(
|
||||
string(networkingv1.PathTypeExact),
|
||||
string(networkingv1.PathTypePrefix),
|
||||
string(networkingv1.PathTypeImplementationSpecific),
|
||||
)
|
||||
invalidPathSequences = []string{"//", "/./", "/../", "%2f", "%2F"}
|
||||
invalidPathSuffixes = []string{"/..", "/."}
|
||||
)
|
||||
|
||||
// IngressValidationOptions cover beta to GA transitions for HTTP PathType
|
||||
type IngressValidationOptions struct {
|
||||
// AllowInvalidSecretName indicates whether spec.tls[*].secretName values that are not valid Secret names should be allowed
|
||||
AllowInvalidSecretName bool
|
||||
|
||||
// AllowInvalidWildcardHostRule indicates whether invalid rule values are allowed in rules with wildcard hostnames
|
||||
AllowInvalidWildcardHostRule bool
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L326-L348
|
||||
|
||||
// ValidateIngressSpec tests if required fields in the IngressSpec are set.
|
||||
func ValidateIngressSpec(spec *networkingv1.IngressSpec, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(spec.Rules) == 0 && spec.DefaultBackend == nil {
|
||||
errMsg := fmt.Sprintf("either `%s` or `rules` must be specified", "defaultBackend")
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, spec.Rules, errMsg))
|
||||
}
|
||||
if spec.DefaultBackend != nil {
|
||||
allErrs = append(allErrs, validateIngressBackend(spec.DefaultBackend, fldPath.Child("defaultBackend"), opts)...)
|
||||
}
|
||||
if len(spec.Rules) > 0 {
|
||||
allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"), opts)...)
|
||||
}
|
||||
if len(spec.TLS) > 0 {
|
||||
allErrs = append(allErrs, validateIngressTLS(spec, fldPath.Child("tls"), opts)...)
|
||||
}
|
||||
if spec.IngressClassName != nil {
|
||||
for _, msg := range validateIngressClassName(*spec.IngressClassName, false) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("ingressClassName"), *spec.IngressClassName, msg))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L468C1-L509
|
||||
|
||||
func validateIngressBackend(backend *networkingv1.IngressBackend, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
hasResourceBackend := backend.Resource != nil
|
||||
hasServiceBackend := backend.Service != nil
|
||||
|
||||
switch {
|
||||
case hasResourceBackend && hasServiceBackend:
|
||||
return append(allErrs, field.Invalid(fldPath, "", "cannot set both resource and service backends"))
|
||||
case hasResourceBackend:
|
||||
allErrs = append(allErrs, validateIngressTypedLocalObjectReference(backend.Resource, fldPath.Child("resource"))...)
|
||||
case hasServiceBackend:
|
||||
if len(backend.Service.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("service", "name"), ""))
|
||||
} else {
|
||||
for _, msg := range validateServiceName(backend.Service.Name, false) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "name"), backend.Service.Name, msg))
|
||||
}
|
||||
}
|
||||
|
||||
hasPortName := len(backend.Service.Port.Name) > 0
|
||||
hasPortNumber := backend.Service.Port.Number != 0
|
||||
if hasPortName && hasPortNumber {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "", "cannot set both port name & port number"))
|
||||
} else if hasPortName {
|
||||
for _, msg := range validation.IsValidPortName(backend.Service.Port.Name) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "port", "name"), backend.Service.Port.Name, msg))
|
||||
}
|
||||
} else if hasPortNumber {
|
||||
for _, msg := range validation.IsValidPortNum(int(backend.Service.Port.Number)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "port", "number"), backend.Service.Port.Number, msg))
|
||||
}
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Required(fldPath, "port name or number is required"))
|
||||
}
|
||||
default:
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "", "resource or service backend is required"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L547C1-L578
|
||||
|
||||
func validateIngressTypedLocalObjectReference(params *corev1.TypedLocalObjectReference, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if params == nil {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
if params.APIGroup != nil {
|
||||
for _, msg := range validation.IsDNS1123Subdomain(*params.APIGroup) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), *params.APIGroup, msg))
|
||||
}
|
||||
}
|
||||
|
||||
if params.Kind == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "kind is required"))
|
||||
} else {
|
||||
for _, msg := range pathvalidation.IsValidPathSegmentName(params.Kind) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), params.Kind, msg))
|
||||
}
|
||||
}
|
||||
|
||||
if params.Name == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name is required"))
|
||||
} else {
|
||||
for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L379-L409
|
||||
|
||||
func validateIngressRules(ingressRules []networkingv1.IngressRule, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(ingressRules) == 0 {
|
||||
return append(allErrs, field.Required(fldPath, ""))
|
||||
}
|
||||
for i, ih := range ingressRules {
|
||||
wildcardHost := false
|
||||
if len(ih.Host) > 0 {
|
||||
if isIP := netutils.ParseIPSloppy(ih.Host) != nil; isIP {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, "must be a DNS name, not an IP address"))
|
||||
}
|
||||
// TODO: Ports and ips are allowed in the host part of a url
|
||||
// according to RFC 3986, consider allowing them.
|
||||
if strings.Contains(ih.Host, "*") {
|
||||
for _, msg := range validation.IsWildcardDNS1123Subdomain(ih.Host) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg))
|
||||
}
|
||||
wildcardHost = true
|
||||
} else {
|
||||
for _, msg := range validation.IsDNS1123Subdomain(ih.Host) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !wildcardHost || !opts.AllowInvalidWildcardHostRule {
|
||||
allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(i), opts)...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L411C1-L417
|
||||
|
||||
func validateIngressRuleValue(ingressRule *networkingv1.IngressRuleValue, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if ingressRule.HTTP != nil {
|
||||
allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"), opts)...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L419-L428
|
||||
|
||||
func validateHTTPIngressRuleValue(httpIngressRuleValue *networkingv1.HTTPIngressRuleValue, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(httpIngressRuleValue.Paths) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("paths"), ""))
|
||||
}
|
||||
for i := range httpIngressRuleValue.Paths {
|
||||
allErrs = append(allErrs, validateHTTPIngressPath(&httpIngressRuleValue.Paths[i], fldPath.Child("paths").Index(i), opts)...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L430-L466
|
||||
|
||||
func validateHTTPIngressPath(path *networkingv1.HTTPIngressPath, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if path.PathType == nil {
|
||||
return append(allErrs, field.Required(fldPath.Child("pathType"), "pathType must be specified"))
|
||||
}
|
||||
|
||||
switch *path.PathType {
|
||||
case networkingv1.PathTypeExact, networkingv1.PathTypePrefix:
|
||||
if !strings.HasPrefix(path.Path, "/") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, "must be an absolute path"))
|
||||
}
|
||||
if len(path.Path) > 0 {
|
||||
for _, invalidSeq := range invalidPathSequences {
|
||||
if strings.Contains(path.Path, invalidSeq) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, fmt.Sprintf("must not contain '%s'", invalidSeq)))
|
||||
}
|
||||
}
|
||||
|
||||
for _, invalidSuff := range invalidPathSuffixes {
|
||||
if strings.HasSuffix(path.Path, invalidSuff) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, fmt.Sprintf("cannot end with '%s'", invalidSuff)))
|
||||
}
|
||||
}
|
||||
}
|
||||
case networkingv1.PathTypeImplementationSpecific:
|
||||
if len(path.Path) > 0 {
|
||||
if !strings.HasPrefix(path.Path, "/") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, "must be an absolute path"))
|
||||
}
|
||||
}
|
||||
default:
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("pathType"), *path.PathType, supportedPathTypes.List()))
|
||||
}
|
||||
allErrs = append(allErrs, validateIngressBackend(&path.Backend, fldPath.Child("backend"), opts)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L299-L324
|
||||
|
||||
func validateIngressTLS(spec *networkingv1.IngressSpec, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
// TODO: Perform a more thorough validation of spec.TLS.Hosts that takes
|
||||
// the wildcard spec from RFC 6125 into account.
|
||||
for tlsIndex, itls := range spec.TLS {
|
||||
for i, host := range itls.Hosts {
|
||||
if strings.Contains(host, "*") {
|
||||
for _, msg := range validation.IsWildcardDNS1123Subdomain(host) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("hosts").Index(i), host, msg))
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, msg := range validation.IsDNS1123Subdomain(host) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("hosts").Index(i), host, msg))
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.AllowInvalidSecretName {
|
||||
for _, msg := range validateTLSSecretName(itls.SecretName) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("secretName"), itls.SecretName, msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L641-L646
|
||||
|
||||
func validateTLSSecretName(name string) []string {
|
||||
if len(name) == 0 {
|
||||
return nil
|
||||
}
|
||||
return validateSecretName(name, false)
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation.go#L357-L377
|
||||
|
||||
// ValidateIngressLoadBalancerStatus validates required fields on an IngressLoadBalancerStatus
|
||||
func ValidateIngressLoadBalancerStatus(status *networkingv1.IngressLoadBalancerStatus, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for i, ingress := range status.Ingress {
|
||||
idxPath := fldPath.Child("ingress").Index(i)
|
||||
if len(ingress.IP) > 0 {
|
||||
if isIP := netutils.ParseIPSloppy(ingress.IP) != nil; !isIP {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("ip"), ingress.IP, "must be a valid IP address"))
|
||||
}
|
||||
}
|
||||
if len(ingress.Hostname) > 0 {
|
||||
for _, msg := range validation.IsDNS1123Subdomain(ingress.Hostname) {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, msg))
|
||||
}
|
||||
if isIP := netutils.ParseIPSloppy(ingress.Hostname) != nil; isIP {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, "must be a DNS name, not an IP address"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
|
@ -0,0 +1,479 @@
|
|||
package lifted
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
|
||||
networkingv1alpha1 "github.com/karmada-io/karmada/pkg/apis/networking/v1alpha1"
|
||||
)
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation_test.go
|
||||
// +lifted:changed
|
||||
|
||||
func TestValidateIngress(t *testing.T) {
|
||||
serviceBackend := &networkingv1.IngressServiceBackend{
|
||||
Name: "defaultbackend",
|
||||
Port: networkingv1.ServiceBackendPort{
|
||||
Name: "",
|
||||
Number: 80,
|
||||
},
|
||||
}
|
||||
defaultBackend := networkingv1.IngressBackend{
|
||||
Service: serviceBackend,
|
||||
}
|
||||
pathTypePrefix := networkingv1.PathTypePrefix
|
||||
pathTypeImplementationSpecific := networkingv1.PathTypeImplementationSpecific
|
||||
pathTypeFoo := networkingv1.PathType("foo")
|
||||
|
||||
baseMci := networkingv1alpha1.MultiClusterIngress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Spec: networkingv1.IngressSpec{
|
||||
DefaultBackend: &defaultBackend,
|
||||
Rules: []networkingv1.IngressRule{{
|
||||
Host: "foo.bar.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{{
|
||||
Path: "/foo",
|
||||
PathType: &pathTypeImplementationSpecific,
|
||||
Backend: defaultBackend,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Status: networkingv1.IngressStatus{
|
||||
LoadBalancer: networkingv1.IngressLoadBalancerStatus{
|
||||
Ingress: []networkingv1.IngressLoadBalancerIngress{
|
||||
{IP: "127.0.0.1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
tweakIngress func(mci *networkingv1alpha1.MultiClusterIngress)
|
||||
expectErrsOnFields []string
|
||||
}{
|
||||
"empty path (implementation specific)": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = ""
|
||||
},
|
||||
expectErrsOnFields: []string{},
|
||||
},
|
||||
"valid path": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = "/valid"
|
||||
},
|
||||
expectErrsOnFields: []string{},
|
||||
},
|
||||
// invalid use cases
|
||||
"backend with no service": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.DefaultBackend.Service.Name = ""
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.defaultBackend.service.name",
|
||||
},
|
||||
},
|
||||
"invalid path type": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathTypeFoo
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].http.paths[0].pathType",
|
||||
},
|
||||
},
|
||||
"empty path (prefix)": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = ""
|
||||
mci.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathTypePrefix
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].http.paths[0].path",
|
||||
},
|
||||
},
|
||||
"no paths": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []networkingv1.HTTPIngressPath{}
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].http.paths",
|
||||
},
|
||||
},
|
||||
"invalid host (foobar:80)": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].Host = "foobar:80"
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].host",
|
||||
},
|
||||
},
|
||||
"invalid host (127.0.0.1)": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].Host = "127.0.0.1"
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].host",
|
||||
},
|
||||
},
|
||||
"valid wildcard host": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].Host = "*.bar.com"
|
||||
},
|
||||
expectErrsOnFields: []string{},
|
||||
},
|
||||
"invalid wildcard host (foo.*.bar.com)": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].Host = "foo.*.bar.com"
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].host",
|
||||
},
|
||||
},
|
||||
"invalid wildcard host (*)": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].Host = "*"
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].host",
|
||||
},
|
||||
},
|
||||
"path resource backend and service name are not allowed together": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].IngressRuleValue = networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{{
|
||||
Path: "/foo",
|
||||
PathType: &pathTypeImplementationSpecific,
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: serviceBackend,
|
||||
Resource: &corev1.TypedLocalObjectReference{
|
||||
APIGroup: utilpointer.String("example.com"),
|
||||
Kind: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].http.paths[0].backend",
|
||||
},
|
||||
},
|
||||
"path resource backend and service port are not allowed together": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.Rules[0].IngressRuleValue = networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{{
|
||||
Path: "/foo",
|
||||
PathType: &pathTypeImplementationSpecific,
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: serviceBackend,
|
||||
Resource: &corev1.TypedLocalObjectReference{
|
||||
APIGroup: utilpointer.String("example.com"),
|
||||
Kind: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.rules[0].http.paths[0].backend",
|
||||
},
|
||||
},
|
||||
"spec.backend resource and service name are not allowed together": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.DefaultBackend = &networkingv1.IngressBackend{
|
||||
Service: serviceBackend,
|
||||
Resource: &corev1.TypedLocalObjectReference{
|
||||
APIGroup: utilpointer.String("example.com"),
|
||||
Kind: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
}
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.defaultBackend",
|
||||
},
|
||||
},
|
||||
"spec.backend resource and service port are not allowed together": {
|
||||
tweakIngress: func(mci *networkingv1alpha1.MultiClusterIngress) {
|
||||
mci.Spec.DefaultBackend = &networkingv1.IngressBackend{
|
||||
Service: serviceBackend,
|
||||
Resource: &corev1.TypedLocalObjectReference{
|
||||
APIGroup: utilpointer.String("example.com"),
|
||||
Kind: "foo",
|
||||
Name: "bar",
|
||||
},
|
||||
}
|
||||
},
|
||||
expectErrsOnFields: []string{
|
||||
"spec.defaultBackend",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
mci := baseMci.DeepCopy()
|
||||
testCase.tweakIngress(mci)
|
||||
errs := ValidateIngressSpec(&mci.Spec, field.NewPath("spec"), IngressValidationOptions{})
|
||||
if len(testCase.expectErrsOnFields) != len(errs) {
|
||||
t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectErrsOnFields), len(errs), errs)
|
||||
}
|
||||
for i, err := range errs {
|
||||
if err.Field != testCase.expectErrsOnFields[i] {
|
||||
t.Errorf("Expected error on field: %s, got: %s", testCase.expectErrsOnFields[i], err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation_test.go
|
||||
|
||||
func TestValidateIngressTLS(t *testing.T) {
|
||||
pathTypeImplementationSpecific := networkingv1.PathTypeImplementationSpecific
|
||||
serviceBackend := &networkingv1.IngressServiceBackend{
|
||||
Name: "defaultbackend",
|
||||
Port: networkingv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
}
|
||||
defaultBackend := networkingv1.IngressBackend{
|
||||
Service: serviceBackend,
|
||||
}
|
||||
newValid := func() networkingv1alpha1.MultiClusterIngress {
|
||||
return networkingv1alpha1.MultiClusterIngress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Spec: networkingv1.IngressSpec{
|
||||
DefaultBackend: &defaultBackend,
|
||||
Rules: []networkingv1.IngressRule{{
|
||||
Host: "foo.bar.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{{
|
||||
Path: "/foo",
|
||||
PathType: &pathTypeImplementationSpecific,
|
||||
Backend: defaultBackend,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Status: networkingv1.IngressStatus{
|
||||
LoadBalancer: networkingv1.IngressLoadBalancerStatus{
|
||||
Ingress: []networkingv1.IngressLoadBalancerIngress{
|
||||
{IP: "127.0.0.1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := map[string]networkingv1alpha1.MultiClusterIngress{}
|
||||
|
||||
wildcardHost := "foo.*.bar.com"
|
||||
badWildcardTLS := newValid()
|
||||
badWildcardTLS.Spec.Rules[0].Host = "*.foo.bar.com"
|
||||
badWildcardTLS.Spec.TLS = []networkingv1.IngressTLS{{
|
||||
Hosts: []string{wildcardHost},
|
||||
}}
|
||||
badWildcardTLSErr := fmt.Sprintf("spec.tls[0].hosts[0]: Invalid value: '%v'", wildcardHost)
|
||||
errorCases[badWildcardTLSErr] = badWildcardTLS
|
||||
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateIngressSpec(&v.Spec, field.NewPath("spec"), IngressValidationOptions{})
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected failure for %q", k)
|
||||
} else {
|
||||
s := strings.Split(k, ":")
|
||||
err := errs[0]
|
||||
if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
|
||||
t.Errorf("unexpected error: %q, expected: %q", err, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test for wildcard host and wildcard TLS
|
||||
validCases := map[string]networkingv1alpha1.MultiClusterIngress{}
|
||||
wildHost := "*.bar.com"
|
||||
goodWildcardTLS := newValid()
|
||||
goodWildcardTLS.Spec.Rules[0].Host = "*.bar.com"
|
||||
goodWildcardTLS.Spec.TLS = []networkingv1.IngressTLS{{
|
||||
Hosts: []string{wildHost},
|
||||
}}
|
||||
validCases[fmt.Sprintf("spec.tls[0].hosts: Valid value: '%v'", wildHost)] = goodWildcardTLS
|
||||
for k, v := range validCases {
|
||||
errs := ValidateIngressSpec(&v.Spec, field.NewPath("spec"), IngressValidationOptions{})
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("expected success for %q", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation_test.go
|
||||
|
||||
// TestValidateEmptyIngressTLS verifies that an empty TLS configuration can be
|
||||
// specified, which ingress controllers may interpret to mean that TLS should be
|
||||
// used with a default certificate that the ingress controller furnishes.
|
||||
func TestValidateEmptyIngressTLS(t *testing.T) {
|
||||
pathTypeImplementationSpecific := networkingv1.PathTypeImplementationSpecific
|
||||
serviceBackend := &networkingv1.IngressServiceBackend{
|
||||
Name: "defaultbackend",
|
||||
Port: networkingv1.ServiceBackendPort{
|
||||
Number: 443,
|
||||
},
|
||||
}
|
||||
defaultBackend := networkingv1.IngressBackend{
|
||||
Service: serviceBackend,
|
||||
}
|
||||
newValid := func() networkingv1alpha1.MultiClusterIngress {
|
||||
return networkingv1alpha1.MultiClusterIngress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Spec: networkingv1.IngressSpec{
|
||||
Rules: []networkingv1.IngressRule{{
|
||||
Host: "foo.bar.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{{
|
||||
PathType: &pathTypeImplementationSpecific,
|
||||
Backend: defaultBackend,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
validCases := map[string]networkingv1alpha1.MultiClusterIngress{}
|
||||
goodEmptyTLS := newValid()
|
||||
goodEmptyTLS.Spec.TLS = []networkingv1.IngressTLS{
|
||||
{},
|
||||
}
|
||||
validCases[fmt.Sprintf("spec.tls[0]: Valid value: %v", goodEmptyTLS.Spec.TLS[0])] = goodEmptyTLS
|
||||
goodEmptyHosts := newValid()
|
||||
goodEmptyHosts.Spec.TLS = []networkingv1.IngressTLS{{
|
||||
Hosts: []string{},
|
||||
}}
|
||||
validCases[fmt.Sprintf("spec.tls[0]: Valid value: %v", goodEmptyHosts.Spec.TLS[0])] = goodEmptyHosts
|
||||
for k, v := range validCases {
|
||||
errs := ValidateIngressSpec(&v.Spec, field.NewPath("spec"), IngressValidationOptions{})
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("expected success for %q", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// +lifted:source=https://github.com/kubernetes/kubernetes/blob/release-1.27/pkg/apis/networking/validation/validation_test.go
|
||||
// +lifted:changed
|
||||
|
||||
func TestValidateIngressStatusUpdate(t *testing.T) {
|
||||
serviceBackend := &networkingv1.IngressServiceBackend{
|
||||
Name: "defaultbackend",
|
||||
Port: networkingv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
}
|
||||
defaultBackend := networkingv1.IngressBackend{
|
||||
Service: serviceBackend,
|
||||
}
|
||||
|
||||
newValid := func() networkingv1alpha1.MultiClusterIngress {
|
||||
return networkingv1alpha1.MultiClusterIngress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
ResourceVersion: "9",
|
||||
},
|
||||
Spec: networkingv1.IngressSpec{
|
||||
DefaultBackend: &defaultBackend,
|
||||
Rules: []networkingv1.IngressRule{{
|
||||
Host: "foo.bar.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{{
|
||||
Path: "/foo",
|
||||
Backend: defaultBackend,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Status: networkingv1.IngressStatus{
|
||||
LoadBalancer: networkingv1.IngressLoadBalancerStatus{
|
||||
Ingress: []networkingv1.IngressLoadBalancerIngress{
|
||||
{IP: "127.0.0.1", Hostname: "foo.bar.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
newValue := newValid()
|
||||
newValue.Status = networkingv1.IngressStatus{
|
||||
LoadBalancer: networkingv1.IngressLoadBalancerStatus{
|
||||
Ingress: []networkingv1.IngressLoadBalancerIngress{
|
||||
{IP: "127.0.0.2", Hostname: "foo.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
invalidIP := newValid()
|
||||
invalidIP.Status = networkingv1.IngressStatus{
|
||||
LoadBalancer: networkingv1.IngressLoadBalancerStatus{
|
||||
Ingress: []networkingv1.IngressLoadBalancerIngress{
|
||||
{IP: "abcd", Hostname: "foo.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
invalidHostname := newValid()
|
||||
invalidHostname.Status = networkingv1.IngressStatus{
|
||||
LoadBalancer: networkingv1.IngressLoadBalancerStatus{
|
||||
Ingress: []networkingv1.IngressLoadBalancerIngress{
|
||||
{IP: "127.0.0.1", Hostname: "127.0.0.1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateIngressLoadBalancerStatus(&newValue.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("Unexpected error %v", errs)
|
||||
}
|
||||
|
||||
errorCases := map[string]networkingv1alpha1.MultiClusterIngress{
|
||||
"status.loadBalancer.ingress[0].ip: Invalid value": invalidIP,
|
||||
"status.loadBalancer.ingress[0].hostname: Invalid value": invalidHostname,
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateIngressLoadBalancerStatus(&v.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
} else {
|
||||
s := strings.Split(k, ":")
|
||||
err := errs[0]
|
||||
if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
|
||||
t.Errorf("unexpected error: %q, expected: %q", err, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package multiclusteringress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
networkingv1alpha1 "github.com/karmada-io/karmada/pkg/apis/networking/v1alpha1"
|
||||
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||
)
|
||||
|
||||
// ValidatingAdmission validates MultiClusterIngress object when creating/updating.
|
||||
type ValidatingAdmission struct {
|
||||
decoder *admission.Decoder
|
||||
}
|
||||
|
||||
// Check if our ValidatingAdmission implements necessary interface
|
||||
var _ admission.Handler = &ValidatingAdmission{}
|
||||
var _ admission.DecoderInjector = &ValidatingAdmission{}
|
||||
|
||||
// Handle implements admission.Handler interface.
|
||||
// It yields a response to an AdmissionRequest.
|
||||
func (v *ValidatingAdmission) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
mci := &networkingv1alpha1.MultiClusterIngress{}
|
||||
|
||||
err := v.decoder.Decode(req, mci)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
klog.Infof("Validating MultiClusterIngress(%s/%s) for request: %s", mci.Namespace, mci.Name, req.Operation)
|
||||
|
||||
if req.Operation == admissionv1.Update {
|
||||
oldMci := &networkingv1alpha1.MultiClusterIngress{}
|
||||
err = v.decoder.DecodeRaw(req.OldObject, oldMci)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
if errs := validateMCIUpdate(oldMci, mci); len(errs) != 0 {
|
||||
klog.Errorf("%v", errs)
|
||||
return admission.Denied(errs.ToAggregate().Error())
|
||||
}
|
||||
} else {
|
||||
if errs := validateMCI(mci); len(errs) != 0 {
|
||||
klog.Errorf("%v", errs)
|
||||
return admission.Denied(errs.ToAggregate().Error())
|
||||
}
|
||||
}
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
// InjectDecoder implements admission.DecoderInjector interface.
|
||||
// A decoder will be automatically injected.
|
||||
func (v *ValidatingAdmission) InjectDecoder(d *admission.Decoder) error {
|
||||
v.decoder = d
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMCIUpdate(oldMci, newMci *networkingv1alpha1.MultiClusterIngress) field.ErrorList {
|
||||
allErrs := apimachineryvalidation.ValidateObjectMetaUpdate(&newMci.ObjectMeta, &oldMci.ObjectMeta, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, validateMCI(newMci)...)
|
||||
allErrs = append(allErrs, lifted.ValidateIngressLoadBalancerStatus(&newMci.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateMCI(mci *networkingv1alpha1.MultiClusterIngress) field.ErrorList {
|
||||
allErrs := apimachineryvalidation.ValidateObjectMeta(&mci.ObjectMeta, true,
|
||||
apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
||||
opts := lifted.IngressValidationOptions{
|
||||
AllowInvalidSecretName: false,
|
||||
AllowInvalidWildcardHostRule: false,
|
||||
}
|
||||
allErrs = append(allErrs, lifted.ValidateIngressSpec(&mci.Spec, field.NewPath("spec"), opts)...)
|
||||
return allErrs
|
||||
}
|
Loading…
Reference in New Issue