NewREST

// NewREST returns a RESTStorage object that will work against statefulsets.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
	store := &genericregistry.Store{
		NewFunc:                   func() runtime.Object { return &apps.StatefulSet{} },
		NewListFunc:               func() runtime.Object { return &apps.StatefulSetList{} },
		DefaultQualifiedResource:  apps.Resource("statefulsets"),
		SingularQualifiedResource: apps.Resource("statefulset"),

		CreateStrategy:      statefulset.Strategy,
		UpdateStrategy:      statefulset.Strategy,
		DeleteStrategy:      statefulset.Strategy,
		ResetFieldsStrategy: statefulset.Strategy,

		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter}
	if err := store.CompleteWithOptions(options); err != nil {
		return nil, nil, err
	}

	statusStore := *store
	statusStore.UpdateStrategy = statefulset.StatusStrategy
	statusStore.ResetFieldsStrategy = statefulset.StatusStrategy
	return &REST{store}, &StatusREST{store: &statusStore}, nil
}


Categories

// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
func (r *REST) Categories() []string {
	return []string{"all"}
}


New

// New returns empty StatefulSet object.
func (r *StatusREST) New() runtime.Object {
	return &apps.StatefulSet{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *StatusREST) Destroy() {
	// Given that underlying store is shared with REST,
	// we don't destroy it here explicitly.
}


Get

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}


Update

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}


GetResetFields

// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return r.store.GetResetFields()
}


ConvertToTable

func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
	return r.store.ConvertToTable(ctx, object, tableOptions)
}


ShortNames

// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
func (r *REST) ShortNames() []string {
	return []string{"sts"}
}


GroupVersionKind

// GroupVersionKind returns GroupVersionKind for StatefulSet Scale object
func (r *ScaleREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
	switch containingGV {
	case appsv1beta1.SchemeGroupVersion:
		return appsv1beta1.SchemeGroupVersion.WithKind("Scale")
	case appsv1beta2.SchemeGroupVersion:
		return appsv1beta2.SchemeGroupVersion.WithKind("Scale")
	default:
		return autoscalingv1.SchemeGroupVersion.WithKind("Scale")
	}
}


New

// New creates a new Scale object
func (r *ScaleREST) New() runtime.Object {
	return &autoscaling.Scale{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *ScaleREST) Destroy() {
	// Given that underlying store is shared with REST,
	// we don't destroy it here explicitly.
}


Get

// Get retrieves object from Scale storage.
func (r *ScaleREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	obj, err := r.store.Get(ctx, name, options)
	if err != nil {
		return nil, err
	}
	ss := obj.(*apps.StatefulSet)
	scale, err := scaleFromStatefulSet(ss)
	if err != nil {
		return nil, errors.NewBadRequest(fmt.Sprintf("%v", err))
	}
	return scale, err
}


Update

// Update alters scale subset of StatefulSet object.
func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	obj, _, err := r.store.Update(
		ctx,
		name,
		&scaleUpdatedObjectInfo{name, objInfo},
		toScaleCreateValidation(createValidation),
		toScaleUpdateValidation(updateValidation),
		false,
		options,
	)
	if err != nil {
		return nil, false, err
	}
	ss := obj.(*apps.StatefulSet)
	newScale, err := scaleFromStatefulSet(ss)
	if err != nil {
		return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err))
	}
	return newScale, false, err
}


ConvertToTable

func (r *ScaleREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
	return r.store.ConvertToTable(ctx, object, tableOptions)
}


toScaleCreateValidation

func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
	return func(ctx context.Context, obj runtime.Object) error {
		scale, err := scaleFromStatefulSet(obj.(*apps.StatefulSet))
		if err != nil {
			return err
		}
		return f(ctx, scale)
	}
}


toScaleUpdateValidation

func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
	return func(ctx context.Context, obj, old runtime.Object) error {
		newScale, err := scaleFromStatefulSet(obj.(*apps.StatefulSet))
		if err != nil {
			return err
		}
		oldScale, err := scaleFromStatefulSet(old.(*apps.StatefulSet))
		if err != nil {
			return err
		}
		return f(ctx, newScale, oldScale)
	}
}


scaleFromStatefulSet

// scaleFromStatefulSet returns a scale subresource for a statefulset.
func scaleFromStatefulSet(ss *apps.StatefulSet) (*autoscaling.Scale, error) {
	selector, err := metav1.LabelSelectorAsSelector(ss.Spec.Selector)
	if err != nil {
		return nil, err
	}
	return &autoscaling.Scale{
		// TODO: Create a variant of ObjectMeta type that only contains the fields below.
		ObjectMeta: metav1.ObjectMeta{
			Name:              ss.Name,
			Namespace:         ss.Namespace,
			UID:               ss.UID,
			ResourceVersion:   ss.ResourceVersion,
			CreationTimestamp: ss.CreationTimestamp,
		},
		Spec: autoscaling.ScaleSpec{
			Replicas: ss.Spec.Replicas,
		},
		Status: autoscaling.ScaleStatus{
			Replicas: ss.Status.Replicas,
			Selector: selector.String(),
		},
	}, nil
}


Preconditions

func (i *scaleUpdatedObjectInfo) Preconditions() *metav1.Preconditions {
	return i.reqObjInfo.Preconditions()
}


UpdatedObject

func (i *scaleUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) {
	statefulset, ok := oldObj.DeepCopyObject().(*apps.StatefulSet)
	if !ok {
		return nil, errors.NewBadRequest(fmt.Sprintf("expected existing object type to be StatefulSet, got %T", statefulset))
	}
	// if zero-value, the existing object does not exist
	if len(statefulset.ResourceVersion) == 0 {
		return nil, errors.NewNotFound(apps.Resource("statefulsets/scale"), i.name)
	}

	groupVersion := schema.GroupVersion{Group: "apps", Version: "v1"}
	if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
		requestGroupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
		if _, ok := replicasPathInStatefulSet[requestGroupVersion.String()]; ok {
			groupVersion = requestGroupVersion
		} else {
			klog.Fatalf("Unrecognized group/version in request info %q", requestGroupVersion.String())
		}
	}

	managedFieldsHandler := managedfields.NewScaleHandler(
		statefulset.ManagedFields,
		groupVersion,
		replicasPathInStatefulSet,
	)

	// statefulset -> old scale
	oldScale, err := scaleFromStatefulSet(statefulset)
	if err != nil {
		return nil, err
	}
	scaleManagedFields, err := managedFieldsHandler.ToSubresource()
	if err != nil {
		return nil, err
	}
	oldScale.ManagedFields = scaleManagedFields

	// old scale -> new scale
	newScaleObj, err := i.reqObjInfo.UpdatedObject(ctx, oldScale)
	if err != nil {
		return nil, err
	}
	if newScaleObj == nil {
		return nil, errors.NewBadRequest("nil update passed to Scale")
	}
	scale, ok := newScaleObj.(*autoscaling.Scale)
	if !ok {
		return nil, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", newScaleObj))
	}

	// validate
	if errs := autoscalingvalidation.ValidateScale(scale); len(errs) > 0 {
		return nil, errors.NewInvalid(autoscaling.Kind("Scale"), statefulset.Name, errs)
	}

	// validate precondition if specified (resourceVersion matching is handled by storage)
	if len(scale.UID) > 0 && scale.UID != statefulset.UID {
		return nil, errors.NewConflict(
			apps.Resource("statefulsets/scale"),
			statefulset.Name,
			fmt.Errorf("Precondition failed: UID in precondition: %v, UID in object meta: %v", scale.UID, statefulset.UID),
		)
	}

	// move replicas/resourceVersion fields to object and return
	statefulset.Spec.Replicas = scale.Spec.Replicas
	statefulset.ResourceVersion = scale.ResourceVersion

	updatedEntries, err := managedFieldsHandler.ToParent(scale.ManagedFields)
	if err != nil {
		return nil, err
	}
	statefulset.ManagedFields = updatedEntries

	return statefulset, nil
}


DefaultGarbageCollectionPolicy

// DefaultGarbageCollectionPolicy returns DeleteDependents for all currently served versions.
func (statefulSetStrategy) DefaultGarbageCollectionPolicy(ctx context.Context) rest.GarbageCollectionPolicy {
	return rest.DeleteDependents
}


NamespaceScoped

// NamespaceScoped returns true because all StatefulSet' need to be within a namespace.
func (statefulSetStrategy) NamespaceScoped() bool {
	return true
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (statefulSetStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		"apps/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("status"),
		),
	}

	return fields
}


PrepareForCreate

// PrepareForCreate clears the status of an StatefulSet before creation.
func (statefulSetStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
	statefulSet := obj.(*apps.StatefulSet)
	// create cannot set status
	statefulSet.Status = apps.StatefulSetStatus{}

	statefulSet.Generation = 1

	dropStatefulSetDisabledFields(statefulSet, nil)
	pod.DropDisabledTemplateFields(&statefulSet.Spec.Template, nil)
}


maxUnavailableInUse

// maxUnavailableInUse returns true if StatefulSet's maxUnavailable set(used)
func maxUnavailableInUse(statefulset *apps.StatefulSet) bool {
	if statefulset == nil {
		return false
	}
	if statefulset.Spec.UpdateStrategy.RollingUpdate == nil {
		return false
	}

	return statefulset.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable != nil
}


PrepareForUpdate

// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (statefulSetStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newStatefulSet := obj.(*apps.StatefulSet)
	oldStatefulSet := old.(*apps.StatefulSet)
	// Update is not allowed to set status
	newStatefulSet.Status = oldStatefulSet.Status

	dropStatefulSetDisabledFields(newStatefulSet, oldStatefulSet)
	pod.DropDisabledTemplateFields(&newStatefulSet.Spec.Template, &oldStatefulSet.Spec.Template)

	// Any changes to the spec increment the generation number, any changes to the
	// status should reflect the generation number of the corresponding object.
	// See metav1.ObjectMeta description for more information on Generation.
	if !apiequality.Semantic.DeepEqual(oldStatefulSet.Spec, newStatefulSet.Spec) {
		newStatefulSet.Generation = oldStatefulSet.Generation + 1
	}
}


