@@ -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,72 @@ 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 := map [configv1.FeatureGateName ]bool {}
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 [gateName .FeatureGateAttributes .Name ] = false
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 [gateName .FeatureGateAttributes .Name ] = false
316
+ } else {
317
+ currentDetails .Disabled = append (currentDetails .Disabled , * gateName .FeatureGateAttributes .DeepCopy ())
318
+ }
319
+ }
320
+
321
+ if minimumKubeletVersion != nil {
322
+ for versionStr , attrs := range featureGateStatus .EnabledGivenMinimumVersion [configv1 .MinimumComponentKubelet ] {
323
+ gateVersion , err := semver .Parse (versionStr )
324
+ if err != nil {
325
+ // Programming error, as these are built into the binary in features/feature.go
326
+ panic (err )
327
+ }
328
+ if minimumKubeletVersion .LTE (gateVersion ) {
329
+ for _ , attr := range attrs {
330
+ currentDetails .Enabled = append (currentDetails .Enabled , attr )
331
+ skippedForVersion [attr .Name ] = true
332
+ }
333
+ } else {
334
+ for _ , attr := range attrs {
335
+ currentDetails .Disabled = append (currentDetails .Disabled , attr )
336
+ skippedForVersion [attr .Name ] = true
337
+ }
338
+ }
339
+ }
340
+ } else {
341
+ for _ , attrs := range featureGateStatus .EnabledGivenMinimumVersion [configv1 .MinimumComponentKubelet ] {
342
+ for _ , attr := range attrs {
343
+ currentDetails .Disabled = append (currentDetails .Disabled , attr )
344
+ skippedForVersion [attr .Name ] = true
345
+ }
346
+ }
347
+ }
348
+
349
+ for name , missed := range skippedForVersion {
350
+ if ! missed {
351
+ // Programming error: a gate was registered as skipped but not addressed
352
+ panic (fmt .Errorf ("Missed feature gate name %s when constructing featureGateStatus" , name ))
353
+ }
354
+ }
355
+ if minimumKubeletVersion != nil {
356
+ currentDetails .RenderedMinimumComponentVersions = []configv1.MinimumComponentVersion {
357
+ {
358
+ Component : configv1 .MinimumComponentKubelet ,
359
+ Version : minimumKubeletVersion .String (),
360
+ },
361
+ }
264
362
}
265
363
266
364
// sort for stability
0 commit comments