Skip to content

Commit 14dff40

Browse files
stbenjamclaude
andcommitted
Fix feature gate filename parsing for major version segmentation
openshift/api#2637 introduced major version segmentation for feature gates, changing filenames from featureGate-{topology}-{featureSet}.yaml to featureGate-{major}-{minor}-{topology}-{featureSet}.yaml. The parser was extracting the version components as topology/featureSet, causing all 4.22 files to collide on the same composite key and fail with "ON CONFLICT DO UPDATE command cannot affect row a second time". Fix by taking the last two dash-separated segments as topology and featureSet, which works for both old and new filename formats. Note: the new files also include release.openshift.io/feature-set and release.openshift.io/major-version annotations. We considered parsing from annotations instead, but the old format (4.21 and earlier) lacks the feature-set annotation and neither format has an explicit topology annotation, so the filename remains the most reliable source. If the upstream format changes further, extracting from annotations may be worth revisiting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 327e327 commit 14dff40

2 files changed

Lines changed: 83 additions & 2 deletions

File tree

pkg/dataloader/featuregateloader/featuregateloader.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,15 @@ func processFeatureGateFile(path, release, filename string) ([]models.FeatureGat
175175
return convertAPIToDB(fg, release, topology, featureSet, path), nil
176176
}
177177

178-
// parseFeatureGateFilename extracts topology and feature set from the filename
178+
// parseFeatureGateFilename extracts topology and feature set from the filename.
179+
// Handles both old format (featureGate-{topology}-{featureSet}.yaml) and
180+
// new versioned format (featureGate-{major}-{minor}-{topology}-{featureSet}.yaml).
179181
func parseFeatureGateFilename(filename string) (string, string, bool) {
180182
parts := strings.Split(strings.TrimSuffix(filename, ".yaml"), "-")
181183
if len(parts) < 3 {
182184
return "", "", false
183185
}
184-
return parts[1], parts[2], true
186+
return parts[len(parts)-2], parts[len(parts)-1], true
185187
}
186188

187189
// convertAPIToDB converts the parsed feature gate data into db models
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package featuregateloader
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestParseFeatureGateFilename(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
filename string
11+
wantTopo string
12+
wantFeatSet string
13+
wantValid bool
14+
}{
15+
{
16+
name: "old format Hypershift Default",
17+
filename: "featureGate-Hypershift-Default.yaml",
18+
wantTopo: "Hypershift",
19+
wantFeatSet: "Default",
20+
wantValid: true,
21+
},
22+
{
23+
name: "old format SelfManagedHA TechPreviewNoUpgrade",
24+
filename: "featureGate-SelfManagedHA-TechPreviewNoUpgrade.yaml",
25+
wantTopo: "SelfManagedHA",
26+
wantFeatSet: "TechPreviewNoUpgrade",
27+
wantValid: true,
28+
},
29+
{
30+
name: "versioned format Hypershift Default",
31+
filename: "featureGate-4-10-Hypershift-Default.yaml",
32+
wantTopo: "Hypershift",
33+
wantFeatSet: "Default",
34+
wantValid: true,
35+
},
36+
{
37+
name: "versioned format SelfManagedHA DevPreviewNoUpgrade",
38+
filename: "featureGate-4-10-SelfManagedHA-DevPreviewNoUpgrade.yaml",
39+
wantTopo: "SelfManagedHA",
40+
wantFeatSet: "DevPreviewNoUpgrade",
41+
wantValid: true,
42+
},
43+
{
44+
name: "versioned format Hypershift OKD",
45+
filename: "featureGate-4-10-Hypershift-OKD.yaml",
46+
wantTopo: "Hypershift",
47+
wantFeatSet: "OKD",
48+
wantValid: true,
49+
},
50+
{
51+
name: "too few parts",
52+
filename: "featureGate-Default.yaml",
53+
wantValid: false,
54+
},
55+
{
56+
name: "just prefix",
57+
filename: "featureGate.yaml",
58+
wantValid: false,
59+
},
60+
}
61+
62+
for _, tt := range tests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
gotTopo, gotFeatSet, gotValid := parseFeatureGateFilename(tt.filename)
65+
if gotValid != tt.wantValid {
66+
t.Fatalf("valid = %v, want %v", gotValid, tt.wantValid)
67+
}
68+
if !gotValid {
69+
return
70+
}
71+
if gotTopo != tt.wantTopo {
72+
t.Errorf("topology = %q, want %q", gotTopo, tt.wantTopo)
73+
}
74+
if gotFeatSet != tt.wantFeatSet {
75+
t.Errorf("featureSet = %q, want %q", gotFeatSet, tt.wantFeatSet)
76+
}
77+
})
78+
}
79+
}

0 commit comments

Comments
 (0)