k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/delete.go (about) 1 /* 2 Copyright 2017 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 handlers 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "time" 24 25 "go.opentelemetry.io/otel/attribute" 26 27 "k8s.io/apimachinery/pkg/api/errors" 28 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" 29 metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" 30 metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/runtime/schema" 35 "k8s.io/apiserver/pkg/admission" 36 "k8s.io/apiserver/pkg/audit" 37 "k8s.io/apiserver/pkg/endpoints/handlers/finisher" 38 requestmetrics "k8s.io/apiserver/pkg/endpoints/handlers/metrics" 39 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" 40 "k8s.io/apiserver/pkg/endpoints/request" 41 "k8s.io/apiserver/pkg/features" 42 "k8s.io/apiserver/pkg/registry/rest" 43 "k8s.io/apiserver/pkg/util/dryrun" 44 utilfeature "k8s.io/apiserver/pkg/util/feature" 45 "k8s.io/component-base/tracing" 46 ) 47 48 // DeleteResource returns a function that will handle a resource deletion 49 // TODO admission here becomes solely validating admission 50 func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestScope, admit admission.Interface) http.HandlerFunc { 51 return func(w http.ResponseWriter, req *http.Request) { 52 ctx := req.Context() 53 // For performance tracking purposes. 54 ctx, span := tracing.Start(ctx, "Delete", traceFields(req)...) 55 defer span.End(500 * time.Millisecond) 56 57 namespace, name, err := scope.Namer.Name(req) 58 if err != nil { 59 scope.err(err, w, req) 60 return 61 } 62 63 // enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided 64 // timeout inside the parent context is lower than requestTimeoutUpperBound. 65 ctx, cancel := context.WithTimeout(ctx, requestTimeoutUpperBound) 66 defer cancel() 67 68 ctx = request.WithNamespace(ctx, namespace) 69 admit = admission.WithAudit(admit) 70 71 outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) 72 if err != nil { 73 scope.err(err, w, req) 74 return 75 } 76 77 options := &metav1.DeleteOptions{} 78 if allowsOptions { 79 body, err := limitedReadBodyWithRecordMetric(ctx, req, scope.MaxRequestBodyBytes, scope.Resource.GroupResource().String(), requestmetrics.Delete) 80 if err != nil { 81 span.AddEvent("limitedReadBody failed", attribute.Int("len", len(body)), attribute.String("err", err.Error())) 82 scope.err(err, w, req) 83 return 84 } 85 span.AddEvent("limitedReadBody succeeded", attribute.Int("len", len(body))) 86 if len(body) > 0 { 87 s, err := negotiation.NegotiateInputSerializer(req, false, metainternalversionscheme.Codecs) 88 if err != nil { 89 scope.err(err, w, req) 90 return 91 } 92 // For backwards compatibility, we need to allow existing clients to submit per group DeleteOptions 93 // It is also allowed to pass a body with meta.k8s.io/v1.DeleteOptions 94 defaultGVK := scope.MetaGroupVersion.WithKind("DeleteOptions") 95 obj, gvk, err := metainternalversionscheme.Codecs.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) 96 if err != nil { 97 scope.err(err, w, req) 98 return 99 } 100 if obj != options { 101 scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req) 102 return 103 } 104 span.AddEvent("Decoded delete options") 105 106 objGV := gvk.GroupVersion() 107 audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, metainternalversionscheme.Codecs) 108 span.AddEvent("Recorded the audit event") 109 } else { 110 if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil { 111 err = errors.NewBadRequest(err.Error()) 112 scope.err(err, w, req) 113 return 114 } 115 } 116 } 117 if errs := validation.ValidateDeleteOptions(options); len(errs) > 0 { 118 err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "DeleteOptions"}, "", errs) 119 scope.err(err, w, req) 120 return 121 } 122 options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions")) 123 124 span.AddEvent("About to delete object from database") 125 wasDeleted := true 126 userInfo, _ := request.UserFrom(ctx) 127 staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo) 128 result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) { 129 obj, deleted, err := r.Delete(ctx, name, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options) 130 wasDeleted = deleted 131 return obj, err 132 }) 133 if err != nil { 134 scope.err(err, w, req) 135 return 136 } 137 span.AddEvent("Object deleted from database") 138 139 status := http.StatusOK 140 // Return http.StatusAccepted if the resource was not deleted immediately and 141 // user requested cascading deletion by setting OrphanDependents=false. 142 // Note: We want to do this always if resource was not deleted immediately, but 143 // that will break existing clients. 144 // Other cases where resource is not instantly deleted are: namespace deletion 145 // and pod graceful deletion. 146 //nolint:staticcheck // SA1019 backwards compatibility 147 //nolint: staticcheck 148 if !wasDeleted && options.OrphanDependents != nil && !*options.OrphanDependents { 149 status = http.StatusAccepted 150 } 151 // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid 152 // object with the response. 153 if result == nil { 154 result = &metav1.Status{ 155 Status: metav1.StatusSuccess, 156 Code: int32(status), 157 Details: &metav1.StatusDetails{ 158 Name: name, 159 Kind: scope.Kind.Kind, 160 }, 161 } 162 } 163 164 span.AddEvent("About to write a response") 165 defer span.AddEvent("Writing http response done") 166 transformResponseObject(ctx, scope, req, w, status, outputMediaType, result) 167 } 168 } 169 170 // DeleteCollection returns a function that will handle a collection deletion 171 func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestScope, admit admission.Interface) http.HandlerFunc { 172 return func(w http.ResponseWriter, req *http.Request) { 173 ctx := req.Context() 174 ctx, span := tracing.Start(ctx, "Delete", traceFields(req)...) 175 defer span.End(500 * time.Millisecond) 176 177 namespace, err := scope.Namer.Namespace(req) 178 if err != nil { 179 scope.err(err, w, req) 180 return 181 } 182 183 // DELETECOLLECTION can be a lengthy operation, 184 // we should not impose any 34s timeout here. 185 // NOTE: This is similar to LIST which does not enforce a 34s timeout. 186 ctx = request.WithNamespace(ctx, namespace) 187 188 outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) 189 if err != nil { 190 scope.err(err, w, req) 191 return 192 } 193 194 listOptions := metainternalversion.ListOptions{} 195 if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &listOptions); err != nil { 196 err = errors.NewBadRequest(err.Error()) 197 scope.err(err, w, req) 198 return 199 } 200 201 metainternalversion.SetListOptionsDefaults(&listOptions, utilfeature.DefaultFeatureGate.Enabled(features.WatchList)) 202 if errs := metainternalversionvalidation.ValidateListOptions(&listOptions, utilfeature.DefaultFeatureGate.Enabled(features.WatchList)); len(errs) > 0 { 203 err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs) 204 scope.err(err, w, req) 205 return 206 } 207 208 // transform fields 209 // TODO: DecodeParametersInto should do this. 210 if listOptions.FieldSelector != nil { 211 fn := func(label, value string) (newLabel, newValue string, err error) { 212 return scope.Convertor.ConvertFieldLabel(scope.Kind, label, value) 213 } 214 if listOptions.FieldSelector, err = listOptions.FieldSelector.Transform(fn); err != nil { 215 // TODO: allow bad request to set field causes based on query parameters 216 err = errors.NewBadRequest(err.Error()) 217 scope.err(err, w, req) 218 return 219 } 220 } 221 222 options := &metav1.DeleteOptions{} 223 if checkBody { 224 body, err := limitedReadBodyWithRecordMetric(ctx, req, scope.MaxRequestBodyBytes, scope.Resource.GroupResource().String(), requestmetrics.DeleteCollection) 225 if err != nil { 226 span.AddEvent("limitedReadBody failed", attribute.Int("len", len(body)), attribute.String("err", err.Error())) 227 scope.err(err, w, req) 228 return 229 } 230 span.AddEvent("limitedReadBody succeeded", attribute.Int("len", len(body))) 231 if len(body) > 0 { 232 s, err := negotiation.NegotiateInputSerializer(req, false, metainternalversionscheme.Codecs) 233 if err != nil { 234 scope.err(err, w, req) 235 return 236 } 237 // For backwards compatibility, we need to allow existing clients to submit per group DeleteOptions 238 // It is also allowed to pass a body with meta.k8s.io/v1.DeleteOptions 239 defaultGVK := scope.MetaGroupVersion.WithKind("DeleteOptions") 240 obj, gvk, err := metainternalversionscheme.Codecs.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) 241 if err != nil { 242 scope.err(err, w, req) 243 return 244 } 245 if obj != options { 246 scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req) 247 return 248 } 249 250 objGV := gvk.GroupVersion() 251 audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, metainternalversionscheme.Codecs) 252 } else { 253 if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil { 254 err = errors.NewBadRequest(err.Error()) 255 scope.err(err, w, req) 256 return 257 } 258 } 259 } 260 if errs := validation.ValidateDeleteOptions(options); len(errs) > 0 { 261 err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "DeleteOptions"}, "", errs) 262 scope.err(err, w, req) 263 return 264 } 265 options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions")) 266 267 admit = admission.WithAudit(admit) 268 userInfo, _ := request.UserFrom(ctx) 269 staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo) 270 result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) { 271 return r.DeleteCollection(ctx, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options, &listOptions) 272 }) 273 if err != nil { 274 scope.err(err, w, req) 275 return 276 } 277 278 // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid 279 // object with the response. 280 if result == nil { 281 result = &metav1.Status{ 282 Status: metav1.StatusSuccess, 283 Code: http.StatusOK, 284 Details: &metav1.StatusDetails{ 285 Kind: scope.Kind.Kind, 286 }, 287 } 288 } 289 290 span.AddEvent("About to write a response") 291 defer span.AddEvent("Writing http response done") 292 transformResponseObject(ctx, scope, req, w, http.StatusOK, outputMediaType, result) 293 } 294 }