dropStatefulSetDisabledFields

// dropStatefulSetDisabledFields drops fields that are not used if their associated feature gates
// are not enabled.
// The typical pattern is:
//
//	if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) {
//	    newSvc.Spec.MyFeature = nil
//	}
func dropStatefulSetDisabledFields(newSS *apps.StatefulSet, oldSS *apps.StatefulSet) {
	if !utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetAutoDeletePVC) {
		if oldSS == nil || oldSS.Spec.PersistentVolumeClaimRetentionPolicy == nil {
			newSS.Spec.PersistentVolumeClaimRetentionPolicy = nil
		}
	}
	if !utilfeature.DefaultFeatureGate.Enabled(features.MaxUnavailableStatefulSet) && !maxUnavailableInUse(oldSS) {
		if newSS.Spec.UpdateStrategy.RollingUpdate != nil {
			newSS.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable = nil
		}
	}
	if !utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetStartOrdinal) {
		if oldSS == nil || oldSS.Spec.Ordinals == nil {
			// Reset Spec.Ordinals to the default value (nil).
			newSS.Spec.Ordinals = nil
		}
	}
}


Validate

// Validate validates a new StatefulSet.
func (statefulSetStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
	statefulSet := obj.(*apps.StatefulSet)
	opts := pod.GetValidationOptionsFromPodTemplate(&statefulSet.Spec.Template, nil)
	return validation.ValidateStatefulSet(statefulSet, opts)
}


WarningsOnCreate

// WarningsOnCreate returns warnings for the creation of the given object.
func (statefulSetStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
	newStatefulSet := obj.(*apps.StatefulSet)
	warnings := pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newStatefulSet.Spec.Template, nil)
	for i, pvc := range newStatefulSet.Spec.VolumeClaimTemplates {
		warnings = append(warnings, pvcutil.GetWarningsForPersistentVolumeClaimSpec(field.NewPath("spec", "volumeClaimTemplates").Index(i), pvc.Spec)...)
	}
	return warnings
}


Canonicalize

// Canonicalize normalizes the object after validation.
func (statefulSetStrategy) Canonicalize(obj runtime.Object) {
}


AllowCreateOnUpdate

// AllowCreateOnUpdate is false for StatefulSet; this means POST is needed to create one.
func (statefulSetStrategy) AllowCreateOnUpdate() bool {
	return false
}


ValidateUpdate

// ValidateUpdate is the default update validation for an end user.
func (statefulSetStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	newStatefulSet := obj.(*apps.StatefulSet)
	oldStatefulSet := old.(*apps.StatefulSet)

	opts := pod.GetValidationOptionsFromPodTemplate(&newStatefulSet.Spec.Template, &oldStatefulSet.Spec.Template)
	return validation.ValidateStatefulSetUpdate(newStatefulSet, oldStatefulSet, opts)
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (statefulSetStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	var warnings []string
	newStatefulSet := obj.(*apps.StatefulSet)
	oldStatefulSet := old.(*apps.StatefulSet)
	if newStatefulSet.Generation != oldStatefulSet.Generation {
		warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newStatefulSet.Spec.Template, &oldStatefulSet.Spec.Template)
	}
	for i, pvc := range newStatefulSet.Spec.VolumeClaimTemplates {
		warnings = append(warnings, pvcutil.GetWarningsForPersistentVolumeClaimSpec(field.NewPath("spec", "volumeClaimTemplates").Index(i).Child("Spec"), pvc.Spec)...)
	}

	return warnings
}


AllowUnconditionalUpdate

// AllowUnconditionalUpdate is the default update policy for StatefulSet objects.
func (statefulSetStrategy) AllowUnconditionalUpdate() bool {
	return true
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (statefulSetStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return map[fieldpath.APIVersion]*fieldpath.Set{
		"apps/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
		),
	}
}


PrepareForUpdate

// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (statefulSetStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newStatefulSet := obj.(*apps.StatefulSet)
	oldStatefulSet := old.(*apps.StatefulSet)
	// status changes are not allowed to update spec
	newStatefulSet.Spec = oldStatefulSet.Spec
}


ValidateUpdate

// ValidateUpdate is the default update validation for an end user updating status
func (statefulSetStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	// TODO: Validate status updates.
	return validation.ValidateStatefulSetStatusUpdate(obj.(*apps.StatefulSet), old.(*apps.StatefulSet))
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (statefulSetStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


NewRESTStorage

func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) {
	// TODO figure out how to make the swagger generation stable, while allowing this endpoint to be disabled.
	// if p.Authenticator == nil {
	// 	return genericapiserver.APIGroupInfo{}, false
	// }

	apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(authentication.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
	// If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities.
	// TODO refactor the plumbing to provide the information in the APIGroupInfo

	if storageMap := p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter); len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[authenticationv1alpha1.SchemeGroupVersion.Version] = storageMap
	}

	if storageMap := p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter); len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[authenticationv1beta1.SchemeGroupVersion.Version] = storageMap
	}

	if storageMap := p.v1Storage(apiResourceConfigSource, restOptionsGetter); len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[authenticationv1.SchemeGroupVersion.Version] = storageMap
	}

	return apiGroupInfo, nil
}


func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage {
	storage := map[string]rest.Storage{}

	// tokenreviews
	if resource := "tokenreviews"; apiResourceConfigSource.ResourceEnabled(authenticationv1.SchemeGroupVersion.WithResource(resource)) {
		tokenReviewStorage := tokenreview.NewREST(p.Authenticator, p.APIAudiences)
		storage[resource] = tokenReviewStorage
	}

	return storage
}


func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage {
	storage := map[string]rest.Storage{}

	// selfsubjectreviews
	if resource := "selfsubjectreviews"; apiResourceConfigSource.ResourceEnabled(authenticationv1alpha1.SchemeGroupVersion.WithResource(resource)) {
		if utilfeature.DefaultFeatureGate.Enabled(features.APISelfSubjectReview) {
			selfSRStorage := selfsubjectreview.NewREST()
			storage[resource] = selfSRStorage
		} else {
			klog.Warningln("SelfSubjectReview API is disabled because corresponding feature gate APISelfSubjectReview is not enabled.")
		}
	}
	return storage
}


func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage {
	storage := map[string]rest.Storage{}

	// selfsubjectreviews
	if resource := "selfsubjectreviews"; apiResourceConfigSource.ResourceEnabled(authenticationv1beta1.SchemeGroupVersion.WithResource(resource)) {
		if utilfeature.DefaultFeatureGate.Enabled(features.APISelfSubjectReview) {
			selfSRStorage := selfsubjectreview.NewREST()
			storage[resource] = selfSRStorage
		} else {
			klog.Warningln("SelfSubjectReview API is disabled because corresponding feature gate APISelfSubjectReview is not enabled.")
		}
	}
	return storage
}


GroupName

func (p RESTStorageProvider) GroupName() string {
	return authentication.GroupName
}


NewREST

// NewREST returns a RESTStorage object that will work against selfsubjectreviews.
func NewREST() *REST {
	return &REST{}
}


NamespaceScoped

// NamespaceScoped fulfill rest.Scoper
func (r *REST) NamespaceScoped() bool {
	return false
}


New

// New creates a new selfsubjectreview object.
func (r *REST) New() runtime.Object {
	return &authenticationapi.SelfSubjectReview{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *REST) Destroy() {
	// Given no underlying store, we don't destroy anything
	// here explicitly.
}


Create

// Create returns attributes of the subject making the request.
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	if createValidation != nil {
		if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
			return nil, err
		}
	}

	_, ok := obj.(*authenticationapi.SelfSubjectReview)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectReview: %#v", obj))
	}

	user, ok := genericapirequest.UserFrom(ctx)
	if !ok {
		return nil, apierrors.NewBadRequest("no user present on request")
	}

	extra := user.GetExtra()

	selfSR := &authenticationapi.SelfSubjectReview{
		ObjectMeta: metav1.ObjectMeta{
			CreationTimestamp: metav1.NewTime(time.Now()),
		},
		Status: authenticationapi.SelfSubjectReviewStatus{
			UserInfo: authenticationapi.UserInfo{
				Username: user.GetName(),
				UID:      user.GetUID(),
				Groups:   user.GetGroups(),
				Extra:    make(map[string]authenticationapi.ExtraValue, len(extra)),
			},
		},
	}
	for key, attr := range extra {
		selfSR.Status.UserInfo.Extra[key] = attr
	}

	return selfSR, nil
}


GetSingularName

func (r *REST) GetSingularName() string {
	return "selfsubjectreview"
}


NewREST

func NewREST(tokenAuthenticator authenticator.Request, apiAudiences []string) *REST {
	return &REST{
		tokenAuthenticator: tokenAuthenticator,
		apiAudiences:       apiAudiences,
	}
}


NamespaceScoped

func (r *REST) NamespaceScoped() bool {
	return false
}


New

func (r *REST) New() runtime.Object {
	return &authentication.TokenReview{}
}


GetSingularName

func (r *REST) GetSingularName() string {
	return "tokenreview"
}


Create

