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-gate"
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-gate"
31+ majorVersionAnnotation = "release.openshift.io/major-version"
3032)
3133
3234var knownFeatureSets = sets.Set [string ]{}
@@ -232,12 +234,69 @@ func checkFeatureGates(enabledGates sets.Set[string], annotations map[string]str
232234 return nil
233235}
234236
237+ // checkMajorVersion validates if manifest should be included based on major version requirements
238+ func checkMajorVersion (gvk schema.GroupVersionKind , clusterMajorVersion uint64 , annotations map [string ]string ) error {
239+ if annotations == nil {
240+ return nil // No annotations, include by default
241+ }
242+ majorVersionRequirements , ok := annotations [majorVersionAnnotation ]
243+ if ! ok {
244+ return nil // No requirements, include by default
245+ }
246+
247+ if ! isFeatureGate (gvk ) && ! isCustomResource (gvk ) {
248+ // Has a requirement, but is not of the expected kind
249+ return fmt .Errorf ("major version filtering is only supported for feature gates and custom resources" )
250+ }
251+
252+ requirements := strings .Split (majorVersionRequirements , "," )
253+ includedVersions := sets .New [uint64 ]()
254+ excludedVersions := sets .New [uint64 ]()
255+
256+ for _ , req := range requirements {
257+ req = strings .TrimSpace (req )
258+ if req == "" {
259+ continue
260+ }
261+
262+ if strings .HasPrefix (req , "-" ) {
263+ excludedVersionStr := req [1 :]
264+ excludedVersion , err := strconv .ParseUint (excludedVersionStr , 10 , 64 )
265+ if err != nil {
266+ return fmt .Errorf ("invalid excluded major version %q in annotation: %v" , excludedVersionStr , err )
267+ }
268+ excludedVersions .Insert (excludedVersion )
269+ } else {
270+ includedVersionStr := req
271+ includedVersion , err := strconv .ParseUint (includedVersionStr , 10 , 64 )
272+ if err != nil {
273+ return fmt .Errorf ("invalid included major version %q in annotation: %v" , includedVersionStr , err )
274+ }
275+ includedVersions .Insert (includedVersion )
276+ }
277+ }
278+
279+ switch {
280+ case includedVersions .Intersection (excludedVersions ).Len () > 0 :
281+ return fmt .Errorf ("inclusion and exclusion requirements overlap: %v" , includedVersions .Intersection (excludedVersions ).UnsortedList ())
282+ case includedVersions .Has (clusterMajorVersion ):
283+ return nil // Found matching version, include this manifest
284+ case excludedVersions .Has (clusterMajorVersion ):
285+ return fmt .Errorf ("major version %d matches excluded version %d" , clusterMajorVersion , clusterMajorVersion )
286+ case len (includedVersions ) > 0 :
287+ return fmt .Errorf ("major version %d does not match any required versions" , clusterMajorVersion )
288+ default :
289+ // No positive requirement and did not match any excluded versions
290+ return nil
291+ }
292+ }
293+
235294// Include returns an error if the manifest fails an inclusion filter and should be excluded from further
236295// processing by cluster version operator. Pointer arguments can be set nil to avoid excluding based on that
237296// filter. For example, setting profile non-nil and capabilities nil will return an error if the manifest's
238297// profile does not match, but will never return an error about capability issues.
239- func (m * Manifest ) Include (excludeIdentifier * string , requiredFeatureSet * string , profile * string , capabilities * configv1.ClusterVersionCapabilitiesStatus , overrides []configv1.ComponentOverride , enabledFeatureGates sets.Set [string ]) error {
240- return m .IncludeAllowUnknownCapabilities (excludeIdentifier , requiredFeatureSet , profile , capabilities , overrides , enabledFeatureGates , false )
298+ func (m * Manifest ) Include (excludeIdentifier * string , requiredFeatureSet * string , profile * string , capabilities * configv1.ClusterVersionCapabilitiesStatus , overrides []configv1.ComponentOverride , enabledFeatureGates sets.Set [string ], majorVersion * uint64 ) error {
299+ return m .IncludeAllowUnknownCapabilities (excludeIdentifier , requiredFeatureSet , profile , capabilities , overrides , enabledFeatureGates , majorVersion , false )
241300}
242301
243302// IncludeAllowUnknownCapabilities returns an error if the manifest fails an inclusion filter and should be excluded from
@@ -247,7 +306,7 @@ func (m *Manifest) Include(excludeIdentifier *string, requiredFeatureSet *string
247306// to capabilities filtering. When set to true a manifest will not be excluded simply because it contains an unknown
248307// capability. This is necessary to allow updates to an OCP version containing newly defined capabilities.
249308func (m * Manifest ) IncludeAllowUnknownCapabilities (excludeIdentifier * string , requiredFeatureSet * string , profile * string ,
250- capabilities * configv1.ClusterVersionCapabilitiesStatus , overrides []configv1.ComponentOverride , enabledFeatureGates sets.Set [string ], allowUnknownCapabilities bool ) error {
309+ capabilities * configv1.ClusterVersionCapabilitiesStatus , overrides []configv1.ComponentOverride , enabledFeatureGates sets.Set [string ], majorVersion * uint64 , allowUnknownCapabilities bool ) error {
251310
252311 annotations := m .Obj .GetAnnotations ()
253312 if annotations == nil {
@@ -282,6 +341,14 @@ func (m *Manifest) IncludeAllowUnknownCapabilities(excludeIdentifier *string, re
282341 }
283342 }
284343
344+ // Major version filtering
345+ if majorVersion != nil {
346+ err := checkMajorVersion (m .GVK , * majorVersion , annotations )
347+ if err != nil {
348+ return err
349+ }
350+ }
351+
285352 if profile != nil {
286353 profileAnnotation := fmt .Sprintf ("include.release.openshift.io/%s" , * profile )
287354 if val , ok := annotations [profileAnnotation ]; ok && val != "true" {
@@ -481,3 +548,11 @@ func addIfNotDuplicateResource(manifest Manifest, resourceIds map[resourceId]boo
481548 }
482549 return fmt .Errorf ("duplicate resource: (%s)" , manifest .id )
483550}
551+
552+ func isFeatureGate (gvk schema.GroupVersionKind ) bool {
553+ return gvk .Group == "config.openshift.io" && gvk .Kind == "FeatureGate"
554+ }
555+
556+ func isCustomResource (gvk schema.GroupVersionKind ) bool {
557+ return gvk .Group == "apiextensions.k8s.io" && gvk .Kind == "CustomResourceDefinition"
558+ }
0 commit comments