mirror of https://github.com/k3s-io/k3s
Let mutating webhook defaults the object after applying the patch sent back by the webhook
parent
009701f181
commit
5029bb56c4
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Reference in New Issue