func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	tokenReview, ok := obj.(*authentication.TokenReview)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("not a TokenReview: %#v", obj))
	}
	namespace := genericapirequest.NamespaceValue(ctx)
	if len(namespace) != 0 {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("namespace is not allowed on this type: %v", namespace))
	}

	if len(tokenReview.Spec.Token) == 0 {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("token is required for TokenReview in authentication"))
	}

	if createValidation != nil {
		if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
			return nil, err
		}
	}

	if r.tokenAuthenticator == nil {
		return tokenReview, nil
	}

	// create a header that contains nothing but the token
	fakeReq := &http.Request{Header: http.Header{}}
	fakeReq.Header.Add("Authorization", "Bearer "+tokenReview.Spec.Token)

	auds := tokenReview.Spec.Audiences
	if len(auds) == 0 {
		auds = r.apiAudiences
	}
	if len(auds) > 0 {
		fakeReq = fakeReq.WithContext(authenticator.WithAudiences(fakeReq.Context(), auds))
	}

	resp, ok, err := r.tokenAuthenticator.AuthenticateRequest(fakeReq)
	tokenReview.Status.Authenticated = ok
	if err != nil {
		tokenReview.Status.Error = err.Error()
	}

	if len(auds) > 0 && resp != nil && len(authenticator.Audiences(auds).Intersect(resp.Audiences)) == 0 {
		klog.Errorf("error validating audience. want=%q got=%q", auds, resp.Audiences)
		return nil, badAuthenticatorAuds
	}

	if resp != nil && resp.User != nil {
		tokenReview.Status.User = authentication.UserInfo{
			Username: resp.User.GetName(),
			UID:      resp.User.GetUID(),
			Groups:   resp.User.GetGroups(),
			Extra:    map[string]authentication.ExtraValue{},
		}
		for k, v := range resp.User.GetExtra() {
			tokenReview.Status.User.Extra[k] = authentication.ExtraValue(v)
		}
		tokenReview.Status.Audiences = resp.Audiences
	}

	return tokenReview, nil
}


NewREST

func NewREST(authorizer authorizer.Authorizer) *REST {
	return &REST{authorizer}
}


NamespaceScoped

func (r *REST) NamespaceScoped() bool {
	return true
}


New

func (r *REST) New() runtime.Object {
	return &authorizationapi.LocalSubjectAccessReview{}
}


GetSingularName

func (r *REST) GetSingularName() string {
	return "localsubjectaccessreview"
}


Create

func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	localSubjectAccessReview, ok := obj.(*authorizationapi.LocalSubjectAccessReview)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("not a LocaLocalSubjectAccessReview: %#v", obj))
	}
	if errs := authorizationvalidation.ValidateLocalSubjectAccessReview(localSubjectAccessReview); len(errs) > 0 {
		return nil, apierrors.NewInvalid(authorizationapi.Kind(localSubjectAccessReview.Kind), "", errs)
	}
	namespace := genericapirequest.NamespaceValue(ctx)
	if len(namespace) == 0 {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("namespace is required on this type: %v", namespace))
	}
	if namespace != localSubjectAccessReview.Namespace {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("spec.resourceAttributes.namespace must match namespace: %v", namespace))
	}

	if createValidation != nil {
		if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
			return nil, err
		}
	}

	authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(localSubjectAccessReview.Spec)
	decision, reason, evaluationErr := r.authorizer.Authorize(ctx, authorizationAttributes)

	localSubjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{
		Allowed: (decision == authorizer.DecisionAllow),
		Denied:  (decision == authorizer.DecisionDeny),
		Reason:  reason,
	}
	if evaluationErr != nil {
		localSubjectAccessReview.Status.EvaluationError = evaluationErr.Error()
	}

	return localSubjectAccessReview, nil
}


NewRESTStorage

func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) {
	if p.Authorizer == nil {
		return genericapiserver.APIGroupInfo{}, nil
	}

	apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(authorization.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
	// If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities.
	// TODO refactor the plumbing to provide the information in the APIGroupInfo

	if storageMap := p.v1Storage(apiResourceConfigSource, restOptionsGetter); len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[authorizationv1.SchemeGroupVersion.Version] = storageMap
	}

	return apiGroupInfo, nil
}


func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage {
	storage := map[string]rest.Storage{}

	// subjectaccessreviews
	if resource := "subjectaccessreviews"; apiResourceConfigSource.ResourceEnabled(authorizationv1.SchemeGroupVersion.WithResource(resource)) {
		storage[resource] = subjectaccessreview.NewREST(p.Authorizer)
	}

	// selfsubjectaccessreviews
	if resource := "selfsubjectaccessreviews"; apiResourceConfigSource.ResourceEnabled(authorizationv1.SchemeGroupVersion.WithResource(resource)) {
		storage[resource] = selfsubjectaccessreview.NewREST(p.Authorizer)
	}

	// localsubjectaccessreviews
	if resource := "localsubjectaccessreviews"; apiResourceConfigSource.ResourceEnabled(authorizationv1.SchemeGroupVersion.WithResource(resource)) {
		storage[resource] = localsubjectaccessreview.NewREST(p.Authorizer)
	}

	// selfsubjectrulesreviews
	if resource := "selfsubjectrulesreviews"; apiResourceConfigSource.ResourceEnabled(authorizationv1.SchemeGroupVersion.WithResource(resource)) {
		storage[resource] = selfsubjectrulesreview.NewREST(p.RuleResolver)
	}

	return storage
}


GroupName

func (p RESTStorageProvider) GroupName() string {
	return authorization.GroupName
}


New

func (r *REST) New() runtime.Object {
	return &authorizationapi.SelfSubjectAccessReview{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *REST) Destroy() {
	// Given no underlying store, we don't destroy anything
	// here explicitly.
}


GetSingularName

func (r *REST) GetSingularName() string {
	return "selfsubjectaccessreview"
}


Create

func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	selfSAR, ok := obj.(*authorizationapi.SelfSubjectAccessReview)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectAccessReview: %#v", obj))
	}
	if errs := authorizationvalidation.ValidateSelfSubjectAccessReview(selfSAR); len(errs) > 0 {
		return nil, apierrors.NewInvalid(authorizationapi.Kind(selfSAR.Kind), "", errs)
	}
	userToCheck, exists := genericapirequest.UserFrom(ctx)
	if !exists {
		return nil, apierrors.NewBadRequest("no user present on request")
	}

	if createValidation != nil {
		if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
			return nil, err
		}
	}

	var authorizationAttributes authorizer.AttributesRecord
	if selfSAR.Spec.ResourceAttributes != nil {
		authorizationAttributes = authorizationutil.ResourceAttributesFrom(userToCheck, *selfSAR.Spec.ResourceAttributes)
	} else {
		authorizationAttributes = authorizationutil.NonResourceAttributesFrom(userToCheck, *selfSAR.Spec.NonResourceAttributes)
	}

	decision, reason, evaluationErr := r.authorizer.Authorize(ctx, authorizationAttributes)

	selfSAR.Status = authorizationapi.SubjectAccessReviewStatus{
		Allowed: (decision == authorizer.DecisionAllow),
		Denied:  (decision == authorizer.DecisionDeny),
		Reason:  reason,
	}
	if evaluationErr != nil {
		selfSAR.Status.EvaluationError = evaluationErr.Error()
	}

	return selfSAR, nil
}


NewREST

// NewREST returns a RESTStorage object that will work against selfsubjectrulesreview.
func NewREST(ruleResolver authorizer.RuleResolver) *REST {
	return &REST{ruleResolver}
}


NamespaceScoped

// NamespaceScoped fulfill rest.Scoper
func (r *REST) NamespaceScoped() bool {
	return false
}


New

// New creates a new selfsubjectrulesreview object.
func (r *REST) New() runtime.Object {
	return &authorizationapi.SelfSubjectRulesReview{}
}


Create

// Create attempts to get self subject rules in specific namespace.
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	selfSRR, ok := obj.(*authorizationapi.SelfSubjectRulesReview)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectRulesReview: %#v", obj))
	}

	user, ok := genericapirequest.UserFrom(ctx)
	if !ok {
		return nil, apierrors.NewBadRequest("no user present on request")
	}

	namespace := selfSRR.Spec.Namespace
	if namespace == "" {
		return nil, apierrors.NewBadRequest("no namespace on request")
	}

	if createValidation != nil {
		if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
			return nil, err
		}
	}

	resourceInfo, nonResourceInfo, incomplete, err := r.ruleResolver.RulesFor(user, namespace)

	ret := &authorizationapi.SelfSubjectRulesReview{
		Status: authorizationapi.SubjectRulesReviewStatus{
			ResourceRules:    getResourceRules(resourceInfo),
			NonResourceRules: getNonResourceRules(nonResourceInfo),
			Incomplete:       incomplete,
		},
	}

	if err != nil {
		ret.Status.EvaluationError = err.Error()
	}

	return ret, nil
}


GetSingularName

func (r *REST) GetSingularName() string {
	return "selfsubjectrulesreview"
}


getResourceRules

func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizationapi.ResourceRule {
	rules := make([]authorizationapi.ResourceRule, len(infos))
	for i, info := range infos {
		rules[i] = authorizationapi.ResourceRule{
			Verbs:         info.GetVerbs(),
			APIGroups:     info.GetAPIGroups(),
			Resources:     info.GetResources(),
			ResourceNames: info.GetResourceNames(),
		}
	}
	return rules
}


getNonResourceRules

func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizationapi.NonResourceRule {
	rules := make([]authorizationapi.NonResourceRule, len(infos))
	for i, info := range infos {
		rules[i] = authorizationapi.NonResourceRule{
			Verbs:           info.GetVerbs(),
			NonResourceURLs: info.GetNonResourceURLs(),
		}
	}
	return rules
}


NewREST

func NewREST(authorizer authorizer.Authorizer) *REST {
	return &REST{authorizer}
}


NamespaceScoped

func (r *REST) NamespaceScoped() bool {
	return false
}


New

func (r *REST) New() runtime.Object {
	return &authorizationapi.SubjectAccessReview{}
}


GetSingularName

func (r *REST) GetSingularName() string {
	return "subjectaccessreview"
}


Create

func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview)
	if !ok {
		return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SubjectAccessReview: %#v", obj))
	}
	if errs := authorizationvalidation.ValidateSubjectAccessReview(subjectAccessReview); len(errs) > 0 {
		return nil, apierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), "", errs)
	}

	if createValidation != nil {
		if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
			return nil, err
		}
	}

	authorizationAttributes := authorizationutil.AuthorizationAttributesFrom(subjectAccessReview.Spec)
	decision, reason, evaluationErr := r.authorizer.Authorize(ctx, authorizationAttributes)

	subjectAccessReview.Status = authorizationapi.SubjectAccessReviewStatus{
		Allowed: (decision == authorizer.DecisionAllow),
		Denied:  (decision == authorizer.DecisionDeny),
		Reason:  reason,
	}
	if evaluationErr != nil {
		subjectAccessReview.Status.EvaluationError = evaluationErr.Error()
	}

	return subjectAccessReview, nil
}


