package admission import ( "context" "io" "net/http" admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1" "example.com/monok8s/pkg/controller/osupgrade" "example.com/monok8s/pkg/kube" "github.com/emicklei/go-restful/v3" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/server/httplog" "k8s.io/klog/v2" ) var ( scheme = runtime.NewScheme() codecs = serializer.NewCodecFactory(scheme) deserializer = codecs.UniversalDeserializer() ) var statusesNoTracePred = httplog.StatusIsNot( http.StatusOK, http.StatusFound, http.StatusMovedPermanently, http.StatusTemporaryRedirect, http.StatusBadRequest, http.StatusNotFound, http.StatusSwitchingProtocols, ) func init() { _ = admissionv1.AddToScheme(scheme) _ = monov1alpha1.AddToScheme(scheme) } type Server struct { restfulCont *restful.Container ctx context.Context clients *kube.Clients namespace string nodeName string } func NewServer(ctx context.Context, clients *kube.Clients, namespace, nodeName string) *Server { s := &Server{ ctx: ctx, clients: clients, namespace: namespace, nodeName: nodeName, } s.Initialize() return s } func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { if s == nil { http.Error(w, "admission server is nil", http.StatusInternalServerError) return } if s.restfulCont == nil { http.Error(w, "admission server not initialized", http.StatusInternalServerError) return } handler := httplog.WithLogging(s.restfulCont, statusesNoTracePred) handler.ServeHTTP(w, req) } func (s *Server) Initialize() { s.restfulCont = restful.NewContainer() ws := new(restful.WebService) ws.Path("/admission"). Consumes(restful.MIME_JSON). Produces(restful.MIME_JSON) ws.Route(ws.POST("").To(s.triggerAdmission). Reads(admissionv1.AdmissionReview{}). Writes(admissionv1.AdmissionReview{})) s.restfulCont.Add(ws) } func (s *Server) triggerAdmission(request *restful.Request, response *restful.Response) { body, err := io.ReadAll(request.Request.Body) if err != nil { _ = response.WriteError(http.StatusBadRequest, err) return } var reviewReq admissionv1.AdmissionReview if _, _, err := deserializer.Decode(body, nil, &reviewReq); err != nil { _ = response.WriteError(http.StatusBadRequest, err) return } if reviewReq.Request == nil { _ = response.WriteErrorString(http.StatusBadRequest, "missing admission request") return } resp := admissionv1.AdmissionReview{ TypeMeta: metav1.TypeMeta{ Kind: "AdmissionReview", APIVersion: "admission.k8s.io/v1", }, Response: &admissionv1.AdmissionResponse{ UID: reviewReq.Request.UID, Allowed: true, Result: &metav1.Status{Message: "OK"}, }, } var osu monov1alpha1.OSUpgrade if _, _, err := deserializer.Decode(reviewReq.Request.Object.Raw, nil, &osu); err != nil { klog.V(1).InfoS("Skipping non-OSUpgrade resource", "uid", reviewReq.Request.UID, "kind", reviewReq.Request.Kind.Kind, "operation", reviewReq.Request.Operation, "err", err, ) _ = response.WriteEntity(resp) return } klog.InfoS("Received OSUpgrade admission", "uid", reviewReq.Request.UID, "operation", reviewReq.Request.Operation, "name", osu.Name, "namespace", osu.Namespace, "node", s.nodeName, ) // Resolve every node name if err := osupgrade.EnsureOSUpgradeProgressForNode( s.ctx, s.clients, s.namespace, s.nodeName, &osu, ); err != nil { klog.ErrorS(err, "ensure OSUpgradeProgress for node failed", "osupgrade", osu.Name, "node", s.nodeName, ) } _ = response.WriteEntity(resp) }