Merge pull request #67798 from mbohlool/crd_refactoring

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions here: https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md.

Refactor admission webhook client code to a apiserver/pkg/util package

As part of #67006 This refactoring enable us to share code between admission webhooks and CRD conversion webhooks.

@deads2k @lavalamp @sttts @kubernetes/sig-api-machinery-misc
pull/8/head
Kubernetes Submit Queue 2018-08-31 06:16:28 -07:00 committed by GitHub
commit 14eb029fba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 218 additions and 118 deletions

View File

@ -54,7 +54,6 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
@ -68,6 +67,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
"//staging/src/k8s.io/client-go/discovery/cached:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",

View File

@ -42,7 +42,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
utilwait "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/admission"
webhookconfig "k8s.io/apiserver/pkg/admission/plugin/webhook/config"
webhookinit "k8s.io/apiserver/pkg/admission/plugin/webhook/initializer"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authorization/authorizer"
@ -54,6 +53,7 @@ import (
"k8s.io/apiserver/pkg/storage/etcd3/preflight"
utilfeature "k8s.io/apiserver/pkg/util/feature"
apiserverflag "k8s.io/apiserver/pkg/util/flag"
"k8s.io/apiserver/pkg/util/webhook"
cacheddiscovery "k8s.io/client-go/discovery/cached"
clientgoinformers "k8s.io/client-go/informers"
clientgoclientset "k8s.io/client-go/kubernetes"
@ -539,8 +539,8 @@ func buildGenericConfig(
genericConfig.DisabledPostStartHooks.Insert(rbacrest.PostStartHookName)
}
webhookAuthResolverWrapper := func(delegate webhookconfig.AuthenticationInfoResolver) webhookconfig.AuthenticationInfoResolver {
return &webhookconfig.AuthenticationInfoResolverDelegator{
webhookAuthResolverWrapper := func(delegate webhook.AuthenticationInfoResolver) webhook.AuthenticationInfoResolver {
return &webhook.AuthenticationInfoResolverDelegator{
ClientConfigForFunc: func(server string) (*rest.Config, error) {
if server == "kubernetes.default.svc" {
return genericConfig.LoopbackClientConfig, nil
@ -593,7 +593,7 @@ func BuildAdmissionPluginInitializers(
client internalclientset.Interface,
sharedInformers informers.SharedInformerFactory,
serviceResolver aggregatorapiserver.ServiceResolver,
webhookAuthWrapper webhookconfig.AuthenticationInfoResolverWrapper,
webhookAuthWrapper webhook.AuthenticationInfoResolverWrapper,
) ([]admission.PluginInitializer, genericapiserver.PostStartHookFunc, error) {
var cloudConfig []byte

View File

@ -550,7 +550,6 @@ staging/src/k8s.io/apiserver/pkg/admission/configuration
staging/src/k8s.io/apiserver/pkg/admission/initializer
staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization
staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating

View File

@ -23,8 +23,8 @@ go_library(
"//pkg/quota:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
],
)

View File

@ -19,8 +19,8 @@ package admission
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apiserver/pkg/admission"
webhookconfig "k8s.io/apiserver/pkg/admission/plugin/webhook/config"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/util/webhook"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/quota"
@ -64,8 +64,8 @@ type PluginInitializer struct {
cloudConfig []byte
restMapper meta.RESTMapper
quotaConfiguration quota.Configuration
serviceResolver webhookconfig.ServiceResolver
authenticationInfoResolverWrapper webhookconfig.AuthenticationInfoResolverWrapper
serviceResolver webhook.ServiceResolver
authenticationInfoResolverWrapper webhook.AuthenticationInfoResolverWrapper
}
var _ admission.PluginInitializer = &PluginInitializer{}

View File

@ -60,6 +60,7 @@ filegroup(
"//staging/src/k8s.io/apimachinery/pkg/util/validation:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/waitgroup:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/webhook:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/version:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/watch:all-srcs",

View File

@ -1234,6 +1234,10 @@
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/rules",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/util",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/validating",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

View File

@ -0,0 +1,13 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -90,6 +90,7 @@ filegroup(
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating:all-srcs",
],
tags = ["automanaged"],

View File

@ -1,46 +1,18 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"authentication.go",
"client.go",
"kubeconfig.go",
"serviceresolver.go",
],
srcs = ["kubeconfig.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config",
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/config",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"authentication_test.go",
"serviceresolver_test.go",
],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
],
)

View File

@ -4,7 +4,6 @@ go_library(
name = "go_default_library",
srcs = [
"doc.go",
"errors.go",
"statuserror.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors",

View File

@ -11,6 +11,7 @@ go_library(
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/generic",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
@ -20,6 +21,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
],

View File

@ -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)
}

View File

@ -8,7 +8,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
],
)
@ -18,7 +18,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
],
)

View File

@ -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,

View File

@ -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{}

View File

@ -21,10 +21,11 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/configuration:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/metrics:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],

View File

@ -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 {

View File

@ -20,9 +20,9 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",

View File

@ -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{

View File

@ -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}
}

View File

@ -0,0 +1,27 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["client_config.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/util",
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/util",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -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
}

View File

@ -18,10 +18,11 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/configuration:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/metrics:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],
)

View File

@ -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

View File

@ -8,7 +8,13 @@ load(
go_library(
name = "go_default_library",
srcs = ["webhook.go"],
srcs = [
"authentication.go",
"client.go",
"error.go",
"serviceresolver.go",
"webhook.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/util/webhook",
importpath = "k8s.io/apiserver/pkg/util/webhook",
deps = [
@ -16,26 +22,34 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"authentication_test.go",
"certs_test.go",
"serviceresolver_test.go",
"webhook_test.go",
],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
],
)

View File

@ -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)
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package config
package webhook
import (
"testing"

View File

@ -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)

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package errors
package webhook
import "fmt"

View File

@ -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{}
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package config
package webhook
import (
"fmt"

View File

@ -926,6 +926,10 @@
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/rules",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/util",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/validating",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

View File

@ -890,6 +890,10 @@
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/rules",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/util",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/admission/plugin/webhook/validating",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"