Skip to content

Commit 61c52fb

Browse files
committed
Filter all manifests at render based on featureset, featuregates and cluster profile
1 parent 226437e commit 61c52fb

1 file changed

Lines changed: 84 additions & 64 deletions

File tree

pkg/payload/render.go

Lines changed: 84 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
configv1 "github.com/openshift/api/config/v1"
1313
"github.com/openshift/library-go/pkg/manifest"
1414
"github.com/pkg/errors"
15-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15+
"k8s.io/apimachinery/pkg/runtime"
1616
"k8s.io/apimachinery/pkg/runtime/schema"
1717
utilerrors "k8s.io/apimachinery/pkg/util/errors"
1818
"k8s.io/apimachinery/pkg/util/sets"
@@ -35,33 +35,9 @@ func Render(outputDir, releaseImage, featureGateManifestPath, clusterProfile str
3535
}
3636
)
3737

38-
var requiredFeatureSet *string
39-
if featureGateManifestPath != "" {
40-
manifests, err := manifest.ManifestsFromFiles([]string{featureGateManifestPath})
41-
if err != nil {
42-
return fmt.Errorf("loading FeatureGate manifest: %w", err)
43-
}
44-
if len(manifests) != 1 {
45-
return fmt.Errorf("FeatureGate manifest %s contains %d manifests, but expected only one", featureGateManifestPath, len(manifests))
46-
}
47-
featureGateManifest := manifests[0]
48-
expectedGVK := schema.GroupVersionKind{Kind: "FeatureGate", Version: configv1.GroupVersion.Version, Group: config.GroupName}
49-
if featureGateManifest.GVK != expectedGVK {
50-
return fmt.Errorf("FeatureGate manifest %s GroupVersionKind %v, but expected %v", featureGateManifest.OriginalFilename, featureGateManifest.GVK, expectedGVK)
51-
}
52-
featureSet, found, err := unstructured.NestedString(featureGateManifest.Obj.Object, "spec", "featureSet")
53-
if err != nil {
54-
return fmt.Errorf("%s spec.featureSet lookup was not a string: %w", featureGateManifest.String(), err)
55-
} else if found {
56-
requiredFeatureSet = &featureSet
57-
klog.Infof("--feature-gate-manifest-path=%s results in feature set %q", featureGateManifest.OriginalFilename, featureSet)
58-
} else {
59-
requiredFeatureSet = ptr.To[string]("")
60-
klog.Infof("--feature-gate-manifest-path=%s does not set spec.featureSet, using the default feature set", featureGateManifest.OriginalFilename)
61-
}
62-
} else {
63-
requiredFeatureSet = ptr.To[string]("")
64-
klog.Info("--feature-gate-manifest-path is unset, using the default feature set")
38+
requiredFeatureSet, enabledFeatureGates, err := parseFeatureGateManifest(featureGateManifestPath)
39+
if err != nil {
40+
return fmt.Errorf("error parsing feature gate manifest: %w", err)
6541
}
6642

6743
tasks := []struct {
@@ -94,7 +70,7 @@ func Render(outputDir, releaseImage, featureGateManifestPath, clusterProfile str
9470
}}
9571
var errs []error
9672
for _, task := range tasks {
97-
if err := renderDir(renderConfig, task.idir, task.odir, requiredFeatureSet, &clusterProfile, task.processTemplate, task.skipFiles, task.filterGroupKind); err != nil {
73+
if err := renderDir(renderConfig, task.idir, task.odir, requiredFeatureSet, enabledFeatureGates, &clusterProfile, task.processTemplate, task.skipFiles, task.filterGroupKind); err != nil {
9874
errs = append(errs, err)
9975
}
10076
}
@@ -106,7 +82,9 @@ func Render(outputDir, releaseImage, featureGateManifestPath, clusterProfile str
10682
return nil
10783
}
10884

109-
func renderDir(renderConfig manifestRenderConfig, idir, odir string, requiredFeatureSet *string, clusterProfile *string, processTemplate bool, skipFiles sets.Set[string], filterGroupKind sets.Set[schema.GroupKind]) error {
85+
func renderDir(renderConfig manifestRenderConfig, idir, odir string, requiredFeatureSet *string, enabledFeatureGates sets.Set[string], clusterProfile *string, processTemplate bool, skipFiles sets.Set[string], filterGroupKind sets.Set[schema.GroupKind]) error {
86+
klog.Infof("Filtering manifests in %s for feature set %v, cluster profile %v and enabled feature gates %v", idir, *requiredFeatureSet, *clusterProfile, enabledFeatureGates.UnsortedList())
87+
11088
if err := os.MkdirAll(odir, 0666); err != nil {
11189
return err
11290
}
@@ -126,16 +104,6 @@ func renderDir(renderConfig manifestRenderConfig, idir, odir string, requiredFea
126104
if skipFiles.Has(file.Name()) {
127105
continue
128106
}
129-
if strings.Contains(file.Name(), "CustomNoUpgrade") ||
130-
strings.Contains(file.Name(), "TechPreviewNoUpgrade") ||
131-
strings.Contains(file.Name(), "DevPreviewNoUpgrade") ||
132-
strings.Contains(file.Name(), "OKD") {
133-
// CustomNoUpgrade, TechPreviewNoUpgrade, DevPreviewNoUpgrade, and OKD may add features to manifests like the ClusterVersion CRD,
134-
// but we do not need those features during bootstrap-render time. In those clusters, the production
135-
// CVO will be along shortly to update the manifests and deliver the gated features.
136-
// fixme: now that we have requiredFeatureSet, use it to do Manifest.Include() filtering here instead of making filename assumptions
137-
continue
138-
}
139107

140108
ipath := filepath.Join(idir, file.Name())
141109
iraw, err := os.ReadFile(ipath)
@@ -155,34 +123,32 @@ func renderDir(renderConfig manifestRenderConfig, idir, odir string, requiredFea
155123
rraw = iraw
156124
}
157125

158-
if len(filterGroupKind) > 0 {
159-
manifests, err := manifest.ParseManifests(bytes.NewReader(rraw))
160-
if err != nil {
161-
errs = append(errs, fmt.Errorf("parse manifest %s from %s: %w", file.Name(), idir, err))
162-
continue
163-
}
126+
manifests, err := manifest.ParseManifests(bytes.NewReader(rraw))
127+
if err != nil {
128+
errs = append(errs, fmt.Errorf("parse manifest %s from %s: %w", file.Name(), idir, err))
129+
continue
130+
}
164131

165-
filteredManifests := make([]string, 0, len(manifests))
166-
for _, manifest := range manifests {
167-
if !filterGroupKind.Has(manifest.GVK.GroupKind()) {
168-
klog.Infof("excluding %s because we do not render that group/kind", manifest.String())
169-
} else if err := manifest.Include(nil, requiredFeatureSet, clusterProfile, nil, nil, sets.Set[string]{} /* Empty, assuming any gated manifest will be applied later by the real CVO */); err != nil {
170-
klog.Infof("excluding %s: %v", manifest.String(), err)
171-
} else {
172-
filteredManifests = append(filteredManifests, string(manifest.Raw))
173-
klog.Infof("including %s filtered by feature set %v and cluster profile %v", manifest.String(), requiredFeatureSet, clusterProfile)
174-
}
132+
filteredManifests := make([]string, 0, len(manifests))
133+
for _, manifest := range manifests {
134+
if len(filterGroupKind) > 0 && !filterGroupKind.Has(manifest.GVK.GroupKind()) {
135+
klog.Infof("excluding %s because we do not render that group/kind", manifest.String())
136+
} else if err := manifest.Include(nil, requiredFeatureSet, clusterProfile, nil, nil, enabledFeatureGates); err != nil {
137+
klog.Infof("excluding %s: %v", manifest.String(), err)
138+
} else {
139+
filteredManifests = append(filteredManifests, string(manifest.Raw))
140+
klog.Infof("including %s", manifest.String())
175141
}
142+
}
176143

177-
if len(filteredManifests) == 0 {
178-
continue
179-
}
144+
if len(filteredManifests) == 0 {
145+
continue
146+
}
180147

181-
if len(filteredManifests) == 1 {
182-
rraw = []byte(filteredManifests[0])
183-
} else {
184-
rraw = []byte(strings.Join(filteredManifests, "\n---\n"))
185-
}
148+
if len(filteredManifests) == 1 {
149+
rraw = []byte(filteredManifests[0])
150+
} else {
151+
rraw = []byte(strings.Join(filteredManifests, "\n---\n"))
186152
}
187153

188154
opath := filepath.Join(odir, file.Name())
@@ -218,3 +184,57 @@ func renderManifest(config manifestRenderConfig, manifestBytes []byte) ([]byte,
218184

219185
return buf.Bytes(), nil
220186
}
187+
188+
func parseFeatureGateManifest(featureGateManifestPath string) (*string, sets.Set[string], error) {
189+
if featureGateManifestPath == "" {
190+
return ptr.To(""), sets.Set[string]{}, nil
191+
}
192+
193+
manifests, err := manifest.ManifestsFromFiles([]string{featureGateManifestPath})
194+
if err != nil {
195+
return nil, nil, fmt.Errorf("loading FeatureGate manifest: %w", err)
196+
}
197+
198+
if len(manifests) != 1 {
199+
return nil, nil, fmt.Errorf("FeatureGate manifest %s contains %d manifests, but expected only one", featureGateManifestPath, len(manifests))
200+
}
201+
202+
featureGateManifest := manifests[0]
203+
expectedGVK := schema.GroupVersionKind{Kind: "FeatureGate", Version: configv1.GroupVersion.Version, Group: config.GroupName}
204+
if featureGateManifest.GVK != expectedGVK {
205+
return nil, nil, fmt.Errorf("FeatureGate manifest %s GroupVersionKind %v, but expected %v", featureGateManifest.OriginalFilename, featureGateManifest.GVK, expectedGVK)
206+
}
207+
208+
// Convert unstructured object to structured FeatureGate
209+
var featureGate configv1.FeatureGate
210+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(featureGateManifest.Obj.Object, &featureGate); err != nil {
211+
return nil, nil, fmt.Errorf("failed to convert FeatureGate manifest %s to structured object: %w", featureGateManifest.OriginalFilename, err)
212+
}
213+
214+
var requiredFeatureSet *string
215+
if featureGate.Spec.FeatureSet != "" {
216+
featureSetString := string(featureGate.Spec.FeatureSet)
217+
requiredFeatureSet = &featureSetString
218+
klog.Infof("--feature-gate-manifest-path=%s results in feature set %q", featureGateManifest.OriginalFilename, featureGate.Spec.FeatureSet)
219+
} else {
220+
requiredFeatureSet = ptr.To("")
221+
klog.Infof("--feature-gate-manifest-path=%s does not set spec.featureSet, using the default feature set", featureGateManifest.OriginalFilename)
222+
}
223+
224+
if len(featureGate.Status.FeatureGates) == 0 {
225+
// In case there are no feature gates, fall back to feature set only behaviour.
226+
return requiredFeatureSet, sets.Set[string]{}, nil
227+
}
228+
229+
// A rendered manifest should only include 1 version of the enabled feature gates.
230+
if len(featureGate.Status.FeatureGates) > 1 {
231+
return nil, nil, fmt.Errorf("FeatureGate manifest %s contains %d feature gates, but expected exactly one", featureGateManifest.OriginalFilename, len(featureGate.Status.FeatureGates))
232+
}
233+
234+
enabledFeatureGates := sets.Set[string]{}
235+
for _, feature := range featureGate.Status.FeatureGates[0].Enabled {
236+
enabledFeatureGates.Insert(string(feature.Name))
237+
}
238+
239+
return requiredFeatureSet, enabledFeatureGates, nil
240+
}

0 commit comments

Comments
 (0)