ResourceAttributesFrom

// ResourceAttributesFrom combines the API object information and the user.Info from the context to build a full authorizer.AttributesRecord for resource access
func ResourceAttributesFrom(user user.Info, in authorizationapi.ResourceAttributes) authorizer.AttributesRecord {
	return authorizer.AttributesRecord{
		User:            user,
		Verb:            in.Verb,
		Namespace:       in.Namespace,
		APIGroup:        in.Group,
		APIVersion:      in.Version,
		Resource:        in.Resource,
		Subresource:     in.Subresource,
		Name:            in.Name,
		ResourceRequest: true,
	}
}


NonResourceAttributesFrom

// NonResourceAttributesFrom combines the API object information and the user.Info from the context to build a full authorizer.AttributesRecord for non resource access
func NonResourceAttributesFrom(user user.Info, in authorizationapi.NonResourceAttributes) authorizer.AttributesRecord {
	return authorizer.AttributesRecord{
		User:            user,
		ResourceRequest: false,
		Path:            in.Path,
		Verb:            in.Verb,
	}
}


convertToUserInfoExtra

func convertToUserInfoExtra(extra map[string]authorizationapi.ExtraValue) map[string][]string {
	if extra == nil {
		return nil
	}
	ret := map[string][]string{}
	for k, v := range extra {
		ret[k] = []string(v)
	}

	return ret
}


AuthorizationAttributesFrom

// AuthorizationAttributesFrom takes a spec and returns the proper authz attributes to check it.
func AuthorizationAttributesFrom(spec authorizationapi.SubjectAccessReviewSpec) authorizer.AttributesRecord {
	userToCheck := &user.DefaultInfo{
		Name:   spec.User,
		Groups: spec.Groups,
		UID:    spec.UID,
		Extra:  convertToUserInfoExtra(spec.Extra),
	}

	var authorizationAttributes authorizer.AttributesRecord
	if spec.ResourceAttributes != nil {
		authorizationAttributes = ResourceAttributesFrom(userToCheck, *spec.ResourceAttributes)
	} else {
		authorizationAttributes = NonResourceAttributesFrom(userToCheck, *spec.NonResourceAttributes)
	}

	return authorizationAttributes
}


NewREST

// NewREST returns a RESTStorage object that will work against horizontal pod autoscalers.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
	store := &genericregistry.Store{
		NewFunc:                   func() runtime.Object { return &autoscaling.HorizontalPodAutoscaler{} },
		NewListFunc:               func() runtime.Object { return &autoscaling.HorizontalPodAutoscalerList{} },
		DefaultQualifiedResource:  autoscaling.Resource("horizontalpodautoscalers"),
		SingularQualifiedResource: autoscaling.Resource("horizontalpodautoscaler"),

		CreateStrategy:      horizontalpodautoscaler.Strategy,
		UpdateStrategy:      horizontalpodautoscaler.Strategy,
		DeleteStrategy:      horizontalpodautoscaler.Strategy,
		ResetFieldsStrategy: horizontalpodautoscaler.Strategy,

		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter}
	if err := store.CompleteWithOptions(options); err != nil {
		return nil, nil, err
	}

	statusStore := *store
	statusStore.UpdateStrategy = horizontalpodautoscaler.StatusStrategy
	statusStore.ResetFieldsStrategy = horizontalpodautoscaler.StatusStrategy
	return &REST{store}, &StatusREST{store: &statusStore}, nil
}


ShortNames

// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
func (r *REST) ShortNames() []string {
	return []string{"hpa"}
}


Categories

// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
func (r *REST) Categories() []string {
	return []string{"all"}
}


New

// New creates a new HorizontalPodAutoscaler object.
func (r *StatusREST) New() runtime.Object {
	return &autoscaling.HorizontalPodAutoscaler{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *StatusREST) Destroy() {
	// Given that underlying store is shared with REST,
	// we don't destroy it here explicitly.
}


Get

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}


Update

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}


GetResetFields

// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return r.store.GetResetFields()
}


ConvertToTable

func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
	return r.store.ConvertToTable(ctx, object, tableOptions)
}


NamespaceScoped

// NamespaceScoped is true for autoscaler.
func (autoscalerStrategy) NamespaceScoped() bool {
	return true
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (autoscalerStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		"autoscaling/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("status"),
		),
		"autoscaling/v2": fieldpath.NewSet(
			fieldpath.MakePathOrDie("status"),
		),
		"autoscaling/v2beta1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("status"),
		),
		"autoscaling/v2beta2": fieldpath.NewSet(
			fieldpath.MakePathOrDie("status"),
		),
	}

	return fields
}


PrepareForCreate

// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (autoscalerStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
	newHPA := obj.(*autoscaling.HorizontalPodAutoscaler)

	// create cannot set status
	newHPA.Status = autoscaling.HorizontalPodAutoscalerStatus{}

	if !utilfeature.DefaultFeatureGate.Enabled(features.HPAContainerMetrics) {
		dropContainerMetricSources(newHPA.Spec.Metrics)
	}
}


Validate

// Validate validates a new autoscaler.
func (autoscalerStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
	autoscaler := obj.(*autoscaling.HorizontalPodAutoscaler)
	return validation.ValidateHorizontalPodAutoscaler(autoscaler)
}


WarningsOnCreate

// WarningsOnCreate returns warnings for the creation of the given object.
func (autoscalerStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
	return nil
}


Canonicalize

// Canonicalize normalizes the object after validation.
func (autoscalerStrategy) Canonicalize(obj runtime.Object) {
}


AllowCreateOnUpdate

// AllowCreateOnUpdate is false for autoscalers.
func (autoscalerStrategy) AllowCreateOnUpdate() bool {
	return false
}


PrepareForUpdate

// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (autoscalerStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newHPA := obj.(*autoscaling.HorizontalPodAutoscaler)
	oldHPA := old.(*autoscaling.HorizontalPodAutoscaler)
	if !utilfeature.DefaultFeatureGate.Enabled(features.HPAContainerMetrics) && !hasContainerMetricSources(oldHPA) {
		dropContainerMetricSources(newHPA.Spec.Metrics)
	}
	// Update is not allowed to set status
	newHPA.Status = oldHPA.Status
}


dropContainerMetricSources

// dropContainerMetricSources ensures all container resource metric sources are nil
func dropContainerMetricSources(metrics []autoscaling.MetricSpec) {
	for i := range metrics {
		metrics[i].ContainerResource = nil
	}
}


hasContainerMetricSources

// hasContainerMetricSources returns true if the hpa has any container resource metric sources
func hasContainerMetricSources(hpa *autoscaling.HorizontalPodAutoscaler) bool {
	for i := range hpa.Spec.Metrics {
		if hpa.Spec.Metrics[i].ContainerResource != nil {
			return true
		}
	}
	return false
}


ValidateUpdate

// ValidateUpdate is the default update validation for an end user.
func (autoscalerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	return validation.ValidateHorizontalPodAutoscalerUpdate(obj.(*autoscaling.HorizontalPodAutoscaler), old.(*autoscaling.HorizontalPodAutoscaler))
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (autoscalerStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


AllowUnconditionalUpdate

func (autoscalerStrategy) AllowUnconditionalUpdate() bool {
	return true
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (autoscalerStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		"autoscaling/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
		),
		"autoscaling/v2": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
		),
		"autoscaling/v2beta1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
		),
		"autoscaling/v2beta2": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
		),
	}

	return fields
}


PrepareForUpdate

func (autoscalerStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newAutoscaler := obj.(*autoscaling.HorizontalPodAutoscaler)
	oldAutoscaler := old.(*autoscaling.HorizontalPodAutoscaler)
	// status changes are not allowed to update spec
	newAutoscaler.Spec = oldAutoscaler.Spec
}


ValidateUpdate

func (autoscalerStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	return validation.ValidateHorizontalPodAutoscalerStatusUpdate(obj.(*autoscaling.HorizontalPodAutoscaler), old.(*autoscaling.HorizontalPodAutoscaler))
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (autoscalerStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


NewRESTStorage

func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) {
	apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(autoscaling.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
	// If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities.
	// TODO refactor the plumbing to provide the information in the APIGroupInfo

	if storageMap, err := p.v2beta2Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
		return genericapiserver.APIGroupInfo{}, err
	} else if len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[autoscalingapiv2beta2.SchemeGroupVersion.Version] = storageMap
	}

	if storageMap, err := p.v2Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
		return genericapiserver.APIGroupInfo{}, err
	} else if len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[autoscalingapiv2.SchemeGroupVersion.Version] = storageMap
	}

	if storageMap, err := p.v2beta1Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
		return genericapiserver.APIGroupInfo{}, err
	} else if len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[autoscalingapiv2beta1.SchemeGroupVersion.Version] = storageMap
	}

	if storageMap, err := p.v1Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
		return genericapiserver.APIGroupInfo{}, err
	} else if len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[autoscalingapiv1.SchemeGroupVersion.Version] = storageMap
	}

	return apiGroupInfo, nil
}


func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
	storage := map[string]rest.Storage{}

	// horizontalpodautoscalers
	if resource := "horizontalpodautoscalers"; apiResourceConfigSource.ResourceEnabled(autoscalingapiv1.SchemeGroupVersion.WithResource(resource)) {
		hpaStorage, hpaStatusStorage, err := horizontalpodautoscalerstore.NewREST(restOptionsGetter)
		if err != nil {
			return storage, err
		}
		storage[resource] = hpaStorage
		storage[resource+"/status"] = hpaStatusStorage
	}

	return storage, nil
}


func (p RESTStorageProvider) v2beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
	storage := map[string]rest.Storage{}

	// horizontalpodautoscalers
	if resource := "horizontalpodautoscalers"; apiResourceConfigSource.ResourceEnabled(autoscalingapiv2beta1.SchemeGroupVersion.WithResource(resource)) {
		hpaStorage, hpaStatusStorage, err := horizontalpodautoscalerstore.NewREST(restOptionsGetter)
		if err != nil {
			return storage, err
		}
		storage[resource] = hpaStorage
		storage[resource+"/status"] = hpaStatusStorage
	}

	return storage, nil
}


