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  }