77 "os"
88 "path/filepath"
99 "reflect"
10+ "strconv"
1011 "strings"
1112
1213 "k8s.io/apimachinery/pkg/util/sets"
@@ -23,10 +24,11 @@ import (
2324)
2425
2526const (
26- CapabilityAnnotation = "capability.openshift.io/name"
27- DefaultClusterProfile = "self-managed-high-availability"
28- featureSetAnnotation = "release.openshift.io/feature-set"
29- featureGateAnnotation = "release.openshift.io/feature-gates"
27+ CapabilityAnnotation = "capability.openshift.io/name"
28+ DefaultClusterProfile = "self-managed-high-availability"
29+ featureSetAnnotation = "release.openshift.io/feature-set"
30+ featureGateAnnotation = "release.openshift.io/feature-gates"
31+ majorVersionAnnotation = "release.openshift.io/major-version"
3032)
3133
3234var knownFeatureSets = sets.Set [string ]{}
@@ -222,12 +224,64 @@ func checkFeatureGates(enabledGates sets.Set[string], annotations map[string]str
222224 return nil
223225}
224226
227+ // checkMajorVersion validates if manifest should be included based on major version requirements
228+ func checkMajorVersion (clusterMajorVersion uint64 , annotations map [string ]string ) error {
229+ if annotations == nil {
230+ return nil // No annotations, include by default
231+ }
232+ majorVersionRequirements , ok := annotations [majorVersionAnnotation ]
233+ if ! ok {
234+ return nil // No requirements, include by default
235+ }
236+
237+ requirements := strings .Split (majorVersionRequirements , "," )
238+ includedVersions := sets .New [uint64 ]()
239+ excludedVersions := sets .New [uint64 ]()
240+
241+ for _ , req := range requirements {
242+ req = strings .TrimSpace (req )
243+ if req == "" {
244+ continue
245+ }
246+
247+ if strings .HasPrefix (req , "-" ) {
248+ excludedVersionStr := req [1 :]
249+ excludedVersion , err := strconv .ParseUint (excludedVersionStr , 10 , 64 )
250+ if err != nil {
251+ return fmt .Errorf ("invalid excluded major version %q in annotation: %v" , excludedVersionStr , err )
252+ }
253+ excludedVersions .Insert (excludedVersion )
254+ } else {
255+ includedVersionStr := req
256+ includedVersion , err := strconv .ParseUint (includedVersionStr , 10 , 64 )
257+ if err != nil {
258+ return fmt .Errorf ("invalid included major version %q in annotation: %v" , includedVersionStr , err )
259+ }
260+ includedVersions .Insert (includedVersion )
261+ }
262+ }
263+
264+ switch {
265+ case includedVersions .Intersection (excludedVersions ).Len () > 0 :
266+ return fmt .Errorf ("inclusion and exclusion requirements overlap: %v" , includedVersions .Intersection (excludedVersions ).UnsortedList ())
267+ case includedVersions .Has (clusterMajorVersion ):
268+ return nil // Found matching version, include this manifest
269+ case excludedVersions .Has (clusterMajorVersion ):
270+ return fmt .Errorf ("major version %d matches excluded version %d" , clusterMajorVersion , clusterMajorVersion )
271+ case len (includedVersions ) > 0 :
272+ return fmt .Errorf ("major version %d does not match any required versions" , clusterMajorVersion )
273+ default :
274+ // No positive requirement and did not match any excluded versions
275+ return nil
276+ }
277+ }
278+
225279// Include returns an error if the manifest fails an inclusion filter and should be excluded from further
226280// processing by cluster version operator. Pointer arguments can be set nil to avoid excluding based on that
227281// filter. For example, setting profile non-nil and capabilities nil will return an error if the manifest's
228282// profile does not match, but will never return an error about capability issues.
229- func (m * Manifest ) Include (excludeIdentifier * string , requiredFeatureSet * string , profile * string , capabilities * configv1.ClusterVersionCapabilitiesStatus , overrides []configv1.ComponentOverride , enabledFeatureGates sets.Set [string ]) error {
230- return m .IncludeAllowUnknownCapabilities (excludeIdentifier , requiredFeatureSet , profile , capabilities , overrides , enabledFeatureGates , false )
283+ func (m * Manifest ) Include (excludeIdentifier * string , requiredFeatureSet * string , profile * string , capabilities * configv1.ClusterVersionCapabilitiesStatus , overrides []configv1.ComponentOverride , enabledFeatureGates sets.Set [string ], majorVersion * uint64 ) error {
284+ return m .IncludeAllowUnknownCapabilities (excludeIdentifier , requiredFeatureSet , profile , capabilities , overrides , enabledFeatureGates , majorVersion , false )
231285}
232286
233287// IncludeAllowUnknownCapabilities returns an error if the manifest fails an inclusion filter and should be excluded from
@@ -237,7 +291,7 @@ func (m *Manifest) Include(excludeIdentifier *string, requiredFeatureSet *string
237291// to capabilities filtering. When set to true a manifest will not be excluded simply because it contains an unknown
238292// capability. This is necessary to allow updates to an OCP version containing newly defined capabilities.
239293func (m * Manifest ) IncludeAllowUnknownCapabilities (excludeIdentifier * string , requiredFeatureSet * string , profile * string ,
240- capabilities * configv1.ClusterVersionCapabilitiesStatus , overrides []configv1.ComponentOverride , enabledFeatureGates sets.Set [string ], allowUnknownCapabilities bool ) error {
294+ capabilities * configv1.ClusterVersionCapabilitiesStatus , overrides []configv1.ComponentOverride , enabledFeatureGates sets.Set [string ], majorVersion * uint64 , allowUnknownCapabilities bool ) error {
241295
242296 annotations := m .Obj .GetAnnotations ()
243297 if annotations == nil {
@@ -266,6 +320,14 @@ func (m *Manifest) IncludeAllowUnknownCapabilities(excludeIdentifier *string, re
266320 }
267321 }
268322
323+ // Major version filtering
324+ if majorVersion != nil {
325+ err := checkMajorVersion (* majorVersion , annotations )
326+ if err != nil {
327+ return err
328+ }
329+ }
330+
269331 if profile != nil {
270332 profileAnnotation := fmt .Sprintf ("include.release.openshift.io/%s" , * profile )
271333 if val , ok := annotations [profileAnnotation ]; ok && val != "true" {
0 commit comments