agones.dev/agones@v1.54.0/pkg/util/webhooks/webhooks.go (about) 1 // Copyright 2018 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package webhooks manages and receives Kubernetes Webhooks 16 package webhooks 17 18 import ( 19 "encoding/json" 20 "net/http" 21 22 "agones.dev/agones/pkg/util/runtime" 23 "github.com/pkg/errors" 24 "github.com/sirupsen/logrus" 25 admissionv1 "k8s.io/api/admission/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 ) 29 30 // WebHook manage Kubernetes webhooks 31 type WebHook struct { 32 logger *logrus.Entry 33 mux *http.ServeMux 34 handlers map[string][]operationHandler 35 } 36 37 // operationHandler stores the data for a handler to match against 38 type operationHandler struct { 39 handler Handler 40 groupKind schema.GroupKind 41 operation admissionv1.Operation 42 } 43 44 // Handler handles a webhook's AdmissionReview coming in, and will return the 45 // AdmissionReview that will be the return value of the webhook 46 type Handler func(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error) 47 48 // NewWebHook returns a Kubernetes webhook manager 49 func NewWebHook(mux *http.ServeMux) *WebHook { 50 wh := &WebHook{ 51 mux: mux, 52 handlers: map[string][]operationHandler{}, 53 } 54 55 wh.logger = runtime.NewLoggerWithType(wh) 56 return wh 57 } 58 59 // AddHandler adds a handler for a given path, group and kind, and operation 60 func (wh *WebHook) AddHandler(path string, gk schema.GroupKind, op admissionv1.Operation, h Handler) { 61 if len(wh.handlers[path]) == 0 { 62 wh.mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { 63 err := wh.handle(path, w, r) 64 if err != nil { 65 runtime.HandleError(wh.logger.WithField("url", r.URL), err) 66 w.WriteHeader(http.StatusInternalServerError) 67 } 68 }) 69 } 70 wh.logger.WithField("path", path).WithField("groupKind", gk).WithField("op", op).Debug("Added webhook handler") 71 wh.handlers[path] = append(wh.handlers[path], operationHandler{groupKind: gk, operation: op, handler: h}) 72 } 73 74 // handle Handles http requests for webhooks 75 func (wh *WebHook) handle(path string, w http.ResponseWriter, r *http.Request) error { // nolint: interfacer 76 wh.logger.WithField("path", path).Debug("running webhook") 77 78 var review admissionv1.AdmissionReview 79 err := json.NewDecoder(r.Body).Decode(&review) 80 if err != nil { 81 return errors.Wrapf(err, "error decoding decoding json for path %v", path) 82 } 83 84 // set it to true, in case there are no handlers 85 if review.Response == nil { 86 review.Response = &admissionv1.AdmissionResponse{Allowed: true} 87 } 88 review.Response.UID = review.Request.UID 89 wh.logger.WithField("name", review.Request.Name).WithField("path", path).WithField("kind", review.Request.Kind.Kind).WithField("group", review.Request.Kind.Group).Debug("handling webhook request") 90 91 for _, oh := range wh.handlers[path] { 92 if oh.operation == review.Request.Operation && 93 oh.groupKind.Kind == review.Request.Kind.Kind && 94 review.Request.Kind.Group == oh.groupKind.Group { 95 96 review, err = oh.handler(review) 97 if err != nil { 98 review.Response.Allowed = false 99 details := metav1.StatusDetails{ 100 Name: review.Request.Name, 101 Group: review.Request.Kind.Group, 102 Kind: review.Request.Kind.Kind, 103 Causes: []metav1.StatusCause{{ 104 Type: metav1.CauseType("InternalError"), 105 Message: err.Error(), 106 }}, 107 } 108 review.Response.Result = &metav1.Status{ 109 Status: metav1.StatusFailure, 110 Message: err.Error(), 111 Reason: metav1.StatusReasonInternalError, 112 Details: &details, 113 } 114 } 115 } 116 } 117 err = json.NewEncoder(w).Encode(review) 118 if err != nil { 119 return errors.Wrapf(err, "error decoding encoding json for path %v", path) 120 } 121 122 return nil 123 }