add validation for cluster api and remove cluster validating webhook

Signed-off-by: carlory <baofa.fan@daocloud.io>
This commit is contained in:
carlory 2021-12-23 10:04:32 +08:00
parent 2060616c4a
commit f697c03c74
13 changed files with 307 additions and 238 deletions

View File

@ -69,20 +69,6 @@ metadata:
labels:
app: validating-config
webhooks:
- name: cluster.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["cluster.karmada.io"]
apiVersions: ["*"]
resources: ["clusters"]
scope: "Cluster"
clientConfig:
url: https://karmada-webhook.karmada-system.svc:443/validate-cluster
caBundle: {{caBundle}}
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1"]
timeoutSeconds: 3
- name: propagationpolicy.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]

View File

@ -87,20 +87,6 @@ metadata:
labels:
app: validating-config
webhooks:
- name: cluster.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["cluster.karmada.io"]
apiVersions: ["*"]
resources: ["clusters"]
scope: "Cluster"
clientConfig:
url: https://{{ $name }}-webhook.{{ $namespace }}.svc:443/validate-cluster
{{- include "karmada.webhook.caBundle" . | nindent 6 }}
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1"]
timeoutSeconds: 3
- name: propagationpolicy.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]

View File

@ -5,7 +5,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
"github.com/karmada-io/karmada/pkg/util/validation"
"github.com/karmada-io/karmada/pkg/apis/cluster/validation"
)
// Validate checks Options and return a slice of found errs.

View File

