@@ -3,17 +3,18 @@ package render
3
3
import (
4
4
"flag"
5
5
"fmt"
6
- "github.com/openshift/api/features"
7
6
"os"
8
7
"path/filepath"
9
8
"sort"
10
9
"strings"
11
10
12
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13
- "k8s.io/apimachinery/pkg/runtime"
14
-
11
+ "github.com/blang/semver/v4"
15
12
configv1 "github.com/openshift/api/config/v1"
13
+ "github.com/openshift/api/features"
16
14
assets "github.com/openshift/api/payload-command/render/renderassets"
15
+
16
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
17
+ "k8s.io/apimachinery/pkg/runtime"
17
18
"k8s.io/apimachinery/pkg/util/sets"
18
19
)
19
20
@@ -66,6 +67,32 @@ func (o *RenderOpts) Run() error {
66
67
if err != nil {
67
68
return fmt .Errorf ("problem with featuregate manifests: %w" , err )
68
69
}
70
+
71
+ nodeConfigManifests , err := nodeConfigManifests ([]string {o .RenderedManifestInputFilename })
72
+ if err != nil {
73
+ return fmt .Errorf ("problem with node config manifests: %w" , err )
74
+ }
75
+ var minimumKubeletVersion * semver.Version
76
+ for _ , manifest := range nodeConfigManifests {
77
+ uncastObj , err := manifest .GetDecodedObj ()
78
+ if err != nil {
79
+ return fmt .Errorf ("error decoding FeatureGate: %w" , err )
80
+ }
81
+ nodeConfig := & configv1.Node {}
82
+ err = runtime .DefaultUnstructuredConverter .FromUnstructured (uncastObj .(* unstructured.Unstructured ).Object , nodeConfig )
83
+ if err != nil {
84
+ return fmt .Errorf ("error converting NodeConfig: %w" , err )
85
+ }
86
+ // TODO FIXME: how do we handle multiple?
87
+ if nodeConfig .Spec .MinimumKubeletVersion != "" {
88
+ v , err := semver .Parse (nodeConfig .Spec .MinimumKubeletVersion )
89
+ if err != nil {
90
+ return fmt .Errorf ("failed to parse provided minimum kubelet version: %w" , err )
91
+ }
92
+ minimumKubeletVersion = & v
93
+ }
94
+ }
95
+
69
96
clusterProfileAnnotationName := fmt .Sprintf ("include.release.openshift.io/%s" , o .UnprefixedClusterProfile )
70
97
71
98
for _ , featureGateFile := range featureGateFiles {
@@ -109,7 +136,8 @@ func (o *RenderOpts) Run() error {
109
136
if err != nil {
110
137
return fmt .Errorf ("unable to resolve featureGateStatus: %w" , err )
111
138
}
112
- currentDetails := FeaturesGateDetailsFromFeatureSets (featureGateStatus , o .PayloadVersion )
139
+ currentDetails := FeaturesGateDetailsFromFeatureSets (featureGateStatus , o .PayloadVersion , minimumKubeletVersion , featureGates .Spec .FeatureSet )
140
+
113
141
featureGates .Status .FeatureGates = []configv1.FeatureGateDetails {* currentDetails }
114
142
115
143
featureGateOutBytes := writeFeatureGateV1OrDie (featureGates )
@@ -227,6 +255,27 @@ func clusterProfilesFrom(annotations map[string]string) sets.Set[string] {
227
255
}
228
256
229
257
func featureGateManifests (renderedManifestInputFilenames []string ) (assets.RenderedManifests , error ) {
258
+ inputManifests , err := loadManifests (renderedManifestInputFilenames )
259
+ if err != nil {
260
+ return nil , err
261
+ }
262
+ featureGates := inputManifests .ListManifestOfType (configv1 .GroupVersion .WithKind ("FeatureGate" ))
263
+ if len (featureGates ) == 0 {
264
+ return nil , fmt .Errorf ("no FeatureGates found in manfest dir: %v" , renderedManifestInputFilenames )
265
+ }
266
+
267
+ return featureGates , nil
268
+ }
269
+
270
+ func nodeConfigManifests (renderedManifestInputFilenames []string ) (assets.RenderedManifests , error ) {
271
+ inputManifests , err := loadManifests (renderedManifestInputFilenames )
272
+ if err != nil {
273
+ return nil , err
274
+ }
275
+ return inputManifests .ListManifestOfType (configv1 .GroupVersion .WithKind ("Node" )), nil
276
+ }
277
+
278
+ func loadManifests (renderedManifestInputFilenames []string ) (assets.RenderedManifests , error ) {
230
279
if len (renderedManifestInputFilenames ) == 0 {
231
280
return nil , fmt .Errorf ("cannot return FeatureGate without rendered manifests" )
232
281
}
@@ -244,23 +293,67 @@ func featureGateManifests(renderedManifestInputFilenames []string) (assets.Rende
244
293
})
245
294
}
246
295
}
247
- featureGates := inputManifests .ListManifestOfType (configv1 .GroupVersion .WithKind ("FeatureGate" ))
248
- if len (featureGates ) == 0 {
249
- return nil , fmt .Errorf ("no FeatureGates found in manfest dir: %v" , renderedManifestInputFilenames )
250
- }
251
-
252
- return featureGates , nil
296
+ return inputManifests , nil
253
297
}
254
298
255
- func FeaturesGateDetailsFromFeatureSets (featureGateStatus * features.FeatureGateEnabledDisabled , currentVersion string ) * configv1.FeatureGateDetails {
299
+ func FeaturesGateDetailsFromFeatureSets (featureGateStatus * features.FeatureGateEnabledDisabled , currentVersion string , minimumKubeletVersion * semver. Version , featureSet configv1. FeatureSet ) * configv1.FeatureGateDetails {
256
300
currentDetails := configv1.FeatureGateDetails {
257
301
Version : currentVersion ,
258
302
}
303
+ skippedForVersion := []configv1.FeatureGateAttributes {}
259
304
for _ , gateName := range featureGateStatus .Enabled {
260
- currentDetails .Enabled = append (currentDetails .Enabled , * gateName .FeatureGateAttributes .DeepCopy ())
305
+ // Skip adding if we have a RequiredMinimumComponentVersion, as we'll handle that below
306
+ if len (gateName .FeatureGateAttributes .RequiredMinimumComponentVersions ) != 0 && featureSet == configv1 .Default {
307
+ skippedForVersion = append (skippedForVersion , * gateName .FeatureGateAttributes .DeepCopy ())
308
+ } else {
309
+ currentDetails .Enabled = append (currentDetails .Enabled , * gateName .FeatureGateAttributes .DeepCopy ())
310
+ }
261
311
}
262
312
for _ , gateName := range featureGateStatus .Disabled {
263
- currentDetails .Disabled = append (currentDetails .Disabled , * gateName .FeatureGateAttributes .DeepCopy ())
313
+ // Skip adding if we have a RequiredMinimumComponentVersion, as we'll handle that below
314
+ if len (gateName .FeatureGateAttributes .RequiredMinimumComponentVersions ) != 0 && featureSet == configv1 .Default {
315
+ skippedForVersion = append (skippedForVersion , * gateName .FeatureGateAttributes .DeepCopy ())
316
+ } else {
317
+ currentDetails .Disabled = append (currentDetails .Disabled , * gateName .FeatureGateAttributes .DeepCopy ())
318
+ }
319
+ }
320
+
321
+ if minimumKubeletVersion != nil {
322
+ allMinimumComponents := map [configv1.MinimumComponent ]* semver.Version {
323
+ configv1 .MinimumComponentKubelet : minimumKubeletVersion ,
324
+ }
325
+ for _ , attr := range skippedForVersion {
326
+ allPassed := true
327
+ for _ , cv := range attr .RequiredMinimumComponentVersions {
328
+ targetVersion , ok := allMinimumComponents [cv .Component ]
329
+ if ! ok {
330
+ allPassed = false
331
+ }
332
+ gateVersion , err := semver .Parse (cv .Version )
333
+ if err != nil {
334
+ // Programming error, as these are built into the binary in features/feature.go
335
+ panic (err )
336
+ }
337
+ if targetVersion .GT (gateVersion ) {
338
+ allPassed = false
339
+ }
340
+ }
341
+ if allPassed {
342
+ currentDetails .Enabled = append (currentDetails .Enabled , attr )
343
+ } else {
344
+ currentDetails .Disabled = append (currentDetails .Disabled , attr )
345
+ }
346
+ }
347
+ currentDetails .RenderedMinimumComponentVersions = []configv1.MinimumComponentVersion {
348
+ {
349
+ Component : configv1 .MinimumComponentKubelet ,
350
+ Version : minimumKubeletVersion .String (),
351
+ },
352
+ }
353
+ } else {
354
+ for _ , attr := range skippedForVersion {
355
+ currentDetails .Disabled = append (currentDetails .Disabled , attr )
356
+ }
264
357
}
265
358
266
359
// sort for stability
0 commit comments