blob: 03576d72a61f56724d0bc07c12f27216b7db79be [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package handlers
18
19import (
20 "fmt"
21 "net/http"
22 "time"
23
24 "k8s.io/apimachinery/pkg/api/errors"
25 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apiserver/pkg/admission"
29 "k8s.io/apiserver/pkg/audit"
30 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
31 "k8s.io/apiserver/pkg/endpoints/request"
32 "k8s.io/apiserver/pkg/registry/rest"
33 utiltrace "k8s.io/apiserver/pkg/util/trace"
34)
35
36// DeleteResource returns a function that will handle a resource deletion
37// TODO admission here becomes solely validating admission
38func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
39 return func(w http.ResponseWriter, req *http.Request) {
40 // For performance tracking purposes.
41 trace := utiltrace.New("Delete " + req.URL.Path)
42 defer trace.LogIfLong(500 * time.Millisecond)
43
44 if isDryRun(req.URL) {
45 scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
46 return
47 }
48
49 // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
50 timeout := parseTimeout(req.URL.Query().Get("timeout"))
51
52 namespace, name, err := scope.Namer.Name(req)
53 if err != nil {
54 scope.err(err, w, req)
55 return
56 }
57 ctx := req.Context()
58 ctx = request.WithNamespace(ctx, namespace)
59 ae := request.AuditEventFrom(ctx)
60 admit = admission.WithAudit(admit, ae)
61
62 options := &metav1.DeleteOptions{}
63 if allowsOptions {
64 body, err := readBody(req)
65 if err != nil {
66 scope.err(err, w, req)
67 return
68 }
69 if len(body) > 0 {
70 s, err := negotiation.NegotiateInputSerializer(req, false, metainternalversion.Codecs)
71 if err != nil {
72 scope.err(err, w, req)
73 return
74 }
75 // For backwards compatibility, we need to allow existing clients to submit per group DeleteOptions
76 // It is also allowed to pass a body with meta.k8s.io/v1.DeleteOptions
77 defaultGVK := scope.MetaGroupVersion.WithKind("DeleteOptions")
78 obj, _, err := metainternalversion.Codecs.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
79 if err != nil {
80 scope.err(err, w, req)
81 return
82 }
83 if obj != options {
84 scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req)
85 return
86 }
87 trace.Step("Decoded delete options")
88
89 ae := request.AuditEventFrom(ctx)
90 audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
91 trace.Step("Recorded the audit event")
92 } else {
93 if values := req.URL.Query(); len(values) > 0 {
94 if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
95 err = errors.NewBadRequest(err.Error())
96 scope.err(err, w, req)
97 return
98 }
99 }
100 }
101 }
102
103 trace.Step("About to check admission control")
104 if admit != nil && admit.Handles(admission.Delete) {
105 userInfo, _ := request.UserFrom(ctx)
106 attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)
107 if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
108 if err := mutatingAdmission.Admit(attrs); err != nil {
109 scope.err(err, w, req)
110 return
111 }
112 }
113 if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
114 if err := validatingAdmission.Validate(attrs); err != nil {
115 scope.err(err, w, req)
116 return
117 }
118 }
119 }
120
121 trace.Step("About to delete object from database")
122 wasDeleted := true
123 result, err := finishRequest(timeout, func() (runtime.Object, error) {
124 obj, deleted, err := r.Delete(ctx, name, options)
125 wasDeleted = deleted
126 return obj, err
127 })
128 if err != nil {
129 scope.err(err, w, req)
130 return
131 }
132 trace.Step("Object deleted from database")
133
134 status := http.StatusOK
135 // Return http.StatusAccepted if the resource was not deleted immediately and
136 // user requested cascading deletion by setting OrphanDependents=false.
137 // Note: We want to do this always if resource was not deleted immediately, but
138 // that will break existing clients.
139 // Other cases where resource is not instantly deleted are: namespace deletion
140 // and pod graceful deletion.
141 if !wasDeleted && options.OrphanDependents != nil && *options.OrphanDependents == false {
142 status = http.StatusAccepted
143 }
144 // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
145 // object with the response.
146 if result == nil {
147 result = &metav1.Status{
148 Status: metav1.StatusSuccess,
149 Code: int32(status),
150 Details: &metav1.StatusDetails{
151 Name: name,
152 Kind: scope.Kind.Kind,
153 },
154 }
155 } else {
156 // when a non-status response is returned, set the self link
157 requestInfo, ok := request.RequestInfoFrom(ctx)
158 if !ok {
159 scope.err(fmt.Errorf("missing requestInfo"), w, req)
160 return
161 }
162 if _, ok := result.(*metav1.Status); !ok {
163 if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
164 scope.err(err, w, req)
165 return
166 }
167 }
168 }
169
170 transformResponseObject(ctx, scope, req, w, status, result)
171 }
172}
173
174// DeleteCollection returns a function that will handle a collection deletion
175func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
176 return func(w http.ResponseWriter, req *http.Request) {
177 if isDryRun(req.URL) {
178 scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
179 return
180 }
181
182 // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
183 timeout := parseTimeout(req.URL.Query().Get("timeout"))
184
185 namespace, err := scope.Namer.Namespace(req)
186 if err != nil {
187 scope.err(err, w, req)
188 return
189 }
190
191 ctx := req.Context()
192 ctx = request.WithNamespace(ctx, namespace)
193 ae := request.AuditEventFrom(ctx)
194 admit = admission.WithAudit(admit, ae)
195
196 if admit != nil && admit.Handles(admission.Delete) {
197 userInfo, _ := request.UserFrom(ctx)
198 attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo)
199 if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
200 err = mutatingAdmission.Admit(attrs)
201 if err != nil {
202 scope.err(err, w, req)
203 return
204 }
205 }
206
207 if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
208 err = validatingAdmission.Validate(attrs)
209 if err != nil {
210 scope.err(err, w, req)
211 return
212 }
213 }
214 }
215
216 listOptions := metainternalversion.ListOptions{}
217 if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &listOptions); err != nil {
218 err = errors.NewBadRequest(err.Error())
219 scope.err(err, w, req)
220 return
221 }
222
223 // transform fields
224 // TODO: DecodeParametersInto should do this.
225 if listOptions.FieldSelector != nil {
226 fn := func(label, value string) (newLabel, newValue string, err error) {
227 return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value)
228 }
229 if listOptions.FieldSelector, err = listOptions.FieldSelector.Transform(fn); err != nil {
230 // TODO: allow bad request to set field causes based on query parameters
231 err = errors.NewBadRequest(err.Error())
232 scope.err(err, w, req)
233 return
234 }
235 }
236
237 options := &metav1.DeleteOptions{}
238 if checkBody {
239 body, err := readBody(req)
240 if err != nil {
241 scope.err(err, w, req)
242 return
243 }
244 if len(body) > 0 {
245 s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
246 if err != nil {
247 scope.err(err, w, req)
248 return
249 }
250 defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions")
251 obj, _, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
252 if err != nil {
253 scope.err(err, w, req)
254 return
255 }
256 if obj != options {
257 scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req)
258 return
259 }
260
261 ae := request.AuditEventFrom(ctx)
262 audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
263 }
264 }
265
266 result, err := finishRequest(timeout, func() (runtime.Object, error) {
267 return r.DeleteCollection(ctx, options, &listOptions)
268 })
269 if err != nil {
270 scope.err(err, w, req)
271 return
272 }
273
274 // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
275 // object with the response.
276 if result == nil {
277 result = &metav1.Status{
278 Status: metav1.StatusSuccess,
279 Code: http.StatusOK,
280 Details: &metav1.StatusDetails{
281 Kind: scope.Kind.Kind,
282 },
283 }
284 } else {
285 // when a non-status response is returned, set the self link
286 if _, ok := result.(*metav1.Status); !ok {
287 if _, err := setListSelfLink(result, ctx, req, scope.Namer); err != nil {
288 scope.err(err, w, req)
289 return
290 }
291 }
292 }
293
294 transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
295 }
296}