Skip to content

Commit 1b1e12b

Browse files
Merge pull request #2069 from JoelSpeed/manifest-inclusion-major-version
OCPSTRAT-2876: Manifest inclusion filtering based on major version
2 parents b6adacb + 6f4c21d commit 1b1e12b

2 files changed

Lines changed: 384 additions & 10 deletions

File tree

pkg/manifest/manifest.go

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
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

2526
const (
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

3234
var 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.
249308
func (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

Comments
 (0)