Record and require all kube-feature dependencies

This commit is contained in:
Tim Allclair 2025-09-05 11:30:28 -07:00
parent 4870d987d0
commit 36e3a8f269
5 changed files with 390 additions and 39 deletions

View file

@ -41,19 +41,4 @@ if [[ -n "${direct_sets}" ]]; then
rc=1
fi
export LC_ALL=C
# ensure all generic features are added in alphabetic order
lines=$(git grep 'genericfeatures[.].*:' -- pkg/features/kube_features.go)
sorted_lines=$(echo "$lines" | sort)
if [[ "$lines" != "$sorted_lines" ]]; then
echo "Generic features in pkg/features/kube_features.go not sorted" >&2
echo >&2
echo "Expected:" >&2
echo "$sorted_lines" >&2
echo >&2
echo "Got:" >&2
echo "$lines" >&2
rc=1
fi
exit $rc

View file

@ -23,7 +23,6 @@ import (
"strings"
apiextensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
"k8s.io/kubernetes/pkg/features"
@ -71,20 +70,6 @@ func validateAPIPriorityAndFairness(options *Options) []error {
return nil
}
func validateNodeSelectorAuthorizationFeature() []error {
if utilfeature.DefaultFeatureGate.Enabled(features.AuthorizeNodeWithSelectors) && !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors) {
return []error{fmt.Errorf("AuthorizeNodeWithSelectors feature requires AuthorizeWithSelectors feature to be enabled")}
}
return nil
}
func validatePodCertificateRequestFeature() []error {
if utilfeature.DefaultFeatureGate.Enabled(features.PodCertificateRequest) && !utilfeature.DefaultFeatureGate.Enabled(features.AuthorizeNodeWithSelectors) {
return []error{fmt.Errorf("PodCertificateRequest feature requires AuthorizeNodeWithSelectors feature to be enabled")}
}
return nil
}
func validateUnknownVersionInteroperabilityProxyFlags(options *Options) []error {
err := []error{}
if !utilfeature.DefaultFeatureGate.Enabled(features.UnknownVersionInteroperabilityProxy) {
@ -151,8 +136,6 @@ func (s *Options) Validate() []error {
errs = append(errs, validateTokenRequest(s)...)
errs = append(errs, s.Metrics.Validate()...)
errs = append(errs, validateUnknownVersionInteroperabilityProxyFlags(s)...)
errs = append(errs, validateNodeSelectorAuthorizationFeature()...)
errs = append(errs, validatePodCertificateRequestFeature()...)
errs = append(errs, validateServiceAccountTokenSigningConfig(s)...)
errs = append(errs, validateCoordinatedLeadershipFlags(s)...)

View file

@ -1999,10 +1999,387 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
//
// Entries are alphabetized.
var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]featuregate.Feature{
DisableAllocatorDualWrite: {MultiCIDRServiceAllocator},
AllowDNSOnlyNodeCSR: {},
AllowInsecureKubeletCertificateSigningRequests: {},
AllowOverwriteTerminationGracePeriodSeconds: {},
AllowServiceLBStatusOnNonLB: {},
AnyVolumeDataSource: {},
AuthorizeNodeWithSelectors: {genericfeatures.AuthorizeWithSelectors},
CPUCFSQuotaPeriod: {},
CPUManagerPolicyAlphaOptions: {},
CPUManagerPolicyBetaOptions: {},
CPUManagerPolicyOptions: {},
CSIMigrationPortworx: {},
CSIVolumeHealth: {},
ClearingNominatedNodeNameAfterBinding: {},
ClusterTrustBundle: {},
ClusterTrustBundleProjection: {ClusterTrustBundle},
ContainerCheckpoint: {},
ContainerRestartRules: {},
ContainerStopSignals: {},
CoordinatedLeaderElection: {},
CrossNamespaceVolumeDataSource: {},
DRAAdminAccess: {DynamicResourceAllocation},
DRAConsumableCapacity: {DynamicResourceAllocation},
DRADeviceBindingConditions: {DynamicResourceAllocation, DRAResourceClaimDeviceStatus},
DRADeviceTaints: {DynamicResourceAllocation},
DRAExtendedResource: {DynamicResourceAllocation},
DRAPartitionableDevices: {DynamicResourceAllocation},
DRAPrioritizedList: {DynamicResourceAllocation},
DRAResourceClaimDeviceStatus: {}, // Soft dependency on DynamicResourceAllocation due to on/off-by-default conflict.
DRASchedulerFilterTimeout: {DynamicResourceAllocation},
DeploymentReplicaSetTerminatingReplicas: {},
DisableAllocatorDualWrite: {MultiCIDRServiceAllocator},
DisableCPUQuotaWithExclusiveCPUs: {},
DisableNodeKubeProxyVersion: {},
DynamicResourceAllocation: {},
EnvFiles: {},
EventedPLEG: {},
ExecProbeTimeout: {},
ExternalServiceAccountTokenSigner: {},
GitRepoVolumeDriver: {},
GracefulNodeShutdown: {},
GracefulNodeShutdownBasedOnPodPriority: {GracefulNodeShutdown},
HPAConfigurableTolerance: {},
HPAScaleToZero: {},
HonorPVReclaimPolicy: {},
HostnameOverride: {},
ImageMaximumGCAge: {},
ImageVolume: {},
InPlacePodVerticalScaling: {},
InPlacePodVerticalScalingAllocatedStatus: {InPlacePodVerticalScaling},
InPlacePodVerticalScalingExclusiveCPUs: {InPlacePodVerticalScaling},
InPlacePodVerticalScalingExclusiveCPUs: {InPlacePodVerticalScaling},
InPlacePodVerticalScalingExclusiveMemory: {InPlacePodVerticalScaling, MemoryManager},
InTreePluginPortworxUnregister: {},
JobBackoffLimitPerIndex: {},
JobManagedBy: {},
JobPodReplacementPolicy: {},
JobSuccessPolicy: {},
KubeletCgroupDriverFromCRI: {},
KubeletCrashLoopBackOffMax: {},
KubeletEnsureSecretPulledImages: {},
KubeletFineGrainedAuthz: {},
KubeletInUserNamespace: {},
KubeletPSI: {},
KubeletPodResourcesDynamicResources: {},
KubeletPodResourcesGet: {},
KubeletPodResourcesListUseActivePods: {},
KubeletRegistrationGetOnExistsOnly: {},
KubeletSeparateDiskGC: {},
KubeletServiceAccountTokenForCredentialProviders: {},
KubeletTracing: {},
LoadBalancerIPMode: {},
LocalStorageCapacityIsolationFSQuotaMonitoring: {},
LogarithmicScaleDown: {},
MatchLabelKeysInPodAffinity: {},
MatchLabelKeysInPodTopologySpread: {},
MatchLabelKeysInPodTopologySpreadSelectorMerge: {MatchLabelKeysInPodTopologySpread},
MaxUnavailableStatefulSet: {},
MemoryManager: {},
MemoryQoS: {},
MultiCIDRServiceAllocator: {},
MutableCSINodeAllocatableCount: {},
NFTablesProxyMode: {},
NodeInclusionPolicyInPodTopologySpread: {},
NodeLogQuery: {},
NodeSwap: {},
NominatedNodeNameForExpectation: {},
OrderedNamespaceDeletion: {},
PodAndContainerStatsFromCRI: {},
PodCertificateRequest: {AuthorizeNodeWithSelectors},
PodDeletionCost: {},
PodLevelResources: {},
PodLifecycleSleepAction: {},
PodLifecycleSleepActionAllowZero: {PodLifecycleSleepAction},
PodLogsQuerySplitStreams: {},
PodObservedGenerationTracking: {},
PodReadyToStartContainersCondition: {},
PodSchedulingReadiness: {},
PodTopologyLabelsAdmission: {},
PortForwardWebsockets: {},
PreferSameTrafficDistribution: {},
PreventStaticPodAPIReferences: {},
ProcMountType: {UserNamespacesSupport},
QOSReserved: {},
RecoverVolumeExpansionFailure: {},
RecursiveReadOnlyMounts: {},
ReduceDefaultCrashLoopBackOffDecay: {},
RelaxedDNSSearchValidation: {},
RelaxedEnvironmentVariableValidation: {},
RelaxedServiceNameValidation: {},
ReloadKubeletServerCertificateFile: {},
ResourceHealthStatus: {DynamicResourceAllocation},
RotateKubeletServerCertificate: {},
RuntimeClassInImageCriAPI: {},
SELinuxChangePolicy: {},
SELinuxMount: {},
SELinuxMountReadWriteOncePod: {},
SchedulerAsyncAPICalls: {},
SchedulerAsyncPreemption: {},
SchedulerPopFromBackoffQ: {},
SchedulerQueueingHints: {},
SeparateTaintEvictionController: {},
ServiceAccountNodeAudienceRestriction: {},
ServiceAccountTokenJTI: {},
ServiceAccountTokenNodeBinding: {ServiceAccountTokenNodeBindingValidation},
ServiceAccountTokenNodeBindingValidation: {},
ServiceAccountTokenPodNodeInfo: {},
ServiceTrafficDistribution: {},
SidecarContainers: {},
StorageCapacityScoring: {},
StorageNamespaceIndex: {},
StorageVersionMigrator: {},
StreamingCollectionEncodingToJSON: {},
StreamingCollectionEncodingToProtobuf: {},
StrictIPCIDRValidation: {},
SupplementalGroupsPolicy: {},
SystemdWatchdog: {},
TopologyAwareHints: {},
TopologyManagerPolicyAlphaOptions: {},
TopologyManagerPolicyBetaOptions: {},
TopologyManagerPolicyOptions: {},
TranslateStreamCloseWebsocketRequests: {},
UnknownVersionInteroperabilityProxy: {},
UserNamespacesPodSecurityStandards: {},
UserNamespacesSupport: {},
VolumeAttributesClass: {},
WinDSR: {},
WinOverlay: {},
WindowsCPUAndMemoryAffinity: {MemoryManager},
WindowsGracefulNodeShutdown: {GracefulNodeShutdown},
WindowsHostNetwork: {},
apiextensionsfeatures.CRDValidationRatcheting: {},
apiextensionsfeatures.CustomResourceFieldSelectors: {},
genericfeatures.APIResponseCompression: {},
genericfeatures.APIServerIdentity: {},
genericfeatures.APIServerTracing: {},
genericfeatures.APIServingWithRoutine: {},
genericfeatures.AggregatedDiscoveryRemoveBetaType: {},
genericfeatures.AllowParsingUserUIDFromCertAuth: {},
genericfeatures.AllowUnsafeMalformedObjectDeletion: {},
genericfeatures.AnonymousAuthConfigurableEndpoints: {},
genericfeatures.AuthorizeWithSelectors: {},
genericfeatures.BtreeWatchCache: {},
genericfeatures.CBORServingAndStorage: {},
genericfeatures.ConcurrentWatchObjectDecode: {},
genericfeatures.ConsistentListFromCache: {},
genericfeatures.DeclarativeValidation: {},
genericfeatures.DeclarativeValidationTakeover: {genericfeatures.DeclarativeValidation},
genericfeatures.DetectCacheInconsistency: {},
genericfeatures.KMSv1: {},
genericfeatures.ListFromCacheSnapshot: {},
genericfeatures.MutatingAdmissionPolicy: {},
genericfeatures.OpenAPIEnums: {},
genericfeatures.RemoteRequestHeaderUID: {},
genericfeatures.ResilientWatchCacheInitialization: {},
genericfeatures.RetryGenerateName: {},
genericfeatures.SeparateCacheWatchRPC: {},
genericfeatures.SizeBasedListCostEstimate: {},
genericfeatures.StorageVersionAPI: {genericfeatures.APIServerIdentity},
genericfeatures.StorageVersionHash: {},
genericfeatures.StrictCostEnforcementForVAP: {},
genericfeatures.StrictCostEnforcementForWebhooks: {},
genericfeatures.StructuredAuthenticationConfiguration: {},
genericfeatures.StructuredAuthenticationConfigurationEgressSelector: {genericfeatures.StructuredAuthenticationConfiguration},
genericfeatures.StructuredAuthorizationConfiguration: {},
genericfeatures.TokenRequestServiceAccountUIDValidation: {},
genericfeatures.UnauthenticatedHTTP2DOSMitigation: {},
genericfeatures.WatchCacheInitializationPostStartHook: {},
genericfeatures.WatchFromStorageWithoutResourceVersion: {},
genericfeatures.WatchList: {},
kcmfeatures.CloudControllerManagerWebhook: {},
zpagesfeatures.ComponentFlagz: {},
zpagesfeatures.ComponentStatusz: {},
}
func init() {

View file

@ -17,6 +17,8 @@ limitations under the License.
package features
import (
"maps"
"slices"
"testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
@ -91,3 +93,12 @@ func TestEnsureAlphaGatesAreNotSwitchedOnByDefault(t *testing.T) {
}
}
}
func TestAllDependenciesRegistered(t *testing.T) {
registeredDependencies := utilfeature.DefaultFeatureGate.Dependencies()
for _, f := range slices.Sorted(maps.Keys(defaultVersionedKubernetesFeatureGates)) {
if _, depsRegistered := registeredDependencies[f]; !depsRegistered {
t.Errorf("Feature %s did not register dependencies. All features must record explicit feature dependencies, even if there are none.", f)
}
}
}

View file

@ -295,11 +295,6 @@ func (o *BuiltInAuthenticationOptions) Validate() []error {
}
}
// verify that if ServiceAccountTokenNodeBinding is enabled, ServiceAccountTokenNodeBindingValidation is also enabled.
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBinding) && !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBindingValidation) {
allErrors = append(allErrors, fmt.Errorf("the %q feature gate can only be enabled if the %q feature gate is also enabled", features.ServiceAccountTokenNodeBinding, features.ServiceAccountTokenNodeBindingValidation))
}
if o.WebHook != nil {
retryBackoff := o.WebHook.RetryBackoff
if retryBackoff != nil && retryBackoff.Steps <= 0 {