k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/update.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  	"sync"
    24  	"time"
    25  
    26  	"go.opentelemetry.io/otel/attribute"
    27  
    28  	"k8s.io/apimachinery/pkg/api/errors"
    29  	"k8s.io/apimachinery/pkg/api/meta"
    30  	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
    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/authorization/authorizer"
    38  	"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
    39  	"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
    40  	requestmetrics "k8s.io/apiserver/pkg/endpoints/handlers/metrics"
    41  	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
    42  	"k8s.io/apiserver/pkg/endpoints/request"
    43  	"k8s.io/apiserver/pkg/registry/rest"
    44  	"k8s.io/apiserver/pkg/util/dryrun"
    45  	"k8s.io/component-base/tracing"
    46  	"k8s.io/klog/v2"
    47  )
    48  
    49  // UpdateResource returns a function that will handle a resource update
    50  func UpdateResource(r rest.Updater, 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, "Update", 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  
    70  		outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
    71  		if err != nil {
    72  			scope.err(err, w, req)
    73  			return
    74  		}
    75  
    76  		body, err := limitedReadBodyWithRecordMetric(ctx, req, scope.MaxRequestBodyBytes, scope.Resource.GroupResource().String(), requestmetrics.Update)
    77  		if err != nil {
    78  			span.AddEvent("limitedReadBody failed", attribute.Int("len", len(body)), attribute.String("err", err.Error()))
    79  			scope.err(err, w, req)
    80  			return
    81  		}
    82  		span.AddEvent("limitedReadBody succeeded", attribute.Int("len", len(body)))
    83  
    84  		options := &metav1.UpdateOptions{}
    85  		if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
    86  			err = errors.NewBadRequest(err.Error())
    87  			scope.err(err, w, req)
    88  			return
    89  		}
    90  		if errs := validation.ValidateUpdateOptions(options); len(errs) > 0 {
    91  			err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "UpdateOptions"}, "", errs)
    92  			scope.err(err, w, req)
    93  			return
    94  		}
    95  		options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("UpdateOptions"))
    96  
    97  		s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
    98  		if err != nil {
    99  			scope.err(err, w, req)
   100  			return
   101  		}
   102  		defaultGVK := scope.Kind
   103  		original := r.New()
   104  
   105  		validationDirective := fieldValidation(options.FieldValidation)
   106  		decodeSerializer := s.Serializer
   107  		if validationDirective == metav1.FieldValidationWarn || validationDirective == metav1.FieldValidationStrict {
   108  			decodeSerializer = s.StrictSerializer
   109  		}
   110  
   111  		decoder := scope.Serializer.DecoderToVersion(decodeSerializer, scope.HubGroupVersion)
   112  		span.AddEvent("About to convert to expected version")
   113  		obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
   114  		if err != nil {
   115  			strictError, isStrictError := runtime.AsStrictDecodingError(err)
   116  			switch {
   117  			case isStrictError && obj != nil && validationDirective == metav1.FieldValidationWarn:
   118  				addStrictDecodingWarnings(req.Context(), strictError.Errors())
   119  			case isStrictError && validationDirective == metav1.FieldValidationIgnore:
   120  				klog.Warningf("unexpected strict error when field validation is set to ignore")
   121  				fallthrough
   122  			default:
   123  				err = transformDecodeError(scope.Typer, err, original, gvk, body)
   124  				scope.err(err, w, req)
   125  				return
   126  			}
   127  		}
   128  
   129  		objGV := gvk.GroupVersion()
   130  		if !scope.AcceptsGroupVersion(objGV) {
   131  			err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%s)", objGV, defaultGVK.GroupVersion()))
   132  			scope.err(err, w, req)
   133  			return
   134  		}
   135  		span.AddEvent("Conversion done")
   136  
   137  		audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
   138  		admit = admission.WithAudit(admit)
   139  
   140  		// if this object supports namespace info
   141  		if objectMeta, err := meta.Accessor(obj); err == nil {
   142  			// ensure namespace on the object is correct, or error if a conflicting namespace was set in the object
   143  			if err := rest.EnsureObjectNamespaceMatchesRequestNamespace(rest.ExpectedNamespaceForResource(namespace, scope.Resource), objectMeta); err != nil {
   144  				scope.err(err, w, req)
   145  				return
   146  			}
   147  		}
   148  
   149  		if err := checkName(obj, name, namespace, scope.Namer); err != nil {
   150  			scope.err(err, w, req)
   151  			return
   152  		}
   153  
   154  		userInfo, _ := request.UserFrom(ctx)
   155  		transformers := []rest.TransformFunc{}
   156  
   157  		// allows skipping managedFields update if the resulting object is too big
   158  		shouldUpdateManagedFields := true
   159  		admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
   160  		transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
   161  			if shouldUpdateManagedFields {
   162  				return scope.FieldManager.UpdateNoErrors(liveObj, newObj, managerOrUserAgent(options.FieldManager, req.UserAgent())), nil
   163  			}
   164  			return newObj, nil
   165  		})
   166  
   167  		if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
   168  			transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
   169  				isNotZeroObject, err := hasUID(oldObj)
   170  				if err != nil {
   171  					return nil, fmt.Errorf("unexpected error when extracting UID from oldObj: %v", err.Error())
   172  				} else if !isNotZeroObject {
   173  					if mutatingAdmission.Handles(admission.Create) {
   174  						return newObj, mutatingAdmission.Admit(ctx, admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, updateToCreateOptions(options), dryrun.IsDryRun(options.DryRun), userInfo), scope)
   175  					}
   176  				} else {
   177  					if mutatingAdmission.Handles(admission.Update) {
   178  						return newObj, mutatingAdmission.Admit(ctx, admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, options, dryrun.IsDryRun(options.DryRun), userInfo), scope)
   179  					}
   180  				}
   181  				return newObj, nil
   182  			})
   183  			transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
   184  				// Dedup owner references again after mutating admission happens
   185  				dedupOwnerReferencesAndAddWarning(newObj, req.Context(), true)
   186  				return newObj, nil
   187  			})
   188  		}
   189  
   190  		createAuthorizerAttributes := authorizer.AttributesRecord{
   191  			User:            userInfo,
   192  			ResourceRequest: true,
   193  			Path:            req.URL.Path,
   194  			Verb:            "create",
   195  			APIGroup:        scope.Resource.Group,
   196  			APIVersion:      scope.Resource.Version,
   197  			Resource:        scope.Resource.Resource,
   198  			Subresource:     scope.Subresource,
   199  			Namespace:       namespace,
   200  			Name:            name,
   201  		}
   202  
   203  		span.AddEvent("About to store object in database")
   204  		wasCreated := false
   205  		requestFunc := func() (runtime.Object, error) {
   206  			obj, created, err := r.Update(
   207  				ctx,
   208  				name,
   209  				rest.DefaultUpdatedObjectInfo(obj, transformers...),
   210  				withAuthorization(rest.AdmissionToValidateObjectFunc(
   211  					admit,
   212  					admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, updateToCreateOptions(options), dryrun.IsDryRun(options.DryRun), userInfo), scope),
   213  					scope.Authorizer, createAuthorizerAttributes),
   214  				rest.AdmissionToValidateObjectUpdateFunc(
   215  					admit,
   216  					admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, options, dryrun.IsDryRun(options.DryRun), userInfo), scope),
   217  				false,
   218  				options,
   219  			)
   220  			wasCreated = created
   221  			return obj, err
   222  		}
   223  		// Dedup owner references before updating managed fields
   224  		dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
   225  		result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
   226  			result, err := requestFunc()
   227  			// If the object wasn't committed to storage because it's serialized size was too large,
   228  			// it is safe to remove managedFields (which can be large) and try again.
   229  			if isTooLargeError(err) {
   230  				if accessor, accessorErr := meta.Accessor(obj); accessorErr == nil {
   231  					accessor.SetManagedFields(nil)
   232  					shouldUpdateManagedFields = false
   233  					result, err = requestFunc()
   234  				}
   235  			}
   236  			return result, err
   237  		})
   238  		if err != nil {
   239  			span.AddEvent("Write to database call failed", attribute.Int("len", len(body)), attribute.String("err", err.Error()))
   240  			scope.err(err, w, req)
   241  			return
   242  		}
   243  		span.AddEvent("Write to database call succeeded", attribute.Int("len", len(body)))
   244  
   245  		status := http.StatusOK
   246  		if wasCreated {
   247  			status = http.StatusCreated
   248  		}
   249  
   250  		span.AddEvent("About to write a response")
   251  		defer span.AddEvent("Writing http response done")
   252  		transformResponseObject(ctx, scope, req, w, status, outputMediaType, result)
   253  	}
   254  }
   255  
   256  func withAuthorization(validate rest.ValidateObjectFunc, a authorizer.Authorizer, attributes authorizer.Attributes) rest.ValidateObjectFunc {
   257  	var once sync.Once
   258  	var authorizerDecision authorizer.Decision
   259  	var authorizerReason string
   260  	var authorizerErr error
   261  	return func(ctx context.Context, obj runtime.Object) error {
   262  		if a == nil {
   263  			return errors.NewInternalError(fmt.Errorf("no authorizer provided, unable to authorize a create on update"))
   264  		}
   265  		once.Do(func() {
   266  			authorizerDecision, authorizerReason, authorizerErr = a.Authorize(ctx, attributes)
   267  		})
   268  		// an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
   269  		if authorizerDecision == authorizer.DecisionAllow {
   270  			// Continue to validating admission
   271  			return validate(ctx, obj)
   272  		}
   273  		if authorizerErr != nil {
   274  			return errors.NewInternalError(authorizerErr)
   275  		}
   276  
   277  		// The user is not authorized to perform this action, so we need to build the error response
   278  		gr := schema.GroupResource{
   279  			Group:    attributes.GetAPIGroup(),
   280  			Resource: attributes.GetResource(),
   281  		}
   282  		name := attributes.GetName()
   283  		err := fmt.Errorf("%v", authorizerReason)
   284  		return errors.NewForbidden(gr, name, err)
   285  	}
   286  }
   287  
   288  // updateToCreateOptions creates a CreateOptions with the same field values as the provided UpdateOptions.
   289  func updateToCreateOptions(uo *metav1.UpdateOptions) *metav1.CreateOptions {
   290  	if uo == nil {
   291  		return nil
   292  	}
   293  	co := &metav1.CreateOptions{
   294  		DryRun:          uo.DryRun,
   295  		FieldManager:    uo.FieldManager,
   296  		FieldValidation: uo.FieldValidation,
   297  	}
   298  	co.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
   299  	return co
   300  }