How to write a response for kubernetes admission controller

8/8/2020

I am trying to write a simple admission controller for pod naming (validation) but for some reason I am generating a wrong response.

Here is my code:

package main

import (
	  "fmt" 
	  "encoding/json"
	  "io/ioutil"
	  "net/http"
      "github.com/golang/glog"

	  // for Kubernetes 
	  "k8s.io/api/admission/v1beta1"
	  "k8s.io/api/core/v1"
	  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	  "regexp"
)

type myValidServerhandler struct {

}
// this is the handler fuction from the HTTP server 
func (gs *myValidServerhandler) serve(w http.ResponseWriter, r *http.Request) {
	var Body []byte
	if r.Body != nil {
		if data , err := ioutil.ReadAll(r.Body); err == nil {
	    	Body = data
		}
	}

 	if len(Body) == 0 {
    	glog.Error("Unable to retrive Body from API")
    	http.Error(w,"Empty Body", http.StatusBadRequest)
    	return
 	}

	glog.Info("Received Request")
// this is where I make sure the request is for the validation prefix
	if r.URL.Path != "/validate" {
 		 glog.Error("Not a Validataion String")
	    http.Error(w,"Not a Validataion String", http.StatusBadRequest)
		return
 	}
// in this part the function takes the AdmissionReivew and make sure in is in the right
// JSON format 
    arRequest := &v1beta1.AdmissionReview{}
    if err := json.Unmarshal(Body, arRequest); err != nil {
	    glog.Error("incorrect Body")
	    http.Error(w, "incorrect Body", http.StatusBadRequest)
	    return
    }

    raw := arRequest.Request.Object.Raw
    pod := v1.Pod{}
    if err := json.Unmarshal(raw, &pod); err != nil {
	    glog.Error("Error Deserializing Pod")
	    return
    }
// this is where I make sure the pod name contains the kuku string
    podnamingReg := regexp.MustCompile(`kuku`)
    if podnamingReg.MatchString(string(pod.Name)) {
	    return
    } else {
	    glog.Error("the pod does not contain \"kuku\"")
	    http.Error(w, "the pod does not contain \"kuku\"", http.StatusBadRequest)
	    return
    }

// I think the main problem is with this part of the code because the 
// error from the events I getting in the Kubernetes namespace is that 
// I am sending 200 without a body response

    arResponse := v1beta1.AdmissionReview{
	    Response: &v1beta1.AdmissionResponse{
		    Result:  &metav1.Status{},
		    Allowed: true,
	    },
    }
// generating the JSON response after the validation 
    resp, err := json.Marshal(arResponse)
    if err != nil {
	    glog.Error("Can't encode response:", err)
	    http.Error(w, fmt.Sprintf("couldn't encode response: %v", err), http.StatusInternalServerError)
    }

    glog.Infof("Ready to write  response ...")
    if _, err := w.Write(resp); err != nil {
	    glog.Error("Can't write response", err)
	    http.Error(w, fmt.Sprintf("cloud not write response: %v", err), http.StatusInternalServerError)
    }
}

The code is working as expected except for a positive output (where the pod name meets the criteria) there is another file with a main just grabbing the TLS files and starting the HTTP service.

-- Oren Oichman
api
controller
go
kubernetes
validation

1 Answer

8/10/2020

so after a few digging I found what was wrong with my code

first this part

  if podnamingReg.MatchString(string(pod.Name)) {
    return
  } else {
    glog.Error("the pod does not contain \"kuku\"")
    http.Error(w, "the pod does not contain \"kuku\"", http.StatusBadRequest)
    return
  }

by writing "return" twice I discarded the rest of the code and more so I haven't attached the request UID to the response UID and because I am using the v1 and not the v1beta1 I needed to adding the APIVersion in the response so the rest of the code looks like :

arResponse := v1beta1.AdmissionReview{ Response: &v1beta1.AdmissionResponse{ Result: &metav1.Status{}, Allowed: false, }, }

    podnamingReg := regexp.MustCompile(`kuku`)

    if podnamingReg.MatchString(string(pod.Name)) {
	    fmt.Printf("the pod %s is up to the name standard", pod.Name)
	    arResponse.Response.Allowed = true	
    } 

    arResponse.APIVersion = "admission.k8s.io/v1"
    arResponse.Kind = arRequest.Kind
    arResponse.Response.UID = arRequest.Request.UID

so I needed to add the 2 parts and make sure that in case the pod name is not up to standard then I need to return the right response

-- Oren Oichman
Source: StackOverflow