Let mutating webhook defaults the object after applying the patch sent back by the webhook

pull/6/head
Chao Xu 2018-01-09 15:58:18 -08:00
parent 009701f181
commit 5029bb56c4
5 changed files with 120 additions and 5 deletions

View File

@ -112,6 +112,7 @@ type MutatingWebhook struct {
namespaceMatcher namespace.Matcher
clientManager config.ClientManager
convertor versioned.Convertor
defaulter runtime.ObjectDefaulter
jsonSerializer runtime.Serializer
}
@ -137,6 +138,7 @@ func (a *MutatingWebhook) SetScheme(scheme *runtime.Scheme) {
Serializer: serializer.NewCodecFactory(scheme).LegacyCodec(admissionv1beta1.SchemeGroupVersion),
}))
a.convertor.Scheme = scheme
a.defaulter = scheme
a.jsonSerializer = json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
}
}
@ -171,6 +173,9 @@ func (a *MutatingWebhook) ValidateInitialization() error {
if err := a.convertor.Validate(); err != nil {
return fmt.Errorf("MutatingWebhook.convertor is not properly setup: %v", err)
}
if a.defaulter == nil {
return fmt.Errorf("MutatingWebhook.defaulter is not properly setup: %v")
}
go a.hookSource.Run(wait.NeverStop)
return nil
}
@ -312,10 +317,9 @@ func (a *MutatingWebhook) callAttrMutatingHook(ctx context.Context, h *v1beta1.W
if err != nil {
return apierrors.NewInternalError(err)
}
// TODO: if we have multiple mutating webhooks, we can remember the json
// instead of encoding and decoding for each one.
if _, _, err := a.jsonSerializer.Decode(patchedJS, nil, attr.Object); err != nil {
return apierrors.NewInternalError(err)
}
a.defaulter.Default(attr.Object)
return nil
}

View File

@ -133,6 +133,12 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
testMutatingConfigMapWebhook(f)
})
It("Should mutate pod and apply defaults after mutation", func() {
registerMutatingWebhookForPod(f, context)
defer client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Delete(mutatingWebhookConfigName, nil)
testMutatingPodWebhook(f)
})
It("Should mutate crd", func() {
crdCleanup, dynamicClient := createCRD(f)
defer crdCleanup()
@ -423,6 +429,7 @@ func registerMutatingWebhookForConfigMap(f *framework.Framework, context *certCo
// The webhook configuration is honored in 1s.
time.Sleep(10 * time.Second)
}
func testMutatingConfigMapWebhook(f *framework.Framework) {
By("create a configmap that should be updated by the webhook")
client := f.ClientSet
@ -439,6 +446,77 @@ func testMutatingConfigMapWebhook(f *framework.Framework) {
}
}
func registerMutatingWebhookForPod(f *framework.Framework, context *certContext) {
client := f.ClientSet
By("Registering the mutating pod webhook via the AdmissionRegistration API")
namespace := f.Namespace.Name
_, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&v1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: mutatingWebhookConfigName,
},
Webhooks: []v1beta1.Webhook{
{
Name: "adding-init-container.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
Rule: v1beta1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"pods"},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
Path: strPtr("/mutating-pods"),
},
CABundle: context.signingCert,
},
},
},
})
framework.ExpectNoError(err, "registering mutating webhook config %s with namespace %s", mutatingWebhookConfigName, namespace)
// The webhook configuration is honored in 1s.
time.Sleep(10 * time.Second)
}
func testMutatingPodWebhook(f *framework.Framework) {
By("create a pod that should be updated by the webhook")
client := f.ClientSet
configMap := toBeMutatedPod(f)
mutatedPod, err := client.CoreV1().Pods(f.Namespace.Name).Create(configMap)
Expect(err).To(BeNil())
if len(mutatedPod.Spec.InitContainers) != 1 {
framework.Failf("expect pod to have 1 init container, got %#v", mutatedPod.Spec.InitContainers)
}
if got, expected := mutatedPod.Spec.InitContainers[0].Name, "webhook-added-init-container"; got != expected {
framework.Failf("expect the init container name to be %q, got %q", expected, got)
}
if got, expected := mutatedPod.Spec.InitContainers[0].TerminationMessagePolicy, v1.TerminationMessageReadFile; got != expected {
framework.Failf("expect the init terminationMessagePolicy to be default to %q, got %q", expected, got)
}
}
func toBeMutatedPod(f *framework.Framework) *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "webhook-to-be-mutated",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example",
Image: framework.GetPauseImageName(f.ClientSet),
},
},
},
}
}
func testWebhook(f *framework.Framework) {
By("create a pod that should be denied by the webhook")
client := f.ClientSet

View File

@ -14,7 +14,7 @@
build:
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook .
docker build --no-cache -t gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v7 .
docker build --no-cache -t gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.9v1 .
rm -rf webhook
push:
gcloud docker -- push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v7
gcloud docker -- push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.9v1

View File

@ -40,6 +40,9 @@ const (
patch2 string = `[
{ "op": "add", "path": "/data/mutation-stage-2", "value": "yes" }
]`
addInitContainerPatch string = `[
{"op":"add","path":"/spec/initContainers","value":[{"image":"webhook-added-image","name":"webhook-added-init-container","resources":{}}]}
]`
)
// Config contains the server (the webhook) cert and key.
@ -108,6 +111,31 @@ func admitPods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
return &reviewResponse
}
func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
glog.V(2).Info("mutating pods")
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
if ar.Request.Resource != podResource {
glog.Errorf("expect resource to be %s", podResource)
return nil
}
raw := ar.Request.Object.Raw
pod := corev1.Pod{}
deserializer := codecs.UniversalDeserializer()
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
glog.Error(err)
return toAdmissionResponse(err)
}
reviewResponse := v1beta1.AdmissionResponse{}
reviewResponse.Allowed = true
if pod.Name == "webhook-to-be-mutated" {
reviewResponse.Patch = []byte(addInitContainerPatch)
pt := v1beta1.PatchTypeJSONPatch
reviewResponse.PatchType = &pt
}
return &reviewResponse
}
// deny configmaps with specific key-value pair.
func admitConfigMaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
glog.V(2).Info("admitting configmaps")
@ -271,6 +299,10 @@ func servePods(w http.ResponseWriter, r *http.Request) {
serve(w, r, admitPods)
}
func serveMutatePods(w http.ResponseWriter, r *http.Request) {
serve(w, r, mutatePods)
}
func serveConfigmaps(w http.ResponseWriter, r *http.Request) {
serve(w, r, admitConfigMaps)
}
@ -293,6 +325,7 @@ func main() {
flag.Parse()
http.HandleFunc("/pods", servePods)
http.HandleFunc("/mutating-pods", serveMutatePods)
http.HandleFunc("/configmaps", serveConfigmaps)
http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)
http.HandleFunc("/crd", serveCRD)

View File

@ -48,7 +48,7 @@ func (i *ImageConfig) SetVersion(version string) {
}
var (
AdmissionWebhook = ImageConfig{e2eRegistry, "k8s-sample-admission-webhook", "1.8v7", true}
AdmissionWebhook = ImageConfig{e2eRegistry, "k8s-sample-admission-webhook", "1.9v1", true}
APIServer = ImageConfig{e2eRegistry, "k8s-aggregator-sample-apiserver", "1.7v2", true}
AppArmorLoader = ImageConfig{gcRegistry, "apparmor-loader", "0.1", false}
BusyBox = ImageConfig{gcRegistry, "busybox", "1.24", false}