| /* |
| Copyright 2014 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package rest |
| |
| import ( |
| "context" |
| |
| "k8s.io/apimachinery/pkg/api/errors" |
| "k8s.io/apimachinery/pkg/api/meta" |
| genericvalidation "k8s.io/apimachinery/pkg/api/validation" |
| "k8s.io/apimachinery/pkg/api/validation/path" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/runtime/schema" |
| "k8s.io/apimachinery/pkg/util/validation/field" |
| "k8s.io/apiserver/pkg/admission" |
| "k8s.io/apiserver/pkg/features" |
| "k8s.io/apiserver/pkg/storage/names" |
| utilfeature "k8s.io/apiserver/pkg/util/feature" |
| ) |
| |
| // RESTCreateStrategy defines the minimum validation, accepted input, and |
| // name generation behavior to create an object that follows Kubernetes |
| // API conventions. |
| type RESTCreateStrategy interface { |
| runtime.ObjectTyper |
| // The name generator is used when the standard GenerateName field is set. |
| // The NameGenerator will be invoked prior to validation. |
| names.NameGenerator |
| |
| // NamespaceScoped returns true if the object must be within a namespace. |
| NamespaceScoped() bool |
| // PrepareForCreate is invoked on create before validation to normalize |
| // the object. For example: remove fields that are not to be persisted, |
| // sort order-insensitive list fields, etc. This should not remove fields |
| // whose presence would be considered a validation error. |
| // |
| // Often implemented as a type check and an initailization or clearing of |
| // status. Clear the status because status changes are internal. External |
| // callers of an api (users) should not be setting an initial status on |
| // newly created objects. |
| PrepareForCreate(ctx context.Context, obj runtime.Object) |
| // Validate returns an ErrorList with validation errors or nil. Validate |
| // is invoked after default fields in the object have been filled in |
| // before the object is persisted. This method should not mutate the |
| // object. |
| Validate(ctx context.Context, obj runtime.Object) field.ErrorList |
| // Canonicalize allows an object to be mutated into a canonical form. This |
| // ensures that code that operates on these objects can rely on the common |
| // form for things like comparison. Canonicalize is invoked after |
| // validation has succeeded but before the object has been persisted. |
| // This method may mutate the object. Often implemented as a type check or |
| // empty method. |
| Canonicalize(obj runtime.Object) |
| } |
| |
| // BeforeCreate ensures that common operations for all resources are performed on creation. It only returns |
| // errors that can be converted to api.Status. It invokes PrepareForCreate, then GenerateName, then Validate. |
| // It returns nil if the object should be created. |
| func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime.Object) error { |
| objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) |
| if kerr != nil { |
| return kerr |
| } |
| |
| if strategy.NamespaceScoped() { |
| if !ValidNamespace(ctx, objectMeta) { |
| return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request") |
| } |
| } else { |
| objectMeta.SetNamespace(metav1.NamespaceNone) |
| } |
| objectMeta.SetDeletionTimestamp(nil) |
| objectMeta.SetDeletionGracePeriodSeconds(nil) |
| strategy.PrepareForCreate(ctx, obj) |
| FillObjectMetaSystemFields(objectMeta) |
| if len(objectMeta.GetGenerateName()) > 0 && len(objectMeta.GetName()) == 0 { |
| objectMeta.SetName(strategy.GenerateName(objectMeta.GetGenerateName())) |
| } |
| |
| // Ensure Initializers are not set unless the feature is enabled |
| if !utilfeature.DefaultFeatureGate.Enabled(features.Initializers) { |
| objectMeta.SetInitializers(nil) |
| } |
| |
| // ClusterName is ignored and should not be saved |
| objectMeta.SetClusterName("") |
| |
| if errs := strategy.Validate(ctx, obj); len(errs) > 0 { |
| return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs) |
| } |
| |
| // Custom validation (including name validation) passed |
| // Now run common validation on object meta |
| // Do this *after* custom validation so that specific error messages are shown whenever possible |
| if errs := genericvalidation.ValidateObjectMetaAccessor(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 { |
| return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs) |
| } |
| |
| strategy.Canonicalize(obj) |
| |
| return nil |
| } |
| |
| // CheckGeneratedNameError checks whether an error that occurred creating a resource is due |
| // to generation being unable to pick a valid name. |
| func CheckGeneratedNameError(strategy RESTCreateStrategy, err error, obj runtime.Object) error { |
| if !errors.IsAlreadyExists(err) { |
| return err |
| } |
| |
| objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) |
| if kerr != nil { |
| return kerr |
| } |
| |
| if len(objectMeta.GetGenerateName()) == 0 { |
| return err |
| } |
| |
| return errors.NewServerTimeoutForKind(kind.GroupKind(), "POST", 0) |
| } |
| |
| // objectMetaAndKind retrieves kind and ObjectMeta from a runtime object, or returns an error. |
| func objectMetaAndKind(typer runtime.ObjectTyper, obj runtime.Object) (metav1.Object, schema.GroupVersionKind, error) { |
| objectMeta, err := meta.Accessor(obj) |
| if err != nil { |
| return nil, schema.GroupVersionKind{}, errors.NewInternalError(err) |
| } |
| kinds, _, err := typer.ObjectKinds(obj) |
| if err != nil { |
| return nil, schema.GroupVersionKind{}, errors.NewInternalError(err) |
| } |
| return objectMeta, kinds[0], nil |
| } |
| |
| // NamespaceScopedStrategy has a method to tell if the object must be in a namespace. |
| type NamespaceScopedStrategy interface { |
| // NamespaceScoped returns if the object must be in a namespace. |
| NamespaceScoped() bool |
| } |
| |
| // AdmissionToValidateObjectFunc converts validating admission to a rest validate object func |
| func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes) ValidateObjectFunc { |
| validatingAdmission, ok := admit.(admission.ValidationInterface) |
| if !ok { |
| return func(obj runtime.Object) error { return nil } |
| } |
| return func(obj runtime.Object) error { |
| finalAttributes := admission.NewAttributesRecord( |
| obj, |
| staticAttributes.GetOldObject(), |
| staticAttributes.GetKind(), |
| staticAttributes.GetNamespace(), |
| staticAttributes.GetName(), |
| staticAttributes.GetResource(), |
| staticAttributes.GetSubresource(), |
| staticAttributes.GetOperation(), |
| staticAttributes.GetUserInfo(), |
| ) |
| if !validatingAdmission.Handles(finalAttributes.GetOperation()) { |
| return nil |
| } |
| return validatingAdmission.Validate(finalAttributes) |
| } |
| } |