split admissionregistration.v1beta1/Webhook into MutatingWebhook and ValidatingWebhook

This commit is contained in:
Joe Betz 2019-05-29 21:30:45 -07:00 committed by Chao Xu
parent 9356561c86
commit 55ecc45455
34 changed files with 846 additions and 269 deletions

View File

@ -423,6 +423,7 @@ staging/src/k8s.io/apimachinery/pkg/watch
staging/src/k8s.io/apiserver/pkg/admission staging/src/k8s.io/apiserver/pkg/admission
staging/src/k8s.io/apiserver/pkg/admission/configuration staging/src/k8s.io/apiserver/pkg/admission/configuration
staging/src/k8s.io/apiserver/pkg/admission/initializer staging/src/k8s.io/apiserver/pkg/admission/initializer
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission 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/config/apis/webhookadmission/v1alpha1
staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts

View File

@ -33,7 +33,21 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
obj.Scope = &s obj.Scope = &s
} }
}, },
func(obj *admissionregistration.Webhook, c fuzz.Continue) { func(obj *admissionregistration.ValidatingWebhook, c fuzz.Continue) {
c.FuzzNoCustom(obj) // fuzz self without calling this function again
p := admissionregistration.FailurePolicyType("Fail")
obj.FailurePolicy = &p
m := admissionregistration.MatchPolicyType("Exact")
obj.MatchPolicy = &m
s := admissionregistration.SideEffectClassUnknown
obj.SideEffects = &s
if obj.TimeoutSeconds == nil {
i := int32(30)
obj.TimeoutSeconds = &i
}
obj.AdmissionReviewVersions = []string{"v1beta1"}
},
func(obj *admissionregistration.MutatingWebhook, c fuzz.Continue) {
c.FuzzNoCustom(obj) // fuzz self without calling this function again c.FuzzNoCustom(obj) // fuzz self without calling this function again
p := admissionregistration.FailurePolicyType("Fail") p := admissionregistration.FailurePolicyType("Fail")
obj.FailurePolicy = &p obj.FailurePolicy = &p

View File

@ -123,7 +123,7 @@ type ValidatingWebhookConfiguration struct {
metav1.ObjectMeta metav1.ObjectMeta
// Webhooks is a list of webhooks and the affected resources and operations. // Webhooks is a list of webhooks and the affected resources and operations.
// +optional // +optional
Webhooks []Webhook Webhooks []ValidatingWebhook
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -149,7 +149,7 @@ type MutatingWebhookConfiguration struct {
metav1.ObjectMeta metav1.ObjectMeta
// Webhooks is a list of webhooks and the affected resources and operations. // Webhooks is a list of webhooks and the affected resources and operations.
// +optional // +optional
Webhooks []Webhook Webhooks []MutatingWebhook
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -165,8 +165,118 @@ type MutatingWebhookConfigurationList struct {
Items []MutatingWebhookConfiguration Items []MutatingWebhookConfiguration
} }
// Webhook describes an admission webhook and the resources and operations it applies to. // ValidatingWebhook describes an admission webhook and the resources and operations it applies to.
type Webhook struct { type ValidatingWebhook struct {
// The name of the admission webhook.
// Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where
// "imagepolicy" is the name of the webhook, and kubernetes.io is the name
// of the organization.
// Required.
Name string
// ClientConfig defines how to communicate with the hook.
// Required
ClientConfig WebhookClientConfig
// Rules describes what operations on what resources/subresources the webhook cares about.
// The webhook cares about an operation if it matches _any_ Rule.
Rules []RuleWithOperations
// FailurePolicy defines how unrecognized errors from the admission endpoint are handled -
// allowed values are Ignore or Fail. Defaults to Ignore.
// +optional
FailurePolicy *FailurePolicyType
// matchPolicy defines how the "rules" list is used to match incoming requests.
// Allowed values are "Exact" or "Equivalent".
//
// - Exact: match a request only if it exactly matches a specified rule.
// For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1,
// but "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`,
// a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.
//
// - Equivalent: match a request if modifies a resource listed in rules, even via another API group or version.
// For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1,
// and "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`,
// a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.
//
// +optional
MatchPolicy *MatchPolicyType
// NamespaceSelector decides whether to run the webhook on an object based
// on whether the namespace for that object matches the selector. If the
// object itself is a namespace, the matching is performed on
// object.metadata.labels. If the object is another cluster scoped resource,
// it never skips the webhook.
//
// For example, to run the webhook on any objects whose namespace is not
// associated with "runlevel" of "0" or "1"; you will set the selector as
// follows:
// "namespaceSelector": {
// "matchExpressions": [
// {
// "key": "runlevel",
// "operator": "NotIn",
// "values": [
// "0",
// "1"
// ]
// }
// ]
// }
//
// If instead you want to only run the webhook on any objects whose
// namespace is associated with the "environment" of "prod" or "staging";
// you will set the selector as follows:
// "namespaceSelector": {
// "matchExpressions": [
// {
// "key": "environment",
// "operator": "In",
// "values": [
// "prod",
// "staging"
// ]
// }
// ]
// }
//
// See
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
// for more examples of label selectors.
//
// Default to the empty LabelSelector, which matches everything.
// +optional
NamespaceSelector *metav1.LabelSelector
// SideEffects states whether this webhookk has side effects.
// Acceptable values are: Unknown, None, Some, NoneOnDryRun
// Webhooks with side effects MUST implement a reconciliation system, since a request may be
// rejected by a future step in the admission change and the side effects therefore need to be undone.
// Requests with the dryRun attribute will be auto-rejected if they match a webhook with
// sideEffects == Unknown or Some. Defaults to Unknown.
// +optional
SideEffects *SideEffectClass
// TimeoutSeconds specifies the timeout for this webhook. After the timeout passes,
// the webhook call will be ignored or the API call will fail based on the
// failure policy.
// The timeout value must be between 1 and 30 seconds.
// +optional
TimeoutSeconds *int32
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, validation will fail for this object.
// If the webhook configuration has already been persisted with a version apiserver
// does not understand, calls to the webhook will fail and be subject to the failure policy.
// +optional
AdmissionReviewVersions []string
}
// MutatingWebhook describes an admission webhook and the resources and operations it applies to.
type MutatingWebhook struct {
// The name of the admission webhook. // The name of the admission webhook.
// Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where
// "imagepolicy" is the name of the webhook, and kubernetes.io is the name // "imagepolicy" is the name of the webhook, and kubernetes.io is the name

View File

@ -27,7 +27,35 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme) return RegisterDefaults(scheme)
} }
func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) { func SetDefaults_ValidatingWebhook(obj *admissionregistrationv1beta1.ValidatingWebhook) {
if obj.FailurePolicy == nil {
policy := admissionregistrationv1beta1.Ignore
obj.FailurePolicy = &policy
}
if obj.MatchPolicy == nil {
policy := admissionregistrationv1beta1.Exact
obj.MatchPolicy = &policy
}
if obj.NamespaceSelector == nil {
selector := metav1.LabelSelector{}
obj.NamespaceSelector = &selector
}
if obj.SideEffects == nil {
// TODO: revisit/remove this default and possibly make the field required when promoting to v1
unknown := admissionregistrationv1beta1.SideEffectClassUnknown
obj.SideEffects = &unknown
}
if obj.TimeoutSeconds == nil {
obj.TimeoutSeconds = new(int32)
*obj.TimeoutSeconds = 30
}
if len(obj.AdmissionReviewVersions) == 0 {
obj.AdmissionReviewVersions = []string{admissionregistrationv1beta1.SchemeGroupVersion.Version}
}
}
func SetDefaults_MutatingWebhook(obj *admissionregistrationv1beta1.MutatingWebhook) {
if obj.FailurePolicy == nil { if obj.FailurePolicy == nil {
policy := admissionregistrationv1beta1.Ignore policy := admissionregistrationv1beta1.Ignore
obj.FailurePolicy = &policy obj.FailurePolicy = &policy

View File

@ -201,7 +201,7 @@ func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingW
func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList { func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList {
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
for i, hook := range e.Webhooks { for i, hook := range e.Webhooks {
allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) allErrors = append(allErrors, validateValidatingWebhook(&hook, field.NewPath("webhooks").Index(i))...)
allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...) allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
} }
return allErrors return allErrors
@ -214,13 +214,50 @@ func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebho
func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList { func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList {
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
for i, hook := range e.Webhooks { for i, hook := range e.Webhooks {
allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) allErrors = append(allErrors, validateMutatingWebhook(&hook, field.NewPath("webhooks").Index(i))...)
allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...) allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...)
} }
return allErrors return allErrors
} }
func validateWebhook(hook *admissionregistration.Webhook, fldPath *field.Path) field.ErrorList { func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, fldPath *field.Path) field.ErrorList {
var allErrors field.ErrorList
// hook.Name must be fully qualified
allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
for i, rule := range hook.Rules {
allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
}
if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) {
allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List()))
}
if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) {
allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List()))
}
if hook.SideEffects != nil && !supportedSideEffectClasses.Has(string(*hook.SideEffects)) {
allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, supportedSideEffectClasses.List()))
}
if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) {
allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds"))
}
if hook.NamespaceSelector != nil {
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
}
cc := hook.ClientConfig
switch {
case (cc.URL == nil) == (cc.Service == nil):
allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required"))
case cc.URL != nil:
allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...)
case cc.Service != nil:
allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
}
return allErrors
}
func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, fldPath *field.Path) field.ErrorList {
var allErrors field.ErrorList var allErrors field.ErrorList
// hook.Name must be fully qualified // hook.Name must be fully qualified
allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...) allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
@ -309,9 +346,27 @@ func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWi
return allErrors return allErrors
} }
// hasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one // mutatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
// admission review version this apiserver accepts. // admission review version this apiserver accepts.
func hasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.Webhook) bool { func mutatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.MutatingWebhook) bool {
for _, hook := range webhooks {
hasRecognizedVersion := false
for _, version := range hook.AdmissionReviewVersions {
if isAcceptedAdmissionReviewVersion(version) {
hasRecognizedVersion = true
break
}
}
if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 {
return false
}
}
return true
}
// validatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one
// admission review version this apiserver accepts.
func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.ValidatingWebhook) bool {
for _, hook := range webhooks { for _, hook := range webhooks {
hasRecognizedVersion := false hasRecognizedVersion := false
for _, version := range hook.AdmissionReviewVersions { for _, version := range hook.AdmissionReviewVersions {
@ -328,9 +383,9 @@ func hasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.Webhook
} }
func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
return validateValidatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks)) return validateValidatingWebhookConfiguration(newC, validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks))
} }
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
return validateMutatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks)) return validateMutatingWebhookConfiguration(newC, mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks))
} }

