k8s.io/apiserver@v0.31.1/pkg/registry/rest/create.go (about)

     1  /*
     2  Copyright 2014 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 rest
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"k8s.io/apimachinery/pkg/api/errors"
    24  	"k8s.io/apimachinery/pkg/api/meta"
    25  	genericvalidation "k8s.io/apimachinery/pkg/api/validation"
    26  	"k8s.io/apimachinery/pkg/api/validation/path"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/util/validation/field"
    31  	"k8s.io/apiserver/pkg/admission"
    32  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    33  	"k8s.io/apiserver/pkg/storage/names"
    34  	"k8s.io/apiserver/pkg/warning"
    35  )
    36  
    37  // RESTCreateStrategy defines the minimum validation, accepted input, and
    38  // name generation behavior to create an object that follows Kubernetes
    39  // API conventions.
    40  type RESTCreateStrategy interface {
    41  	runtime.ObjectTyper
    42  	// The name generator is used when the standard GenerateName field is set.
    43  	// The NameGenerator will be invoked prior to validation.
    44  	names.NameGenerator
    45  
    46  	// NamespaceScoped returns true if the object must be within a namespace.
    47  	NamespaceScoped() bool
    48  	// PrepareForCreate is invoked on create before validation to normalize
    49  	// the object.  For example: remove fields that are not to be persisted,
    50  	// sort order-insensitive list fields, etc.  This should not remove fields
    51  	// whose presence would be considered a validation error.
    52  	//
    53  	// Often implemented as a type check and an initailization or clearing of
    54  	// status. Clear the status because status changes are internal. External
    55  	// callers of an api (users) should not be setting an initial status on
    56  	// newly created objects.
    57  	PrepareForCreate(ctx context.Context, obj runtime.Object)
    58  	// Validate returns an ErrorList with validation errors or nil.  Validate
    59  	// is invoked after default fields in the object have been filled in
    60  	// before the object is persisted.  This method should not mutate the
    61  	// object.
    62  	Validate(ctx context.Context, obj runtime.Object) field.ErrorList
    63  	// WarningsOnCreate returns warnings to the client performing a create.
    64  	// WarningsOnCreate is invoked after default fields in the object have been filled in
    65  	// and after Validate has passed, before Canonicalize is called, and the object is persisted.
    66  	// This method must not mutate the object.
    67  	//
    68  	// Be brief; limit warnings to 120 characters if possible.
    69  	// Don't include a "Warning:" prefix in the message (that is added by clients on output).
    70  	// Warnings returned about a specific field should be formatted as "path.to.field: message".
    71  	// For example: `spec.imagePullSecrets[0].name: invalid empty name ""`
    72  	//
    73  	// Use warning messages to describe problems the client making the API request should correct or be aware of.
    74  	// For example:
    75  	// - use of deprecated fields/labels/annotations that will stop working in a future release
    76  	// - use of obsolete fields/labels/annotations that are non-functional
    77  	// - malformed or invalid specifications that prevent successful handling of the submitted object,
    78  	//   but are not rejected by validation for compatibility reasons
    79  	//
    80  	// Warnings should not be returned for fields which cannot be resolved by the caller.
    81  	// For example, do not warn about spec fields in a subresource creation request.
    82  	WarningsOnCreate(ctx context.Context, obj runtime.Object) []string
    83  	// Canonicalize allows an object to be mutated into a canonical form. This
    84  	// ensures that code that operates on these objects can rely on the common
    85  	// form for things like comparison.  Canonicalize is invoked after
    86  	// validation has succeeded but before the object has been persisted.
    87  	// This method may mutate the object. Often implemented as a type check or
    88  	// empty method.
    89  	Canonicalize(obj runtime.Object)
    90  }
    91  
    92  // BeforeCreate ensures that common operations for all resources are performed on creation. It only returns
    93  // errors that can be converted to api.Status. It invokes PrepareForCreate, then Validate.
    94  // It returns nil if the object should be created.
    95  func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime.Object) error {
    96  	objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
    97  	if kerr != nil {
    98  		return kerr
    99  	}
   100  
   101  	// ensure that system-critical metadata has been populated
   102  	if !metav1.HasObjectMetaSystemFieldValues(objectMeta) {
   103  		return errors.NewInternalError(fmt.Errorf("system metadata was not initialized"))
   104  	}
   105  
   106  	// ensure the name has been generated
   107  	if len(objectMeta.GetGenerateName()) > 0 && len(objectMeta.GetName()) == 0 {
   108  		return errors.NewInternalError(fmt.Errorf("metadata.name was not generated"))
   109  	}
   110  
   111  	// ensure namespace on the object is correct, or error if a conflicting namespace was set in the object
   112  	requestNamespace, ok := genericapirequest.NamespaceFrom(ctx)
   113  	if !ok {
   114  		return errors.NewInternalError(fmt.Errorf("no namespace information found in request context"))
   115  	}
   116  	if err := EnsureObjectNamespaceMatchesRequestNamespace(ExpectedNamespaceForScope(requestNamespace, strategy.NamespaceScoped()), objectMeta); err != nil {
   117  		return err
   118  	}
   119  
   120  	strategy.PrepareForCreate(ctx, obj)
   121  
   122  	if errs := strategy.Validate(ctx, obj); len(errs) > 0 {
   123  		return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs)
   124  	}
   125  
   126  	// Custom validation (including name validation) passed
   127  	// Now run common validation on object meta
   128  	// Do this *after* custom validation so that specific error messages are shown whenever possible
   129  	if errs := genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 {
   130  		return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs)
   131  	}
   132  
   133  	for _, w := range strategy.WarningsOnCreate(ctx, obj) {
   134  		warning.AddWarning(ctx, "", w)
   135  	}
   136  
   137  	strategy.Canonicalize(obj)
   138  
   139  	return nil
   140  }
   141  
   142  // CheckGeneratedNameError checks whether an error that occurred creating a resource is due
   143  // to generation being unable to pick a valid name.
   144  func CheckGeneratedNameError(ctx context.Context, strategy RESTCreateStrategy, err error, obj runtime.Object) error {
   145  	if !errors.IsAlreadyExists(err) {
   146  		return err
   147  	}
   148  
   149  	objectMeta, gvk, kerr := objectMetaAndKind(strategy, obj)
   150  	if kerr != nil {
   151  		return kerr
   152  	}
   153  
   154  	if len(objectMeta.GetGenerateName()) == 0 {
   155  		// If we don't have a generated name, return the original error (AlreadyExists).
   156  		// When we're here, the user picked a name that is causing a conflict.
   157  		return err
   158  	}
   159  
   160  	// Get the group resource information from the context, if populated.
   161  	gr := schema.GroupResource{}
   162  	if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
   163  		gr = schema.GroupResource{Group: gvk.Group, Resource: requestInfo.Resource}
   164  	}
   165  
   166  	// If we have a name and generated name, the server picked a name
   167  	// that already exists.
   168  	return errors.NewGenerateNameConflict(gr, objectMeta.GetName(), 1)
   169  }
   170  
   171  // objectMetaAndKind retrieves kind and ObjectMeta from a runtime object, or returns an error.
   172  func objectMetaAndKind(typer runtime.ObjectTyper, obj runtime.Object) (metav1.Object, schema.GroupVersionKind, error) {
   173  	objectMeta, err := meta.Accessor(obj)
   174  	if err != nil {
   175  		return nil, schema.GroupVersionKind{}, errors.NewInternalError(err)
   176  	}
   177  	kinds, _, err := typer.ObjectKinds(obj)
   178  	if err != nil {
   179  		return nil, schema.GroupVersionKind{}, errors.NewInternalError(err)
   180  	}
   181  	return objectMeta, kinds[0], nil
   182  }
   183  
   184  // NamespaceScopedStrategy has a method to tell if the object must be in a namespace.
   185  type NamespaceScopedStrategy interface {
   186  	// NamespaceScoped returns if the object must be in a namespace.
   187  	NamespaceScoped() bool
   188  }
   189  
   190  // AdmissionToValidateObjectFunc converts validating admission to a rest validate object func
   191  func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes, o admission.ObjectInterfaces) ValidateObjectFunc {
   192  	validatingAdmission, ok := admit.(admission.ValidationInterface)
   193  	if !ok {
   194  		return func(ctx context.Context, obj runtime.Object) error { return nil }
   195  	}
   196  	return func(ctx context.Context, obj runtime.Object) error {
   197  		name := staticAttributes.GetName()
   198  		// in case the generated name is populated
   199  		if len(name) == 0 {
   200  			if metadata, err := meta.Accessor(obj); err == nil {
   201  				name = metadata.GetName()
   202  			}
   203  		}
   204  
   205  		finalAttributes := admission.NewAttributesRecord(
   206  			obj,
   207  			staticAttributes.GetOldObject(),
   208  			staticAttributes.GetKind(),
   209  			staticAttributes.GetNamespace(),
   210  			name,
   211  			staticAttributes.GetResource(),
   212  			staticAttributes.GetSubresource(),
   213  			staticAttributes.GetOperation(),
   214  			staticAttributes.GetOperationOptions(),
   215  			staticAttributes.IsDryRun(),
   216  			staticAttributes.GetUserInfo(),
   217  		)
   218  		if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
   219  			return nil
   220  		}
   221  		return validatingAdmission.Validate(ctx, finalAttributes, o)
   222  	}
   223  }