Refactor addmission webhook hook client to a util package
Kubernetes-commit: 5652d5cffadcd8a2f107b6aecf5fc06c0fc473f1
This commit is contained in:
		
							parent
							
								
									498c246d7a
								
							
						
					
					
						commit
						21f6e2bcdd
					
				|  | @ -21,6 +21,7 @@ import ( | |||
| 	"fmt" | ||||
| 	"io" | ||||
| 
 | ||||
| 	admissionv1beta1 "k8s.io/api/admission/v1beta1" | ||||
| 	"k8s.io/api/admissionregistration/v1beta1" | ||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
|  | @ -29,6 +30,7 @@ import ( | |||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/config" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/rules" | ||||
| 	"k8s.io/apiserver/pkg/util/webhook" | ||||
| 	"k8s.io/client-go/informers" | ||||
| 	clientset "k8s.io/client-go/kubernetes" | ||||
| ) | ||||
|  | @ -40,7 +42,7 @@ type Webhook struct { | |||
| 	sourceFactory sourceFactory | ||||
| 
 | ||||
| 	hookSource       Source | ||||
| 	clientManager    *config.ClientManager | ||||
| 	clientManager    *webhook.ClientManager | ||||
| 	convertor        *convertor | ||||
| 	namespaceMatcher *namespace.Matcher | ||||
| 	dispatcher       Dispatcher | ||||
|  | @ -52,7 +54,7 @@ var ( | |||
| ) | ||||
| 
 | ||||
| type sourceFactory func(f informers.SharedInformerFactory) Source | ||||
| type dispatcherFactory func(cm *config.ClientManager) Dispatcher | ||||
| type dispatcherFactory func(cm *webhook.ClientManager) Dispatcher | ||||
| 
 | ||||
| // NewWebhook creates a new generic admission webhook.
 | ||||
| func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) { | ||||
|  | @ -61,17 +63,17 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory | |||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	cm, err := config.NewClientManager() | ||||
| 	cm, err := webhook.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	authInfoResolver, err := config.NewDefaultAuthenticationInfoResolver(kubeconfigFile) | ||||
| 	authInfoResolver, err := webhook.NewDefaultAuthenticationInfoResolver(kubeconfigFile) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	// Set defaults which may be overridden later.
 | ||||
| 	cm.SetAuthenticationInfoResolver(authInfoResolver) | ||||
| 	cm.SetServiceResolver(config.NewDefaultServiceResolver()) | ||||
| 	cm.SetServiceResolver(webhook.NewDefaultServiceResolver()) | ||||
| 
 | ||||
| 	return &Webhook{ | ||||
| 		Handler:          handler, | ||||
|  | @ -86,13 +88,13 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory | |||
| // SetAuthenticationInfoResolverWrapper sets the
 | ||||
| // AuthenticationInfoResolverWrapper.
 | ||||
| // TODO find a better way wire this, but keep this pull small for now.
 | ||||
| func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper config.AuthenticationInfoResolverWrapper) { | ||||
| func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhook.AuthenticationInfoResolverWrapper) { | ||||
| 	a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper) | ||||
| } | ||||
| 
 | ||||
| // SetServiceResolver sets a service resolver for the webhook admission plugin.
 | ||||
| // Passing a nil resolver does not have an effect, instead a default one will be used.
 | ||||
| func (a *Webhook) SetServiceResolver(sr config.ServiceResolver) { | ||||
| func (a *Webhook) SetServiceResolver(sr webhook.ServiceResolver) { | ||||
| 	a.clientManager.SetServiceResolver(sr) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,13 +20,13 @@ import ( | |||
| 	"net/url" | ||||
| 
 | ||||
| 	"k8s.io/apiserver/pkg/admission" | ||||
| 	webhookconfig "k8s.io/apiserver/pkg/admission/plugin/webhook/config" | ||||
| 	"k8s.io/apiserver/pkg/util/webhook" | ||||
| ) | ||||
| 
 | ||||
| // WantsServiceResolver defines a function that accepts a ServiceResolver for
 | ||||
| // admission plugins that need to make calls to services.
 | ||||
| type WantsServiceResolver interface { | ||||
| 	SetServiceResolver(webhookconfig.ServiceResolver) | ||||
| 	SetServiceResolver(webhook.ServiceResolver) | ||||
| } | ||||
| 
 | ||||
| // ServiceResolver knows how to convert a service reference into an actual
 | ||||
|  | @ -38,22 +38,22 @@ type ServiceResolver interface { | |||
| // WantsAuthenticationInfoResolverWrapper defines a function that wraps the standard AuthenticationInfoResolver
 | ||||
| // to allow the apiserver to control what is returned as auth info
 | ||||
| type WantsAuthenticationInfoResolverWrapper interface { | ||||
| 	SetAuthenticationInfoResolverWrapper(webhookconfig.AuthenticationInfoResolverWrapper) | ||||
| 	SetAuthenticationInfoResolverWrapper(wrapper webhook.AuthenticationInfoResolverWrapper) | ||||
| 	admission.InitializationValidator | ||||
| } | ||||
| 
 | ||||
| // PluginInitializer is used for initialization of the webhook admission plugin.
 | ||||
| type PluginInitializer struct { | ||||
| 	serviceResolver                   webhookconfig.ServiceResolver | ||||
| 	authenticationInfoResolverWrapper webhookconfig.AuthenticationInfoResolverWrapper | ||||
| 	serviceResolver                   webhook.ServiceResolver | ||||
| 	authenticationInfoResolverWrapper webhook.AuthenticationInfoResolverWrapper | ||||
| } | ||||
| 
 | ||||
| var _ admission.PluginInitializer = &PluginInitializer{} | ||||
| 
 | ||||
| // NewPluginInitializer constructs new instance of PluginInitializer
 | ||||
| func NewPluginInitializer( | ||||
| 	authenticationInfoResolverWrapper webhookconfig.AuthenticationInfoResolverWrapper, | ||||
| 	serviceResolver webhookconfig.ServiceResolver, | ||||
| 	authenticationInfoResolverWrapper webhook.AuthenticationInfoResolverWrapper, | ||||
| 	serviceResolver webhook.ServiceResolver, | ||||
| ) *PluginInitializer { | ||||
| 	return &PluginInitializer{ | ||||
| 		authenticationInfoResolverWrapper: authenticationInfoResolverWrapper, | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ import ( | |||
| 	"testing" | ||||
| 
 | ||||
| 	"k8s.io/apiserver/pkg/admission" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/config" | ||||
| 	"k8s.io/apiserver/pkg/util/webhook" | ||||
| ) | ||||
| 
 | ||||
| type doNothingAdmission struct{} | ||||
|  | @ -39,7 +39,7 @@ type serviceWanter struct { | |||
| 	got ServiceResolver | ||||
| } | ||||
| 
 | ||||
| func (s *serviceWanter) SetServiceResolver(sr config.ServiceResolver) { s.got = sr } | ||||
| func (s *serviceWanter) SetServiceResolver(sr webhook.ServiceResolver) { s.got = sr } | ||||
| 
 | ||||
| func TestWantsServiceResolver(t *testing.T) { | ||||
| 	sw := &serviceWanter{} | ||||
|  |  | |||
|  | @ -33,19 +33,20 @@ import ( | |||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||||
| 	admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/config" | ||||
| 	webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/generic" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/request" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/util" | ||||
| 	"k8s.io/apiserver/pkg/util/webhook" | ||||
| ) | ||||
| 
 | ||||
| type mutatingDispatcher struct { | ||||
| 	cm     *config.ClientManager | ||||
| 	cm     *webhook.ClientManager | ||||
| 	plugin *Plugin | ||||
| } | ||||
| 
 | ||||
| func newMutatingDispatcher(p *Plugin) func(cm *config.ClientManager) generic.Dispatcher { | ||||
| 	return func(cm *config.ClientManager) generic.Dispatcher { | ||||
| func newMutatingDispatcher(p *Plugin) func(cm *webhook.ClientManager) generic.Dispatcher { | ||||
| 	return func(cm *webhook.ClientManager) generic.Dispatcher { | ||||
| 		return &mutatingDispatcher{cm, p} | ||||
| 	} | ||||
| } | ||||
|  | @ -62,7 +63,7 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr *generic.Version | |||
| 		} | ||||
| 
 | ||||
| 		ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore | ||||
| 		if callErr, ok := err.(*webhookerrors.ErrCallingWebhook); ok { | ||||
| 		if callErr, ok := err.(*webhook.ErrCallingWebhook); ok { | ||||
| 			if ignoreClientCallFailures { | ||||
| 				glog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr) | ||||
| 				utilruntime.HandleError(callErr) | ||||
|  | @ -84,7 +85,7 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr *generic.Version | |||
| func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.Webhook, attr *generic.VersionedAttributes) error { | ||||
| 	if attr.IsDryRun() { | ||||
| 		if h.SideEffects == nil { | ||||
| 			return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} | ||||
| 			return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} | ||||
| 		} | ||||
| 		if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) { | ||||
| 			return webhookerrors.NewDryRunUnsupportedErr(h.Name) | ||||
|  | @ -93,17 +94,17 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta | |||
| 
 | ||||
| 	// Make the webhook request
 | ||||
| 	request := request.CreateAdmissionReview(attr) | ||||
| 	client, err := a.cm.HookClient(h) | ||||
| 	client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h)) | ||||
| 	if err != nil { | ||||
| 		return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} | ||||
| 		return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} | ||||
| 	} | ||||
| 	response := &admissionv1beta1.AdmissionReview{} | ||||
| 	if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil { | ||||
| 		return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} | ||||
| 		return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} | ||||
| 	} | ||||
| 
 | ||||
| 	if response.Response == nil { | ||||
| 		return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} | ||||
| 		return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} | ||||
| 	} | ||||
| 
 | ||||
| 	for k, v := range response.Response.AuditAnnotations { | ||||
|  |  | |||
|  | @ -19,22 +19,22 @@ package testing | |||
| import ( | ||||
| 	"sync/atomic" | ||||
| 
 | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/config" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts" | ||||
| 	"k8s.io/apiserver/pkg/util/webhook" | ||||
| 	"k8s.io/client-go/rest" | ||||
| ) | ||||
| 
 | ||||
| // Wrapper turns an AuthenticationInfoResolver into a AuthenticationInfoResolverWrapper that unconditionally
 | ||||
| // returns the given AuthenticationInfoResolver.
 | ||||
| func Wrapper(r config.AuthenticationInfoResolver) func(config.AuthenticationInfoResolver) config.AuthenticationInfoResolver { | ||||
| 	return func(config.AuthenticationInfoResolver) config.AuthenticationInfoResolver { | ||||
| func Wrapper(r webhook.AuthenticationInfoResolver) func(webhook.AuthenticationInfoResolver) webhook.AuthenticationInfoResolver { | ||||
| 	return func(webhook.AuthenticationInfoResolver) webhook.AuthenticationInfoResolver { | ||||
| 		return r | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NewAuthenticationInfoResolver creates a fake AuthenticationInfoResolver that counts cache misses on
 | ||||
| // every call to its methods.
 | ||||
| func NewAuthenticationInfoResolver(cacheMisses *int32) config.AuthenticationInfoResolver { | ||||
| func NewAuthenticationInfoResolver(cacheMisses *int32) webhook.AuthenticationInfoResolver { | ||||
| 	return &authenticationInfoResolver{ | ||||
| 		restConfig: &rest.Config{ | ||||
| 			TLSClientConfig: rest.TLSClientConfig{ | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ import ( | |||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 
 | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/config" | ||||
| 	"k8s.io/apiserver/pkg/util/webhook" | ||||
| ) | ||||
| 
 | ||||
| type serviceResolver struct { | ||||
|  | @ -29,7 +29,7 @@ type serviceResolver struct { | |||
| 
 | ||||
| // NewServiceResolver returns a static service resolve that return the given URL or
 | ||||
| // an error for the failResolve namespace.
 | ||||
| func NewServiceResolver(base url.URL) config.ServiceResolver { | ||||
| func NewServiceResolver(base url.URL) webhook.ServiceResolver { | ||||
| 	return &serviceResolver{base} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,42 @@ | |||
| /* | ||||
| Copyright 2018 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 util | ||||
| 
 | ||||
| import ( | ||||
| 	"k8s.io/api/admissionregistration/v1beta1" | ||||
| 	"k8s.io/apiserver/pkg/util/webhook" | ||||
| ) | ||||
| 
 | ||||
| // HookClientConfigForWebhook construct a webhook.ClientConfig using a v1beta1.Webhook API object.
 | ||||
| // webhook.ClientConfig is used to create a HookClient and the purpose of the config struct is to
 | ||||
| // share that with other packages that need to create a HookClient.
 | ||||
| func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig { | ||||
| 	ret := webhook.ClientConfig{Name: w.Name, CABundle: w.ClientConfig.CABundle} | ||||
| 	if w.ClientConfig.URL != nil { | ||||
| 		ret.URL = *w.ClientConfig.URL | ||||
| 	} | ||||
| 	if w.ClientConfig.Service != nil { | ||||
| 		ret.Service = &webhook.ClientConfigService{ | ||||
| 			Name:      w.ClientConfig.Service.Name, | ||||
| 			Namespace: w.ClientConfig.Service.Namespace, | ||||
| 		} | ||||
| 		if w.ClientConfig.Service.Path != nil { | ||||
| 			ret.Service.Path = *w.ClientConfig.Service.Path | ||||
| 		} | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | @ -29,17 +29,18 @@ import ( | |||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||||
| 	admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/config" | ||||
| 	webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/generic" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/request" | ||||
| 	"k8s.io/apiserver/pkg/admission/plugin/webhook/util" | ||||
| 	"k8s.io/apiserver/pkg/util/webhook" | ||||
| ) | ||||
| 
 | ||||
| type validatingDispatcher struct { | ||||
| 	cm *config.ClientManager | ||||
| 	cm *webhook.ClientManager | ||||
| } | ||||
| 
 | ||||
| func newValidatingDispatcher(cm *config.ClientManager) generic.Dispatcher { | ||||
| func newValidatingDispatcher(cm *webhook.ClientManager) generic.Dispatcher { | ||||
| 	return &validatingDispatcher{cm} | ||||
| } | ||||
| 
 | ||||
|  | @ -61,7 +62,7 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.Versi | |||
| 			} | ||||
| 
 | ||||
| 			ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore | ||||
| 			if callErr, ok := err.(*webhookerrors.ErrCallingWebhook); ok { | ||||
| 			if callErr, ok := err.(*webhook.ErrCallingWebhook); ok { | ||||
| 				if ignoreClientCallFailures { | ||||
| 					glog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr) | ||||
| 					utilruntime.HandleError(callErr) | ||||
|  | @ -99,7 +100,7 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr *generic.Versi | |||
| func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook, attr *generic.VersionedAttributes) error { | ||||
| 	if attr.IsDryRun() { | ||||
| 		if h.SideEffects == nil { | ||||
| 			return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} | ||||
| 			return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} | ||||
| 		} | ||||
| 		if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) { | ||||
| 			return webhookerrors.NewDryRunUnsupportedErr(h.Name) | ||||
|  | @ -108,17 +109,17 @@ func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Webhook, | |||
| 
 | ||||
| 	// Make the webhook request
 | ||||
| 	request := request.CreateAdmissionReview(attr) | ||||
| 	client, err := d.cm.HookClient(h) | ||||
| 	client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h)) | ||||
| 	if err != nil { | ||||
| 		return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} | ||||
| 		return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} | ||||
| 	} | ||||
| 	response := &admissionv1beta1.AdmissionReview{} | ||||
| 	if err := client.Post().Context(ctx).Body(&request).Do().Into(response); err != nil { | ||||
| 		return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} | ||||
| 		return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} | ||||
| 	} | ||||
| 
 | ||||
| 	if response.Response == nil { | ||||
| 		return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} | ||||
| 		return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} | ||||
| 	} | ||||
| 	for k, v := range response.Response.AuditAnnotations { | ||||
| 		key := h.Name + "/" + k | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package config | ||||
| package webhook | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | @ -47,10 +47,12 @@ type AuthenticationInfoResolverDelegator struct { | |||
| 	ClientConfigForServiceFunc func(serviceName, serviceNamespace string) (*rest.Config, error) | ||||
| } | ||||
| 
 | ||||
| // ClientConfigFor returns client config for given server.
 | ||||
| func (a *AuthenticationInfoResolverDelegator) ClientConfigFor(server string) (*rest.Config, error) { | ||||
| 	return a.ClientConfigForFunc(server) | ||||
| } | ||||
| 
 | ||||
| // ClientConfigForService returns client config for given service.
 | ||||
| func (a *AuthenticationInfoResolverDelegator) ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error) { | ||||
| 	return a.ClientConfigForServiceFunc(serviceName, serviceNamespace) | ||||
| } | ||||
|  | @ -14,7 +14,7 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package config | ||||
| package webhook | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
|  | @ -14,7 +14,7 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package config | ||||
| package webhook | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
|  | @ -24,13 +24,11 @@ import ( | |||
| 	"net" | ||||
| 	"net/url" | ||||
| 
 | ||||
| 	lru "github.com/hashicorp/golang-lru" | ||||
| 	admissionv1beta1 "k8s.io/api/admission/v1beta1" | ||||
| 	"k8s.io/api/admissionregistration/v1beta1" | ||||
| 	"github.com/hashicorp/golang-lru" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/serializer" | ||||
| 	utilerrors "k8s.io/apimachinery/pkg/util/errors" | ||||
| 	webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" | ||||
| 	"k8s.io/client-go/rest" | ||||
| ) | ||||
| 
 | ||||
|  | @ -38,9 +36,20 @@ const ( | |||
| 	defaultCacheSize = 200 | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	ErrNeedServiceOrURL = errors.New("webhook configuration must have either service or URL") | ||||
| ) | ||||
| // ClientConfig defines parameters required for creating a hook client.
 | ||||
| type ClientConfig struct { | ||||
| 	Name     string | ||||
| 	URL      string | ||||
| 	CABundle []byte | ||||
| 	Service  *ClientConfigService | ||||
| } | ||||
| 
 | ||||
| // ClientConfigService defines service discovery parameters of the webhook.
 | ||||
| type ClientConfigService struct { | ||||
| 	Name      string | ||||
| 	Namespace string | ||||
| 	Path      string | ||||
| } | ||||
| 
 | ||||
| // ClientManager builds REST clients to talk to webhooks. It caches the clients
 | ||||
| // to avoid duplicate creation.
 | ||||
|  | @ -52,19 +61,19 @@ type ClientManager struct { | |||
| } | ||||
| 
 | ||||
| // NewClientManager creates a clientManager.
 | ||||
| func NewClientManager() (ClientManager, error) { | ||||
| func NewClientManager(gv schema.GroupVersion, addToSchemaFunc func(s *runtime.Scheme) error) (ClientManager, error) { | ||||
| 	cache, err := lru.New(defaultCacheSize) | ||||
| 	if err != nil { | ||||
| 		return ClientManager{}, err | ||||
| 	} | ||||
| 	admissionScheme := runtime.NewScheme() | ||||
| 	if err := admissionv1beta1.AddToScheme(admissionScheme); err != nil { | ||||
| 	if err := addToSchemaFunc(admissionScheme); err != nil { | ||||
| 		return ClientManager{}, err | ||||
| 	} | ||||
| 	return ClientManager{ | ||||
| 		cache: cache, | ||||
| 		negotiatedSerializer: serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{ | ||||
| 			Serializer: serializer.NewCodecFactory(admissionScheme).LegacyCodec(admissionv1beta1.SchemeGroupVersion), | ||||
| 			Serializer: serializer.NewCodecFactory(admissionScheme).LegacyCodec(gv), | ||||
| 		}), | ||||
| 	}, nil | ||||
| } | ||||
|  | @ -106,8 +115,10 @@ func (cm *ClientManager) Validate() error { | |||
| 
 | ||||
| // HookClient get a RESTClient from the cache, or constructs one based on the
 | ||||
| // webhook configuration.
 | ||||
| func (cm *ClientManager) HookClient(h *v1beta1.Webhook) (*rest.RESTClient, error) { | ||||
| 	cacheKey, err := json.Marshal(h.ClientConfig) | ||||
| func (cm *ClientManager) HookClient(cc ClientConfig) (*rest.RESTClient, error) { | ||||
| 	ccWithNoName := cc | ||||
| 	ccWithNoName.Name = "" | ||||
| 	cacheKey, err := json.Marshal(ccWithNoName) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | @ -120,7 +131,7 @@ func (cm *ClientManager) HookClient(h *v1beta1.Webhook) (*rest.RESTClient, error | |||
| 		if len(cfg.TLSClientConfig.CAData) > 0 { | ||||
| 			cfg.TLSClientConfig.CAData = append(cfg.TLSClientConfig.CAData, '\n') | ||||
| 		} | ||||
| 		cfg.TLSClientConfig.CAData = append(cfg.TLSClientConfig.CAData, h.ClientConfig.CABundle...) | ||||
| 		cfg.TLSClientConfig.CAData = append(cfg.TLSClientConfig.CAData, cc.CABundle...) | ||||
| 
 | ||||
| 		cfg.ContentConfig.NegotiatedSerializer = cm.negotiatedSerializer | ||||
| 		cfg.ContentConfig.ContentType = runtime.ContentTypeJSON | ||||
|  | @ -131,18 +142,16 @@ func (cm *ClientManager) HookClient(h *v1beta1.Webhook) (*rest.RESTClient, error | |||
| 		return client, err | ||||
| 	} | ||||
| 
 | ||||
| 	if svc := h.ClientConfig.Service; svc != nil { | ||||
| 		restConfig, err := cm.authInfoResolver.ClientConfigForService(svc.Name, svc.Namespace) | ||||
| 	if cc.Service != nil { | ||||
| 		restConfig, err := cm.authInfoResolver.ClientConfigForService(cc.Service.Name, cc.Service.Namespace) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		cfg := rest.CopyConfig(restConfig) | ||||
| 		serverName := svc.Name + "." + svc.Namespace + ".svc" | ||||
| 		serverName := cc.Service.Name + "." + cc.Service.Namespace + ".svc" | ||||
| 		host := serverName + ":443" | ||||
| 		cfg.Host = "https://" + host | ||||
| 		if svc.Path != nil { | ||||
| 			cfg.APIPath = *svc.Path | ||||
| 		} | ||||
| 		cfg.APIPath = cc.Service.Path | ||||
| 		// Set the server name if not already set
 | ||||
| 		if len(cfg.TLSClientConfig.ServerName) == 0 { | ||||
| 			cfg.TLSClientConfig.ServerName = serverName | ||||
|  | @ -155,7 +164,7 @@ func (cm *ClientManager) HookClient(h *v1beta1.Webhook) (*rest.RESTClient, error | |||
| 		} | ||||
| 		cfg.Dial = func(ctx context.Context, network, addr string) (net.Conn, error) { | ||||
| 			if addr == host { | ||||
| 				u, err := cm.serviceResolver.ResolveEndpoint(svc.Namespace, svc.Name) | ||||
| 				u, err := cm.serviceResolver.ResolveEndpoint(cc.Service.Namespace, cc.Service.Name) | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
|  | @ -167,13 +176,13 @@ func (cm *ClientManager) HookClient(h *v1beta1.Webhook) (*rest.RESTClient, error | |||
| 		return complete(cfg) | ||||
| 	} | ||||
| 
 | ||||
| 	if h.ClientConfig.URL == nil { | ||||
| 		return nil, &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: ErrNeedServiceOrURL} | ||||
| 	if cc.URL == "" { | ||||
| 		return nil, &ErrCallingWebhook{WebhookName: cc.Name, Reason: errors.New("webhook configuration must have either service or URL")} | ||||
| 	} | ||||
| 
 | ||||
| 	u, err := url.Parse(*h.ClientConfig.URL) | ||||
| 	u, err := url.Parse(cc.URL) | ||||
| 	if err != nil { | ||||
| 		return nil, &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Unparsable URL: %v", err)} | ||||
| 		return nil, &ErrCallingWebhook{WebhookName: cc.Name, Reason: fmt.Errorf("Unparsable URL: %v", err)} | ||||
| 	} | ||||
| 
 | ||||
| 	restConfig, err := cm.authInfoResolver.ClientConfigFor(u.Host) | ||||
|  | @ -14,7 +14,7 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package errors | ||||
| package webhook | ||||
| 
 | ||||
| import "fmt" | ||||
| 
 | ||||
|  | @ -14,7 +14,7 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package config | ||||
| package webhook | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
|  | @ -29,6 +29,7 @@ type ServiceResolver interface { | |||
| 
 | ||||
| type defaultServiceResolver struct{} | ||||
| 
 | ||||
| // NewDefaultServiceResolver creates a new default server resolver.
 | ||||
| func NewDefaultServiceResolver() ServiceResolver { | ||||
| 	return &defaultServiceResolver{} | ||||
| } | ||||
|  | @ -14,7 +14,7 @@ See the License for the specific language governing permissions and | |||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package config | ||||
| package webhook | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
		Loading…
	
		Reference in New Issue