func (p RESTStorageProvider) v2beta2Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
	storage := map[string]rest.Storage{}

	// horizontalpodautoscalers
	if resource := "horizontalpodautoscalers"; apiResourceConfigSource.ResourceEnabled(autoscalingapiv2beta2.SchemeGroupVersion.WithResource(resource)) {
		hpaStorage, hpaStatusStorage, err := horizontalpodautoscalerstore.NewREST(restOptionsGetter)
		if err != nil {
			return storage, err
		}
		storage[resource] = hpaStorage
		storage[resource+"/status"] = hpaStatusStorage
	}
	return storage, nil
}


func (p RESTStorageProvider) v2Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
	storage := map[string]rest.Storage{}

	// horizontalpodautoscalers
	if resource := "horizontalpodautoscalers"; apiResourceConfigSource.ResourceEnabled(autoscalingapiv2.SchemeGroupVersion.WithResource(resource)) {
		hpaStorage, hpaStatusStorage, err := horizontalpodautoscalerstore.NewREST(restOptionsGetter)
		if err != nil {
			return storage, err
		}
		storage[resource] = hpaStorage
		storage[resource+"/status"] = hpaStatusStorage
	}

	return storage, nil
}


GroupName

func (p RESTStorageProvider) GroupName() string {
	return autoscaling.GroupName
}


NewREST

// NewREST returns a RESTStorage object that will work against CronJobs.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
	store := &genericregistry.Store{
		NewFunc:                   func() runtime.Object { return &batch.CronJob{} },
		NewListFunc:               func() runtime.Object { return &batch.CronJobList{} },
		DefaultQualifiedResource:  batch.Resource("cronjobs"),
		SingularQualifiedResource: batch.Resource("cronjob"),

		CreateStrategy:      cronjob.Strategy,
		UpdateStrategy:      cronjob.Strategy,
		DeleteStrategy:      cronjob.Strategy,
		ResetFieldsStrategy: cronjob.Strategy,

		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter}
	if err := store.CompleteWithOptions(options); err != nil {
		return nil, nil, err
	}

	statusStore := *store
	statusStore.UpdateStrategy = cronjob.StatusStrategy
	statusStore.ResetFieldsStrategy = cronjob.StatusStrategy

	return &REST{store}, &StatusREST{store: &statusStore}, nil
}


Categories

// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
func (r *REST) Categories() []string {
	return []string{"all"}
}


ShortNames

// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
func (r *REST) ShortNames() []string {
	return []string{"cj"}
}


New

// New creates a new CronJob object.
func (r *StatusREST) New() runtime.Object {
	return &batch.CronJob{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *StatusREST) Destroy() {
	// Given that underlying store is shared with REST,
	// we don't destroy it here explicitly.
}


Get

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}


Update

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}


GetResetFields

// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return r.store.GetResetFields()
}


ConvertToTable

func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
	return r.store.ConvertToTable(ctx, object, tableOptions)
}


DefaultGarbageCollectionPolicy

// DefaultGarbageCollectionPolicy returns OrphanDependents for batch/v1beta1 for backwards compatibility,
// and DeleteDependents for all other versions.
func (cronJobStrategy) DefaultGarbageCollectionPolicy(ctx context.Context) rest.GarbageCollectionPolicy {
	var groupVersion schema.GroupVersion
	if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
		groupVersion = schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
	}
	switch groupVersion {
	case batchv1beta1.SchemeGroupVersion:
		// for back compatibility
		return rest.OrphanDependents
	default:
		return rest.DeleteDependents
	}
}


NamespaceScoped

// NamespaceScoped returns true because all scheduled jobs need to be within a namespace.
func (cronJobStrategy) NamespaceScoped() bool {
	return true
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (cronJobStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		"batch/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("status"),
		),
		"batch/v1beta1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("status"),
		),
	}

	return fields
}


PrepareForCreate

// PrepareForCreate clears the status of a scheduled job before creation.
func (cronJobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
	cronJob := obj.(*batch.CronJob)
	cronJob.Status = batch.CronJobStatus{}

	cronJob.Generation = 1

	pod.DropDisabledTemplateFields(&cronJob.Spec.JobTemplate.Spec.Template, nil)
}


PrepareForUpdate

// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (cronJobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newCronJob := obj.(*batch.CronJob)
	oldCronJob := old.(*batch.CronJob)
	newCronJob.Status = oldCronJob.Status

	pod.DropDisabledTemplateFields(&newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template)

	// Any changes to the spec increment the generation number.
	// See metav1.ObjectMeta description for more information on Generation.
	if !apiequality.Semantic.DeepEqual(newCronJob.Spec, oldCronJob.Spec) {
		newCronJob.Generation = oldCronJob.Generation + 1
	}
}


Validate

// Validate validates a new scheduled job.
func (cronJobStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
	cronJob := obj.(*batch.CronJob)
	opts := pod.GetValidationOptionsFromPodTemplate(&cronJob.Spec.JobTemplate.Spec.Template, nil)
	return batchvalidation.ValidateCronJobCreate(cronJob, opts)
}


WarningsOnCreate

// WarningsOnCreate returns warnings for the creation of the given object.
func (cronJobStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
	newCronJob := obj.(*batch.CronJob)
	var warnings []string
	if msgs := utilvalidation.IsDNS1123Label(newCronJob.Name); len(msgs) != 0 {
		warnings = append(warnings, fmt.Sprintf("metadata.name: this is used in Pod names and hostnames, which can result in surprising behavior; a DNS label is recommended: %v", msgs))
	}
	warnings = append(warnings, pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "jobTemplate", "spec", "template"), &newCronJob.Spec.JobTemplate.Spec.Template, nil)...)
	if strings.Contains(newCronJob.Spec.Schedule, "TZ") {
		warnings = append(warnings, fmt.Sprintf("CRON_TZ or TZ used in %s is not officially supported, see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ for more details", field.NewPath("spec", "spec", "schedule")))
	}
	return warnings
}


Canonicalize

// Canonicalize normalizes the object after validation.
func (cronJobStrategy) Canonicalize(obj runtime.Object) {
}


AllowUnconditionalUpdate

func (cronJobStrategy) AllowUnconditionalUpdate() bool {
	return true
}


AllowCreateOnUpdate

// AllowCreateOnUpdate is false for scheduled jobs; this means a POST is needed to create one.
func (cronJobStrategy) AllowCreateOnUpdate() bool {
	return false
}


ValidateUpdate

// ValidateUpdate is the default update validation for an end user.
func (cronJobStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	newCronJob := obj.(*batch.CronJob)
	oldCronJob := old.(*batch.CronJob)

	opts := pod.GetValidationOptionsFromPodTemplate(&newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template)
	return batchvalidation.ValidateCronJobUpdate(newCronJob, oldCronJob, opts)
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (cronJobStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	var warnings []string
	newCronJob := obj.(*batch.CronJob)
	oldCronJob := old.(*batch.CronJob)
	if newCronJob.Generation != oldCronJob.Generation {
		warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "jobTemplate", "spec", "template"), &newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template)
	}
	if strings.Contains(newCronJob.Spec.Schedule, "TZ") {
		warnings = append(warnings, fmt.Sprintf("CRON_TZ or TZ used in %s is not officially supported, see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ for more details", field.NewPath("spec", "spec", "schedule")))
	}
	return warnings
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (cronJobStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return map[fieldpath.APIVersion]*fieldpath.Set{
		"batch/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
		),
		"batch/v1beta1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
		),
	}
}


PrepareForUpdate

func (cronJobStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newJob := obj.(*batch.CronJob)
	oldJob := old.(*batch.CronJob)
	newJob.Spec = oldJob.Spec
}


ValidateUpdate

func (cronJobStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	return field.ErrorList{}
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (cronJobStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


NewStorage

// NewStorage creates a new JobStorage against etcd.
func NewStorage(optsGetter generic.RESTOptionsGetter) (JobStorage, error) {
	jobRest, jobStatusRest, err := NewREST(optsGetter)
	if err != nil {
		return JobStorage{}, err
	}

	return JobStorage{
		Job:    jobRest,
		Status: jobStatusRest,
	}, nil
}


NewREST

// NewREST returns a RESTStorage object that will work against Jobs.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
	store := &genericregistry.Store{
		NewFunc:                   func() runtime.Object { return &batch.Job{} },
		NewListFunc:               func() runtime.Object { return &batch.JobList{} },
		PredicateFunc:             job.MatchJob,
		DefaultQualifiedResource:  batch.Resource("jobs"),
		SingularQualifiedResource: batch.Resource("job"),

		CreateStrategy:      job.Strategy,
		UpdateStrategy:      job.Strategy,
		DeleteStrategy:      job.Strategy,
		ResetFieldsStrategy: job.Strategy,

		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: job.GetAttrs}
	if err := store.CompleteWithOptions(options); err != nil {
		return nil, nil, err
	}

	statusStore := *store
	statusStore.UpdateStrategy = job.StatusStrategy
	statusStore.ResetFieldsStrategy = job.StatusStrategy

	return &REST{store}, &StatusREST{store: &statusStore}, nil
}


Categories

// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
func (r *REST) Categories() []string {
	return []string{"all"}
}


Delete

func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
	//nolint:staticcheck // SA1019 backwards compatibility
	//nolint: staticcheck
	if options != nil && options.PropagationPolicy == nil && options.OrphanDependents == nil &&
		job.Strategy.DefaultGarbageCollectionPolicy(ctx) == rest.OrphanDependents {
		// Throw a warning if delete options are not explicitly set as Job deletion strategy by default is orphaning
		// pods in v1.
		warning.AddWarning(ctx, "", deleteOptionWarnings)
	}
	return r.Store.Delete(ctx, name, deleteValidation, options)
}


DeleteCollection

