Add MutatingWebhookConfiguration type

This commit is contained in:
mbohlool 2017-11-07 12:49:19 -08:00
parent cb43840492
commit fc5a613c17
21 changed files with 1987 additions and 587 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
cluster/images/etcd-version-monitor cluster/images/etcd-version-monitor
cmd/gke-certificates-controller/app cmd/gke-certificates-controller/app
cmd/hyperkube cmd/hyperkube
@ -236,9 +237,10 @@ pkg/proxy/util
pkg/proxy/winkernel pkg/proxy/winkernel
pkg/proxy/winuserspace pkg/proxy/winuserspace
pkg/quota/evaluator/core pkg/quota/evaluator/core
pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage
pkg/registry/admissionregistration/initializerconfiguration/storage pkg/registry/admissionregistration/initializerconfiguration/storage
pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage
pkg/registry/admissionregistration/rest pkg/registry/admissionregistration/rest
pkg/registry/admissionregistration/validatingwebhookconfiguration/storage
pkg/registry/apps/rest pkg/registry/apps/rest
pkg/registry/apps/statefulset pkg/registry/apps/statefulset
pkg/registry/apps/statefulset/storage pkg/registry/apps/statefulset/storage

View File

@ -137,6 +137,8 @@ func TestDefaulting(t *testing.T) {
{Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPresetList"}: {}, {Group: "settings.k8s.io", Version: "v1alpha1", Kind: "PodPresetList"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingWebhookConfiguration"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingWebhookConfiguration"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingWebhookConfigurationList"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "ValidatingWebhookConfigurationList"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "MutatingWebhookConfiguration"}: {},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1", Kind: "MutatingWebhookConfigurationList"}: {},
{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"}: {}, {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"}: {},
{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {}, {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {},
{Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {}, {Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {},

View File

@ -18,7 +18,7 @@ limitations under the License.
// Package admissionregistration is the internal version of the API. // Package admissionregistration is the internal version of the API.
// AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration
// InitializerConfiguration and ValidatingWebhookConfiguration is for the // InitializerConfiguration, ValidatingWebhookConfiguration, and MutatingWebhookConfiguration are for the
// new dynamic admission controller configuration. // new dynamic admission controller configuration.
// +groupName=admissionregistration.k8s.io // +groupName=admissionregistration.k8s.io
package admissionregistration // import "k8s.io/kubernetes/pkg/apis/admissionregistration" package admissionregistration // import "k8s.io/kubernetes/pkg/apis/admissionregistration"

View File

@ -35,7 +35,7 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r
if err := announced.NewGroupMetaFactory( if err := announced.NewGroupMetaFactory(
&announced.GroupMetaFactoryArgs{ &announced.GroupMetaFactoryArgs{
GroupName: admissionregistration.GroupName, GroupName: admissionregistration.GroupName,
RootScopedKinds: sets.NewString("InitializerConfiguration", "ValidatingWebhookConfiguration"), RootScopedKinds: sets.NewString("InitializerConfiguration", "ValidatingWebhookConfiguration", "MutatingWebhookConfiguration"),
VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version}, VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version},
AddInternalObjectsToScheme: admissionregistration.AddToScheme, AddInternalObjectsToScheme: admissionregistration.AddToScheme,
}, },

View File

@ -48,7 +48,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&InitializerConfigurationList{}, &InitializerConfigurationList{},
&ValidatingWebhookConfiguration{}, &ValidatingWebhookConfiguration{},
&ValidatingWebhookConfigurationList{}, &ValidatingWebhookConfigurationList{},
// TODO add mutating configs here too &MutatingWebhookConfiguration{},
&MutatingWebhookConfigurationList{},
) )
return nil return nil
} }

View File

@ -118,7 +118,7 @@ const (
// +genclient:nonNamespaced // +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ValidatingWebhookConfiguration describes the configuration of an admission webhook that accept or reject and object without changing it. // ValidatingWebhookConfiguration describes the configuration of an admission webhook that accepts or rejects and object without changing it.
type ValidatingWebhookConfiguration struct { type ValidatingWebhookConfiguration struct {
metav1.TypeMeta metav1.TypeMeta
// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
@ -138,10 +138,38 @@ type ValidatingWebhookConfigurationList struct {
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
// +optional // +optional
metav1.ListMeta metav1.ListMeta
// List of ValidatingWebhookConfiguration. // List of ValidatingWebhookConfigurations.
Items []ValidatingWebhookConfiguration Items []ValidatingWebhookConfiguration
} }
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.
type MutatingWebhookConfiguration struct {
metav1.TypeMeta
// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
// +optional
metav1.ObjectMeta
// Webhooks is a list of webhooks and the affected resources and operations.
// +optional
Webhooks []Webhook
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.
type MutatingWebhookConfigurationList struct {
metav1.TypeMeta
// Standard list metadata.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
// +optional
metav1.ListMeta
// List of MutatingWebhookConfiguration.
Items []MutatingWebhookConfiguration
}
// Webhook describes an admission webhook and the resources and operations it applies to. // Webhook describes an admission webhook and the resources and operations it applies to.
type Webhook struct { type Webhook struct {
// The name of the admission webhook. // The name of the admission webhook.

View File

@ -21,7 +21,7 @@ limitations under the License.
// Package v1alpha1 is the v1alpha1 version of the API. // Package v1alpha1 is the v1alpha1 version of the API.
// AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration // AdmissionConfiguration and AdmissionPluginConfiguration are legacy static admission plugin configuration
// admissionregistrationv1alpha1.InitializerConfiguration and admissionregistrationv1alpha1.ValidatingWebhookConfiguration is for the // InitializerConfiguration, ValidatingWebhookConfiguration, and MutatingWebhookConfiguration are for the
// new dynamic admission controller configuration. // new dynamic admission controller configuration.
// +groupName=admissionregistration.k8s.io // +groupName=admissionregistration.k8s.io
package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1" package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1"

View File

@ -166,7 +166,15 @@ func ValidateInitializerConfigurationUpdate(newIC, oldIC *admissionregistration.
func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) 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("validatingWebhooks").Index(i))...) allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...)
}
return allErrors
}
func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
for i, hook := range e.Webhooks {
allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...)
} }
return allErrors return allErrors
} }
@ -263,6 +271,10 @@ func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWi
return allErrors return allErrors
} }
func ValidateValidatingwebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
return ValidateValidatingWebhookConfiguration(newC) return ValidateValidatingWebhookConfiguration(newC)
} }
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
return ValidateMutatingWebhookConfiguration(newC)
}

