Skip to content

Commit 982e9d3

Browse files
(fix): (go/v4): Fixed: Init command incorrectly rejects directories with non-conflicting configuration files
The kubebuilder init command was incorrectly failing when the target directory contained legitimate configuration files like mise.toml, .envrc, or other tool-specific files. This was due to inverted validation logic that allowed files with conflicting extensions (.go, .yaml, .mod, .sum) while rejecting all others. What changed: - The directory validation now correctly allows configuration files and other non-conflicting files - Only blocks files that would genuinely conflict with scaffolding: .go, .yaml, .mod, .sum, and PROJECT - Added the PROJECT file to the explicit disallow list since it's reserved for scaffolding
1 parent 29479a4 commit 982e9d3

File tree

2 files changed

+285
-7
lines changed

2 files changed

+285
-7
lines changed

pkg/plugins/golang/v4/init.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,21 @@ func checkDir() error {
179179
if strings.HasSuffix(info.Name(), ".md") && !info.IsDir() {
180180
return nil
181181
}
182-
// Allow capitalized files except PROJECT
182+
// Deny PROJECT file (reserved for scaffolding)
183+
if info.Name() == "PROJECT" {
184+
return fmt.Errorf("target directory is not empty and contains a disallowed file %q. "+
185+
"PROJECT file is reserved and will be generated by the scaffolding tool",
186+
path)
187+
}
188+
// Allow capitalized files
183189
isCapitalized := true
184190
for _, l := range info.Name() {
185191
if !unicode.IsUpper(l) {
186192
isCapitalized = false
187193
break
188194
}
189195
}
190-
if isCapitalized && info.Name() != "PROJECT" {
196+
if isCapitalized {
191197
return nil
192198
}
193199
disallowedExtensions := []string{
@@ -199,13 +205,13 @@ func checkDir() error {
199205
// Deny files with .go or .yaml or .mod or .sum extensions
200206
for _, ext := range disallowedExtensions {
201207
if strings.HasSuffix(info.Name(), ext) {
202-
return nil
208+
return fmt.Errorf("target directory is not empty and contains a disallowed file %q. "+
209+
"files with the following extensions [%s] are not allowed to avoid conflicts with the tooling",
210+
path, strings.Join(disallowedExtensions, ", "))
203211
}
204212
}
205-
// Do not allow any other file
206-
return fmt.Errorf("target directory is not empty and contains a disallowed file %q. "+
207-
"files with the following extensions [%s] are not allowed to avoid conflicts with the tooling",
208-
path, strings.Join(disallowedExtensions, ", "))
213+
// Allow any other file
214+
return nil
209215
})
210216
if err != nil {
211217
return fmt.Errorf("error walking directory: %w", err)

pkg/plugins/golang/v4/init_test.go

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v4
18+
19+
import (
20+
"os"
21+
"path/filepath"
22+
"testing"
23+
24+
. "github.com/onsi/ginkgo/v2"
25+
. "github.com/onsi/gomega"
26+
)
27+
28+
func TestCheckDir(t *testing.T) {
29+
RegisterFailHandler(Fail)
30+
RunSpecs(t, "CheckDir Suite")
31+
}
32+
33+
var _ = Describe("checkDir", func() {
34+
var testDir string
35+
36+
BeforeEach(func() {
37+
var err error
38+
testDir, err = os.MkdirTemp("", "checkdir-test-*")
39+
Expect(err).NotTo(HaveOccurred())
40+
41+
// Change to test directory
42+
err = os.Chdir(testDir)
43+
Expect(err).NotTo(HaveOccurred())
44+
})
45+
46+
AfterEach(func() {
47+
// Clean up test directory
48+
_ = os.RemoveAll(testDir)
49+
})
50+
51+
Context("when the directory is empty", func() {
52+
It("should succeed", func() {
53+
err := checkDir()
54+
Expect(err).NotTo(HaveOccurred())
55+
})
56+
})
57+
58+
Context("when the directory contains allowed files", func() {
59+
It("should succeed with .md files", func() {
60+
err := os.WriteFile("README.md", []byte("# Test"), 0o644)
61+
Expect(err).NotTo(HaveOccurred())
62+
63+
err = checkDir()
64+
Expect(err).NotTo(HaveOccurred())
65+
})
66+
67+
It("should succeed with dot files", func() {
68+
err := os.WriteFile(".gitignore", []byte("*.log"), 0o644)
69+
Expect(err).NotTo(HaveOccurred())
70+
71+
err = checkDir()
72+
Expect(err).NotTo(HaveOccurred())
73+
})
74+
75+
It("should succeed with dot directories", func() {
76+
err := os.Mkdir(".git", 0o755)
77+
Expect(err).NotTo(HaveOccurred())
78+
err = os.WriteFile(".git/config", []byte("[core]"), 0o644)
79+
Expect(err).NotTo(HaveOccurred())
80+
81+
err = checkDir()
82+
Expect(err).NotTo(HaveOccurred())
83+
})
84+
85+
It("should succeed with capitalized files", func() {
86+
err := os.WriteFile("LICENSE", []byte("MIT"), 0o644)
87+
Expect(err).NotTo(HaveOccurred())
88+
89+
err = checkDir()
90+
Expect(err).NotTo(HaveOccurred())
91+
})
92+
93+
It("should succeed with multiple capitalized files", func() {
94+
err := os.WriteFile("LICENSE", []byte("MIT"), 0o644)
95+
Expect(err).NotTo(HaveOccurred())
96+
err = os.WriteFile("CONTRIBUTING", []byte("contribute"), 0o644)
97+
Expect(err).NotTo(HaveOccurred())
98+
99+
err = checkDir()
100+
Expect(err).NotTo(HaveOccurred())
101+
})
102+
103+
It("should succeed with mise.toml file", func() {
104+
err := os.WriteFile("mise.toml", []byte("[tools]"), 0o644)
105+
Expect(err).NotTo(HaveOccurred())
106+
107+
err = checkDir()
108+
Expect(err).NotTo(HaveOccurred())
109+
})
110+
111+
It("should succeed with .tool-versions file", func() {
112+
err := os.WriteFile(".tool-versions", []byte("golang 1.23.0"), 0o644)
113+
Expect(err).NotTo(HaveOccurred())
114+
115+
err = checkDir()
116+
Expect(err).NotTo(HaveOccurred())
117+
})
118+
119+
It("should succeed with .envrc file", func() {
120+
err := os.WriteFile(".envrc", []byte("export FOO=bar"), 0o644)
121+
Expect(err).NotTo(HaveOccurred())
122+
123+
err = checkDir()
124+
Expect(err).NotTo(HaveOccurred())
125+
})
126+
127+
It("should succeed with config.toml file", func() {
128+
err := os.WriteFile("config.toml", []byte("[config]"), 0o644)
129+
Expect(err).NotTo(HaveOccurred())
130+
131+
err = checkDir()
132+
Expect(err).NotTo(HaveOccurred())
133+
})
134+
135+
It("should succeed with settings.json file", func() {
136+
err := os.WriteFile("settings.json", []byte("{}"), 0o644)
137+
Expect(err).NotTo(HaveOccurred())
138+
139+
err = checkDir()
140+
Expect(err).NotTo(HaveOccurred())
141+
})
142+
143+
It("should succeed with a combination of allowed files", func() {
144+
err := os.WriteFile("README.md", []byte("# Test"), 0o644)
145+
Expect(err).NotTo(HaveOccurred())
146+
err = os.WriteFile(".gitignore", []byte("*.log"), 0o644)
147+
Expect(err).NotTo(HaveOccurred())
148+
err = os.WriteFile("LICENSE", []byte("MIT"), 0o644)
149+
Expect(err).NotTo(HaveOccurred())
150+
err = os.WriteFile("mise.toml", []byte("[tools]"), 0o644)
151+
Expect(err).NotTo(HaveOccurred())
152+
153+
err = checkDir()
154+
Expect(err).NotTo(HaveOccurred())
155+
})
156+
})
157+
158+
Context("when the directory contains disallowed files", func() {
159+
It("should fail with PROJECT file (reserved for scaffolding)", func() {
160+
err := os.WriteFile("PROJECT", []byte("version: 3"), 0o644)
161+
Expect(err).NotTo(HaveOccurred())
162+
163+
err = checkDir()
164+
Expect(err).To(HaveOccurred())
165+
Expect(err.Error()).To(ContainSubstring("PROJECT"))
166+
})
167+
168+
It("should fail with .go files", func() {
169+
err := os.WriteFile("main.go", []byte("package main"), 0o644)
170+
Expect(err).NotTo(HaveOccurred())
171+
172+
err = checkDir()
173+
Expect(err).To(HaveOccurred())
174+
Expect(err.Error()).To(ContainSubstring("main.go"))
175+
Expect(err.Error()).To(ContainSubstring(".go"))
176+
})
177+
178+
It("should fail with .yaml files", func() {
179+
err := os.WriteFile("config.yaml", []byte("key: value"), 0o644)
180+
Expect(err).NotTo(HaveOccurred())
181+
182+
err = checkDir()
183+
Expect(err).To(HaveOccurred())
184+
Expect(err.Error()).To(ContainSubstring("config.yaml"))
185+
Expect(err.Error()).To(ContainSubstring(".yaml"))
186+
})
187+
188+
It("should fail with .mod files", func() {
189+
err := os.WriteFile("go.mod", []byte("module test"), 0o644)
190+
Expect(err).NotTo(HaveOccurred())
191+
192+
err = checkDir()
193+
Expect(err).To(HaveOccurred())
194+
Expect(err.Error()).To(ContainSubstring("go.mod"))
195+
Expect(err.Error()).To(ContainSubstring(".mod"))
196+
})
197+
198+
It("should fail with .sum files", func() {
199+
err := os.WriteFile("go.sum", []byte("checksums"), 0o644)
200+
Expect(err).NotTo(HaveOccurred())
201+
202+
err = checkDir()
203+
Expect(err).To(HaveOccurred())
204+
Expect(err.Error()).To(ContainSubstring("go.sum"))
205+
Expect(err.Error()).To(ContainSubstring(".sum"))
206+
})
207+
208+
It("should fail even when disallowed files are in subdirectories", func() {
209+
err := os.Mkdir("subdir", 0o755)
210+
Expect(err).NotTo(HaveOccurred())
211+
err = os.WriteFile(filepath.Join("subdir", "main.go"), []byte("package main"), 0o644)
212+
Expect(err).NotTo(HaveOccurred())
213+
214+
err = checkDir()
215+
Expect(err).To(HaveOccurred())
216+
Expect(err.Error()).To(ContainSubstring("main.go"))
217+
})
218+
})
219+
220+
Context("when the directory contains a mix of allowed and disallowed files", func() {
221+
It("should fail when there is at least one disallowed file", func() {
222+
err := os.WriteFile("README.md", []byte("# Test"), 0o644)
223+
Expect(err).NotTo(HaveOccurred())
224+
err = os.WriteFile("mise.toml", []byte("[tools]"), 0o644)
225+
Expect(err).NotTo(HaveOccurred())
226+
err = os.WriteFile("main.go", []byte("package main"), 0o644)
227+
Expect(err).NotTo(HaveOccurred())
228+
229+
err = checkDir()
230+
Expect(err).To(HaveOccurred())
231+
Expect(err.Error()).To(ContainSubstring("main.go"))
232+
})
233+
})
234+
235+
Context("edge cases", func() {
236+
It("should handle files in dot directories correctly", func() {
237+
err := os.Mkdir(".hidden", 0o755)
238+
Expect(err).NotTo(HaveOccurred())
239+
// Files in dot directories should be skipped entirely
240+
err = os.WriteFile(".hidden/main.go", []byte("package main"), 0o644)
241+
Expect(err).NotTo(HaveOccurred())
242+
243+
err = checkDir()
244+
Expect(err).NotTo(HaveOccurred())
245+
})
246+
247+
It("should allow empty subdirectories", func() {
248+
err := os.Mkdir("emptydir", 0o755)
249+
Expect(err).NotTo(HaveOccurred())
250+
251+
err = checkDir()
252+
Expect(err).NotTo(HaveOccurred())
253+
})
254+
255+
It("should handle files with multiple extensions", func() {
256+
err := os.WriteFile("config.backup.yaml", []byte("key: value"), 0o644)
257+
Expect(err).NotTo(HaveOccurred())
258+
259+
err = checkDir()
260+
Expect(err).To(HaveOccurred())
261+
Expect(err.Error()).To(ContainSubstring("config.backup.yaml"))
262+
})
263+
264+
It("should allow partially capitalized files", func() {
265+
err := os.WriteFile("READme", []byte("test"), 0o644)
266+
Expect(err).NotTo(HaveOccurred())
267+
268+
err = checkDir()
269+
Expect(err).NotTo(HaveOccurred())
270+
})
271+
})
272+
})

0 commit comments

Comments
 (0)