func (r *REST) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, deleteOptions *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
	//nolint:staticcheck // SA1019 backwards compatibility
	if deleteOptions.PropagationPolicy == nil && deleteOptions.OrphanDependents == nil &&
		job.Strategy.DefaultGarbageCollectionPolicy(ctx) == rest.OrphanDependents {
		// Throw a warning if delete options are not explicitly set as Job deletion strategy by default is orphaning
		// pods in v1.
		warning.AddWarning(ctx, "", deleteOptionWarnings)
	}
	return r.Store.DeleteCollection(ctx, deleteValidation, deleteOptions, listOptions)
}


New

// New creates a new Job object.
func (r *StatusREST) New() runtime.Object {
	return &batch.Job{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *StatusREST) Destroy() {
	// Given that underlying store is shared with REST,
	// we don't destroy it here explicitly.
}


Get

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}


Update

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}


GetResetFields

// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return r.store.GetResetFields()
}


ConvertToTable

func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
	return r.store.ConvertToTable(ctx, object, tableOptions)
}


DefaultGarbageCollectionPolicy

// DefaultGarbageCollectionPolicy returns OrphanDependents for batch/v1 for backwards compatibility,
// and DeleteDependents for all other versions.
func (jobStrategy) DefaultGarbageCollectionPolicy(ctx context.Context) rest.GarbageCollectionPolicy {
	var groupVersion schema.GroupVersion
	if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
		groupVersion = schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
	}
	switch groupVersion {
	case batchv1.SchemeGroupVersion:
		// for back compatibility
		return rest.OrphanDependents
	default:
		return rest.DeleteDependents
	}
}


NamespaceScoped

// NamespaceScoped returns true because all jobs need to be within a namespace.
func (jobStrategy) NamespaceScoped() bool {
	return true
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (jobStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		"batch/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("status"),
		),
	}

	return fields
}


PrepareForCreate

// PrepareForCreate clears the status of a job before creation.
func (jobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
	job := obj.(*batch.Job)
	job.Status = batch.JobStatus{}

	job.Generation = 1

	// While legacy tracking is supported, we use an annotation to mark whether
	// jobs are tracked with finalizers.
	addJobTrackingAnnotation(job)

	if !utilfeature.DefaultFeatureGate.Enabled(features.JobPodFailurePolicy) {
		job.Spec.PodFailurePolicy = nil
	}

	pod.DropDisabledTemplateFields(&job.Spec.Template, nil)
}


addJobTrackingAnnotation

func addJobTrackingAnnotation(job *batch.Job) {
	if job.Annotations == nil {
		job.Annotations = map[string]string{}
	}
	job.Annotations[batchv1.JobTrackingFinalizer] = ""
}


hasJobTrackingAnnotation

func hasJobTrackingAnnotation(job *batch.Job) bool {
	if job.Annotations == nil {
		return false
	}
	_, ok := job.Annotations[batchv1.JobTrackingFinalizer]
	return ok
}


PrepareForUpdate

// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (jobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newJob := obj.(*batch.Job)
	oldJob := old.(*batch.Job)
	newJob.Status = oldJob.Status

	if !utilfeature.DefaultFeatureGate.Enabled(features.JobPodFailurePolicy) && oldJob.Spec.PodFailurePolicy == nil {
		newJob.Spec.PodFailurePolicy = nil
	}

	pod.DropDisabledTemplateFields(&newJob.Spec.Template, &oldJob.Spec.Template)

	// Any changes to the spec increment the generation number.
	// See metav1.ObjectMeta description for more information on Generation.
	if !apiequality.Semantic.DeepEqual(newJob.Spec, oldJob.Spec) {
		newJob.Generation = oldJob.Generation + 1
	}

	// While legacy tracking is supported, we use an annotation to mark whether
	// jobs are tracked with finalizers. This annotation cannot be removed by
	// users.
	if hasJobTrackingAnnotation(oldJob) {
		addJobTrackingAnnotation(newJob)
	}
}


Validate

// Validate validates a new job.
func (jobStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
	job := obj.(*batch.Job)
	// TODO: move UID generation earlier and do this in defaulting logic?
	if job.Spec.ManualSelector == nil || *job.Spec.ManualSelector == false {
		generateSelector(job)
	}
	opts := validationOptionsForJob(job, nil)
	return batchvalidation.ValidateJob(job, opts)
}


validationOptionsForJob

func validationOptionsForJob(newJob, oldJob *batch.Job) batchvalidation.JobValidationOptions {
	var newPodTemplate, oldPodTemplate *core.PodTemplateSpec
	if newJob != nil {
		newPodTemplate = &newJob.Spec.Template
	}
	if oldJob != nil {
		oldPodTemplate = &oldJob.Spec.Template
	}
	opts := batchvalidation.JobValidationOptions{
		PodValidationOptions:    pod.GetValidationOptionsFromPodTemplate(newPodTemplate, oldPodTemplate),
		AllowTrackingAnnotation: true,
		AllowElasticIndexedJobs: utilfeature.DefaultFeatureGate.Enabled(features.ElasticIndexedJob),
		RequirePrefixedLabels:   true,
	}
	if oldJob != nil {
		opts.AllowInvalidLabelValueInSelector = opts.AllowInvalidLabelValueInSelector || metav1validation.LabelSelectorHasInvalidLabelValue(oldJob.Spec.Selector)

		// Because we don't support the tracking with finalizers for already
		// existing jobs, we allow the annotation only if the Job already had it,
		// regardless of the feature gate.
		opts.AllowTrackingAnnotation = hasJobTrackingAnnotation(oldJob)

		// Updating node affinity, node selector and tolerations is allowed
		// only for suspended jobs that never started before.
		suspended := oldJob.Spec.Suspend != nil && *oldJob.Spec.Suspend
		notStarted := oldJob.Status.StartTime == nil
		opts.AllowMutableSchedulingDirectives = suspended && notStarted

		// Validation should not fail jobs if they don't have the new labels.
		// This can be removed once we have high confidence that both labels exist (1.30 at least)
		_, hadJobName := oldJob.Spec.Template.Labels[batch.JobNameLabel]
		_, hadControllerUid := oldJob.Spec.Template.Labels[batch.ControllerUidLabel]
		opts.RequirePrefixedLabels = hadJobName && hadControllerUid
	}
	return opts
}


WarningsOnCreate

// WarningsOnCreate returns warnings for the creation of the given object.
func (jobStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
	newJob := obj.(*batch.Job)
	var warnings []string
	if msgs := utilvalidation.IsDNS1123Label(newJob.Name); len(msgs) != 0 {
		warnings = append(warnings, fmt.Sprintf("metadata.name: this is used in Pod names and hostnames, which can result in surprising behavior; a DNS label is recommended: %v", msgs))
	}
	warnings = append(warnings, pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newJob.Spec.Template, nil)...)
	return warnings
}


generateSelector

// generateSelector adds a selector to a job and labels to its template
// which can be used to uniquely identify the pods created by that job,
// if the user has requested this behavior.
func generateSelector(obj *batch.Job) {
	if obj.Spec.Template.Labels == nil {
		obj.Spec.Template.Labels = make(map[string]string)
	}
	// The job-name label is unique except in cases that are expected to be
	// quite uncommon, and is more user friendly than uid.  So, we add it as
	// a label.
	jobNameLabels := []string{batch.LegacyJobNameLabel, batch.JobNameLabel}
	for _, value := range jobNameLabels {
		_, found := obj.Spec.Template.Labels[value]
		if found {
			// User asked us to automatically generate a selector, but set manual labels.
			// If there is a conflict, we will reject in validation.
		} else {
			obj.Spec.Template.Labels[value] = string(obj.ObjectMeta.Name)
		}
	}

	// The controller-uid label makes the pods that belong to this job
	// only match this job.
	controllerUidLabels := []string{batch.LegacyControllerUidLabel, batch.ControllerUidLabel}
	for _, value := range controllerUidLabels {
		_, found := obj.Spec.Template.Labels[value]
		if found {
			// User asked us to automatically generate a selector, but set manual labels.
			// If there is a conflict, we will reject in validation.
		} else {
			obj.Spec.Template.Labels[value] = string(obj.ObjectMeta.UID)
		}
	}
	// Select the controller-uid label.  This is sufficient for uniqueness.
	if obj.Spec.Selector == nil {
		obj.Spec.Selector = &metav1.LabelSelector{}
	}
	if obj.Spec.Selector.MatchLabels == nil {
		obj.Spec.Selector.MatchLabels = make(map[string]string)
	}

	if _, found := obj.Spec.Selector.MatchLabels[batch.ControllerUidLabel]; !found {
		obj.Spec.Selector.MatchLabels[batch.ControllerUidLabel] = string(obj.ObjectMeta.UID)
	}
	// If the user specified matchLabel controller-uid=$WRONGUID, then it should fail
	// in validation, either because the selector does not match the pod template
	// (controller-uid=$WRONGUID does not match controller-uid=$UID, which we applied
	// above, or we will reject in validation because the template has the wrong
	// labels.
}


Canonicalize

// Canonicalize normalizes the object after validation.
func (jobStrategy) Canonicalize(obj runtime.Object) {
}


AllowUnconditionalUpdate

func (jobStrategy) AllowUnconditionalUpdate() bool {
	return true
}


AllowCreateOnUpdate

// AllowCreateOnUpdate is false for jobs; this means a POST is needed to create one.
func (jobStrategy) AllowCreateOnUpdate() bool {
	return false
}


ValidateUpdate

// ValidateUpdate is the default update validation for an end user.
func (jobStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	job := obj.(*batch.Job)
	oldJob := old.(*batch.Job)

	opts := validationOptionsForJob(job, oldJob)
	validationErrorList := batchvalidation.ValidateJob(job, opts)
	updateErrorList := batchvalidation.ValidateJobUpdate(job, oldJob, opts)
	return append(validationErrorList, updateErrorList...)
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (jobStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	var warnings []string
	newJob := obj.(*batch.Job)
	oldJob := old.(*batch.Job)
	if newJob.Generation != oldJob.Generation {
		warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newJob.Spec.Template, &oldJob.Spec.Template)
	}
	return warnings
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (jobStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return map[fieldpath.APIVersion]*fieldpath.Set{
		"batch/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
		),
	}
}