View File

@ -231,7 +231,7 @@ func TestValidateInitializerConfiguration(t *testing.T) {
} }
} }
func getValidatingWebhookConfiguration(hooks []admissionregistration.Webhook) *admissionregistration.ValidatingWebhookConfiguration { func newValidatingWebhookConfiguration(hooks []admissionregistration.Webhook) *admissionregistration.ValidatingWebhookConfiguration {
return &admissionregistration.ValidatingWebhookConfiguration{ return &admissionregistration.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "config", Name: "config",
@ -240,6 +240,8 @@ func getValidatingWebhookConfiguration(hooks []admissionregistration.Webhook) *a
} }
} }
// TODO: Add TestValidateMutatingWebhookConfiguration to test validation for mutating webhooks.
func TestValidateValidatingWebhookConfiguration(t *testing.T) { func TestValidateValidatingWebhookConfiguration(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -248,7 +250,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: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -260,11 +262,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
Name: "", Name: "",
}, },
}), }),
expectedError: `validatingWebhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, validatingWebhooks[2].name: Required value`, expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`,
}, },
{ {
name: "Operations must not be empty or nil", name: "Operations must not be empty or nil",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -288,11 +290,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}), }),
expectedError: `validatingWebhooks[0].rules[0].operations: Required value, validatingWebhooks[0].rules[1].operations: Required value`, expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`,
}, },
{ {
name: "\"\" is NOT a valid operation", name: "\"\" is NOT a valid operation",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -312,7 +314,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: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -332,7 +334,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: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -352,7 +354,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: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -371,7 +373,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: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -391,7 +393,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "resource a/* cannot mix with a/x", name: "resource a/* cannot mix with a/x",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -407,11 +409,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}), }),
expectedError: `validatingWebhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`, expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`,
}, },
{ {
name: "resource a/* can mix with a", name: "resource a/* can mix with a",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -430,7 +432,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "resource */a cannot mix with x/a", name: "resource */a cannot mix with x/a",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -446,11 +448,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}), }),
expectedError: `validatingWebhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`, expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`,
}, },
{ {
name: "resource */* cannot mix with other resources", name: "resource */* cannot mix with other resources",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -466,11 +468,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
}, },
}), }),
expectedError: `validatingWebhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`, expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
}, },
{ {
name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", name: "FailurePolicy can only be \"Ignore\" or \"Fail\"",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -480,11 +482,11 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}(), }(),
}, },
}), }),
expectedError: `validatingWebhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`, expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
}, },
{ {
name: "URLPath must start with slash", name: "URLPath must start with slash",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -497,7 +499,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "URLPath accepts slash", name: "URLPath accepts slash",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -510,7 +512,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "URLPath accepts no trailing slash", name: "URLPath accepts no trailing slash",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -523,7 +525,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "URLPath fails //", name: "URLPath fails //",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -536,7 +538,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "URLPath no empty step", name: "URLPath no empty step",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -548,7 +550,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
expectedError: `clientConfig.urlPath: Invalid value: "/foo//bar/": segment[1] may not be empty`, expectedError: `clientConfig.urlPath: Invalid value: "/foo//bar/": segment[1] may not be empty`,
}, { }, {
name: "URLPath no empty step 2", name: "URLPath no empty step 2",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",
@ -561,7 +563,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}, },
{ {
name: "URLPath no non-subdomain", name: "URLPath no non-subdomain",
config: getValidatingWebhookConfiguration( config: newValidatingWebhookConfiguration(
[]admissionregistration.Webhook{ []admissionregistration.Webhook{
{ {
Name: "webhook.k8s.io", Name: "webhook.k8s.io",

View File

@ -106,7 +106,7 @@ func init() {
if err := announced.NewGroupMetaFactory( if err := announced.NewGroupMetaFactory(
&announced.GroupMetaFactoryArgs{ &announced.GroupMetaFactoryArgs{
GroupName: admissionregistrationv1alpha1.GroupName, GroupName: admissionregistrationv1alpha1.GroupName,
RootScopedKinds: sets.NewString("InitializerConfiguration", "ValidatingWebhookConfiguration"), RootScopedKinds: sets.NewString("InitializerConfiguration", "ValidatingWebhookConfiguration", "MutatingWebhookConfiguration"),
VersionPreferenceOrder: []string{admissionregistrationv1alpha1.SchemeGroupVersion.Version}, VersionPreferenceOrder: []string{admissionregistrationv1alpha1.SchemeGroupVersion.Version},
}, },
announced.VersionToSchemeFunc{ announced.VersionToSchemeFunc{

View File

@ -0,0 +1,17 @@
/*
Copyright 2015 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 mutatingwebhookconfiguration // import "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration"

View File

@ -0,0 +1,51 @@
/*
Copyright 2015 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 storage
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration"
)
// rest implements a RESTStorage for pod disruption budgets against etcd
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against pod disruption budgets.
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &admissionregistration.MutatingWebhookConfiguration{} },
NewListFunc: func() runtime.Object { return &admissionregistration.MutatingWebhookConfigurationList{} },
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*admissionregistration.MutatingWebhookConfiguration).Name, nil
},
DefaultQualifiedResource: admissionregistration.Resource("mutatingwebhookconfigurations"),
CreateStrategy: mutatingwebhookconfiguration.Strategy,
UpdateStrategy: mutatingwebhookconfiguration.Strategy,
DeleteStrategy: mutatingwebhookconfiguration.Strategy,
}
options := &generic.StoreOptions{RESTOptions: optsGetter}
if err := store.CompleteWithOptions(options); err != nil {
panic(err) // TODO: Propagate error up
}
return &REST{store}
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mutatingwebhookconfiguration
import (
"reflect"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/apis/admissionregistration/validation"
)
// mutatingWebhookConfigurationStrategy implements verification logic for mutatingWebhookConfiguration.
type mutatingWebhookConfigurationStrategy struct {
runtime.ObjectTyper
names.NameGenerator
}
// Strategy is the default logic that applies when creating and updating mutatingWebhookConfiguration objects.
var Strategy = mutatingWebhookConfigurationStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
// NamespaceScoped returns true because all mutatingWebhookConfiguration' need to be within a namespace.
func (mutatingWebhookConfigurationStrategy) NamespaceScoped() bool {
return false
}
// PrepareForCreate clears the status of an mutatingWebhookConfiguration before creation.
func (mutatingWebhookConfigurationStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
ic := obj.(*admissionregistration.MutatingWebhookConfiguration)
ic.Generation = 1
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (mutatingWebhookConfigurationStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
newIC := obj.(*admissionregistration.MutatingWebhookConfiguration)
oldIC := old.(*admissionregistration.MutatingWebhookConfiguration)
// Any changes to the spec increment the generation number, any changes to the
// status should reflect the generation number of the corresponding object.
// See metav1.ObjectMeta description for more information on Generation.
if !reflect.DeepEqual(oldIC.Webhooks, newIC.Webhooks) {
newIC.Generation = oldIC.Generation + 1
}
}
// Validate validates a new mutatingWebhookConfiguration.
func (mutatingWebhookConfigurationStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
ic := obj.(*admissionregistration.MutatingWebhookConfiguration)
return validation.ValidateMutatingWebhookConfiguration(ic)
}
// Canonicalize normalizes the object after validation.
func (mutatingWebhookConfigurationStrategy) Canonicalize(obj runtime.Object) {
}
// AllowCreateOnUpdate is true for mutatingWebhookConfiguration; this means you may create one with a PUT request.
func (mutatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool {
return false
}
// ValidateUpdate is the default update validation for an end user.
func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
validationErrorList := validation.ValidateMutatingWebhookConfiguration(obj.(*admissionregistration.MutatingWebhookConfiguration))
updateErrorList := validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration))
return append(validationErrorList, updateErrorList...)
}
// AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should
// only be allowed if version match.
func (mutatingWebhookConfigurationStrategy) AllowUnconditionalUpdate() bool {
return false
}

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration"
initializerconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/initializerconfiguration/storage" initializerconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/initializerconfiguration/storage"
mutatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/mutatingwebhookconfiguration/storage"
validatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage" validatingwebhookconfigurationstorage "k8s.io/kubernetes/pkg/registry/admissionregistration/validatingwebhookconfiguration/storage"
) )
@ -53,6 +54,10 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstora
s := validatingwebhookconfigurationstorage.NewREST(restOptionsGetter) s := validatingwebhookconfigurationstorage.NewREST(restOptionsGetter)
storage["validatingwebhookconfigurations"] = s storage["validatingwebhookconfigurations"] = s
} }
if apiResourceConfigSource.ResourceEnabled(version.WithResource("mutatingwebhookconfigurations")) {
s := mutatingwebhookconfigurationstorage.NewREST(restOptionsGetter)
storage["mutatingwebhookconfigurations"] = s
}
return storage return storage
} }

View File

@ -79,7 +79,7 @@ func (validatingWebhookConfigurationStrategy) AllowCreateOnUpdate() bool {
// ValidateUpdate is the default update validation for an end user. // ValidateUpdate is the default update validation for an end user.
func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
validationErrorList := validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration)) validationErrorList := validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration))
updateErrorList := validation.ValidateValidatingwebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration)) updateErrorList := validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration))
return append(validationErrorList, updateErrorList...) return append(validationErrorList, updateErrorList...)
} }

View File

@ -47,6 +47,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&InitializerConfigurationList{}, &InitializerConfigurationList{},
&ValidatingWebhookConfiguration{}, &ValidatingWebhookConfiguration{},
&ValidatingWebhookConfigurationList{}, &ValidatingWebhookConfigurationList{},
&MutatingWebhookConfiguration{},
&MutatingWebhookConfigurationList{},
) )
metav1.AddToGroupVersion(scheme, SchemeGroupVersion) metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil return nil

View File

@ -130,7 +130,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 []Webhook `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
@ -146,6 +146,36 @@ type ValidatingWebhookConfigurationList struct {
Items []ValidatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` Items []ValidatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
} }
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.
type MutatingWebhookConfiguration struct {
metav1.TypeMeta `json:",inline"`
// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Webhooks is a list of webhooks and the affected resources and operations.
// +optional
// +patchMergeKey=name
// +patchStrategy=merge
Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration.
type MutatingWebhookConfigurationList struct {
metav1.TypeMeta `json:",inline"`
// Standard list metadata.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
// +optional
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// List of MutatingWebhookConfiguration.
Items []MutatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// Webhook describes an admission webhook and the resources and operations it applies to. // Webhook describes an admission webhook and the resources and operations it applies to.
type Webhook struct { type Webhook struct {
// The name of the admission webhook. // The name of the admission webhook.

View File

@ -0,0 +1,101 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package configuration
import (
"fmt"
"reflect"
"sort"
"github.com/golang/glog"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
type MutatingWebhookConfigurationLister interface {
List(opts metav1.ListOptions) (*v1alpha1.MutatingWebhookConfigurationList, error)
}
// MutatingWebhookConfigurationManager collects the mutating webhook objects so that they can be called.
type MutatingWebhookConfigurationManager struct {
*poller
}
func NewMutatingWebhookConfigurationManager(c MutatingWebhookConfigurationLister) *MutatingWebhookConfigurationManager {
getFn := func() (runtime.Object, error) {
list, err := c.List(metav1.ListOptions{})
if err != nil {
if errors.IsNotFound(err) || errors.IsForbidden(err) {
glog.V(5).Infof("MutatingWebhookConfiguration are disabled due to an error: %v", err)
return nil, ErrDisabled
}
return nil, err
}
return mergeMutatingWebhookConfigurations(list), nil
}
return &MutatingWebhookConfigurationManager{
newPoller(getFn),
}
}
// Webhooks returns the merged MutatingWebhookConfiguration.
func (im *MutatingWebhookConfigurationManager) Webhooks() (*v1alpha1.MutatingWebhookConfiguration, error) {
configuration, err := im.poller.configuration()
if err != nil {
return nil, err
}
mutatingWebhookConfiguration, ok := configuration.(*v1alpha1.MutatingWebhookConfiguration)
if !ok {
return nil, fmt.Errorf("expected type %v, got type %v", reflect.TypeOf(mutatingWebhookConfiguration), reflect.TypeOf(configuration))
}
return mutatingWebhookConfiguration, nil
}
func (im *MutatingWebhookConfigurationManager) Run(stopCh <-chan struct{}) {
im.poller.Run(stopCh)
}
func mergeMutatingWebhookConfigurations(
list *v1alpha1.MutatingWebhookConfigurationList,
) *v1alpha1.MutatingWebhookConfiguration {
configurations := append([]v1alpha1.MutatingWebhookConfiguration{}, list.Items...)
var ret v1alpha1.MutatingWebhookConfiguration
// 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
// webhooks in serial, they are sorted here to have a deterministic order.
sort.Sort(byName(configurations))
for _, c := range configurations {
ret.Webhooks = append(ret.Webhooks, c.Webhooks...)
}
return &ret
}
// byName sorts MutatingWebhookConfiguration by name. These objects are all in
// cluster namespace (aka no namespace) thus they all have unique names.
type byName []v1alpha1.MutatingWebhookConfiguration
func (x byName) Len() int { return len(x) }
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byName) Less(i, j int) bool {
return x[i].ObjectMeta.Name < x[j].ObjectMeta.Name
}

View File

@ -0,0 +1,40 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package configuration
import (
"testing"
"k8s.io/api/admissionregistration/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type disabledMutatingWebhookConfigLister struct{}
func (l *disabledMutatingWebhookConfigLister) List(options metav1.ListOptions) (*v1alpha1.MutatingWebhookConfigurationList, error) {
return nil, errors.NewNotFound(schema.GroupResource{Group: "admissionregistration", Resource: "MutatingWebhookConfigurations"}, "")
}
func TestMutatingWebhookConfigDisabled(t *testing.T) {
manager := NewMutatingWebhookConfigurationManager(&disabledMutatingWebhookConfigLister{})
manager.sync()
_, err := manager.Webhooks()
if err.Error() != ErrDisabled.Error() {
t.Errorf("expected %v, got %v", ErrDisabled, err)
}
}

View File

@ -385,6 +385,10 @@ var etcdStorageData = map[schema.GroupVersionResource]struct {
stub: `{"metadata":{"name":"hook1","creationTimestamp":null},"webhooks":[{"name":"externaladmissionhook.k8s.io","clientConfig":{"service":{"namespace":"","name":""},"caBundle":null},"rules":[{"operations":["CREATE"],"apiGroups":["group"],"apiVersions":["version"],"resources":["resource"]}],"failurePolicy":"Ignore"}]}`, stub: `{"metadata":{"name":"hook1","creationTimestamp":null},"webhooks":[{"name":"externaladmissionhook.k8s.io","clientConfig":{"service":{"namespace":"","name":""},"caBundle":null},"rules":[{"operations":["CREATE"],"apiGroups":["group"],"apiVersions":["version"],"resources":["resource"]}],"failurePolicy":"Ignore"}]}`,
expectedEtcdPath: "/registry/validatingwebhookconfigurations/hook1", expectedEtcdPath: "/registry/validatingwebhookconfigurations/hook1",
}, },
gvr("admissionregistration.k8s.io", "v1alpha1", "mutatingwebhookconfigurations"): {
stub: `{"metadata":{"name":"hook1","creationTimestamp":null},"webhooks":[{"name":"externaladmissionhook.k8s.io","clientConfig":{"service":{"namespace":"","name":""},"caBundle":null},"rules":[{"operations":["CREATE"],"apiGroups":["group"],"apiVersions":["version"],"resources":["resource"]}],"failurePolicy":"Ignore"}]}`,
expectedEtcdPath: "/registry/mutatingwebhookconfigurations/hook1",
},
// -- // --
// k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1 // k8s.io/kubernetes/pkg/apis/scheduling/v1alpha1