View File

@ -28,7 +28,7 @@ func strPtr(s string) *string { return &s }
func int32Ptr(i int32) *int32 { return &i } func int32Ptr(i int32) *int32 { return &i }
func newValidatingWebhookConfiguration(hooks []admissionregistration.Webhook, defaultAdmissionReviewVersions bool) *admissionregistration.ValidatingWebhookConfiguration { func newValidatingWebhookConfiguration(hooks []admissionregistration.ValidatingWebhook, defaultAdmissionReviewVersions bool) *admissionregistration.ValidatingWebhookConfiguration {
// If the test case did not specify an AdmissionReviewVersions, default it so the test passes as // If the test case did not specify an AdmissionReviewVersions, default it so the test passes as
// this field will be defaulted in production code. // this field will be defaulted in production code.
for i := range hooks { for i := range hooks {
@ -57,7 +57,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}{ }{
{ {
name: "should fail on bad AdmissionReviewVersion value", name: "should fail on bad AdmissionReviewVersion value",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -68,7 +68,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "should pass on valid AdmissionReviewVersion", name: "should pass on valid AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -79,7 +79,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion", name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -90,7 +90,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "should fail on invalid AdmissionReviewVersion", name: "should fail on invalid AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -101,7 +101,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "should fail on duplicate AdmissionReviewVersion", name: "should fail on duplicate AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -112,7 +112,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "all Webhooks must have a fully qualified name", name: "all Webhooks must have a fully qualified name",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -130,7 +130,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "Operations must not be empty or nil", name: "Operations must not be empty or nil",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
Rules: []admissionregistration.RuleWithOperations{ Rules: []admissionregistration.RuleWithOperations{
@ -157,7 +157,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "\"\" is NOT a valid operation", name: "\"\" is NOT a valid operation",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
Rules: []admissionregistration.RuleWithOperations{ Rules: []admissionregistration.RuleWithOperations{
@ -176,7 +176,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "operation must be either create/update/delete/connect", name: "operation must be either create/update/delete/connect",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
Rules: []admissionregistration.RuleWithOperations{ Rules: []admissionregistration.RuleWithOperations{
@ -195,7 +195,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "wildcard operation cannot be mixed with other strings", name: "wildcard operation cannot be mixed with other strings",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
Rules: []admissionregistration.RuleWithOperations{ Rules: []admissionregistration.RuleWithOperations{
@ -214,7 +214,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: `resource "*" can co-exist with resources that have subresources`, name: `resource "*" can co-exist with resources that have subresources`,
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -233,7 +233,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: `resource "*" cannot mix with resources that don't have subresources`, name: `resource "*" cannot mix with resources that don't have subresources`,
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -253,7 +253,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "resource a/* cannot mix with a/x", name: "resource a/* cannot mix with a/x",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -273,7 +273,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "resource a/* can mix with a", name: "resource a/* can mix with a",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -292,7 +292,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "resource */a cannot mix with x/a", name: "resource */a cannot mix with x/a",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -312,7 +312,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "resource */* cannot mix with other resources", name: "resource */* cannot mix with other resources",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -332,7 +332,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", name: "FailurePolicy can only be \"Ignore\" or \"Fail\"",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -346,7 +346,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "SideEffects can only be \"Unknown\", \"None\", \"Some\", or \"NoneOnDryRun\"", name: "SideEffects can only be \"Unknown\", \"None\", \"Some\", or \"NoneOnDryRun\"",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -360,7 +360,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "both service and URL missing", name: "both service and URL missing",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{}, ClientConfig: admissionregistration.WebhookClientConfig{},
@ -370,7 +370,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "both service and URL provided", name: "both service and URL provided",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -387,7 +387,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "blank URL", name: "blank URL",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -399,7 +399,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "wrong scheme", name: "wrong scheme",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -411,7 +411,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "missing host", name: "missing host",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -423,7 +423,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "fragment", name: "fragment",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -435,7 +435,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "query", name: "query",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -447,7 +447,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "user", name: "user",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -459,7 +459,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "just totally wrong", name: "just totally wrong",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -471,7 +471,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "path must start with slash", name: "path must start with slash",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -488,7 +488,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "path accepts slash", name: "path accepts slash",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -505,7 +505,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "path accepts no trailing slash", name: "path accepts no trailing slash",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -522,7 +522,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "path fails //", name: "path fails //",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -539,7 +539,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "path no empty step", name: "path no empty step",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -555,7 +555,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`,
}, { }, {
name: "path no empty step 2", name: "path no empty step 2",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -572,7 +572,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "path no non-subdomain", name: "path no non-subdomain",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -590,7 +590,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
{ {
name: "invalid port 0", name: "invalid port 0",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -608,7 +608,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
{ {
name: "invalid port >65535", name: "invalid port >65535",
config: newValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: admissionregistration.WebhookClientConfig{ ClientConfig: admissionregistration.WebhookClientConfig{
@ -625,7 +625,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "timeout seconds cannot be greater than 30", name: "timeout seconds cannot be greater than 30",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -636,7 +636,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "timeout seconds cannot be smaller than 1", name: "timeout seconds cannot be smaller than 1",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -647,7 +647,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "timeout seconds must be positive", name: "timeout seconds must be positive",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -658,7 +658,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "valid timeout seconds", name: "valid timeout seconds",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -707,14 +707,14 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
}{ }{
{ {
name: "should pass on valid new AdmissionReviewVersion", name: "should pass on valid new AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"v1beta1"}, AdmissionReviewVersions: []string{"v1beta1"},
}, },
}, true), }, true),
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -724,14 +724,14 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
}, },
{ {
name: "should pass on invalid AdmissionReviewVersion with invalid previous versions", name: "should pass on invalid AdmissionReviewVersion with invalid previous versions",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"}, AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"},
}, },
}, true), }, true),
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -742,14 +742,14 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
}, },
{ {
name: "should fail on invalid AdmissionReviewVersion with valid previous versions", name: "should fail on invalid AdmissionReviewVersion with valid previous versions",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"invalid-v1"}, AdmissionReviewVersions: []string{"invalid-v1"},
}, },
}, true), }, true),
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
@ -760,14 +760,14 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
}, },
{ {
name: "should fail on invalid AdmissionReviewVersion with missing previous versions", name: "should fail on invalid AdmissionReviewVersion with missing previous versions",
config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,
AdmissionReviewVersions: []string{"invalid-v1"}, AdmissionReviewVersions: []string{"invalid-v1"},
}, },
}, true), }, true),
oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
ClientConfig: validClientConfig, ClientConfig: validClientConfig,

View File

@ -124,7 +124,7 @@ type ValidatingWebhookConfiguration struct {
// +optional // +optional
// +patchMergeKey=name // +patchMergeKey=name
// +patchStrategy=merge // +patchStrategy=merge
Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"` Webhooks []ValidatingWebhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"`
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -154,7 +154,7 @@ type MutatingWebhookConfiguration struct {
// +optional // +optional
// +patchMergeKey=name // +patchMergeKey=name
// +patchStrategy=merge // +patchStrategy=merge
Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"` Webhooks []MutatingWebhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"`
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -170,8 +170,126 @@ type MutatingWebhookConfigurationList struct {
Items []MutatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` Items []MutatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
} }
// Webhook describes an admission webhook and the resources and operations it applies to. // ValidatingWebhook describes an admission webhook and the resources and operations it applies to.
type Webhook struct { type ValidatingWebhook struct {
// The name of the admission webhook.
// Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where
// "imagepolicy" is the name of the webhook, and kubernetes.io is the name
// of the organization.
// Required.
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// ClientConfig defines how to communicate with the hook.
// Required
ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"`
// Rules describes what operations on what resources/subresources the webhook cares about.
// The webhook cares about an operation if it matches _any_ Rule.
// However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks
// from putting the cluster in a state which cannot be recovered from without completely
// disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called
// on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.
Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"`
// FailurePolicy defines how unrecognized errors from the admission endpoint are handled -
// allowed values are Ignore or Fail. Defaults to Ignore.
// +optional
FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"`
// matchPolicy defines how the "rules" list is used to match incoming requests.
// Allowed values are "Exact" or "Equivalent".
//
// - Exact: match a request only if it exactly matches a specified rule.
// For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1,
// but "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`,
// a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.
//
// - Equivalent: match a request if modifies a resource listed in rules, even via another API group or version.
// For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1,
// and "rules" only included `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]`,
// a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.
//
// Defaults to "Exact"
// +optional
MatchPolicy *MatchPolicyType `json:"matchPolicy,omitempty" protobuf:"bytes,9,opt,name=matchPolicy,casttype=MatchPolicyType"`
// NamespaceSelector decides whether to run the webhook on an object based
// on whether the namespace for that object matches the selector. If the
// object itself is a namespace, the matching is performed on
// object.metadata.labels. If the object is another cluster scoped resource,
// it never skips the webhook.
//
// For example, to run the webhook on any objects whose namespace is not
// associated with "runlevel" of "0" or "1"; you will set the selector as
// follows:
// "namespaceSelector": {
// "matchExpressions": [
// {
// "key": "runlevel",
// "operator": "NotIn",
// "values": [
// "0",
// "1"
// ]
// }
// ]
// }
//
// If instead you want to only run the webhook on any objects whose
// namespace is associated with the "environment" of "prod" or "staging";
// you will set the selector as follows:
// "namespaceSelector": {
// "matchExpressions": [
// {
// "key": "environment",
// "operator": "In",
// "values": [
// "prod",
// "staging"
// ]
// }
// ]
// }
//
// See
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
// for more examples of label selectors.
//
// Default to the empty LabelSelector, which matches everything.
// +optional
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"`
// SideEffects states whether this webhookk has side effects.
// Acceptable values are: Unknown, None, Some, NoneOnDryRun
// Webhooks with side effects MUST implement a reconciliation system, since a request may be
// rejected by a future step in the admission change and the side effects therefore need to be undone.
// Requests with the dryRun attribute will be auto-rejected if they match a webhook with
// sideEffects == Unknown or Some. Defaults to Unknown.
// +optional
SideEffects *SideEffectClass `json:"sideEffects,omitempty" protobuf:"bytes,6,opt,name=sideEffects,casttype=SideEffectClass"`
// TimeoutSeconds specifies the timeout for this webhook. After the timeout passes,
// the webhook call will be ignored or the API call will fail based on the
// failure policy.
// The timeout value must be between 1 and 30 seconds.
// Default to 30 seconds.
// +optional
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"`
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects. API server will try to use first version in
// the list which it supports. If none of the versions specified in this list
// supported by API server, validation will fail for this object.
// If a persisted webhook configuration specifies allowed versions and does not
// include any versions known to the API Server, calls to the webhook will fail
// and be subject to the failure policy.
// Default to `['v1beta1']`.
// +optional
AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty" protobuf:"bytes,8,rep,name=admissionReviewVersions"`
}
// MutatingWebhook describes an admission webhook and the resources and operations it applies to.
type MutatingWebhook struct {
// The name of the admission webhook. // The name of the admission webhook.
// Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where // Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where
// "imagepolicy" is the name of the webhook, and kubernetes.io is the name // "imagepolicy" is the name of the webhook, and kubernetes.io is the name

View File

@ -80,18 +80,7 @@ filegroup(
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/initializer:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/metrics:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/metrics:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:all-srcs",
"//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",
"//staging/src/k8s.io/apiserver/pkg/admission/testing:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/testing:all-srcs",
], ],
tags = ["automanaged"], tags = ["automanaged"],

View File

@ -39,6 +39,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook: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/generic:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/listers/admissionregistration/v1beta1:go_default_library", "//staging/src/k8s.io/client-go/listers/admissionregistration/v1beta1:go_default_library",

View File

@ -24,6 +24,7 @@ import (
"k8s.io/api/admissionregistration/v1beta1" "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1" admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
@ -48,7 +49,7 @@ func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) g
} }
// Start with an empty list // Start with an empty list
manager.configuration.Store(&v1beta1.MutatingWebhookConfiguration{}) manager.configuration.Store([]webhook.WebhookAccessor{})
// On any change, rebuild the config // On any change, rebuild the config
informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
@ -61,8 +62,8 @@ func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) g
} }
// Webhooks returns the merged MutatingWebhookConfiguration. // Webhooks returns the merged MutatingWebhookConfiguration.
func (m *mutatingWebhookConfigurationManager) Webhooks() []v1beta1.Webhook { func (m *mutatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
return m.configuration.Load().(*v1beta1.MutatingWebhookConfiguration).Webhooks return m.configuration.Load().([]webhook.WebhookAccessor)
} }
func (m *mutatingWebhookConfigurationManager) HasSynced() bool { func (m *mutatingWebhookConfigurationManager) HasSynced() bool {
@ -78,16 +79,18 @@ func (m *mutatingWebhookConfigurationManager) updateConfiguration() {
m.configuration.Store(mergeMutatingWebhookConfigurations(configurations)) m.configuration.Store(mergeMutatingWebhookConfigurations(configurations))
} }
func mergeMutatingWebhookConfigurations(configurations []*v1beta1.MutatingWebhookConfiguration) *v1beta1.MutatingWebhookConfiguration { func mergeMutatingWebhookConfigurations(configurations []*v1beta1.MutatingWebhookConfiguration) []webhook.WebhookAccessor {
var ret v1beta1.MutatingWebhookConfiguration
// The internal order of webhooks for each configuration is provided by the user // The internal order of webhooks for each configuration is provided by the user
// but configurations themselves can be in any order. As we are going to run these // but configurations themselves can be in any order. As we are going to run these
// webhooks in serial, they are sorted here to have a deterministic order. // webhooks in serial, they are sorted here to have a deterministic order.
sort.SliceStable(configurations, MutatingWebhookConfigurationSorter(configurations).ByName) sort.SliceStable(configurations, MutatingWebhookConfigurationSorter(configurations).ByName)
accessors := []webhook.WebhookAccessor{}
for _, c := range configurations { for _, c := range configurations {
ret.Webhooks = append(ret.Webhooks, c.Webhooks...) for i := range c.Webhooks {
accessors = append(accessors, webhook.NewMutatingWebhookAccessor(&c.Webhooks[i]))
}
} }
return &ret return accessors
} }
type MutatingWebhookConfigurationSorter []*v1beta1.MutatingWebhookConfiguration type MutatingWebhookConfigurationSorter []*v1beta1.MutatingWebhookConfiguration

View File

@ -45,7 +45,7 @@ func TestGetMutatingWebhookConfig(t *testing.T) {
webhookConfiguration := &v1beta1.MutatingWebhookConfiguration{ webhookConfiguration := &v1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook1"}, ObjectMeta: metav1.ObjectMeta{Name: "webhook1"},
Webhooks: []v1beta1.Webhook{{Name: "webhook1.1"}}, Webhooks: []v1beta1.MutatingWebhook{{Name: "webhook1.1"}},
} }
mutatingInformer := informerFactory.Admissionregistration().V1beta1().MutatingWebhookConfigurations() mutatingInformer := informerFactory.Admissionregistration().V1beta1().MutatingWebhookConfigurations()
@ -57,7 +57,14 @@ func TestGetMutatingWebhookConfig(t *testing.T) {
if len(configurations) == 0 { if len(configurations) == 0 {
t.Errorf("expected non empty webhooks") t.Errorf("expected non empty webhooks")
} }
if !reflect.DeepEqual(configurations, webhookConfiguration.Webhooks) { for i := range configurations {
t.Errorf("Expected\n%#v\ngot\n%#v", webhookConfiguration.Webhooks, configurations) h, ok := configurations[i].GetMutatingWebhook()
if !ok {
t.Errorf("Expected mutating webhook")
continue
}
if !reflect.DeepEqual(h, &webhookConfiguration.Webhooks[i]) {
t.Errorf("Expected\n%#v\ngot\n%#v", &webhookConfiguration.Webhooks[i], h)
}
} }
} }

View File

@ -24,6 +24,7 @@ import (
"k8s.io/api/admissionregistration/v1beta1" "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1" admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
@ -48,7 +49,7 @@ func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory)
} }
// Start with an empty list // Start with an empty list
manager.configuration.Store(&v1beta1.ValidatingWebhookConfiguration{}) manager.configuration.Store([]webhook.WebhookAccessor{})
// On any change, rebuild the config // On any change, rebuild the config
informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
@ -61,8 +62,8 @@ func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory)
} }
// Webhooks returns the merged ValidatingWebhookConfiguration. // Webhooks returns the merged ValidatingWebhookConfiguration.
func (v *validatingWebhookConfigurationManager) Webhooks() []v1beta1.Webhook { func (v *validatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
return v.configuration.Load().(*v1beta1.ValidatingWebhookConfiguration).Webhooks return v.configuration.Load().([]webhook.WebhookAccessor)
} }
// HasSynced returns true if the shared informers have synced. // HasSynced returns true if the shared informers have synced.
@ -79,15 +80,15 @@ func (v *validatingWebhookConfigurationManager) updateConfiguration() {
v.configuration.Store(mergeValidatingWebhookConfigurations(configurations)) v.configuration.Store(mergeValidatingWebhookConfigurations(configurations))
} }
func mergeValidatingWebhookConfigurations( func mergeValidatingWebhookConfigurations(configurations []*v1beta1.ValidatingWebhookConfiguration) []webhook.WebhookAccessor {
configurations []*v1beta1.ValidatingWebhookConfiguration,
) *v1beta1.ValidatingWebhookConfiguration {
sort.SliceStable(configurations, ValidatingWebhookConfigurationSorter(configurations).ByName) sort.SliceStable(configurations, ValidatingWebhookConfigurationSorter(configurations).ByName)
var ret v1beta1.ValidatingWebhookConfiguration accessors := []webhook.WebhookAccessor{}
for _, c := range configurations { for _, c := range configurations {
ret.Webhooks = append(ret.Webhooks, c.Webhooks...) for i := range c.Webhooks {
accessors = append(accessors, webhook.NewValidatingWebhookAccessor(&c.Webhooks[i]))
}
} }
return &ret return accessors
} }
type ValidatingWebhookConfigurationSorter []*v1beta1.ValidatingWebhookConfiguration type ValidatingWebhookConfigurationSorter []*v1beta1.ValidatingWebhookConfiguration

View File

@ -46,7 +46,7 @@ func TestGetValidatingWebhookConfig(t *testing.T) {
webhookConfiguration := &v1beta1.ValidatingWebhookConfiguration{ webhookConfiguration := &v1beta1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhook1"}, ObjectMeta: metav1.ObjectMeta{Name: "webhook1"},
Webhooks: []v1beta1.Webhook{{Name: "webhook1.1"}}, Webhooks: []v1beta1.ValidatingWebhook{{Name: "webhook1.1"}},
} }
validatingInformer := informerFactory.Admissionregistration().V1beta1().ValidatingWebhookConfigurations() validatingInformer := informerFactory.Admissionregistration().V1beta1().ValidatingWebhookConfigurations()
@ -59,7 +59,14 @@ func TestGetValidatingWebhookConfig(t *testing.T) {
if len(configurations) == 0 { if len(configurations) == 0 {
t.Errorf("expected non empty webhooks") t.Errorf("expected non empty webhooks")
} }
if !reflect.DeepEqual(configurations, webhookConfiguration.Webhooks) { for i := range configurations {
t.Errorf("Expected\n%#v\ngot\n%#v", webhookConfiguration.Webhooks, configurations) h, ok := configurations[i].GetValidatingWebhook()
if !ok {
t.Errorf("Expected validating webhook")
continue
}
if !reflect.DeepEqual(h, &webhookConfiguration.Webhooks[i]) {
t.Errorf("Expected\n%#v\ngot\n%#v", &webhookConfiguration.Webhooks[i], h)
}
} }
} }

View File

@ -0,0 +1,41 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["accessors.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook",
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:all-srcs",
"//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"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,139 @@
/*
Copyright 2019 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 webhook
import (
"k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// WebhookAccessor provides a common interface to both mutating and validating webhook types.
type WebhookAccessor interface {
// GetName gets the webhook Name field.
GetName() string
// GetClientConfig gets the webhook ClientConfig field.
GetClientConfig() v1beta1.WebhookClientConfig
// GetRules gets the webhook Rules field.
GetRules() []v1beta1.RuleWithOperations
// GetFailurePolicy gets the webhook FailurePolicy field.
GetFailurePolicy() *v1beta1.FailurePolicyType
// GetMatchPolicy gets the webhook MatchPolicy field.
GetMatchPolicy() *v1beta1.MatchPolicyType
// GetNamespaceSelector gets the webhook NamespaceSelector field.
GetNamespaceSelector() *metav1.LabelSelector
// GetSideEffects gets the webhook SideEffects field.
GetSideEffects() *v1beta1.SideEffectClass
// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
GetTimeoutSeconds() *int32
// GetAdmissionReviewVersions gets the webhook AdmissionReviewVersions field.
GetAdmissionReviewVersions() []string
// GetMutatingWebhook if the accessor contains a MutatingWebhook, returns it and true, else returns false.
GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool)
// GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false.
GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool)
}
// NewMutatingWebhookAccessor creates an accessor for a MutatingWebhook.
func NewMutatingWebhookAccessor(h *v1beta1.MutatingWebhook) WebhookAccessor {
return mutatingWebhookAccessor{h}
}
type mutatingWebhookAccessor struct {
*v1beta1.MutatingWebhook
}
func (m mutatingWebhookAccessor) GetName() string {
return m.Name
}
func (m mutatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
return m.ClientConfig
}
func (m mutatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
return m.Rules
}
func (m mutatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
return m.FailurePolicy
}
func (m mutatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
return m.MatchPolicy
}
func (m mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
return m.NamespaceSelector
}
func (m mutatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
return m.SideEffects
}
func (m mutatingWebhookAccessor) GetTimeoutSeconds() *int32 {
return m.TimeoutSeconds
}
func (m mutatingWebhookAccessor) GetAdmissionReviewVersions() []string {
return m.AdmissionReviewVersions
}
func (m mutatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
return m.MutatingWebhook, true
}
func (m mutatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
return nil, false
}
// NewValidatingWebhookAccessor creates an accessor for a ValidatingWebhook.
func NewValidatingWebhookAccessor(h *v1beta1.ValidatingWebhook) WebhookAccessor {
return validatingWebhookAccessor{h}
}
type validatingWebhookAccessor struct {
*v1beta1.ValidatingWebhook
}
func (v validatingWebhookAccessor) GetName() string {
return v.Name
}
func (v validatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
return v.ClientConfig
}
func (v validatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
return v.Rules
}
func (v validatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
return v.FailurePolicy
}
func (v validatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
return v.MatchPolicy
}
func (v validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
return v.NamespaceSelector
}
func (v validatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
return v.SideEffects
}
func (v validatingWebhookAccessor) GetTimeoutSeconds() *int32 {
return v.TimeoutSeconds
}
func (v validatingWebhookAccessor) GetAdmissionReviewVersions() []string {
return v.AdmissionReviewVersions
}
func (v validatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
return nil, false
}
func (v validatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
return v.ValidatingWebhook, true
}

View File

@ -18,6 +18,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema: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:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook: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/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/namespace:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library",
@ -56,6 +57,7 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema: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/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook: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/namespace:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/example:go_default_library", "//staging/src/k8s.io/apiserver/pkg/apis/example:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/example/v1:go_default_library", "//staging/src/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",

View File

@ -19,15 +19,15 @@ package generic
import ( import (
"context" "context"
"k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
) )
// Source can list dynamic webhook plugins. // Source can list dynamic webhook plugins.
type Source interface { type Source interface {
Webhooks() []v1beta1.Webhook Webhooks() []webhook.WebhookAccessor
HasSynced() bool HasSynced() bool
} }
@ -51,8 +51,7 @@ type VersionedAttributes struct {
// WebhookInvocation describes how to call a webhook, including the resource and subresource the webhook registered for, // WebhookInvocation describes how to call a webhook, including the resource and subresource the webhook registered for,
// and the kind that should be sent to the webhook. // and the kind that should be sent to the webhook.
type WebhookInvocation struct { type WebhookInvocation struct {
Webhook *v1beta1.Webhook Webhook webhook.WebhookAccessor
Resource schema.GroupVersionResource Resource schema.GroupVersionResource
Subresource string Subresource string
Kind schema.GroupVersionKind Kind schema.GroupVersionKind

View File

@ -27,10 +27,11 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/admission/plugin/webhook/config" "k8s.io/apiserver/pkg/admission/plugin/webhook/config"
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace" "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
"k8s.io/apiserver/pkg/admission/plugin/webhook/rules" "k8s.io/apiserver/pkg/admission/plugin/webhook/rules"
"k8s.io/apiserver/pkg/util/webhook" webhookutil "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
) )
@ -42,7 +43,7 @@ type Webhook struct {
sourceFactory sourceFactory sourceFactory sourceFactory
hookSource Source hookSource Source
clientManager *webhook.ClientManager clientManager *webhookutil.ClientManager
namespaceMatcher *namespace.Matcher namespaceMatcher *namespace.Matcher
dispatcher Dispatcher dispatcher Dispatcher
} }
@ -53,7 +54,7 @@ var (
) )
type sourceFactory func(f informers.SharedInformerFactory) Source type sourceFactory func(f informers.SharedInformerFactory) Source
type dispatcherFactory func(cm *webhook.ClientManager) Dispatcher type dispatcherFactory func(cm *webhookutil.ClientManager) Dispatcher
// NewWebhook creates a new generic admission webhook. // NewWebhook creates a new generic admission webhook.
func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) { func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) {
@ -62,17 +63,17 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
return nil, err return nil, err
} }
cm, err := webhook.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme) cm, err := webhookutil.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme)
if err != nil { if err != nil {
return nil, err return nil, err
} }
authInfoResolver, err := webhook.NewDefaultAuthenticationInfoResolver(kubeconfigFile) authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Set defaults which may be overridden later. // Set defaults which may be overridden later.
cm.SetAuthenticationInfoResolver(authInfoResolver) cm.SetAuthenticationInfoResolver(authInfoResolver)
cm.SetServiceResolver(webhook.NewDefaultServiceResolver()) cm.SetServiceResolver(webhookutil.NewDefaultServiceResolver())
return &Webhook{ return &Webhook{
Handler: handler, Handler: handler,
@ -86,13 +87,13 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
// SetAuthenticationInfoResolverWrapper sets the // SetAuthenticationInfoResolverWrapper sets the
// AuthenticationInfoResolverWrapper. // AuthenticationInfoResolverWrapper.
// TODO find a better way wire this, but keep this pull small for now. // TODO find a better way wire this, but keep this pull small for now.
func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhook.AuthenticationInfoResolverWrapper) { func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhookutil.AuthenticationInfoResolverWrapper) {
a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper) a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper)
} }
// SetServiceResolver sets a service resolver for the webhook admission plugin. // 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. // Passing a nil resolver does not have an effect, instead a default one will be used.
func (a *Webhook) SetServiceResolver(sr webhook.ServiceResolver) { func (a *Webhook) SetServiceResolver(sr webhookutil.ServiceResolver) {
a.clientManager.SetServiceResolver(sr) a.clientManager.SetServiceResolver(sr)
} }
@ -128,10 +129,10 @@ func (a *Webhook) ValidateInitialization() error {
// shouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called, // shouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called,
// or an error if an error was encountered during evaluation. // or an error if an error was encountered during evaluation.
func (a *Webhook) shouldCallHook(h *v1beta1.Webhook, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) { func (a *Webhook) shouldCallHook(h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) {
var err *apierrors.StatusError var err *apierrors.StatusError
var invocation *WebhookInvocation var invocation *WebhookInvocation
for _, r := range h.Rules { for _, r := range h.GetRules() {
m := rules.Matcher{Rule: r, Attr: attr} m := rules.Matcher{Rule: r, Attr: attr}
if m.Matches() { if m.Matches() {
invocation = &WebhookInvocation{ invocation = &WebhookInvocation{
@ -143,12 +144,12 @@ func (a *Webhook) shouldCallHook(h *v1beta1.Webhook, attr admission.Attributes,
break break
} }
} }
if invocation == nil && h.MatchPolicy != nil && *h.MatchPolicy == v1beta1.Equivalent { if invocation == nil && h.GetMatchPolicy() != nil && *h.GetMatchPolicy() == v1beta1.Equivalent {
attrWithOverride := &attrWithResourceOverride{Attributes: attr} attrWithOverride := &attrWithResourceOverride{Attributes: attr}
equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource()) equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource())
// honor earlier rules first // honor earlier rules first
OuterLoop: OuterLoop:
for _, r := range h.Rules { for _, r := range h.GetRules() {
// see if the rule matches any of the equivalent resources // see if the rule matches any of the equivalent resources
for _, equivalent := range equivalents { for _, equivalent := range equivalents {
if equivalent == attr.GetResource() { if equivalent == attr.GetResource() {
@ -207,7 +208,7 @@ func (a *Webhook) Dispatch(attr admission.Attributes, o admission.ObjectInterfac
var relevantHooks []*WebhookInvocation var relevantHooks []*WebhookInvocation
for i := range hooks { for i := range hooks {
invocation, err := a.shouldCallHook(&hooks[i], attr, o) invocation, err := a.shouldCallHook(hooks[i], attr, o)
if err != nil { if err != nil {
return err return err
} }

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace" "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
) )
@ -61,7 +62,7 @@ func TestShouldCallHook(t *testing.T) {
testcases := []struct { testcases := []struct {
name string name string
webhook *v1beta1.Webhook webhook *v1beta1.ValidatingWebhook
attrs admission.Attributes attrs admission.Attributes
expectCall bool expectCall bool
@ -72,13 +73,13 @@ func TestShouldCallHook(t *testing.T) {
}{ }{
{ {
name: "no rules (just write)", name: "no rules (just write)",
webhook: &v1beta1.Webhook{Rules: []v1beta1.RuleWithOperations{}}, webhook: &v1beta1.ValidatingWebhook{Rules: []v1beta1.RuleWithOperations{}},
attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil),
expectCall: false, expectCall: false,
}, },
{ {
name: "invalid kind lookup", name: "invalid kind lookup",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
MatchPolicy: &equivalentMatch, MatchPolicy: &equivalentMatch,
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -91,7 +92,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "wildcard rule, match as requested", name: "wildcard rule, match as requested",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{"*"}, Operations: []v1beta1.OperationType{"*"},
@ -105,7 +106,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, prefer exact match", name: "specific rules, prefer exact match",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{"*"}, Operations: []v1beta1.OperationType{"*"},
@ -125,7 +126,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, match miss", name: "specific rules, match miss",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{"*"}, Operations: []v1beta1.OperationType{"*"},
@ -139,7 +140,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, exact match miss", name: "specific rules, exact match miss",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
MatchPolicy: &exactMatch, MatchPolicy: &exactMatch,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -154,7 +155,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, equivalent match, prefer extensions", name: "specific rules, equivalent match, prefer extensions",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
MatchPolicy: &equivalentMatch, MatchPolicy: &equivalentMatch,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -172,7 +173,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, equivalent match, prefer apps", name: "specific rules, equivalent match, prefer apps",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
MatchPolicy: &equivalentMatch, MatchPolicy: &equivalentMatch,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -191,7 +192,7 @@ func TestShouldCallHook(t *testing.T) {
{ {
name: "specific rules, subresource prefer exact match", name: "specific rules, subresource prefer exact match",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{"*"}, Operations: []v1beta1.OperationType{"*"},
@ -211,7 +212,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, subresource match miss", name: "specific rules, subresource match miss",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{"*"}, Operations: []v1beta1.OperationType{"*"},
@ -225,7 +226,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, subresource exact match miss", name: "specific rules, subresource exact match miss",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
MatchPolicy: &exactMatch, MatchPolicy: &exactMatch,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -240,7 +241,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, subresource equivalent match, prefer extensions", name: "specific rules, subresource equivalent match, prefer extensions",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
MatchPolicy: &equivalentMatch, MatchPolicy: &equivalentMatch,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -258,7 +259,7 @@ func TestShouldCallHook(t *testing.T) {
}, },
{ {
name: "specific rules, subresource equivalent match, prefer apps", name: "specific rules, subresource equivalent match, prefer apps",
webhook: &v1beta1.Webhook{ webhook: &v1beta1.ValidatingWebhook{
MatchPolicy: &equivalentMatch, MatchPolicy: &equivalentMatch,
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -278,7 +279,7 @@ func TestShouldCallHook(t *testing.T) {
for _, testcase := range testcases { for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) { t.Run(testcase.name, func(t *testing.T) {
invocation, err := a.shouldCallHook(testcase.webhook, testcase.attrs, interfaces) invocation, err := a.shouldCallHook(webhook.NewValidatingWebhookAccessor(testcase.webhook), testcase.attrs, interfaces)
if err != nil { if err != nil {
if len(testcase.expectErr) == 0 { if len(testcase.expectErr) == 0 {
t.Fatal(err) t.Fatal(err)

View File

@ -39,16 +39,16 @@ import (
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/apiserver/pkg/admission/plugin/webhook/request" "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
"k8s.io/apiserver/pkg/admission/plugin/webhook/util" "k8s.io/apiserver/pkg/admission/plugin/webhook/util"
"k8s.io/apiserver/pkg/util/webhook" webhookutil "k8s.io/apiserver/pkg/util/webhook"
) )
type mutatingDispatcher struct { type mutatingDispatcher struct {
cm *webhook.ClientManager cm *webhookutil.ClientManager
plugin *Plugin plugin *Plugin
} }
func newMutatingDispatcher(p *Plugin) func(cm *webhook.ClientManager) generic.Dispatcher { func newMutatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher {
return func(cm *webhook.ClientManager) generic.Dispatcher { return func(cm *webhookutil.ClientManager) generic.Dispatcher {
return &mutatingDispatcher{cm, p} return &mutatingDispatcher{cm, p}
} }
} }
@ -58,7 +58,10 @@ var _ generic.Dispatcher = &mutatingDispatcher{}
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, relevantHooks []*generic.WebhookInvocation) error { func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, relevantHooks []*generic.WebhookInvocation) error {
var versionedAttr *generic.VersionedAttributes var versionedAttr *generic.VersionedAttributes
for _, invocation := range relevantHooks { for _, invocation := range relevantHooks {
hook := invocation.Webhook hook, ok := invocation.Webhook.GetMutatingWebhook()
if !ok {
return fmt.Errorf("mutating webhook dispatch requires v1beta1.MutatingWebhook, but got %T", hook)
}
if versionedAttr == nil { if versionedAttr == nil {
// First webhook, create versioned attributes // First webhook, create versioned attributes
var err error var err error
@ -73,14 +76,14 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
} }
t := time.Now() t := time.Now()
err := a.callAttrMutatingHook(ctx, invocation, versionedAttr, o) err := a.callAttrMutatingHook(ctx, hook, invocation, versionedAttr, o)
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, versionedAttr.Attributes, "admit", hook.Name) admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, versionedAttr.Attributes, "admit", hook.Name)
if err == nil { if err == nil {
continue continue
} }
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
if callErr, ok := err.(*webhook.ErrCallingWebhook); ok { if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
if ignoreClientCallFailures { if ignoreClientCallFailures {
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr) klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
utilruntime.HandleError(callErr) utilruntime.HandleError(callErr)
@ -100,11 +103,11 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
} }
// note that callAttrMutatingHook updates attr // note that callAttrMutatingHook updates attr
func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes, o admission.ObjectInterfaces) error {
h := invocation.Webhook func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.MutatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes, o admission.ObjectInterfaces) error {
if attr.Attributes.IsDryRun() { if attr.Attributes.IsDryRun() {
if h.SideEffects == nil { if h.SideEffects == nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
} }
if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) { if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) {
return webhookerrors.NewDryRunUnsupportedErr(h.Name) return webhookerrors.NewDryRunUnsupportedErr(h.Name)
@ -113,15 +116,15 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, invocatio
// Currently dispatcher only supports `v1beta1` AdmissionReview // Currently dispatcher only supports `v1beta1` AdmissionReview
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions // TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) { if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, invocation.Webhook) {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")}
} }
// Make the webhook request // Make the webhook request
request := request.CreateAdmissionReview(attr, invocation) request := request.CreateAdmissionReview(attr, invocation)
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h)) client, err := a.cm.HookClient(util.HookClientConfigForWebhook(invocation.Webhook))
if err != nil { if err != nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
} }
response := &admissionv1beta1.AdmissionReview{} response := &admissionv1beta1.AdmissionReview{}
r := client.Post().Context(ctx).Body(&request) r := client.Post().Context(ctx).Body(&request)
@ -129,11 +132,11 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, invocatio
r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second) r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second)
} }
if err := r.Do().Into(response); err != nil { if err := r.Do().Into(response); err != nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
} }
if response.Response == nil { if response.Response == nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
} }
for k, v := range response.Response.AuditAnnotations { for k, v := range response.Response.AuditAnnotations {

View File

@ -46,7 +46,7 @@ func TestAdmit(t *testing.T) {
defer close(stopCh) defer close(stopCh)
testCases := append(webhooktesting.NewMutatingTestCases(serverURL), testCases := append(webhooktesting.NewMutatingTestCases(serverURL),
webhooktesting.NewNonMutatingTestCases(serverURL)...) webhooktesting.ConvertToMutatingTestCases(webhooktesting.NewNonMutatingTestCases(serverURL))...)
for _, tt := range testCases { for _, tt := range testCases {
wh, err := NewMutatingWebhook(nil) wh, err := NewMutatingWebhook(nil)
@ -56,7 +56,7 @@ func TestAdmit(t *testing.T) {
} }
ns := "webhook-test" ns := "webhook-test"
client, informer := webhooktesting.NewFakeDataSource(ns, tt.Webhooks, true, stopCh) client, informer := webhooktesting.NewFakeMutatingDataSource(ns, tt.Webhooks, stopCh)
wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32)))) wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32))))
wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL)) wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL))
@ -136,7 +136,7 @@ func TestAdmitCachedClient(t *testing.T) {
for _, tt := range webhooktesting.NewCachedClientTestcases(serverURL) { for _, tt := range webhooktesting.NewCachedClientTestcases(serverURL) {
ns := "webhook-test" ns := "webhook-test"
client, informer := webhooktesting.NewFakeDataSource(ns, tt.Webhooks, true, stopCh) client, informer := webhooktesting.NewFakeMutatingDataSource(ns, webhooktesting.ConvertToMutatingWebhooks(tt.Webhooks), stopCh)
// override the webhook source. The client cache will stay the same. // override the webhook source. The client cache will stay the same.
cacheMisses := new(int32) cacheMisses := new(int32)

View File

@ -10,13 +10,13 @@ go_library(
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace", importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//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/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
], ],
@ -34,6 +34,7 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema: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:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library",
], ],
) )

View File

@ -19,13 +19,13 @@ package namespace
import ( import (
"fmt" "fmt"
"k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
) )
@ -86,7 +86,7 @@ func (m *Matcher) GetNamespaceLabels(attr admission.Attributes) (map[string]stri
// MatchNamespaceSelector decideds whether the request matches the // MatchNamespaceSelector decideds whether the request matches the
// namespaceSelctor of the webhook. Only when they match, the webhook is called. // namespaceSelctor of the webhook. Only when they match, the webhook is called.
func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) { func (m *Matcher) MatchNamespaceSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) {
namespaceName := attr.GetNamespace() namespaceName := attr.GetNamespace()
if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" { if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" {
// If the request is about a cluster scoped resource, and it is not a // If the request is about a cluster scoped resource, and it is not a
@ -96,7 +96,7 @@ func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attr
return true, nil return true, nil
} }
// TODO: adding an LRU cache to cache the translation // TODO: adding an LRU cache to cache the translation
selector, err := metav1.LabelSelectorAsSelector(h.NamespaceSelector) selector, err := metav1.LabelSelectorAsSelector(h.GetNamespaceSelector())
if err != nil { if err != nil {
return false, apierrors.NewInternalError(err) return false, apierrors.NewInternalError(err)
} }

View File

@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
) )
type fakeNamespaceLister struct { type fakeNamespaceLister struct {
@ -114,12 +115,12 @@ func TestGetNamespaceLabels(t *testing.T) {
} }
func TestNotExemptClusterScopedResource(t *testing.T) { func TestNotExemptClusterScopedResource(t *testing.T) {
hook := &registrationv1beta1.Webhook{ hook := &registrationv1beta1.ValidatingWebhook{
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
} }
attr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "mock-name", schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "", admission.Create, &metav1.CreateOptions{}, false, nil) attr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "mock-name", schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "", admission.Create, &metav1.CreateOptions{}, false, nil)
matcher := Matcher{} matcher := Matcher{}
matches, err := matcher.MatchNamespaceSelector(hook, attr) matches, err := matcher.MatchNamespaceSelector(webhook.NewValidatingWebhookAccessor(hook), attr)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -49,8 +49,8 @@ var sideEffectsNone = registrationv1beta1.SideEffectClassNone
var sideEffectsSome = registrationv1beta1.SideEffectClassSome var sideEffectsSome = registrationv1beta1.SideEffectClassSome
var sideEffectsNoneOnDryRun = registrationv1beta1.SideEffectClassNoneOnDryRun var sideEffectsNoneOnDryRun = registrationv1beta1.SideEffectClassNoneOnDryRun
// NewFakeDataSource returns a mock client and informer returning the given webhooks. // NewFakeValidatingDataSource returns a mock client and informer returning the given webhooks.
func NewFakeDataSource(name string, webhooks []registrationv1beta1.Webhook, mutating bool, stopCh <-chan struct{}) (clientset kubernetes.Interface, factory informers.SharedInformerFactory) { func NewFakeValidatingDataSource(name string, webhooks []registrationv1beta1.ValidatingWebhook, stopCh <-chan struct{}) (clientset kubernetes.Interface, factory informers.SharedInformerFactory) {
var objs = []runtime.Object{ var objs = []runtime.Object{
&corev1.Namespace{ &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -61,21 +61,37 @@ func NewFakeDataSource(name string, webhooks []registrationv1beta1.Webhook, muta
}, },
}, },
} }
if mutating { objs = append(objs, &registrationv1beta1.ValidatingWebhookConfiguration{
objs = append(objs, &registrationv1beta1.MutatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{
Name: "test-webhooks",
},
Webhooks: webhooks,
})
client := fakeclientset.NewSimpleClientset(objs...)
informerFactory := informers.NewSharedInformerFactory(client, 0)
return client, informerFactory
}
// NewFakeMutatingDataSource returns a mock client and informer returning the given webhooks.
func NewFakeMutatingDataSource(name string, webhooks []registrationv1beta1.MutatingWebhook, stopCh <-chan struct{}) (clientset kubernetes.Interface, factory informers.SharedInformerFactory) {
var objs = []runtime.Object{
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "test-webhooks", Name: name,
Labels: map[string]string{
"runlevel": "0",
},
}, },
Webhooks: webhooks, },
})
} else {
objs = append(objs, &registrationv1beta1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "test-webhooks",
},
Webhooks: webhooks,
})
} }
objs = append(objs, &registrationv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "test-webhooks",
},
Webhooks: webhooks,
})
client := fakeclientset.NewSimpleClientset(objs...) client := fakeclientset.NewSimpleClientset(objs...)
informerFactory := informers.NewSharedInformerFactory(client, 0) informerFactory := informers.NewSharedInformerFactory(client, 0)
@ -181,10 +197,10 @@ func (c urlConfigGenerator) ccfgURL(urlPath string) registrationv1beta1.WebhookC
} }
} }
// Test is a webhook test case. // ValidatingTest is a validating webhook test case.
type Test struct { type ValidatingTest struct {
Name string Name string
Webhooks []registrationv1beta1.Webhook Webhooks []registrationv1beta1.ValidatingWebhook
Path string Path string
IsCRD bool IsCRD bool
IsDryRun bool IsDryRun bool
@ -196,19 +212,52 @@ type Test struct {
ExpectStatusCode int32 ExpectStatusCode int32
} }
// MutatingTest is a mutating webhook test case.
type MutatingTest struct {
Name string
Webhooks []registrationv1beta1.MutatingWebhook
Path string
IsCRD bool
IsDryRun bool
AdditionalLabels map[string]string
ExpectLabels map[string]string
ExpectAllow bool
ErrorContains string
ExpectAnnotations map[string]string
ExpectStatusCode int32
}
// ConvertToMutatingTestCases converts a validating test case to a mutating one for test purposes.
func ConvertToMutatingTestCases(tests []ValidatingTest) []MutatingTest {
r := make([]MutatingTest, len(tests))
for i, t := range tests {
r[i] = MutatingTest{t.Name, ConvertToMutatingWebhooks(t.Webhooks), t.Path, t.IsCRD, t.IsDryRun, t.AdditionalLabels, t.ExpectLabels, t.ExpectAllow, t.ErrorContains, t.ExpectAnnotations, t.ExpectStatusCode}
}
return r
}
// ConvertToMutatingWebhooks converts a validating webhook to a mutating one for test purposes.
func ConvertToMutatingWebhooks(webhooks []registrationv1beta1.ValidatingWebhook) []registrationv1beta1.MutatingWebhook {
mutating := make([]registrationv1beta1.MutatingWebhook, len(webhooks))
for i, h := range webhooks {
mutating[i] = registrationv1beta1.MutatingWebhook{h.Name, h.ClientConfig, h.Rules, h.FailurePolicy, h.MatchPolicy, h.NamespaceSelector, h.SideEffects, h.TimeoutSeconds, h.AdmissionReviewVersions}
}
return mutating
}
// NewNonMutatingTestCases returns test cases with a given base url. // NewNonMutatingTestCases returns test cases with a given base url.
// All test cases in NewNonMutatingTestCases have no Patch set in // All test cases in NewNonMutatingTestCases have no Patch set in
// AdmissionResponse. The test cases are used by both MutatingAdmissionWebhook // AdmissionResponse. The test cases are used by both MutatingAdmissionWebhook
// and ValidatingAdmissionWebhook. // and ValidatingAdmissionWebhook.
func NewNonMutatingTestCases(url *url.URL) []Test { func NewNonMutatingTestCases(url *url.URL) []ValidatingTest {
policyFail := registrationv1beta1.Fail policyFail := registrationv1beta1.Fail
policyIgnore := registrationv1beta1.Ignore policyIgnore := registrationv1beta1.Ignore
ccfgURL := urlConfigGenerator{url}.ccfgURL ccfgURL := urlConfigGenerator{url}.ccfgURL
return []Test{ return []ValidatingTest{
{ {
Name: "no match", Name: "no match",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "nomatch", Name: "nomatch",
ClientConfig: ccfgSVC("disallow"), ClientConfig: ccfgSVC("disallow"),
Rules: []registrationv1beta1.RuleWithOperations{{ Rules: []registrationv1beta1.RuleWithOperations{{
@ -221,7 +270,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & allow", Name: "match & allow",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "allow.example.com", Name: "allow.example.com",
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -233,7 +282,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & disallow", Name: "match & disallow",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "disallow", Name: "disallow",
ClientConfig: ccfgSVC("disallow"), ClientConfig: ccfgSVC("disallow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -245,7 +294,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & disallow ii", Name: "match & disallow ii",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "disallowReason", Name: "disallowReason",
ClientConfig: ccfgSVC("disallowReason"), ClientConfig: ccfgSVC("disallowReason"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -257,7 +306,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & disallow & but allowed because namespaceSelector exempt the ns", Name: "match & disallow & but allowed because namespaceSelector exempt the ns",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "disallow", Name: "disallow",
ClientConfig: ccfgSVC("disallow"), ClientConfig: ccfgSVC("disallow"),
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
@ -275,7 +324,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & disallow & but allowed because namespaceSelector exempt the ns ii", Name: "match & disallow & but allowed because namespaceSelector exempt the ns ii",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "disallow", Name: "disallow",
ClientConfig: ccfgSVC("disallow"), ClientConfig: ccfgSVC("disallow"),
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
@ -292,7 +341,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & fail (but allow because fail open)", Name: "match & fail (but allow because fail open)",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "internalErr A", Name: "internalErr A",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -319,7 +368,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & fail (but disallow because fail close on nil FailurePolicy)", Name: "match & fail (but disallow because fail close on nil FailurePolicy)",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "internalErr A", Name: "internalErr A",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
@ -343,7 +392,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & fail (but fail because fail closed)", Name: "match & fail (but fail because fail closed)",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "internalErr A", Name: "internalErr A",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -370,7 +419,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & allow (url)", Name: "match & allow (url)",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "allow.example.com", Name: "allow.example.com",
ClientConfig: ccfgURL("allow"), ClientConfig: ccfgURL("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -382,7 +431,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & disallow (url)", Name: "match & disallow (url)",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "disallow", Name: "disallow",
ClientConfig: ccfgURL("disallow"), ClientConfig: ccfgURL("disallow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -393,7 +442,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
ErrorContains: "without explanation", ErrorContains: "without explanation",
}, { }, {
Name: "absent response and fail open", Name: "absent response and fail open",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "nilResponse", Name: "nilResponse",
ClientConfig: ccfgURL("nilResponse"), ClientConfig: ccfgURL("nilResponse"),
FailurePolicy: &policyIgnore, FailurePolicy: &policyIgnore,
@ -405,7 +454,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "absent response and fail closed", Name: "absent response and fail closed",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "nilResponse", Name: "nilResponse",
ClientConfig: ccfgURL("nilResponse"), ClientConfig: ccfgURL("nilResponse"),
FailurePolicy: &policyFail, FailurePolicy: &policyFail,
@ -418,7 +467,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "no match dry run", Name: "no match dry run",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "nomatch", Name: "nomatch",
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: []registrationv1beta1.RuleWithOperations{{ Rules: []registrationv1beta1.RuleWithOperations{{
@ -433,7 +482,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match dry run side effects Unknown", Name: "match dry run side effects Unknown",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "allow", Name: "allow",
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -447,7 +496,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match dry run side effects None", Name: "match dry run side effects None",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "allow", Name: "allow",
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -461,7 +510,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match dry run side effects Some", Name: "match dry run side effects Some",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "allow", Name: "allow",
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -475,7 +524,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match dry run side effects NoneOnDryRun", Name: "match dry run side effects NoneOnDryRun",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "allow", Name: "allow",
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -489,7 +538,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "illegal annotation format", Name: "illegal annotation format",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "invalidAnnotation", Name: "invalidAnnotation",
ClientConfig: ccfgURL("invalidAnnotation"), ClientConfig: ccfgURL("invalidAnnotation"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -506,11 +555,11 @@ func NewNonMutatingTestCases(url *url.URL) []Test {
// NewMutatingTestCases returns test cases with a given base url. // NewMutatingTestCases returns test cases with a given base url.
// All test cases in NewMutatingTestCases have Patch set in // All test cases in NewMutatingTestCases have Patch set in
// AdmissionResponse. The test cases are only used by both MutatingAdmissionWebhook. // AdmissionResponse. The test cases are only used by both MutatingAdmissionWebhook.
func NewMutatingTestCases(url *url.URL) []Test { func NewMutatingTestCases(url *url.URL) []MutatingTest {
return []Test{ return []MutatingTest{
{ {
Name: "match & remove label", Name: "match & remove label",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.MutatingWebhook{{
Name: "removelabel.example.com", Name: "removelabel.example.com",
ClientConfig: ccfgSVC("removeLabel"), ClientConfig: ccfgSVC("removeLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -524,7 +573,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & add label", Name: "match & add label",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.MutatingWebhook{{
Name: "addLabel", Name: "addLabel",
ClientConfig: ccfgSVC("addLabel"), ClientConfig: ccfgSVC("addLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -536,7 +585,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match CRD & add label", Name: "match CRD & add label",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.MutatingWebhook{{
Name: "addLabel", Name: "addLabel",
ClientConfig: ccfgSVC("addLabel"), ClientConfig: ccfgSVC("addLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -549,7 +598,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match CRD & remove label", Name: "match CRD & remove label",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.MutatingWebhook{{
Name: "removelabel.example.com", Name: "removelabel.example.com",
ClientConfig: ccfgSVC("removeLabel"), ClientConfig: ccfgSVC("removeLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -564,7 +613,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & invalid mutation", Name: "match & invalid mutation",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.MutatingWebhook{{
Name: "invalidMutation", Name: "invalidMutation",
ClientConfig: ccfgSVC("invalidMutation"), ClientConfig: ccfgSVC("invalidMutation"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -576,7 +625,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
}, },
{ {
Name: "match & remove label dry run unsupported", Name: "match & remove label dry run unsupported",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.MutatingWebhook{{
Name: "removeLabel", Name: "removeLabel",
ClientConfig: ccfgSVC("removeLabel"), ClientConfig: ccfgSVC("removeLabel"),
Rules: matchEverythingRules, Rules: matchEverythingRules,
@ -596,7 +645,7 @@ func NewMutatingTestCases(url *url.URL) []Test {
// CachedTest is a test case for the client manager. // CachedTest is a test case for the client manager.
type CachedTest struct { type CachedTest struct {
Name string Name string
Webhooks []registrationv1beta1.Webhook Webhooks []registrationv1beta1.ValidatingWebhook
ExpectAllow bool ExpectAllow bool
ExpectCacheMiss bool ExpectCacheMiss bool
} }
@ -609,7 +658,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
return []CachedTest{ return []CachedTest{
{ {
Name: "uncached: service webhook, path 'allow'", Name: "uncached: service webhook, path 'allow'",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "cache1", Name: "cache1",
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
@ -622,7 +671,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
}, },
{ {
Name: "uncached: service webhook, path 'internalErr'", Name: "uncached: service webhook, path 'internalErr'",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "cache2", Name: "cache2",
ClientConfig: ccfgSVC("internalErr"), ClientConfig: ccfgSVC("internalErr"),
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
@ -635,7 +684,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
}, },
{ {
Name: "cached: service webhook, path 'allow'", Name: "cached: service webhook, path 'allow'",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "cache3", Name: "cache3",
ClientConfig: ccfgSVC("allow"), ClientConfig: ccfgSVC("allow"),
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
@ -648,7 +697,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
}, },
{ {
Name: "uncached: url webhook, path 'allow'", Name: "uncached: url webhook, path 'allow'",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "cache4", Name: "cache4",
ClientConfig: ccfgURL("allow"), ClientConfig: ccfgURL("allow"),
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),
@ -661,7 +710,7 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest {
}, },
{ {
Name: "cached: service webhook, path 'allow'", Name: "cached: service webhook, path 'allow'",
Webhooks: []registrationv1beta1.Webhook{{ Webhooks: []registrationv1beta1.ValidatingWebhook{{
Name: "cache5", Name: "cache5",
ClientConfig: ccfgURL("allow"), ClientConfig: ccfgURL("allow"),
Rules: newMatchEverythingRules(), Rules: newMatchEverythingRules(),

View File

@ -7,7 +7,7 @@ go_library(
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/util", importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/util",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
], ],
) )

View File

@ -17,38 +17,39 @@ limitations under the License.
package util package util
import ( import (
"k8s.io/api/admissionregistration/v1beta1" "k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/util/webhook" webhookutil "k8s.io/apiserver/pkg/util/webhook"
) )
// HookClientConfigForWebhook construct a webhook.ClientConfig using a v1beta1.Webhook API object. // HookClientConfigForWebhook construct a webhookutil.ClientConfig using a WebhookAccessor to access
// webhook.ClientConfig is used to create a HookClient and the purpose of the config struct is to // v1beta1.MutatingWebhook and v1beta1.ValidatingWebhook API objects. webhookutil.ClientConfig is used
// share that with other packages that need to create a HookClient. // to create a HookClient and the purpose of the config struct is to share that with other packages
func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig { // that need to create a HookClient.
ret := webhook.ClientConfig{Name: w.Name, CABundle: w.ClientConfig.CABundle} func HookClientConfigForWebhook(w webhook.WebhookAccessor) webhookutil.ClientConfig {
if w.ClientConfig.URL != nil { ret := webhookutil.ClientConfig{Name: w.GetName(), CABundle: w.GetClientConfig().CABundle}
ret.URL = *w.ClientConfig.URL if w.GetClientConfig().URL != nil {
ret.URL = *w.GetClientConfig().URL
} }
if w.ClientConfig.Service != nil { if w.GetClientConfig().Service != nil {
ret.Service = &webhook.ClientConfigService{ ret.Service = &webhookutil.ClientConfigService{
Name: w.ClientConfig.Service.Name, Name: w.GetClientConfig().Service.Name,
Namespace: w.ClientConfig.Service.Namespace, Namespace: w.GetClientConfig().Service.Namespace,
} }
if w.ClientConfig.Service.Port != nil { if w.GetClientConfig().Service.Port != nil {
ret.Service.Port = *w.ClientConfig.Service.Port ret.Service.Port = *w.GetClientConfig().Service.Port
} else { } else {
ret.Service.Port = 443 ret.Service.Port = 443
} }
if w.ClientConfig.Service.Path != nil { if w.GetClientConfig().Service.Path != nil {
ret.Service.Path = *w.ClientConfig.Service.Path ret.Service.Path = *w.GetClientConfig().Service.Path
} }
} }
return ret return ret
} }
// HasAdmissionReviewVersion check whether a version is accepted by a given webhook. // HasAdmissionReviewVersion check whether a version is accepted by a given webhook.
func HasAdmissionReviewVersion(a string, w *v1beta1.Webhook) bool { func HasAdmissionReviewVersion(a string, w webhook.WebhookAccessor) bool {
for _, b := range w.AdmissionReviewVersions { for _, b := range w.GetAdmissionReviewVersions() {
if b == a { if b == a {
return true return true
} }

View File

@ -22,8 +22,6 @@ import (
"sync" "sync"
"time" "time"
"k8s.io/klog"
admissionv1beta1 "k8s.io/api/admission/v1beta1" admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/api/admissionregistration/v1beta1" "k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -35,14 +33,15 @@ import (
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/apiserver/pkg/admission/plugin/webhook/request" "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
"k8s.io/apiserver/pkg/admission/plugin/webhook/util" "k8s.io/apiserver/pkg/admission/plugin/webhook/util"
"k8s.io/apiserver/pkg/util/webhook" webhookutil "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/klog"
) )
type validatingDispatcher struct { type validatingDispatcher struct {
cm *webhook.ClientManager cm *webhookutil.ClientManager
} }
func newValidatingDispatcher(cm *webhook.ClientManager) generic.Dispatcher { func newValidatingDispatcher(cm *webhookutil.ClientManager) generic.Dispatcher {
return &validatingDispatcher{cm} return &validatingDispatcher{cm}
} }
@ -69,18 +68,21 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attr
for i := range relevantHooks { for i := range relevantHooks {
go func(invocation *generic.WebhookInvocation) { go func(invocation *generic.WebhookInvocation) {
defer wg.Done() defer wg.Done()
hook := invocation.Webhook hook, ok := invocation.Webhook.GetValidatingWebhook()
if !ok {
utilruntime.HandleError(fmt.Errorf("validating webhook dispatch requires v1beta1.ValidatingWebhook, but got %T", hook))
return
}
versionedAttr := versionedAttrs[invocation.Kind] versionedAttr := versionedAttrs[invocation.Kind]
t := time.Now() t := time.Now()
err := d.callHook(ctx, invocation, versionedAttr) err := d.callHook(ctx, hook, invocation, versionedAttr)
admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, versionedAttr.Attributes, "validating", hook.Name) admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, versionedAttr.Attributes, "validating", hook.Name)
if err == nil { if err == nil {
return return
} }
ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore
if callErr, ok := err.(*webhook.ErrCallingWebhook); ok { if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok {
if ignoreClientCallFailures { if ignoreClientCallFailures {
klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr) klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr)
utilruntime.HandleError(callErr) utilruntime.HandleError(callErr)
@ -115,11 +117,10 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attr
return errs[0] return errs[0]
} }
func (d *validatingDispatcher) callHook(ctx context.Context, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes) error { func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.ValidatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes) error {
h := invocation.Webhook
if attr.Attributes.IsDryRun() { if attr.Attributes.IsDryRun() {
if h.SideEffects == nil { if h.SideEffects == nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")}
} }
if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) { if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) {
return webhookerrors.NewDryRunUnsupportedErr(h.Name) return webhookerrors.NewDryRunUnsupportedErr(h.Name)
@ -128,15 +129,15 @@ func (d *validatingDispatcher) callHook(ctx context.Context, invocation *generic
// Currently dispatcher only supports `v1beta1` AdmissionReview // Currently dispatcher only supports `v1beta1` AdmissionReview
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions // TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) { if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, invocation.Webhook) {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")}
} }
// Make the webhook request // Make the webhook request
request := request.CreateAdmissionReview(attr, invocation) request := request.CreateAdmissionReview(attr, invocation)
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h)) client, err := d.cm.HookClient(util.HookClientConfigForWebhook(invocation.Webhook))
if err != nil { if err != nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
} }
response := &admissionv1beta1.AdmissionReview{} response := &admissionv1beta1.AdmissionReview{}
r := client.Post().Context(ctx).Body(&request) r := client.Post().Context(ctx).Body(&request)
@ -144,11 +145,11 @@ func (d *validatingDispatcher) callHook(ctx context.Context, invocation *generic
r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second) r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second)
} }
if err := r.Do().Into(response); err != nil { if err := r.Do().Into(response); err != nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
} }
if response.Response == nil { if response.Response == nil {
return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
} }
for k, v := range response.Response.AuditAnnotations { for k, v := range response.Response.AuditAnnotations {
key := h.Name + "/" + k key := h.Name + "/" + k

View File

@ -51,7 +51,7 @@ func TestValidate(t *testing.T) {
} }
ns := "webhook-test" ns := "webhook-test"
client, informer := webhooktesting.NewFakeDataSource(ns, tt.Webhooks, false, stopCh) client, informer := webhooktesting.NewFakeValidatingDataSource(ns, tt.Webhooks, stopCh)
wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32)))) wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32))))
wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL)) wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL))
@ -116,7 +116,7 @@ func TestValidateCachedClient(t *testing.T) {
for _, tt := range webhooktesting.NewCachedClientTestcases(serverURL) { for _, tt := range webhooktesting.NewCachedClientTestcases(serverURL) {
ns := "webhook-test" ns := "webhook-test"
client, informer := webhooktesting.NewFakeDataSource(ns, tt.Webhooks, false, stopCh) client, informer := webhooktesting.NewFakeValidatingDataSource(ns, tt.Webhooks, stopCh)
// override the webhook source. The client cache will stay the same. // override the webhook source. The client cache will stay the same.
cacheMisses := new(int32) cacheMisses := new(int32)

View File

@ -436,7 +436,7 @@ func registerWebhook(f *framework.Framework, context *certContext) func() {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.ValidatingWebhook{
{ {
Name: "deny-unwanted-pod-container-name-and-label.k8s.io", Name: "deny-unwanted-pod-container-name-and-label.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -517,7 +517,7 @@ func registerWebhookForAttachingPod(f *framework.Framework, context *certContext
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.ValidatingWebhook{
{ {
Name: "deny-attaching-pod.k8s.io", Name: "deny-attaching-pod.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -561,7 +561,7 @@ func registerMutatingWebhookForConfigMap(f *framework.Framework, context *certCo
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.MutatingWebhook{
{ {
Name: "adding-configmap-data-stage-1.k8s.io", Name: "adding-configmap-data-stage-1.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -638,7 +638,7 @@ func registerMutatingWebhookForPod(f *framework.Framework, context *certContext)
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.MutatingWebhook{
{ {
Name: "adding-init-container.k8s.io", Name: "adding-init-container.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -841,8 +841,8 @@ func testAttachingPodWebhook(f *framework.Framework) {
// failingWebhook returns a webhook with rule of create configmaps, // failingWebhook returns a webhook with rule of create configmaps,
// but with an invalid client config so that server cannot communicate with it // but with an invalid client config so that server cannot communicate with it
func failingWebhook(namespace, name string) v1beta1.Webhook { func failingWebhook(namespace, name string) v1beta1.ValidatingWebhook {
return v1beta1.Webhook{ return v1beta1.ValidatingWebhook{
Name: name, Name: name,
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create}, Operations: []v1beta1.OperationType{v1beta1.Create},
@ -889,7 +889,7 @@ func registerFailClosedWebhook(f *framework.Framework, context *certContext) fun
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.ValidatingWebhook{
// Server cannot talk to this webhook, so it always fails. // Server cannot talk to this webhook, so it always fails.
// Because this webhook is configured fail-closed, request should be rejected after the call fails. // Because this webhook is configured fail-closed, request should be rejected after the call fails.
hook, hook,
@ -945,7 +945,7 @@ func registerValidatingWebhookForWebhookConfigurations(f *framework.Framework, c
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.ValidatingWebhook{
{ {
Name: "deny-webhook-configuration-deletions.k8s.io", Name: "deny-webhook-configuration-deletions.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -998,7 +998,7 @@ func registerMutatingWebhookForWebhookConfigurations(f *framework.Framework, con
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.MutatingWebhook{
{ {
Name: "add-label-to-webhook-configurations.k8s.io", Name: "add-label-to-webhook-configurations.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -1050,7 +1050,7 @@ func testWebhooksForWebhookConfigurations(f *framework.Framework) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: dummyValidatingWebhookConfigName, Name: dummyValidatingWebhookConfigName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.ValidatingWebhook{
{ {
Name: "dummy-validating-webhook.k8s.io", Name: "dummy-validating-webhook.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -1098,7 +1098,7 @@ func testWebhooksForWebhookConfigurations(f *framework.Framework) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: dummyMutatingWebhookConfigName, Name: dummyMutatingWebhookConfigName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.MutatingWebhook{
{ {
Name: "dummy-mutating-webhook.k8s.io", Name: "dummy-mutating-webhook.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -1306,7 +1306,7 @@ func registerWebhookForCustomResource(f *framework.Framework, context *certConte
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.ValidatingWebhook{
{ {
Name: "deny-unwanted-custom-resource-data.k8s.io", Name: "deny-unwanted-custom-resource-data.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -1348,7 +1348,7 @@ func registerMutatingWebhookForCustomResource(f *framework.Framework, context *c
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.MutatingWebhook{
{ {
Name: "mutate-custom-resource-data-stage-1.k8s.io", Name: "mutate-custom-resource-data-stage-1.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -1543,7 +1543,7 @@ func registerValidatingWebhookForCRD(f *framework.Framework, context *certContex
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.ValidatingWebhook{
{ {
Name: "deny-crd-with-unwanted-label.k8s.io", Name: "deny-crd-with-unwanted-label.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{
@ -1649,7 +1649,7 @@ func registerSlowWebhook(f *framework.Framework, context *certContext, policy *v
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: configName, Name: configName,
}, },
Webhooks: []v1beta1.Webhook{ Webhooks: []v1beta1.ValidatingWebhook{
{ {
Name: "allow-configmap-with-delay-webhook.k8s.io", Name: "allow-configmap-with-delay-webhook.k8s.io",
Rules: []v1beta1.RuleWithOperations{{ Rules: []v1beta1.RuleWithOperations{{

View File

@ -283,6 +283,9 @@ func (h *holder) record(phase string, converted bool, request *v1beta1.Admission
return return
} }
if debug {
h.t.Logf("recording: %#v = %s %#v %v", webhookOptions{phase: phase, converted: converted}, request.Operation, request.Resource, request.SubResource)
}
h.recorded[webhookOptions{phase: phase, converted: converted}] = request h.recorded[webhookOptions{phase: phase, converted: converted}] = request
} }
@ -1287,7 +1290,7 @@ func createV1beta1ValidationWebhook(client clientset.Interface, endpoint, conver
// Attaching Admission webhook to API server // Attaching Admission webhook to API server
_, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&admissionv1beta1.ValidatingWebhookConfiguration{ _, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&admissionv1beta1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "admission.integration.test"}, ObjectMeta: metav1.ObjectMeta{Name: "admission.integration.test"},
Webhooks: []admissionv1beta1.Webhook{ Webhooks: []admissionv1beta1.ValidatingWebhook{
{ {
Name: "admission.integration.test", Name: "admission.integration.test",
ClientConfig: admissionv1beta1.WebhookClientConfig{ ClientConfig: admissionv1beta1.WebhookClientConfig{
@ -1323,7 +1326,7 @@ func createV1beta1MutationWebhook(client clientset.Interface, endpoint, converte
// Attaching Mutation webhook to API server // Attaching Mutation webhook to API server
_, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{ _, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "mutation.integration.test"}, ObjectMeta: metav1.ObjectMeta{Name: "mutation.integration.test"},
Webhooks: []admissionv1beta1.Webhook{ Webhooks: []admissionv1beta1.MutatingWebhook{
{ {
Name: "mutation.integration.test", Name: "mutation.integration.test",
ClientConfig: admissionv1beta1.WebhookClientConfig{ ClientConfig: admissionv1beta1.WebhookClientConfig{

View File

@ -155,7 +155,7 @@ func brokenWebhookConfig(name string) *admissionregistrationv1beta1.ValidatingWe
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
}, },
Webhooks: []admissionregistrationv1beta1.Webhook{ Webhooks: []admissionregistrationv1beta1.ValidatingWebhook{
{ {
Name: "broken-webhook.k8s.io", Name: "broken-webhook.k8s.io",
Rules: []admissionregistrationv1beta1.RuleWithOperations{{ Rules: []admissionregistrationv1beta1.RuleWithOperations{{

View File

@ -22,7 +22,7 @@ import (
"time" "time"
admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1" admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
auditinternal "k8s.io/apiserver/pkg/apis/audit" auditinternal "k8s.io/apiserver/pkg/apis/audit"
@ -65,7 +65,7 @@ func TestWebhookLoopback(t *testing.T) {
fail := admissionv1beta1.Fail fail := admissionv1beta1.Fail
_, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{ _, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "webhooktest.example.com"}, ObjectMeta: metav1.ObjectMeta{Name: "webhooktest.example.com"},
Webhooks: []admissionv1beta1.Webhook{{ Webhooks: []admissionv1beta1.MutatingWebhook{{
Name: "webhooktest.example.com", Name: "webhooktest.example.com",
ClientConfig: admissionv1beta1.WebhookClientConfig{ ClientConfig: admissionv1beta1.WebhookClientConfig{
Service: &admissionv1beta1.ServiceReference{Namespace: "default", Name: "kubernetes", Path: &webhookPath}, Service: &admissionv1beta1.ServiceReference{Namespace: "default", Name: "kubernetes", Path: &webhookPath},

1
vendor/modules.txt vendored
View File

@ -1184,6 +1184,7 @@ k8s.io/apiserver/pkg/admission/configuration
k8s.io/apiserver/pkg/admission/initializer k8s.io/apiserver/pkg/admission/initializer
k8s.io/apiserver/pkg/admission/metrics k8s.io/apiserver/pkg/admission/metrics
k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle
k8s.io/apiserver/pkg/admission/plugin/webhook
k8s.io/apiserver/pkg/admission/plugin/webhook/config k8s.io/apiserver/pkg/admission/plugin/webhook/config
k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission
k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1 k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1