PrepareForUpdate

func (jobStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newJob := obj.(*batch.Job)
	oldJob := old.(*batch.Job)
	newJob.Spec = oldJob.Spec
}


ValidateUpdate

func (jobStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	return batchvalidation.ValidateJobUpdateStatus(obj.(*batch.Job), old.(*batch.Job))
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (jobStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


JobToSelectableFields

// JobSelectableFields returns a field set that represents the object for matching purposes.
func JobToSelectableFields(job *batch.Job) fields.Set {
	objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&job.ObjectMeta, true)
	specificFieldsSet := fields.Set{
		"status.successful": strconv.Itoa(int(job.Status.Succeeded)),
	}
	return generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet)
}


GetAttrs

// GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
	job, ok := obj.(*batch.Job)
	if !ok {
		return nil, nil, fmt.Errorf("given object is not a job.")
	}
	return labels.Set(job.ObjectMeta.Labels), JobToSelectableFields(job), nil
}


MatchJob

// MatchJob is the filter used by the generic etcd backend to route
// watch events from etcd to clients of the apiserver only interested in specific
// labels/fields.
func MatchJob(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
	return storage.SelectionPredicate{
		Label:    label,
		Field:    field,
		GetAttrs: GetAttrs,
	}
}


NewRESTStorage

func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) {
	apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(batch.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
	// If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities.
	// TODO refactor the plumbing to provide the information in the APIGroupInfo

	if storageMap, err := p.v1Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
		return genericapiserver.APIGroupInfo{}, err
	} else if len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[batchapiv1.SchemeGroupVersion.Version] = storageMap
	}

	if storageMap, err := p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter); err != nil {
		return genericapiserver.APIGroupInfo{}, err
	} else if len(storageMap) > 0 {
		apiGroupInfo.VersionedResourcesStorageMap[batchapiv1beta1.SchemeGroupVersion.Version] = storageMap
	}

	return apiGroupInfo, nil
}


func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
	storage := map[string]rest.Storage{}

	// jobs
	if resource := "jobs"; apiResourceConfigSource.ResourceEnabled(batchapiv1.SchemeGroupVersion.WithResource(resource)) {
		jobsStorage, jobsStatusStorage, err := jobstore.NewREST(restOptionsGetter)
		if err != nil {
			return storage, err
		}
		storage[resource] = jobsStorage
		storage[resource+"/status"] = jobsStatusStorage
	}

	// cronjobs
	if resource := "cronjobs"; apiResourceConfigSource.ResourceEnabled(batchapiv1.SchemeGroupVersion.WithResource(resource)) {
		cronJobsStorage, cronJobsStatusStorage, err := cronjobstore.NewREST(restOptionsGetter)
		if err != nil {
			return storage, err
		}
		storage[resource] = cronJobsStorage
		storage[resource+"/status"] = cronJobsStatusStorage
	}
	return storage, nil
}


func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) {
	storage := map[string]rest.Storage{}

	// cronjobs
	if resource := "cronjobs"; apiResourceConfigSource.ResourceEnabled(batchapiv1beta1.SchemeGroupVersion.WithResource(resource)) {
		cronJobsStorage, cronJobsStatusStorage, err := cronjobstore.NewREST(restOptionsGetter)
		if err != nil {
			return storage, err
		}
		storage[resource] = cronJobsStorage
		storage[resource+"/status"] = cronJobsStatusStorage
	}

	return storage, nil
}


GroupName

func (p RESTStorageProvider) GroupName() string {
	return batch.GroupName
}


init

func init() {
	registerMetricsOnce.Do(func() {
		legacyregistry.MustRegister(csrDurationRequested)
		legacyregistry.MustRegister(csrDurationHonored)
	})
}


countCSRDurationMetric

func countCSRDurationMetric(requested, honored counterVecMetric) genericregistry.BeginUpdateFunc {
	return func(ctx context.Context, obj, old runtime.Object, options *metav1.UpdateOptions) (genericregistry.FinishFunc, error) {
		return func(ctx context.Context, success bool) {
			if !success {
				return // ignore failures
			}

			if dryrun.IsDryRun(options.DryRun) {
				return // ignore things that would not get persisted
			}

			oldCSR, ok := old.(*certificates.CertificateSigningRequest)
			if !ok {
				return
			}

			// if the old CSR already has a certificate, do not double count it
			if len(oldCSR.Status.Certificate) > 0 {
				return
			}

			if oldCSR.Spec.ExpirationSeconds == nil {
				return // ignore CSRs that are not using the CSR duration feature
			}

			newCSR, ok := obj.(*certificates.CertificateSigningRequest)
			if !ok {
				return
			}
			issuedCert := newCSR.Status.Certificate

			// new CSR has no issued certificate yet so do not count it.
			// note that this means that we will ignore CSRs that set a duration
			// but never get approved/signed.  this is fine because the point
			// of these metrics is to understand if the duration is honored
			// by the signer.  we are not checking the behavior of the approver.
			if len(issuedCert) == 0 {
				return
			}

			signer := compressSignerName(oldCSR.Spec.SignerName)

			// at this point we know that this CSR is going to be persisted and
			// the cert was just issued and the client requested a duration
			requested.WithLabelValues(signer).Inc()

			certs, err := cert.ParseCertsPEM(issuedCert)
			if err != nil {
				utilruntime.HandleError(fmt.Errorf("metrics recording failed to parse certificate for CSR %s: %w", oldCSR.Name, err))
				return
			}

			// now we check to see if the signer honored the requested duration
			certificate := certs[0]
			wantDuration := csr.ExpirationSecondsToDuration(*oldCSR.Spec.ExpirationSeconds)
			actualDuration := certificate.NotAfter.Sub(certificate.NotBefore)
			if isDurationHonored(wantDuration, actualDuration) {
				honored.WithLabelValues(signer).Inc()
			}
		}, nil
	}
}


isDurationHonored

func isDurationHonored(want, got time.Duration) bool {
	delta := want - got
	if delta < 0 {
		delta = -delta
	}

	// short-lived cert backdating + 5% of want
	maxDelta := 5*time.Minute + (want / 20)

	return delta < maxDelta
}


compressSignerName

func compressSignerName(name string) string {
	if strings.HasPrefix(name, "kubernetes.io/") {
		return name
	}

	return "other"
}


NewREST

// NewREST returns a registry which will store CertificateSigningRequest in the given helper.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *ApprovalREST, error) {
	store := &genericregistry.Store{
		NewFunc:                   func() runtime.Object { return &certificates.CertificateSigningRequest{} },
		NewListFunc:               func() runtime.Object { return &certificates.CertificateSigningRequestList{} },
		DefaultQualifiedResource:  certificates.Resource("certificatesigningrequests"),
		SingularQualifiedResource: certificates.Resource("certificatesigningrequest"),

		CreateStrategy:      csrregistry.Strategy,
		UpdateStrategy:      csrregistry.Strategy,
		DeleteStrategy:      csrregistry.Strategy,
		ResetFieldsStrategy: csrregistry.Strategy,

		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: csrregistry.GetAttrs}
	if err := store.CompleteWithOptions(options); err != nil {
		return nil, nil, nil, err
	}

	// Subresources use the same store and creation strategy, which only
	// allows empty subs. Updates to an existing subresource are handled by
	// dedicated strategies.
	statusStore := *store
	statusStore.UpdateStrategy = csrregistry.StatusStrategy
	statusStore.ResetFieldsStrategy = csrregistry.StatusStrategy
	statusStore.BeginUpdate = countCSRDurationMetric(csrDurationRequested, csrDurationHonored)

	approvalStore := *store
	approvalStore.UpdateStrategy = csrregistry.ApprovalStrategy
	approvalStore.ResetFieldsStrategy = csrregistry.ApprovalStrategy

	return &REST{store}, &StatusREST{store: &statusStore}, &ApprovalREST{store: &approvalStore}, nil
}


ShortNames

// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
func (r *REST) ShortNames() []string {
	return []string{"csr"}
}


New

// New creates a new CertificateSigningRequest object.
func (r *StatusREST) New() runtime.Object {
	return &certificates.CertificateSigningRequest{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *StatusREST) Destroy() {
	// Given that underlying store is shared with REST,
	// we don't destroy it here explicitly.
}


Get

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}


Update

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}


GetResetFields

// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return r.store.GetResetFields()
}


ConvertToTable

func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
	return r.store.ConvertToTable(ctx, object, tableOptions)
}


New

// New creates a new CertificateSigningRequest object.
func (r *ApprovalREST) New() runtime.Object {
	return &certificates.CertificateSigningRequest{}
}


Destroy

// Destroy cleans up resources on shutdown.
func (r *ApprovalREST) Destroy() {
	// Given that underlying store is shared with REST,
	// we don't destroy it here explicitly.
}


Get

// Get retrieves the object from the storage. It is required to support Patch.
func (r *ApprovalREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}


Update

// Update alters the approval subset of an object.
func (r *ApprovalREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}


GetResetFields

// GetResetFields implements rest.ResetFieldsStrategy
func (r *ApprovalREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return r.store.GetResetFields()
}


NamespaceScoped

// NamespaceScoped is false for CSRs.
func (csrStrategy) NamespaceScoped() bool {
	return false
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (csrStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		"certificates.k8s.io/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
			fieldpath.MakePathOrDie("status"),
		),
		"certificates.k8s.io/v1beta1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
			fieldpath.MakePathOrDie("status"),
		),
	}

	return fields
}


AllowCreateOnUpdate

// AllowCreateOnUpdate is false for CSRs.
func (csrStrategy) AllowCreateOnUpdate() bool {
	return false
}


PrepareForCreate

