Change:
This applies to StatefulSetStartOrdinal feature gate. Field validation should be done regardless of the feature gate state. From
This issue is currently awaiting triage. If a SIG or subproject determines this is a relevant issue, they will accept it by applying the label and provide further guidance. The label can be by org members by writing in a comment. <detailsInstructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/assign
/sig apps

Discussion:
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" <del> utilfeature "k8s.io/apiserver/pkg/util/feature" </del> "k8s.io/kubernetes/pkg/apis/apps" api "k8s.io/kubernetes/pkg/apis/core" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" <del> "k8s.io/kubernetes/pkg/features" </del> ) // ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid.

allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) <del> if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetStartOrdinal) { if spec.Ordinals != nil { replicaStartOrdinal := spec.Ordinals.Start allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(replicaStartOrdinal), fldPath.Child("ordinals.start"))...) } </del> <ins> if spec.Ordinals != nil { replicaStartOrdinal := spec.Ordinals.Start allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(replicaStartOrdinal), fldPath.Child("ordinals.start"))...) </ins> } if spec.Selector == nil {

newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template               // +k8s:verify-mutation:reason=clone newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy   // +k8s:verify-mutation:reason=clone newStatefulSetClone.Spec.MinReadySeconds = oldStatefulSet.Spec.MinReadySeconds // +k8s:verify-mutation:reason=clone <del> if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetStartOrdinal) { newStatefulSetClone.Spec.Ordinals = oldStatefulSet.Spec.Ordinals // +k8s:verify-mutation:reason=clone } </del> <ins> newStatefulSetClone.Spec.Ordinals = oldStatefulSet.Spec.Ordinals               // +k8s:verify-mutation:reason=clone </ins> newStatefulSetClone.Spec.PersistentVolumeClaimRetentionPolicy = oldStatefulSet.Spec.PersistentVolumeClaimRetentionPolicy // +k8s:verify-mutation:reason=clone if !apiequality.Semantic.DeepEqual(newStatefulSetClone.Spec, oldStatefulSet.Spec) { <del> if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetStartOrdinal) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden")) } else { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden")) } </del> <ins> allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden")) </ins> } return allErrs

const enableStatefulSetAutoDeletePVC = "[enable StatefulSetAutoDeletePVC]" <del> const enableStatefulSetStartOrdinal = "[enable StatefulSetStartOrdinal]" </del> type testCase struct { name string set  apps.StatefulSet

}, }, { <del> name: "ordinals.start positive value " + enableStatefulSetStartOrdinal, </del> <ins> name: "ordinals.start positive value", </ins> set: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{

}, }, { <del> name: "invalid ordinals.start " + enableStatefulSetStartOrdinal, </del> <ins> name: "invalid ordinals.start ", </ins> set: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{

if strings.Contains(name, enableStatefulSetAutoDeletePVC) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)() } <del> if strings.Contains(name, enableStatefulSetStartOrdinal) { defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, true)() } </del> errs := ValidateStatefulSet(&testCase.set, pod.GetValidationOptionsFromPodTemplate(&testCase.set.Spec.Template, nil)) wantErrs := testCase.errs

}, }, }, <ins> { name: "update existing instance with .spec.ordinals.start", old: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc.123.example", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ PodManagementPolicy: apps.OrderedReadyPodManagement, Selector:            &metav1.LabelSelector{MatchLabels: validLabels}, Template:            validPodTemplate.Template, UpdateStrategy:      apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, }, }, update: apps.StatefulSet{ ObjectMeta: metav1.ObjectMeta{Name: "abc.123.example", Namespace: metav1.NamespaceDefault}, Spec: apps.StatefulSetSpec{ PodManagementPolicy: apps.OrderedReadyPodManagement, Selector:            &metav1.LabelSelector{MatchLabels: validLabels}, Template:            validPodTemplate.Template, UpdateStrategy:      apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, Ordinals: &apps.StatefulSetOrdinals{ Start: 3, }, }, }, }, </ins> } errorCases := []testCase{