EXTENDIENDO LA FUNCIONALIDAD DE KUBERNETES (ADMISSION CONTROLLERS)(PARTE II)

En nuestro anterior post creamos el webhook pero no hacia nada. Simplemente el Admission Controllers se llamaba en la creación de un objeto pero la función handle estaba vacia.

Es esta segunda parte vamos a añadir el contenido de de la función handle para que simplemente no permita que los pod nginx no sean la ultima versión estable.

La funcion handle quedaria de esta manera.

func handleMutate(w http.ResponseWriter, r *http.Request) {

	log.Printf("MUTATE CREATE")

	input := &admissionv1.AdmissionReview{}
	err := json.NewDecoder(r.Body).Decode(input)
	if err != nil {

		sendErr(w, fmt.Errorf("could not: %v", err))

	}
	pod := &corev1.Pod{}
	err = json.Unmarshal(input.Request.Object.Raw, pod)
	if err != nil {
		sendErr(w, fmt.Errorf("could not Pods: %v", err))
		return

	}

	patchMap := []map[string]string{}

	for idx, _ := range pod.Spec.Containers {

		patchMap = append(patchMap, map[string]string{
			"op":    "replace",
			"path":  fmt.Sprintf("/spec/containers/%d/image", idx),
			"value": "nginx:latest",
		})

	}

	patchBytes, err := json.Marshal(patchMap)
	log.Printf("%s", patchBytes)
	if err != nil {
		sendErr(w, fmt.Errorf("could not Pods: %v", err))
		return

	}
	jsonPatch := admissionv1.PatchTypeJSONPatch
	respReview := &admissionv1.AdmissionReview{
		TypeMeta: input.TypeMeta,
		Response: &admissionv1.AdmissionResponse{
			UID:       input.Request.UID,
			Allowed:   true,
			Patch:     patchBytes,
			PatchType: &jsonPatch,
		},
	}
	respBytes, err := json.Marshal(respReview)
	if err != nil {
		sendErr(w, fmt.Errorf("could not response: %v", err))
		return

	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(respBytes)

}

El código simplemente creaa un objeto Adminssion Controller y un objeto pod. Modificamos para que el pod solo pueda instalar la ultima version de nginx.

patchMap = append(patchMap, map[string]string{
			"op":    "replace",
			"path":  fmt.Sprintf("/spec/containers/%d/image", idx),
			"value": "nginx:latest",
		})

Y enviamos la respuesta Http.

jsonPatch := admissionv1.PatchTypeJSONPatch
	respReview := &admissionv1.AdmissionReview{
		TypeMeta: input.TypeMeta,
		Response: &admissionv1.AdmissionResponse{
			UID:       input.Request.UID,
			Allowed:   true,
			Patch:     patchBytes,
			PatchType: &jsonPatch,
		},
	}
	respBytes, err := json.Marshal(respReview)
	if err != nil {
		sendErr(w, fmt.Errorf("could not response: %v", err))
		return

	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(respBytes)

Para comprobar el funcionamiento simplemente ejecutariamos:

kubectl run nginx --image=nginx:1.13.10 --restart=Never
k logs kcontroller-f58fb84dc-2dmxj

Y nos devolvería

2021/05/27 11:07:09 MUTATE CREATE
2021/05/27 11:07:09 [{"op":"replace","path":"/spec/containers/0/image","value":"nginx:latest"}]

El código final seria este :

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	admissionv1 "k8s.io/api/admission/v1"
	corev1 "k8s.io/api/core/v1"
)

func handleMutate(w http.ResponseWriter, r *http.Request) {

	log.Printf("MUTATE CREATE")

	input := &admissionv1.AdmissionReview{}
	err := json.NewDecoder(r.Body).Decode(input)
	if err != nil {

		sendErr(w, fmt.Errorf("could not: %v", err))

	}
	pod := &corev1.Pod{}
	err = json.Unmarshal(input.Request.Object.Raw, pod)
	if err != nil {
		sendErr(w, fmt.Errorf("could not Pods: %v", err))
		return

	}

	patchMap := []map[string]string{}

	for idx, _ := range pod.Spec.Containers {

		patchMap = append(patchMap, map[string]string{
			"op":    "replace",
			"path":  fmt.Sprintf("/spec/containers/%d/image", idx),
			"value": "nginx:latest",
		})

	}

	patchBytes, err := json.Marshal(patchMap)
	log.Printf("%s", patchBytes)
	if err != nil {
		sendErr(w, fmt.Errorf("could not Pods: %v", err))
		return

	}
	jsonPatch := admissionv1.PatchTypeJSONPatch
	respReview := &admissionv1.AdmissionReview{
		TypeMeta: input.TypeMeta,
		Response: &admissionv1.AdmissionResponse{
			UID:       input.Request.UID,
			Allowed:   true,
			Patch:     patchBytes,
			PatchType: &jsonPatch,
		},
	}
	respBytes, err := json.Marshal(respReview)
	if err != nil {
		sendErr(w, fmt.Errorf("could not response: %v", err))
		return

	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(respBytes)

}

func sendErr(w http.ResponseWriter, err error) {

	out, err := json.Marshal(map[string]string{"Err": err.Error()})
	if err != nil {
		http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusInternalServerError)
	w.Write(out)
}

func main() {

	mux := http.NewServeMux()
	mux.HandleFunc("/mutate", handleMutate)
	srv := &http.Server{Addr: ":443", Handler: mux}
	log.Printf("Antes de server")
	log.Fatal(srv.ListenAndServeTLS("/certs/webhook.crt", "/certs/webhook-key.pem"))
}