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 }