@ -18,7 +18,6 @@ import (
"github.com/karmada-io/karmada/pkg/util/gclient"
"github.com/karmada-io/karmada/pkg/version"
"github.com/karmada-io/karmada/pkg/version/sharedcommand"
"github.com/karmada-io/karmada/pkg/webhook/cluster"
"github.com/karmada-io/karmada/pkg/webhook/clusteroverridepolicy"
"github.com/karmada-io/karmada/pkg/webhook/clusterpropagationpolicy"
"github.com/karmada-io/karmada/pkg/webhook/configuration"
@ -84,7 +83,6 @@ func Run(ctx context.Context, opts *options.Options) error {
klog.Info("registering webhooks to the webhook server")
hookServer := hookManager.GetWebhookServer()
hookServer.Register("/validate-cluster", &webhook.Admission{Handler: &cluster.ValidatingAdmission{}})
hookServer.Register("/mutate-propagationpolicy", &webhook.Admission{Handler: &propagationpolicy.MutatingAdmission{}})
hookServer.Register("/validate-propagationpolicy", &webhook.Admission{Handler: &propagationpolicy.ValidatingAdmission{}})
hookServer.Register("/mutate-clusterpropagationpolicy", &webhook.Admission{Handler: &clusterpropagationpolicy.MutatingAdmission{}})

View File

@ -0,0 +1,81 @@
/*
Copyright 2014 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.
*/
package validation
import (
"strings"
corev1 "k8s.io/api/core/v1"
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// This code logic is lifted from https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/core/validation/validation.go#L5003
// ValidateClusterTaints tests if given taints have valid data.
func ValidateClusterTaints(taints []corev1.Taint, fldPath *field.Path) field.ErrorList {
allErrors := field.ErrorList{}
uniqueTaints := map[corev1.TaintEffect]sets.String{}
for i, currTaint := range taints {
idxPath := fldPath.Index(i)
// validate the taint key
allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(currTaint.Key, idxPath.Child("key"))...)
// validate the taint value
if errs := validation.IsValidLabelValue(currTaint.Value); len(errs) != 0 {
allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";")))
}
// validate the taint effect
allErrors = append(allErrors, validateTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...)
// validate if taint is unique by <key, effect>
if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) {
duplicatedError := field.Duplicate(idxPath, currTaint)
duplicatedError.Detail = "taints must be unique by key and effect pair"
allErrors = append(allErrors, duplicatedError)
continue
}
// add taint to existingTaints for uniqueness check
if len(uniqueTaints[currTaint.Effect]) == 0 {
uniqueTaints[currTaint.Effect] = sets.String{}
}
uniqueTaints[currTaint.Effect].Insert(currTaint.Key)
}
return allErrors
}
func validateTaintEffect(effect *corev1.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList {
if !allowEmpty && len(*effect) == 0 {
return field.ErrorList{field.Required(fldPath, "")}
}
allErrors := field.ErrorList{}
switch *effect {
// TODO: Replace next line with subsequent commented-out line when implement TaintEffectNoExecute.
case corev1.TaintEffectNoSchedule:
// case corev1.TaintEffectNoSchedule, corev1.TaintEffectNoExecute:
default:
validValues := []string{
string(corev1.TaintEffectNoSchedule),
// TODO: Uncomment this block when implement TaintEffectNoExecute.
// string(corev1.TaintEffectNoExecute),
}
allErrors = append(allErrors, field.NotSupported(fldPath, *effect, validValues))
}
return allErrors
}

View File

@ -0,0 +1,134 @@
package validation
import (
"fmt"
"net/url"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/sets"
kubevalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
api "github.com/karmada-io/karmada/pkg/apis/cluster"
)
const clusterNameMaxLength int = 48
// ValidateClusterName tests whether the cluster name passed is valid.
// If the cluster name is not valid, a list of error strings is returned. Otherwise an empty list (or nil) is returned.
// Rules of a valid cluster name:
// - Must be a valid label value as per RFC1123.
// * An alphanumeric (a-z, and 0-9) string, with a maximum length of 63 characters,
// with the '-' character allowed anywhere except the first or last character.
// - Length must be less than 48 characters.
// * Since cluster name used to generate execution namespace by adding a prefix, so reserve 15 characters for the prefix.
func ValidateClusterName(name string) []string {
if len(name) == 0 {
return []string{"must be not empty"}
}
if len(name) > clusterNameMaxLength {
return []string{fmt.Sprintf("must be no more than %d characters", clusterNameMaxLength)}
}
return kubevalidation.IsDNS1123Label(name)
}
var supportedSyncModes = sets.NewString(string(api.Pull), string(api.Push))
// ValidateCluster tests if required fields in the Cluster are set.
func ValidateCluster(cluster *api.Cluster) field.ErrorList {
allErrs := apimachineryvalidation.ValidateObjectMeta(&cluster.ObjectMeta, false, func(name string, prefix bool) []string { return ValidateClusterName(name) }, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateClusterSpec(&cluster.Spec, field.NewPath("spec"))...)
return allErrs
}
// ValidateClusterUpdate tests if required fields in the Cluster are set.
func ValidateClusterUpdate(newCluster, oldCluster *api.Cluster) field.ErrorList {
allErrs := apimachineryvalidation.ValidateObjectMetaUpdate(&newCluster.ObjectMeta, &oldCluster.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateCluster(newCluster)...)
return allErrs
}
// ValidateClusterSpec tests if required fields in the ClusterSpec are set.
func ValidateClusterSpec(spec *api.ClusterSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if spec.SyncMode == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("syncMode"), ""))
}
if !supportedSyncModes.Has(string(spec.SyncMode)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("syncMode"), spec.SyncMode, supportedSyncModes.List()))
}
if spec.APIEndpoint != "" {
allErrs = append(allErrs, ValidateClusterAPIEndpoint(fldPath.Child("apiEndpoint"), spec.APIEndpoint, false)...)
}
if spec.SecretRef != nil {
if spec.SecretRef.Namespace == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("secretRef").Child("namespace"), ""))
}
if spec.SecretRef.Name == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("secretRef").Child("name"), ""))
}
}
if spec.ImpersonatorSecretRef != nil {
if spec.ImpersonatorSecretRef.Namespace == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("impersonatorSecretRef").Child("namespace"), ""))
}
if spec.ImpersonatorSecretRef.Name == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("impersonatorSecretRef").Child("name"), ""))
}
}
if spec.ProxyURL != "" {
allErrs = append(allErrs, ValidateClusterProxyURL(fldPath.Child("proxyURL"), spec.ProxyURL)...)
}
if len(spec.Taints) > 0 {
allErrs = append(allErrs, ValidateClusterTaints(spec.Taints, fldPath.Child("taints"))...)
}
return allErrs
}
// ValidateClusterAPIEndpoint validates cluster's apiEndpoint
func ValidateClusterAPIEndpoint(fldPath *field.Path, apiEndpoint string, forceHTTPS bool) field.ErrorList {
var allErrs field.ErrorList
const form = "; desired format: hostname, hostname:port, IP or IP:port"
if u, err := url.Parse(apiEndpoint); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, apiEndpoint, "apiEndpoint must be a valid URL: "+err.Error()+form))
} else {
if forceHTTPS && u.Scheme != "https" {
allErrs = append(allErrs, field.Invalid(fldPath, u.Scheme, "'https' is the only allowed URL scheme"+form))
}
if len(u.Host) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, u.Host, "host must be provided"+form))
}
if u.User != nil {
allErrs = append(allErrs, field.Invalid(fldPath, u.User.String(), "user information is not permitted in the URL"))
}
if len(u.Fragment) != 0 {
allErrs = append(allErrs, field.Invalid(fldPath, u.Fragment, "fragments are not permitted in the URL"))
}
if len(u.RawQuery) != 0 {
allErrs = append(allErrs, field.Invalid(fldPath, u.RawQuery, "query parameters are not permitted in the URL"))
}
}
return allErrs
}
// ValidateClusterProxyURL validates cluster's proxyURL.
func ValidateClusterProxyURL(fldPath *field.Path, proxyURL string) field.ErrorList {
allErrs := field.ErrorList{}
if u, err := url.Parse(proxyURL); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, proxyURL, "apiEndpoint must be a valid URL: "+err.Error()))
} else {
switch u.Scheme {
case "http", "https", "socks5":
default:
allErrs = append(allErrs, field.Invalid(fldPath, proxyURL, fmt.Sprintf("unsupported scheme %q, must be http, https, or socks5", u.Scheme)))
}
}
return allErrs
}

