2 Copyright 2018 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
29 "github.com/appscode/jsonpatch"
31 admissionv1beta1 "k8s.io/api/admission/v1beta1"
32 admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "sigs.k8s.io/controller-runtime/pkg/client"
35 "sigs.k8s.io/controller-runtime/pkg/runtime/inject"
36 atypes "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
37 "sigs.k8s.io/controller-runtime/pkg/webhook/types"
40 // Handler can handle an AdmissionRequest.
41 type Handler interface {
42 Handle(context.Context, atypes.Request) atypes.Response
45 // HandlerFunc implements Handler interface using a single function.
46 type HandlerFunc func(context.Context, atypes.Request) atypes.Response
48 var _ Handler = HandlerFunc(nil)
50 // Handle process the AdmissionRequest by invoking the underlying function.
51 func (f HandlerFunc) Handle(ctx context.Context, req atypes.Request) atypes.Response {
55 // Webhook represents each individual webhook.
57 // Name is the name of the webhook
59 // Type is the webhook type, i.e. mutating, validating
60 Type types.WebhookType
61 // Path is the path this webhook will serve.
63 // Rules maps to the Rules field in admissionregistrationv1beta1.Webhook
64 Rules []admissionregistrationv1beta1.RuleWithOperations
65 // FailurePolicy maps to the FailurePolicy field in admissionregistrationv1beta1.Webhook
66 // This optional. If not set, will be defaulted to Ignore (fail-open) by the server.
67 // More details: https://github.com/kubernetes/api/blob/f5c295feaba2cbc946f0bbb8b535fc5f6a0345ee/admissionregistration/v1beta1/types.go#L144-L147
68 FailurePolicy *admissionregistrationv1beta1.FailurePolicyType
69 // NamespaceSelector maps to the NamespaceSelector field in admissionregistrationv1beta1.Webhook
71 NamespaceSelector *metav1.LabelSelector
72 // Handlers contains a list of handlers. Each handler may only contains the business logic for its own feature.
73 // For example, feature foo and bar can be in the same webhook if all the other configurations are the same.
74 // The handler will be invoked sequentially as the order in the list.
75 // Note: if you are using mutating webhook with multiple handlers, it's your responsibility to
76 // ensure the handlers are not generating conflicting JSON patches.
82 func (w *Webhook) setDefaults() {
84 if len(w.Rules) == 0 || len(w.Rules[0].Resources) == 0 {
85 // can't do defaulting, skip it.
88 if w.Type == types.WebhookTypeMutating {
89 w.Path = "/mutate-" + w.Rules[0].Resources[0]
90 } else if w.Type == types.WebhookTypeValidating {
91 w.Path = "/validate-" + w.Rules[0].Resources[0]
95 reg := regexp.MustCompile("[^a-zA-Z0-9]+")
96 processedPath := strings.ToLower(reg.ReplaceAllString(w.Path, ""))
97 w.Name = processedPath + ".example.com"
101 // Add adds additional handler(s) in the webhook
102 func (w *Webhook) Add(handlers ...Handler) {
103 w.Handlers = append(w.Handlers, handlers...)
106 // Webhook implements Handler interface.
107 var _ Handler = &Webhook{}
109 // Handle processes AdmissionRequest.
110 // If the webhook is mutating type, it delegates the AdmissionRequest to each handler and merge the patches.
111 // If the webhook is validating type, it delegates the AdmissionRequest to each handler and
112 // deny the request if anyone denies.
113 func (w *Webhook) Handle(ctx context.Context, req atypes.Request) atypes.Response {
114 if req.AdmissionRequest == nil {
115 return ErrorResponse(http.StatusBadRequest, errors.New("got an empty AdmissionRequest"))
117 var resp atypes.Response
119 case types.WebhookTypeMutating:
120 resp = w.handleMutating(ctx, req)
121 case types.WebhookTypeValidating:
122 resp = w.handleValidating(ctx, req)
124 return ErrorResponse(http.StatusInternalServerError, errors.New("you must specify your webhook type"))
126 resp.Response.UID = req.AdmissionRequest.UID
130 func (w *Webhook) handleMutating(ctx context.Context, req atypes.Request) atypes.Response {
131 patches := []jsonpatch.JsonPatchOperation{}
132 for _, handler := range w.Handlers {
133 resp := handler.Handle(ctx, req)
134 if !resp.Response.Allowed {
135 setStatusOKInAdmissionResponse(resp.Response)
138 if resp.Response.PatchType != nil && *resp.Response.PatchType != admissionv1beta1.PatchTypeJSONPatch {
139 return ErrorResponse(http.StatusInternalServerError,
140 fmt.Errorf("unexpected patch type returned by the handler: %v, only allow: %v",
141 resp.Response.PatchType, admissionv1beta1.PatchTypeJSONPatch))
143 patches = append(patches, resp.Patches...)
146 marshaledPatch, err := json.Marshal(patches)
148 return ErrorResponse(http.StatusBadRequest, fmt.Errorf("error when marshaling the patch: %v", err))
150 return atypes.Response{
151 Response: &admissionv1beta1.AdmissionResponse{
153 Result: &metav1.Status{
156 Patch: marshaledPatch,
157 PatchType: func() *admissionv1beta1.PatchType { pt := admissionv1beta1.PatchTypeJSONPatch; return &pt }(),
162 func (w *Webhook) handleValidating(ctx context.Context, req atypes.Request) atypes.Response {
163 for _, handler := range w.Handlers {
164 resp := handler.Handle(ctx, req)
165 if !resp.Response.Allowed {
166 setStatusOKInAdmissionResponse(resp.Response)
170 return atypes.Response{
171 Response: &admissionv1beta1.AdmissionResponse{
173 Result: &metav1.Status{
180 func setStatusOKInAdmissionResponse(resp *admissionv1beta1.AdmissionResponse) {
184 if resp.Result == nil {
185 resp.Result = &metav1.Status{}
187 if resp.Result.Code == 0 {
188 resp.Result.Code = http.StatusOK
192 // GetName returns the name of the webhook.
193 func (w *Webhook) GetName() string {
194 w.once.Do(w.setDefaults)
198 // GetPath returns the path that the webhook registered.
199 func (w *Webhook) GetPath() string {
200 w.once.Do(w.setDefaults)
204 // GetType returns the type of the webhook.
205 func (w *Webhook) GetType() types.WebhookType {
206 w.once.Do(w.setDefaults)
210 // Handler returns a http.Handler for the webhook
211 func (w *Webhook) Handler() http.Handler {
212 w.once.Do(w.setDefaults)
216 // Validate validates if the webhook is valid.
217 func (w *Webhook) Validate() error {
218 w.once.Do(w.setDefaults)
219 if len(w.Rules) == 0 {
220 return errors.New("field Rules should not be empty")
222 if len(w.Name) == 0 {
223 return errors.New("field Name should not be empty")
225 if w.Type != types.WebhookTypeMutating && w.Type != types.WebhookTypeValidating {
226 return fmt.Errorf("unsupported Type: %v, only WebhookTypeMutating and WebhookTypeValidating are supported", w.Type)
228 if len(w.Path) == 0 {
229 return errors.New("field Path should not be empty")
231 if len(w.Handlers) == 0 {
232 return errors.New("field Handler should not be empty")
237 var _ inject.Client = &Webhook{}
239 // InjectClient injects the client into the handlers
240 func (w *Webhook) InjectClient(c client.Client) error {
241 for _, handler := range w.Handlers {
242 if _, err := inject.ClientInto(c, handler); err != nil {
249 var _ inject.Decoder = &Webhook{}
251 // InjectDecoder injects the decoder into the handlers
252 func (w *Webhook) InjectDecoder(d atypes.Decoder) error {
253 for _, handler := range w.Handlers {
254 if _, err := inject.DecoderInto(d, handler); err != nil {