k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/create.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  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"net/http"
    24  	"strings"
    25  	"time"
    26  	"unicode"
    27  	"unicode/utf8"
    28  
    29  	"go.opentelemetry.io/otel/attribute"
    30  
    31  	"k8s.io/apimachinery/pkg/api/errors"
    32  	"k8s.io/apimachinery/pkg/api/meta"
    33  	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/runtime/schema"
    38  	"k8s.io/apiserver/pkg/admission"
    39  	"k8s.io/apiserver/pkg/audit"
    40  	"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
    41  	"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
    42  	requestmetrics "k8s.io/apiserver/pkg/endpoints/handlers/metrics"
    43  	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
    44  	"k8s.io/apiserver/pkg/endpoints/request"
    45  	"k8s.io/apiserver/pkg/registry/rest"
    46  	"k8s.io/apiserver/pkg/util/dryrun"
    47  	"k8s.io/component-base/tracing"
    48  	"k8s.io/klog/v2"
    49  )
    50  
    51  var namespaceGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"}
    52  
    53  func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
    54  	return func(w http.ResponseWriter, req *http.Request) {
    55  		ctx := req.Context()
    56  		// For performance tracking purposes.
    57  		ctx, span := tracing.Start(ctx, "Create", traceFields(req)...)
    58  		defer span.End(500 * time.Millisecond)
    59  
    60  		namespace, name, err := scope.Namer.Name(req)
    61  		if err != nil {
    62  			if includeName {
    63  				// name was required, return
    64  				scope.err(err, w, req)
    65  				return
    66  			}
    67  
    68  			// otherwise attempt to look up the namespace
    69  			namespace, err = scope.Namer.Namespace(req)
    70  			if err != nil {
    71  				scope.err(err, w, req)
    72  				return
    73  			}
    74  		}
    75  
    76  		// enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided
    77  		// timeout inside the parent context is lower than requestTimeoutUpperBound.
    78  		ctx, cancel := context.WithTimeout(ctx, requestTimeoutUpperBound)
    79  		defer cancel()
    80  		outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
    81  		if err != nil {
    82  			scope.err(err, w, req)
    83  			return
    84  		}
    85  
    86  		gv := scope.Kind.GroupVersion()
    87  		s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
    88  		if err != nil {
    89  			scope.err(err, w, req)
    90  			return
    91  		}
    92  
    93  		body, err := limitedReadBodyWithRecordMetric(ctx, req, scope.MaxRequestBodyBytes, scope.Resource.GroupResource().String(), requestmetrics.Create)
    94  		if err != nil {
    95  			span.AddEvent("limitedReadBody failed", attribute.Int("len", len(body)), attribute.String("err", err.Error()))
    96  			scope.err(err, w, req)
    97  			return
    98  		}
    99  		span.AddEvent("limitedReadBody succeeded", attribute.Int("len", len(body)))
   100  
   101  		options := &metav1.CreateOptions{}
   102  		values := req.URL.Query()
   103  		if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
   104  			err = errors.NewBadRequest(err.Error())
   105  			scope.err(err, w, req)
   106  			return
   107  		}
   108  		if errs := validation.ValidateCreateOptions(options); len(errs) > 0 {
   109  			err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "CreateOptions"}, "", errs)
   110  			scope.err(err, w, req)
   111  			return
   112  		}
   113  		options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
   114  
   115  		defaultGVK := scope.Kind
   116  		original := r.New()
   117  
   118  		validationDirective := fieldValidation(options.FieldValidation)
   119  		decodeSerializer := s.Serializer
   120  		if validationDirective == metav1.FieldValidationWarn || validationDirective == metav1.FieldValidationStrict {
   121  			decodeSerializer = s.StrictSerializer
   122  		}
   123  
   124  		decoder := scope.Serializer.DecoderToVersion(decodeSerializer, scope.HubGroupVersion)
   125  		span.AddEvent("About to convert to expected version")
   126  		obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
   127  		if err != nil {
   128  			strictError, isStrictError := runtime.AsStrictDecodingError(err)
   129  			switch {
   130  			case isStrictError && obj != nil && validationDirective == metav1.FieldValidationWarn:
   131  				addStrictDecodingWarnings(req.Context(), strictError.Errors())
   132  			case isStrictError && validationDirective == metav1.FieldValidationIgnore:
   133  				klog.Warningf("unexpected strict error when field validation is set to ignore")
   134  				fallthrough
   135  			default:
   136  				err = transformDecodeError(scope.Typer, err, original, gvk, body)
   137  				scope.err(err, w, req)
   138  				return
   139  			}
   140  		}
   141  
   142  		objGV := gvk.GroupVersion()
   143  		if !scope.AcceptsGroupVersion(objGV) {
   144  			err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%v)", objGV.String(), gv.String()))
   145  			scope.err(err, w, req)
   146  			return
   147  		}
   148  		span.AddEvent("Conversion done")
   149  
   150  		// On create, get name from new object if unset
   151  		if len(name) == 0 {
   152  			_, name, _ = scope.Namer.ObjectName(obj)
   153  		}
   154  		if len(namespace) == 0 && scope.Resource == namespaceGVR {
   155  			namespace = name
   156  		}
   157  		ctx = request.WithNamespace(ctx, namespace)
   158  
   159  		admit = admission.WithAudit(admit)
   160  		audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
   161  
   162  		userInfo, _ := request.UserFrom(ctx)
   163  
   164  		if objectMeta, err := meta.Accessor(obj); err == nil {
   165  			preserveObjectMetaSystemFields := false
   166  			if c, ok := r.(rest.SubresourceObjectMetaPreserver); ok && len(scope.Subresource) > 0 {
   167  				preserveObjectMetaSystemFields = c.PreserveRequestObjectMetaSystemFieldsOnSubresourceCreate()
   168  			}
   169  			if !preserveObjectMetaSystemFields {
   170  				rest.WipeObjectMetaSystemFields(objectMeta)
   171  			}
   172  
   173  			// ensure namespace on the object is correct, or error if a conflicting namespace was set in the object
   174  			if err := rest.EnsureObjectNamespaceMatchesRequestNamespace(rest.ExpectedNamespaceForResource(namespace, scope.Resource), objectMeta); err != nil {
   175  				scope.err(err, w, req)
   176  				return
   177  			}
   178  		}
   179  
   180  		span.AddEvent("About to store object in database")
   181  		admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
   182  		requestFunc := func() (runtime.Object, error) {
   183  			return r.Create(
   184  				ctx,
   185  				name,
   186  				obj,
   187  				rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
   188  				options,
   189  			)
   190  		}
   191  		// Dedup owner references before updating managed fields
   192  		dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
   193  		result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
   194  			liveObj, err := scope.Creater.New(scope.Kind)
   195  			if err != nil {
   196  				return nil, fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err)
   197  			}
   198  			obj = scope.FieldManager.UpdateNoErrors(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
   199  			admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
   200  
   201  			if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
   202  				if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
   203  					return nil, err
   204  				}
   205  			}
   206  			// Dedup owner references again after mutating admission happens
   207  			dedupOwnerReferencesAndAddWarning(obj, req.Context(), true)
   208  			result, err := requestFunc()
   209  			// If the object wasn't committed to storage because it's serialized size was too large,
   210  			// it is safe to remove managedFields (which can be large) and try again.
   211  			if isTooLargeError(err) {
   212  				if accessor, accessorErr := meta.Accessor(obj); accessorErr == nil {
   213  					accessor.SetManagedFields(nil)
   214  					result, err = requestFunc()
   215  				}
   216  			}
   217  			return result, err
   218  		})
   219  		if err != nil {
   220  			span.AddEvent("Write to database call failed", attribute.Int("len", len(body)), attribute.String("err", err.Error()))
   221  			scope.err(err, w, req)
   222  			return
   223  		}
   224  		span.AddEvent("Write to database call succeeded", attribute.Int("len", len(body)))
   225  
   226  		code := http.StatusCreated
   227  		status, ok := result.(*metav1.Status)
   228  		if ok && status.Code == 0 {
   229  			status.Code = int32(code)
   230  		}
   231  
   232  		span.AddEvent("About to write a response")
   233  		defer span.AddEvent("Writing http response done")
   234  		transformResponseObject(ctx, scope, req, w, code, outputMediaType, result)
   235  	}
   236  }
   237  
   238  // CreateNamedResource returns a function that will handle a resource creation with name.
   239  func CreateNamedResource(r rest.NamedCreater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
   240  	return createHandler(r, scope, admission, true)
   241  }
   242  
   243  // CreateResource returns a function that will handle a resource creation.
   244  func CreateResource(r rest.Creater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
   245  	return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
   246  }
   247  
   248  type namedCreaterAdapter struct {
   249  	rest.Creater
   250  }
   251  
   252  func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
   253  	return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
   254  }
   255  
   256  // manager is assumed to be already a valid value, we need to make
   257  // userAgent into a valid value too.
   258  func managerOrUserAgent(manager, userAgent string) string {
   259  	if manager != "" {
   260  		return manager
   261  	}
   262  	return prefixFromUserAgent(userAgent)
   263  }
   264  
   265  // prefixFromUserAgent takes the characters preceding the first /, quote
   266  // unprintable character and then trim what's beyond the
   267  // FieldManagerMaxLength limit.
   268  func prefixFromUserAgent(u string) string {
   269  	m := strings.Split(u, "/")[0]
   270  	buf := bytes.NewBuffer(nil)
   271  	for _, r := range m {
   272  		// Ignore non-printable characters
   273  		if !unicode.IsPrint(r) {
   274  			continue
   275  		}
   276  		// Only append if we have room for it
   277  		if buf.Len()+utf8.RuneLen(r) > validation.FieldManagerMaxLength {
   278  			break
   279  		}
   280  		buf.WriteRune(r)
   281  	}
   282  	return buf.String()
   283  }