View File

@ -0,0 +1,83 @@
package validation
import (
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "github.com/karmada-io/karmada/pkg/apis/cluster"
)
func TestValidateCluster(t *testing.T) {
testCases := map[string]struct {
cluster api.Cluster
expectError bool
}{
"zero-length name": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: ""}, Spec: api.ClusterSpec{SyncMode: api.Push}},
expectError: true,
},
"invalid name": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid"}, Spec: api.ClusterSpec{SyncMode: api.Push}},
expectError: true,
},
"invalid name that is too long": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: strings.Repeat("a", 48+1)}, Spec: api.ClusterSpec{SyncMode: api.Push}},
expectError: true,
},
"no sync mode": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.ClusterSpec{}},
expectError: true,
},
"unsupported sync mode": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.ClusterSpec{SyncMode: api.ClusterSyncMode("^Invalid")}},
expectError: true,
},
"invalid apiEndpoint": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.ClusterSpec{SyncMode: api.Push, APIEndpoint: "^Invalid"}},
expectError: true,
},
"empty secretRef": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.ClusterSpec{SyncMode: api.Push, SecretRef: &api.LocalSecretReference{}}},
expectError: true,
},
"empty impersonatorSecretRef": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.ClusterSpec{SyncMode: api.Push, ImpersonatorSecretRef: &api.LocalSecretReference{}}},
expectError: true,
},
"invalid proxyURL": {
cluster: api.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: api.ClusterSpec{SyncMode: api.Push, ProxyURL: "^Invalid"}},
expectError: true,
},
"unsupported taint effect": {
cluster: api.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.ClusterSpec{
SyncMode: api.Push,
Taints: []corev1.Taint{
{
Key: "foo",
Value: "bar",
Effect: corev1.TaintEffect("^Invalid"),
},
},
},
},
expectError: true,
},
}
for name, testCase := range testCases {
errs := ValidateCluster(&testCase.cluster)
if len(errs) == 0 && testCase.expectError {
t.Errorf("expected failure for %q, but there were none", name)
return
}
if len(errs) != 0 && !testCase.expectError {
t.Errorf("expected success for %q, but there were errors: %v", name, errs)
return
}
}
}

