@@ -16,6 +16,7 @@ import (
1616 "github.com/stretchr/testify/assert"
1717 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1818 "k8s.io/apimachinery/pkg/runtime/schema"
19+ "k8s.io/apimachinery/pkg/util/sets"
1920 "k8s.io/klog/v2"
2021)
2122
@@ -599,13 +600,14 @@ func Test_include(t *testing.T) {
599600 singleNodeProfile := "single-node"
600601
601602 tests := []struct {
602- name string
603- exclude * string
604- requiredFeatureSet * string
605- profile * string
606- annotations map [string ]interface {}
607- caps * configv1.ClusterVersionCapabilitiesStatus
608- overrides []configv1.ComponentOverride
603+ name string
604+ exclude * string
605+ requiredFeatureSet * string
606+ profile * string
607+ annotations map [string ]interface {}
608+ caps * configv1.ClusterVersionCapabilitiesStatus
609+ overrides []configv1.ComponentOverride
610+ enabledFeatureGates sets.Set [string ]
609611
610612 expected error
611613 }{
@@ -809,6 +811,29 @@ func Test_include(t *testing.T) {
809811 },
810812 expected : fmt .Errorf ("overridden" ),
811813 },
814+ {
815+ name : "feature gate required but not enabled" ,
816+ enabledFeatureGates : sets .New ("FeatureGate1" ),
817+ annotations : map [string ]interface {}{
818+ "release.openshift.io/feature-gate" : "FeatureGate2" ,
819+ },
820+ expected : fmt .Errorf ("feature gate FeatureGate2 is required but not enabled" ),
821+ },
822+ {
823+ name : "feature gate enabled but manifest requires it to be disabled" ,
824+ enabledFeatureGates : sets .New ("FeatureGate1" ),
825+ annotations : map [string ]interface {}{
826+ "release.openshift.io/feature-gate" : "-FeatureGate1" ,
827+ },
828+ expected : fmt .Errorf ("feature gate FeatureGate1 is enabled but manifest requires it to be disabled" ),
829+ },
830+ {
831+ name : "feature gate required and is enabled" ,
832+ enabledFeatureGates : sets .New ("FeatureGate1" ),
833+ annotations : map [string ]interface {}{
834+ "release.openshift.io/feature-gate" : "FeatureGate1" ,
835+ },
836+ },
812837 {
813838 name : "all nil" ,
814839 profile : nil ,
@@ -838,7 +863,7 @@ func Test_include(t *testing.T) {
838863 err := m .populateFromObj ()
839864 assert .Equal (t , nil , err )
840865
841- err = m .Include (tt .exclude , tt .requiredFeatureSet , tt .profile , tt .caps , tt .overrides )
866+ err = m .Include (tt .exclude , tt .requiredFeatureSet , tt .profile , tt .caps , tt .overrides , tt . enabledFeatureGates )
842867 assert .Equal (t , tt .expected , err )
843868 })
844869 }
@@ -847,14 +872,15 @@ func Test_include(t *testing.T) {
847872func TestIncludeAllowUnknownCapabilities (t * testing.T ) {
848873
849874 tests := []struct {
850- name string
851- exclude * string
852- requiredFeatureSet * string
853- profile * string
854- annotations map [string ]interface {}
855- caps * configv1.ClusterVersionCapabilitiesStatus
856- overrides []configv1.ComponentOverride
857- allowUnknown bool
875+ name string
876+ exclude * string
877+ requiredFeatureSet * string
878+ profile * string
879+ annotations map [string ]interface {}
880+ caps * configv1.ClusterVersionCapabilitiesStatus
881+ overrides []configv1.ComponentOverride
882+ enabledFeatureGates sets.Set [string ]
883+ allowUnknown bool
858884
859885 expected error
860886 }{
@@ -897,7 +923,7 @@ func TestIncludeAllowUnknownCapabilities(t *testing.T) {
897923 },
898924 },
899925 }
900- err := m .IncludeAllowUnknownCapabilities (tt .exclude , tt .requiredFeatureSet , tt .profile , tt .caps , tt .overrides , tt .allowUnknown )
926+ err := m .IncludeAllowUnknownCapabilities (tt .exclude , tt .requiredFeatureSet , tt .profile , tt .caps , tt .overrides , tt .enabledFeatureGates , tt . allowUnknown )
901927 assert .Equal (t , tt .expected , err )
902928 })
903929 }
@@ -1009,3 +1035,169 @@ func TestSameResourceID(t *testing.T) {
10091035 })
10101036 }
10111037}
1038+
1039+ func TestCheckFeatureGates (t * testing.T ) {
1040+ tests := []struct {
1041+ name string
1042+ enabledGates sets.Set [string ]
1043+ annotations map [string ]string
1044+ expectedErr string
1045+ }{
1046+ {
1047+ name : "no feature gate annotation" ,
1048+ enabledGates : sets .New ("gate1" , "gate2" ),
1049+ annotations : map [string ]string {},
1050+ expectedErr : "" ,
1051+ },
1052+ {
1053+ name : "empty feature gate annotation" ,
1054+ enabledGates : sets .New ("gate1" , "gate2" ),
1055+ annotations : map [string ]string {featureGateAnnotation : "" },
1056+ expectedErr : "" ,
1057+ },
1058+ {
1059+ name : "required gate is enabled - should include" ,
1060+ enabledGates : sets .New ("TechPreviewFeatureGate" , "gate2" ),
1061+ annotations : map [string ]string {featureGateAnnotation : "TechPreviewFeatureGate" },
1062+ expectedErr : "" ,
1063+ },
1064+ {
1065+ name : "required gate is not enabled - should exclude" ,
1066+ enabledGates : sets .New ("gate1" , "gate2" ),
1067+ annotations : map [string ]string {featureGateAnnotation : "TechPreviewFeatureGate" },
1068+ expectedErr : "feature gate TechPreviewFeatureGate is required but not enabled" ,
1069+ },
1070+ {
1071+ name : "excluded gate is not enabled - should include" ,
1072+ enabledGates : sets .New ("gate1" , "gate2" ),
1073+ annotations : map [string ]string {featureGateAnnotation : "-TechPreviewFeatureGate" },
1074+ expectedErr : "" ,
1075+ },
1076+ {
1077+ name : "excluded gate is enabled - should exclude" ,
1078+ enabledGates : sets .New ("TechPreviewFeatureGate" , "gate2" ),
1079+ annotations : map [string ]string {featureGateAnnotation : "-TechPreviewFeatureGate" },
1080+ expectedErr : "feature gate TechPreviewFeatureGate is enabled but manifest requires it to be disabled" ,
1081+ },
1082+ {
1083+ name : "multiple requirements - all satisfied" ,
1084+ enabledGates : sets .New ("gate1" , "gate2" ),
1085+ annotations : map [string ]string {featureGateAnnotation : "gate1,gate2,-gate3" },
1086+ expectedErr : "" ,
1087+ },
1088+ {
1089+ name : "multiple requirements - inclusion not satisfied" ,
1090+ enabledGates : sets .New ("gate1" ),
1091+ annotations : map [string ]string {featureGateAnnotation : "gate1,gate2,-gate3" },
1092+ expectedErr : "feature gate gate2 is required but not enabled" ,
1093+ },
1094+ {
1095+ name : "multiple requirements - exclusion not satisfied" ,
1096+ enabledGates : sets .New ("gate1" , "gate2" , "gate3" ),
1097+ annotations : map [string ]string {featureGateAnnotation : "gate1,gate2,-gate3" },
1098+ expectedErr : "feature gate gate3 is enabled but manifest requires it to be disabled" ,
1099+ },
1100+ {
1101+ name : "whitespace handling" ,
1102+ enabledGates : sets .New ("gate1" , "gate2" ),
1103+ annotations : map [string ]string {featureGateAnnotation : " gate1 , gate2 , -gate3 " },
1104+ expectedErr : "" ,
1105+ },
1106+ {
1107+ name : "empty requirements in list" ,
1108+ enabledGates : sets .New ("gate1" ),
1109+ annotations : map [string ]string {featureGateAnnotation : "gate1,," },
1110+ expectedErr : "" ,
1111+ },
1112+ {
1113+ name : "complex scenario - OR logic for inclusion" ,
1114+ enabledGates : sets .New ("NewStorageFeature" ),
1115+ annotations : map [string ]string {featureGateAnnotation : "NewStorageFeature,AlternativeStorageFeature" },
1116+ expectedErr : "feature gate AlternativeStorageFeature is required but not enabled" ,
1117+ },
1118+ {
1119+ name : "complex scenario - AND logic for mixed requirements" ,
1120+ enabledGates : sets .New ("FeatureA" ),
1121+ annotations : map [string ]string {featureGateAnnotation : "FeatureA,-FeatureB" },
1122+ expectedErr : "" ,
1123+ },
1124+ }
1125+
1126+ for _ , tt := range tests {
1127+ t .Run (tt .name , func (t * testing.T ) {
1128+ err := checkFeatureGates (tt .enabledGates , tt .annotations )
1129+ if tt .expectedErr == "" {
1130+ if err != nil {
1131+ t .Errorf ("checkFeatureGates() expected no error, got %v" , err )
1132+ }
1133+ } else {
1134+ if err == nil {
1135+ t .Errorf ("checkFeatureGates() expected error %q, got nil" , tt .expectedErr )
1136+ } else if err .Error () != tt .expectedErr {
1137+ t .Errorf ("checkFeatureGates() expected error %q, got %q" , tt .expectedErr , err .Error ())
1138+ }
1139+ }
1140+ })
1141+ }
1142+ }
1143+
1144+ func TestManifestIncludeWithFeatureGates (t * testing.T ) {
1145+ // Test just the feature gate functionality by testing manifest with no other filtering requirements
1146+ tests := []struct {
1147+ name string
1148+ enabledGates sets.Set [string ]
1149+ annotations map [string ]interface {}
1150+ expectedErr string
1151+ }{
1152+ {
1153+ name : "manifest included when feature gate enabled" ,
1154+ enabledGates : sets .New ("TechPreviewFeatureGate" ),
1155+ annotations : map [string ]interface {}{
1156+ featureGateAnnotation : "TechPreviewFeatureGate" ,
1157+ },
1158+ expectedErr : "" ,
1159+ },
1160+ {
1161+ name : "manifest excluded when feature gate disabled" ,
1162+ enabledGates : sets .New ("SomeOtherGate" ),
1163+ annotations : map [string ]interface {}{
1164+ featureGateAnnotation : "TechPreviewFeatureGate" ,
1165+ },
1166+ expectedErr : "feature gate TechPreviewFeatureGate is required but not enabled" ,
1167+ },
1168+ }
1169+
1170+ for _ , tt := range tests {
1171+ metadata := map [string ]interface {}{
1172+ "name" : "test" ,
1173+ "namespace" : "test" ,
1174+ }
1175+
1176+ t .Run (tt .name , func (t * testing.T ) {
1177+ if tt .annotations != nil {
1178+ metadata ["annotations" ] = tt .annotations
1179+ }
1180+ manifest := Manifest {
1181+ Obj : & unstructured.Unstructured {
1182+ Object : map [string ]interface {}{
1183+ "metadata" : metadata ,
1184+ },
1185+ },
1186+ }
1187+
1188+ // Test only feature gates, set all other filters to nil
1189+ err := manifest .Include (nil , nil , nil , nil , nil , tt .enabledGates )
1190+ if tt .expectedErr == "" {
1191+ if err != nil {
1192+ t .Errorf ("manifest.Include() expected no error, got %v" , err )
1193+ }
1194+ } else {
1195+ if err == nil {
1196+ t .Errorf ("manifest.Include() expected error containing %q, got nil" , tt .expectedErr )
1197+ } else if ! strings .Contains (err .Error (), tt .expectedErr ) {
1198+ t .Errorf ("manifest.Include() expected error containing %q, got %q" , tt .expectedErr , err .Error ())
1199+ }
1200+ }
1201+ })
1202+ }
1203+ }
0 commit comments