Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / sigs.k8s.io / controller-runtime / pkg / webhook / admission / webhook.go
1 /*
2 Copyright 2018 The Kubernetes Authors.
3
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
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
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.
15 */
16
17 package admission
18
19 import (
20         "context"
21         "encoding/json"
22         "errors"
23         "fmt"
24         "net/http"
25         "regexp"
26         "strings"
27         "sync"
28
29         "github.com/appscode/jsonpatch"
30
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"
38 )
39
40 // Handler can handle an AdmissionRequest.
41 type Handler interface {
42         Handle(context.Context, atypes.Request) atypes.Response
43 }
44
45 // HandlerFunc implements Handler interface using a single function.
46 type HandlerFunc func(context.Context, atypes.Request) atypes.Response
47
48 var _ Handler = HandlerFunc(nil)
49
50 // Handle process the AdmissionRequest by invoking the underlying function.
51 func (f HandlerFunc) Handle(ctx context.Context, req atypes.Request) atypes.Response {
52         return f(ctx, req)
53 }
54
55 // Webhook represents each individual webhook.
56 type Webhook struct {
57         // Name is the name of the webhook
58         Name string
59         // Type is the webhook type, i.e. mutating, validating
60         Type types.WebhookType
61         // Path is the path this webhook will serve.
62         Path string
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
70         // This optional.
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.
77         Handlers []Handler
78
79         once sync.Once
80 }
81
82 func (w *Webhook) setDefaults() {
83         if len(w.Path) == 0 {
84                 if len(w.Rules) == 0 || len(w.Rules[0].Resources) == 0 {
85                         // can't do defaulting, skip it.
86                         return
87                 }
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]
92                 }
93         }
94         if len(w.Name) == 0 {
95                 reg := regexp.MustCompile("[^a-zA-Z0-9]+")
96                 processedPath := strings.ToLower(reg.ReplaceAllString(w.Path, ""))
97                 w.Name = processedPath + ".example.com"
98         }
99 }
100
101 // Add adds additional handler(s) in the webhook
102 func (w *Webhook) Add(handlers ...Handler) {
103         w.Handlers = append(w.Handlers, handlers...)
104 }
105
106 // Webhook implements Handler interface.
107 var _ Handler = &Webhook{}
108
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"))
116         }
117         var resp atypes.Response
118         switch w.Type {
119         case types.WebhookTypeMutating:
120                 resp = w.handleMutating(ctx, req)
121         case types.WebhookTypeValidating:
122                 resp = w.handleValidating(ctx, req)
123         default:
124                 return ErrorResponse(http.StatusInternalServerError, errors.New("you must specify your webhook type"))
125         }
126         resp.Response.UID = req.AdmissionRequest.UID
127         return resp
128 }
129
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)
136                         return resp
137                 }
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))
142                 }
143                 patches = append(patches, resp.Patches...)
144         }
145         var err error
146         marshaledPatch, err := json.Marshal(patches)
147         if err != nil {
148                 return ErrorResponse(http.StatusBadRequest, fmt.Errorf("error when marshaling the patch: %v", err))
149         }
150         return atypes.Response{
151                 Response: &admissionv1beta1.AdmissionResponse{
152                         Allowed: true,
153                         Result: &metav1.Status{
154                                 Code: http.StatusOK,
155                         },
156                         Patch:     marshaledPatch,
157                         PatchType: func() *admissionv1beta1.PatchType { pt := admissionv1beta1.PatchTypeJSONPatch; return &pt }(),
158                 },
159         }
160 }
161
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)
167                         return resp
168                 }
169         }
170         return atypes.Response{
171                 Response: &admissionv1beta1.AdmissionResponse{
172                         Allowed: true,
173                         Result: &metav1.Status{
174                                 Code: http.StatusOK,
175                         },
176                 },
177         }
178 }
179
180 func setStatusOKInAdmissionResponse(resp *admissionv1beta1.AdmissionResponse) {
181         if resp == nil {
182                 return
183         }
184         if resp.Result == nil {
185                 resp.Result = &metav1.Status{}
186         }
187         if resp.Result.Code == 0 {
188                 resp.Result.Code = http.StatusOK
189         }
190 }
191
192 // GetName returns the name of the webhook.
193 func (w *Webhook) GetName() string {
194         w.once.Do(w.setDefaults)
195         return w.Name
196 }
197
198 // GetPath returns the path that the webhook registered.
199 func (w *Webhook) GetPath() string {
200         w.once.Do(w.setDefaults)
201         return w.Path
202 }
203
204 // GetType returns the type of the webhook.
205 func (w *Webhook) GetType() types.WebhookType {
206         w.once.Do(w.setDefaults)
207         return w.Type
208 }
209
210 // Handler returns a http.Handler for the webhook
211 func (w *Webhook) Handler() http.Handler {
212         w.once.Do(w.setDefaults)
213         return w
214 }
215
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")
221         }
222         if len(w.Name) == 0 {
223                 return errors.New("field Name should not be empty")
224         }
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)
227         }
228         if len(w.Path) == 0 {
229                 return errors.New("field Path should not be empty")
230         }
231         if len(w.Handlers) == 0 {
232                 return errors.New("field Handler should not be empty")
233         }
234         return nil
235 }
236
237 var _ inject.Client = &Webhook{}
238
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 {
243                         return err
244                 }
245         }
246         return nil
247 }
248
249 var _ inject.Decoder = &Webhook{}
250
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 {
255                         return err
256                 }
257         }
258         return nil
259 }