View File

@ -87,20 +87,6 @@ metadata:
labels:
app: validating-config
webhooks:
- name: cluster.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["cluster.karmada.io"]
apiVersions: ["*"]
resources: ["clusters"]
scope: "Cluster"
clientConfig:
url: https://karmada-webhook.%s.svc:443/validate-cluster
caBundle: %s
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1"]
timeoutSeconds: 3
- name: propagationpolicy.karmada.io
rules:
- operations: ["CREATE", "UPDATE"]
@ -170,7 +156,7 @@ webhooks:
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1"]
timeoutSeconds: 3`, namespace, caBundle, namespace, caBundle, namespace, caBundle, namespace, caBundle, namespace, caBundle, namespace, caBundle)
timeoutSeconds: 3`, namespace, caBundle, namespace, caBundle, namespace, caBundle, namespace, caBundle, namespace, caBundle)
}
func createValidatingWebhookConfiguration(c *kubernetes.Clientset, staticYaml string) error {

View File

@ -17,11 +17,11 @@ import (
"k8s.io/klog/v2"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
"github.com/karmada-io/karmada/pkg/apis/cluster/validation"
karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
"github.com/karmada-io/karmada/pkg/karmadactl/options"
"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/names"
"github.com/karmada-io/karmada/pkg/util/validation"
)
var (

View File

@ -14,6 +14,7 @@ import (
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster"
"github.com/karmada-io/karmada/pkg/apis/cluster/validation"
)
// NewStrategy creates and returns a ClusterStrategy instance.
@ -76,8 +77,8 @@ func (Strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
// Validate returns an ErrorList with validation errors or nil.
func (Strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
// TODO: add validation for Cluster
return field.ErrorList{}
cluster := obj.(*clusterapis.Cluster)
return validation.ValidateCluster(cluster)
}
// WarningsOnCreate returns warnings for the creation of the given object.
@ -102,7 +103,9 @@ func (Strategy) Canonicalize(obj runtime.Object) {
// ValidateUpdate is invoked after default fields in the object have been
// filled in before the object is persisted.
func (Strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return field.ErrorList{}
newCluster := obj.(*clusterapis.Cluster)
oldCluster := old.(*clusterapis.Cluster)
return validation.ValidateClusterUpdate(newCluster, oldCluster)
}
// WarningsOnUpdate returns warnings for the given update.

View File

@ -2,56 +2,16 @@ package validation
import (
"fmt"
"net/url"
corev1 "k8s.io/api/core/v1"
kubevalidation "k8s.io/apimachinery/pkg/util/validation"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
"github.com/karmada-io/karmada/pkg/util"
)
const clusterNameMaxLength int = 48
// LabelValueMaxLength is a label's max length
const LabelValueMaxLength int = 63
// ValidateClusterName tests whether the cluster name passed is valid.
// If the cluster name is not valid, a list of error strings is returned. Otherwise an empty list (or nil) is returned.
// Rules of a valid cluster name:
// - Must be a valid label value as per RFC1123.
// * An alphanumeric (a-z, and 0-9) string, with a maximum length of 63 characters,
// with the '-' character allowed anywhere except the first or last character.
// - Length must be less than 48 characters.
// * Since cluster name used to generate execution namespace by adding a prefix, so reserve 15 characters for the prefix.
func ValidateClusterName(name string) []string {
if len(name) == 0 {
return []string{"must be not empty"}
}
if len(name) > clusterNameMaxLength {
return []string{fmt.Sprintf("must be no more than %d characters", clusterNameMaxLength)}
}
return kubevalidation.IsDNS1123Label(name)
}
// ValidateClusterProxyURL tests whether the proxyURL is valid.
// If not valid, a list of error string is returned. Otherwise an empty list (or nil) is returned.
func ValidateClusterProxyURL(proxyURL string) []string {
u, err := url.Parse(proxyURL)
if err != nil {
return []string{fmt.Sprintf("cloud not parse: %s, %v", proxyURL, err)}
}
switch u.Scheme {
case "http", "https", "socks5":
default:
return []string{fmt.Sprintf("unsupported scheme %q, must be http, https, or socks5", u.Scheme)}
}
return nil
}
// ValidatePolicyFieldSelector tests if the fieldSelector of propagation policy is valid.
func ValidatePolicyFieldSelector(fieldSelector *policyv1alpha1.FieldSelector) error {
if fieldSelector == nil {

View File

@ -1,101 +1,11 @@
package validation
import (
"strings"
"testing"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
)
func TestValidateClusterName(t *testing.T) {
var tests = []struct {
name string
cluster string
expectError bool
}{
{
name: "valid cluster",
cluster: "valid-cluster",
expectError: false,
},
{
name: "contains invalid character is not allowed",
cluster: "invalid.cluster",
expectError: true,
},
{
name: "empty name is not allowed",
cluster: "",
expectError: true,
},
{
name: "too long name is not allowed",
cluster: "abcdefghijklmnopqrstuvwxyz01234567890123456789012", // 49 characters
expectError: true,
},
}
for _, test := range tests {
tc := test
errs := ValidateClusterName(tc.cluster)
if len(errs) > 0 && tc.expectError != true {
t.Fatalf("expect no error but got: %s", strings.Join(errs, ";"))
}
if len(errs) == 0 && tc.expectError == true {
t.Fatalf("expect an error but got none")
}
}
}
func TestValidateClusterProxyURL(t *testing.T) {
var tests = []struct {
name string
proxy string
expectError bool
expectErrMsg string
}{
{
name: "valid http",
proxy: "http://example.com",
expectError: false,
},
{
name: "valid https",
proxy: "https://example.com",
expectError: false,
},
{
name: "valid socks5",
proxy: "socks5://example.com",
expectError: false,
},
{
name: "no schema is not allowed",
proxy: "example",
expectError: true,
expectErrMsg: `unsupported scheme "", must be http, https, or socks5`,
},
{
name: "schema out of range is not allowed",
proxy: "socks4://example.com",
expectError: true,
expectErrMsg: `unsupported scheme "socks4", must be http, https, or socks5`,
},
}
for _, test := range tests {
tc := test
t.Run(tc.name, func(t *testing.T) {
errs := ValidateClusterProxyURL(tc.proxy)
if !tc.expectError && len(errs) != 0 {
t.Errorf("not expect errors but got: %v", errs)
} else if tc.expectError && tc.expectErrMsg != strings.Join(errs, ",") {
t.Errorf("expected error: %v, but got: %v", tc.expectErrMsg, strings.Join(errs, ","))
}
})
}
}
func TestValidateOverrideSpec(t *testing.T) {
var tests = []struct {
name string

View File

@ -1,58 +0,0 @@
package cluster
import (
"context"
"fmt"
"net/http"
"strings"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/validation"
)
// ValidatingAdmission validates cluster object when creating/updating/deleting.
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 {
cluster := &clusterv1alpha1.Cluster{}
err := v.decoder.Decode(req, cluster)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
klog.V(2).Infof("Validating cluster(%s) for request: %s", cluster.Name, req.Operation)
if errs := validation.ValidateClusterName(cluster.Name); len(errs) != 0 {
errMsg := fmt.Sprintf("invalid cluster name(%s): %s", cluster.Name, strings.Join(errs, ";"))
klog.Error(errMsg)
return admission.Denied(errMsg)
}
if len(cluster.Spec.ProxyURL) > 0 {
if errs := validation.ValidateClusterProxyURL(cluster.Spec.ProxyURL); len(errs) != 0 {
errMsg := fmt.Sprintf("invalid proxy URL(%s): %s", cluster.Spec.ProxyURL, strings.Join(errs, ";"))
klog.Error(errMsg)
return admission.Denied(errMsg)
}
}
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
}