// PrepareForCreate clears fields that are not allowed to be set by end users
// on creation.
func (csrStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
	csr := obj.(*certificates.CertificateSigningRequest)

	// Clear any user-specified info
	csr.Spec.Username = ""
	csr.Spec.UID = ""
	csr.Spec.Groups = nil
	csr.Spec.Extra = nil
	// Inject user.Info from request context
	if user, ok := genericapirequest.UserFrom(ctx); ok {
		csr.Spec.Username = user.GetName()
		csr.Spec.UID = user.GetUID()
		csr.Spec.Groups = user.GetGroups()
		if extra := user.GetExtra(); len(extra) > 0 {
			csr.Spec.Extra = map[string]certificates.ExtraValue{}
			for k, v := range extra {
				csr.Spec.Extra[k] = v
			}
		}
	}

	// Be explicit that users cannot create pre-approved certificate requests.
	csr.Status = certificates.CertificateSigningRequestStatus{}
	csr.Status.Conditions = []certificates.CertificateSigningRequestCondition{}
}


PrepareForUpdate

// PrepareForUpdate clears fields that are not allowed to be set by end users
// on update. Certificate requests are immutable after creation except via subresources.
func (csrStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newCSR := obj.(*certificates.CertificateSigningRequest)
	oldCSR := old.(*certificates.CertificateSigningRequest)

	newCSR.Spec = oldCSR.Spec
	newCSR.Status = oldCSR.Status
}


Validate

// Validate validates a new CSR. Validation must check for a correct signature.
func (csrStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
	csr := obj.(*certificates.CertificateSigningRequest)
	return validation.ValidateCertificateSigningRequestCreate(csr)
}


WarningsOnCreate

// WarningsOnCreate returns warnings for the creation of the given object.
func (csrStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil }


Canonicalize

// Canonicalize normalizes the object after validation (which includes a signature check).
func (csrStrategy) Canonicalize(obj runtime.Object) {}


ValidateUpdate

// ValidateUpdate is the default update validation for an end user.
func (csrStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	oldCSR := old.(*certificates.CertificateSigningRequest)
	newCSR := obj.(*certificates.CertificateSigningRequest)
	return validation.ValidateCertificateSigningRequestUpdate(newCSR, oldCSR)
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (csrStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


AllowUnconditionalUpdate

// If AllowUnconditionalUpdate() is true and the object specified by
// the user does not have a resource version, then generic Update()
// populates it with the latest version. Else, it checks that the
// version specified by the user matches the version of latest etcd
// object.
func (csrStrategy) AllowUnconditionalUpdate() bool {
	return true
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (csrStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		"certificates.k8s.io/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
			fieldpath.MakePathOrDie("status", "conditions"),
		),
		"certificates.k8s.io/v1beta1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
			fieldpath.MakePathOrDie("status", "conditions"),
		),
	}

	return fields
}


PrepareForUpdate

func (csrStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newCSR := obj.(*certificates.CertificateSigningRequest)
	oldCSR := old.(*certificates.CertificateSigningRequest)

	// Updating /status should not modify spec
	newCSR.Spec = oldCSR.Spec

	// Specifically preserve existing Approved/Denied conditions.
	// Adding/removing Approved/Denied conditions will cause these to fail,
	// and the change in Approved/Denied conditions will produce a validation error
	preserveConditionInstances(newCSR, oldCSR, certificates.CertificateApproved)
	preserveConditionInstances(newCSR, oldCSR, certificates.CertificateDenied)

	populateConditionTimestamps(newCSR, oldCSR)
}


preserveConditionInstances

// preserveConditionInstances copies instances of the specified condition type from oldCSR to newCSR.
// or returns false if the newCSR added or removed instances
func preserveConditionInstances(newCSR, oldCSR *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) bool {
	oldIndices := findConditionIndices(oldCSR, conditionType)
	newIndices := findConditionIndices(newCSR, conditionType)
	if len(oldIndices) != len(newIndices) {
		// instances were added or removed, we cannot preserve the existing values
		return false
	}
	// preserve the old condition values
	for i, oldIndex := range oldIndices {
		newCSR.Status.Conditions[newIndices[i]] = oldCSR.Status.Conditions[oldIndex]
	}
	return true
}


findConditionIndices

// findConditionIndices returns the indices of instances of the specified condition type
func findConditionIndices(csr *certificates.CertificateSigningRequest, conditionType certificates.RequestConditionType) []int {
	var retval []int
	for i, c := range csr.Status.Conditions {
		if c.Type == conditionType {
			retval = append(retval, i)
		}
	}
	return retval
}


populateConditionTimestamps

// populateConditionTimestamps sets LastUpdateTime and LastTransitionTime in newCSR if missing
func populateConditionTimestamps(newCSR, oldCSR *certificates.CertificateSigningRequest) {
	now := nowFunc()
	for i := range newCSR.Status.Conditions {
		if newCSR.Status.Conditions[i].LastUpdateTime.IsZero() {
			newCSR.Status.Conditions[i].LastUpdateTime = now
		}

		// preserve existing lastTransitionTime if the condition with this type/status already exists,
		// otherwise set to now.
		if newCSR.Status.Conditions[i].LastTransitionTime.IsZero() {
			lastTransition := now
			for _, oldCondition := range oldCSR.Status.Conditions {
				if oldCondition.Type == newCSR.Status.Conditions[i].Type &&
					oldCondition.Status == newCSR.Status.Conditions[i].Status &&
					!oldCondition.LastTransitionTime.IsZero() {
					lastTransition = oldCondition.LastTransitionTime
					break
				}
			}
			newCSR.Status.Conditions[i].LastTransitionTime = lastTransition
		}
	}
}


ValidateUpdate

func (csrStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	return validation.ValidateCertificateSigningRequestStatusUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest))
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (csrStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


Canonicalize

// Canonicalize normalizes the object after validation.
func (csrStatusStrategy) Canonicalize(obj runtime.Object) {
}


GetResetFields

// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (csrApprovalStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		"certificates.k8s.io/v1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
			fieldpath.MakePathOrDie("status", "certificate"),
		),
		"certificates.k8s.io/v1beta1": fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
			fieldpath.MakePathOrDie("status", "certificate"),
		),
	}

	return fields
}


PrepareForUpdate

// PrepareForUpdate prepares the new certificate signing request by limiting
// the data that is updated to only the conditions and populating condition timestamps
func (csrApprovalStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newCSR := obj.(*certificates.CertificateSigningRequest)
	oldCSR := old.(*certificates.CertificateSigningRequest)

	populateConditionTimestamps(newCSR, oldCSR)
	newConditions := newCSR.Status.Conditions

	// Updating the approval should only update the conditions.
	newCSR.Spec = oldCSR.Spec
	newCSR.Status = oldCSR.Status
	newCSR.Status.Conditions = newConditions
}


ValidateUpdate

func (csrApprovalStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
	return validation.ValidateCertificateSigningRequestApprovalUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest))
}


WarningsOnUpdate

// WarningsOnUpdate returns warnings for the given update.
func (csrApprovalStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


GetAttrs

// GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
	csr, ok := obj.(*certificates.CertificateSigningRequest)
	if !ok {
		return nil, nil, fmt.Errorf("not a certificatesigningrequest")
	}
	return labels.Set(csr.Labels), SelectableFields(csr), nil
}


SelectableFields

// SelectableFields returns a field set that can be used for filter selection
func SelectableFields(obj *certificates.CertificateSigningRequest) fields.Set {
	objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&obj.ObjectMeta, false)
	csrSpecificFieldsSet := fields.Set{
		"spec.signerName": obj.Spec.SignerName,
	}
	return generic.MergeFieldsSets(objectMetaFieldsSet, csrSpecificFieldsSet)
}


NewREST

// NewREST returns a RESTStorage object for ClusterTrustBundle objects.
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
	store := &genericregistry.Store{
		NewFunc:                   func() runtime.Object { return &api.ClusterTrustBundle{} },
		NewListFunc:               func() runtime.Object { return &api.ClusterTrustBundleList{} },
		DefaultQualifiedResource:  api.Resource("clustertrustbundles"),
		SingularQualifiedResource: api.Resource("clustertrustbundle"),

		CreateStrategy: clustertrustbundle.Strategy,
		UpdateStrategy: clustertrustbundle.Strategy,
		DeleteStrategy: clustertrustbundle.Strategy,

		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
	}
	options := &generic.StoreOptions{
		RESTOptions: optsGetter,
		AttrFunc:    getAttrs,
	}
	if err := store.CompleteWithOptions(options); err != nil {
		return nil, err
	}
	return &REST{store}, nil
}


getAttrs

func getAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
	bundle, ok := obj.(*api.ClusterTrustBundle)
	if !ok {
		return nil, nil, fmt.Errorf("not a clustertrustbundle")
	}

	selectableFields := generic.MergeFieldsSets(generic.ObjectMetaFieldsSet(&bundle.ObjectMeta, false), fields.Set{
		"spec.signerName": bundle.Spec.SignerName,
	})

	return labels.Set(bundle.Labels), selectableFields, nil
}


NamespaceScoped

func (strategy) NamespaceScoped() bool {
	return false
}


PrepareForCreate

func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {}


Validate

func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
	bundle := obj.(*certificates.ClusterTrustBundle)
	return certvalidation.ValidateClusterTrustBundle(bundle, certvalidation.ValidateClusterTrustBundleOptions{})
}


WarningsOnCreate

func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
	return nil
}


Canonicalize

func (strategy) Canonicalize(obj runtime.Object) {}


AllowCreateOnUpdate

func (strategy) AllowCreateOnUpdate() bool {
	return false
}


PrepareForUpdate

func (s strategy) PrepareForUpdate(ctx context.Context, new, old runtime.Object) {}


ValidateUpdate

func (s strategy) ValidateUpdate(ctx context.Context, new, old runtime.Object) field.ErrorList {
	newBundle := new.(*certificates.ClusterTrustBundle)
	oldBundle := old.(*certificates.ClusterTrustBundle)
	return certvalidation.ValidateClusterTrustBundleUpdate(newBundle, oldBundle)
}


WarningsOnUpdate

func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	return nil
}


AllowUnconditionalUpdate

func (strategy) AllowUnconditionalUpdate() bool {
	return false
}