Skip to content

Commit 1e2aca0

Browse files
committed
fix tests for feature gates
1 parent 066bee6 commit 1e2aca0

File tree

6 files changed

+127
-33
lines changed

6 files changed

+127
-33
lines changed

pkg/plugins/golang/v4/scaffolds/api.go

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"log/slog"
23+
"os"
2324
"path/filepath"
2425

2526
"github.com/spf13/afero"
@@ -97,6 +98,12 @@ func (s *apiScaffolder) Scaffold() error {
9798
return fmt.Errorf("error updating resource: %w", err)
9899
}
99100

101+
// If using --force, discover existing feature gates before overwriting files
102+
var existingGates []string
103+
if s.force && doAPI {
104+
existingGates = s.discoverFeatureGates()
105+
}
106+
100107
if doAPI {
101108
if err := scaffold.Execute(
102109
&api.Types{Force: s.force},
@@ -116,13 +123,40 @@ func (s *apiScaffolder) Scaffold() error {
116123
}
117124
}
118125

119-
// Discover feature gates from API types
120-
availableGates := s.discoverFeatureGates()
126+
// Discover feature gates from newly scaffolded API types
127+
newGates := s.discoverFeatureGates()
128+
var availableGates []string
129+
130+
// Merge existing gates with newly discovered ones if we used --force
131+
if len(existingGates) > 0 {
132+
gateMap := make(map[string]bool)
133+
134+
// Add existing gates
135+
for _, gate := range existingGates {
136+
gateMap[gate] = true
137+
}
138+
139+
// Add newly discovered gates (from template)
140+
for _, gate := range newGates {
141+
gateMap[gate] = true
142+
}
143+
144+
// Create merged list
145+
var mergedGates []string
146+
for gate := range gateMap {
147+
mergedGates = append(mergedGates, gate)
148+
}
149+
150+
availableGates = mergedGates
151+
} else {
152+
availableGates = newGates
153+
}
121154

122155
// Generate feature gates file
123-
if err := scaffold.Execute(
124-
&cmd.FeatureGates{AvailableGates: availableGates},
125-
); err != nil {
156+
featureGatesTemplate := &cmd.FeatureGates{}
157+
featureGatesTemplate.AvailableGates = availableGates
158+
featureGatesTemplate.IfExistsAction = machinery.OverwriteFile
159+
if err := scaffold.Execute(featureGatesTemplate); err != nil {
126160
return fmt.Errorf("error scaffolding feature gates: %w", err)
127161
}
128162

@@ -137,29 +171,62 @@ func (s *apiScaffolder) Scaffold() error {
137171

138172
// discoverFeatureGates scans the API directory for feature gate markers
139173
func (s *apiScaffolder) discoverFeatureGates() []string {
140-
parser := machinery.NewFeatureGateMarkerParser()
174+
mgParser := machinery.NewFeatureGateMarkerParser()
141175

142176
// Try to parse the API directory
143177
apiDir := "api"
144178
if s.config.IsMultiGroup() && s.resource.Group != "" {
145179
apiDir = filepath.Join("api", s.resource.Group)
146180
}
147181

182+
// Debug: Get current working directory
183+
if wd, err := os.Getwd(); err == nil {
184+
slog.Debug("Feature gate discovery", "workingDir", wd, "apiDir", apiDir)
185+
}
186+
148187
// Check if the directory exists before trying to parse it
149-
if _, err := s.fs.FS.Stat(apiDir); err != nil {
150-
slog.Debug("API directory does not exist yet, skipping feature gate discovery", "apiDir", apiDir)
188+
if _, err := os.Stat(apiDir); err != nil {
189+
slog.Debug("API directory does not exist yet, skipping feature gate discovery", "apiDir", apiDir, "error", err)
151190
return []string{}
152191
}
153192

154-
markers, err := parser.ParseDirectory(apiDir)
193+
var allMarkers []machinery.FeatureGateMarker
194+
195+
// Walk through each version directory and parse for feature gates
196+
entries, err := os.ReadDir(apiDir)
155197
if err != nil {
156-
slog.Debug("Failed to parse feature gates from directory", "apiDir", apiDir, "error", err)
198+
slog.Debug("Error reading API directory", "error", err, "apiDir", apiDir)
157199
return []string{}
158200
}
159201

160-
featureGates := machinery.ExtractFeatureGates(markers)
202+
slog.Debug("API directory contents", "apiDir", apiDir, "fileCount", len(entries))
203+
204+
for _, entry := range entries {
205+
slog.Debug("API directory file", "name", entry.Name(), "isDir", entry.IsDir())
206+
if entry.IsDir() {
207+
versionDir := filepath.Join(apiDir, entry.Name())
208+
209+
// Use the existing parser to parse the version directory
210+
markers, err := mgParser.ParseDirectory(versionDir)
211+
if err != nil {
212+
slog.Debug("Error parsing version directory for feature gates", "error", err, "versionDir", versionDir)
213+
continue
214+
}
215+
216+
slog.Debug("Parsed markers from directory", "versionDir", versionDir, "markerCount", len(markers))
217+
218+
// Debug: Print all discovered markers
219+
for _, marker := range markers {
220+
slog.Debug("Found feature gate marker", "gateName", marker.GateName, "line", marker.Line, "file", marker.File)
221+
}
222+
223+
allMarkers = append(allMarkers, markers...)
224+
}
225+
}
226+
227+
featureGates := machinery.ExtractFeatureGates(allMarkers)
161228
if len(featureGates) > 0 {
162-
slog.Info("Discovered feature gates", "featureGates", featureGates)
229+
slog.Debug("Discovered feature gates", "featureGates", featureGates)
163230
} else {
164231
slog.Debug("No feature gates found in directory", "apiDir", apiDir)
165232
}

pkg/plugins/golang/v4/scaffolds/api_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,23 @@ func TestAPIScaffolder_discoverFeatureGates_Testdata(t *testing.T) {
185185
apiScaffolder.fs = machinery.Filesystem{FS: afero.NewOsFs()}
186186

187187
featureGates := apiScaffolder.discoverFeatureGates()
188-
if len(featureGates) > 0 {
189-
t.Errorf("Expected no feature gates from testdata, but found %d: %v", len(featureGates), featureGates)
188+
189+
// The testdata contains a feature gate marker for "experimental-bar"
190+
expectedGates := []string{"experimental-bar"}
191+
if len(featureGates) != len(expectedGates) {
192+
t.Errorf("Expected %d feature gates from testdata, but found %d: %v", len(expectedGates), len(featureGates), featureGates)
193+
}
194+
195+
for _, expectedGate := range expectedGates {
196+
found := false
197+
for _, gate := range featureGates {
198+
if gate == expectedGate {
199+
found = true
200+
break
201+
}
202+
}
203+
if !found {
204+
t.Errorf("Expected to find feature gate %s, but it was not discovered", expectedGate)
205+
}
190206
}
191207
}

pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ type {{ .Resource.Kind }}Spec struct {
9090
// +kubebuilder:feature-gate=experimental-bar
9191
// +feature-gate experimental-bar
9292
// +optional
93-
// Bar *string ` + "`" + `json:"bar,omitempty"` + "`" + `
93+
Bar *string ` + "`" + `json:"bar,omitempty"` + "`" + `
9494
}
9595
9696
// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}.

pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ func main() {
293293
flag.BoolVar(&enableHTTP2, "enable-http2", false,
294294
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
295295
flag.StringVar(&featureGates, "feature-gates", "", "A set of key=value pairs that describe feature gates for alpha/experimental features. " +
296+
"Example: --feature-gates \"gate1=true,gate2=false\". " +
296297
"Options are: "+featuregates.GetFeatureGatesHelpText())
297298
opts := zap.Options{
298299
Development: true,

test/e2e/v4/featuregates_discovery_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ type TestStruct struct {
185185
"--group", kbc.Group,
186186
"--version", kbc.Version,
187187
"--kind", kbc.Kind,
188+
"--resource", "--controller",
188189
"--make=false",
189-
"--manifests=false",
190190
)
191191
Expect(err).NotTo(HaveOccurred(), "Failed to create API definition")
192192

@@ -196,8 +196,8 @@ type TestStruct struct {
196196
// Add a feature gate marker to the spec
197197
err = util.InsertCode(
198198
apiTypesFile,
199-
"//+kubebuilder:validation:Optional",
200-
"\n\t// +feature-gate experimental-feature",
199+
"// +optional\n\tFoo *string `json:\"foo,omitempty\"`",
200+
"\n\n\t// +feature-gate experimental-feature",
201201
)
202202
Expect(err).NotTo(HaveOccurred(), "Failed to add feature gate marker")
203203

@@ -210,7 +210,14 @@ type TestStruct struct {
210210
Expect(err).NotTo(HaveOccurred(), "Failed to add experimental field")
211211

212212
By("regenerating the project to discover feature gates")
213-
err = kbc.Regenerate()
213+
err = kbc.CreateAPI(
214+
"--group", kbc.Group,
215+
"--version", kbc.Version,
216+
"--kind", kbc.Kind,
217+
"--resource", "--controller",
218+
"--make=false",
219+
"--force",
220+
)
214221
Expect(err).NotTo(HaveOccurred(), "Failed to regenerate project")
215222

216223
By("verifying feature gates file was generated")

test/e2e/v4/featuregates_test.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,14 @@ import (
2020
"os"
2121
"path/filepath"
2222
"strings"
23-
"testing"
2423

2524
. "github.com/onsi/ginkgo/v2"
2625
. "github.com/onsi/gomega"
2726

27+
"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
2828
"sigs.k8s.io/kubebuilder/v4/test/e2e/utils"
2929
)
3030

31-
func TestFeatureGates(t *testing.T) {
32-
RegisterFailHandler(Fail)
33-
RunSpecs(t, "Feature Gates Suite")
34-
}
35-
3631
var _ = Describe("Feature Gates", func() {
3732
Context("project with feature gates", func() {
3833
var (
@@ -41,7 +36,7 @@ var _ = Describe("Feature Gates", func() {
4136

4237
BeforeEach(func() {
4338
var err error
44-
ctx, err = utils.NewTestContext("go")
39+
ctx, err = utils.NewTestContext(util.KubebuilderBinName, "GO111MODULE=on")
4540
Expect(err).NotTo(HaveOccurred())
4641
Expect(ctx).NotTo(BeNil())
4742

@@ -63,7 +58,9 @@ var _ = Describe("Feature Gates", func() {
6358

6459
It("should scaffold a project with feature gates", func() {
6560
// Initialize the project
66-
err := ctx.Init()
61+
err := ctx.Init(
62+
"--domain", ctx.Domain,
63+
)
6764
Expect(err).NotTo(HaveOccurred())
6865

6966
// Create API with feature gates
@@ -97,7 +94,9 @@ var _ = Describe("Feature Gates", func() {
9794

9895
It("should discover feature gates from API types", func() {
9996
// Initialize the project
100-
err := ctx.Init()
97+
err := ctx.Init(
98+
"--domain", ctx.Domain,
99+
)
101100
Expect(err).NotTo(HaveOccurred())
102101

103102
// Create API
@@ -123,8 +122,8 @@ var _ = Describe("Feature Gates", func() {
123122
`
124123
// Insert the new field in the Spec struct
125124
updatedContent := strings.Replace(string(typesContent),
126-
"// Bar *string ` + \"`\" + `json:\"bar,omitempty\"` + \"`\" + `",
127-
"// Bar *string ` + \"`\" + `json:\"bar,omitempty\"` + \"`\" + `\n"+newField,
125+
"Bar *string `json:\"bar,omitempty\"`",
126+
"Bar *string `json:\"bar,omitempty\"`\n"+newField,
128127
1)
129128

130129
err = os.WriteFile(typesFile, []byte(updatedContent), 0644)
@@ -150,7 +149,9 @@ var _ = Describe("Feature Gates", func() {
150149

151150
It("should build project with feature gates", func() {
152151
// Initialize the project
153-
err := ctx.Init()
152+
err := ctx.Init(
153+
"--domain", ctx.Domain,
154+
)
154155
Expect(err).NotTo(HaveOccurred())
155156

156157
// Create API
@@ -173,7 +174,9 @@ var _ = Describe("Feature Gates", func() {
173174

174175
It("should generate manifests with feature gates", func() {
175176
// Initialize the project
176-
err := ctx.Init()
177+
err := ctx.Init(
178+
"--domain", ctx.Domain,
179+
)
177180
Expect(err).NotTo(HaveOccurred())
178181

179182
// Create API
@@ -191,7 +194,7 @@ var _ = Describe("Feature Gates", func() {
191194

192195
// Verify CRDs were generated
193196
crdFile := filepath.Join(ctx.Dir, "config", "crd", "bases",
194-
strings.ToLower(ctx.Group)+"_"+ctx.Version+"_"+strings.ToLower(ctx.Kind)+".yaml")
197+
strings.ToLower(ctx.Group)+"."+ctx.Domain+"_"+ctx.Resources+".yaml")
195198
Expect(crdFile).To(BeAnExistingFile())
196199
})
197200
})

0 commit comments

Comments
 (0)