From 06bd0f47ea09d187a994462e41eb515aa9ff41bb Mon Sep 17 00:00:00 2001 From: Mario Constanti Date: Wed, 2 Jul 2025 09:17:44 +0200 Subject: [PATCH] fix(helm): propagate project-name into init helm Without a project-name, the generated helm chart was invalid. Signed-off-by: Mario Constanti --- pkg/config/interface.go | 2 + pkg/config/v3/config.go | 26 +++++++ pkg/plugin/util/util_test.go | 58 +++++++++++++++ pkg/plugins/common/kustomize/v2/init.go | 20 +----- pkg/plugins/optional/helm/v1alpha/init.go | 14 ++++ test/e2e/helm/e2e_suite_test.go | 32 +++++++++ test/e2e/helm/generate_test.go | 87 +++++++++++++++++++++++ test/e2e/setup.sh | 1 + 8 files changed, 222 insertions(+), 18 deletions(-) create mode 100644 test/e2e/helm/e2e_suite_test.go create mode 100644 test/e2e/helm/generate_test.go diff --git a/pkg/config/interface.go b/pkg/config/interface.go index b97a52417a4..caef04f38a6 100644 --- a/pkg/config/interface.go +++ b/pkg/config/interface.go @@ -51,6 +51,8 @@ type Config interface { // SetProjectName sets the project name. // This method was introduced in project version 3. SetProjectName(name string) error + // GenerateProjectName generates a project name. + GenerateProjectName(name string) error // GetPluginChain returns the plugin chain. // This method was introduced in project version 3. diff --git a/pkg/config/v3/config.go b/pkg/config/v3/config.go index fac720601d5..16f9a8b05db 100644 --- a/pkg/config/v3/config.go +++ b/pkg/config/v3/config.go @@ -18,11 +18,14 @@ package v3 import ( "fmt" + "os" + "path/filepath" "strings" "sigs.k8s.io/yaml" "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/internal/validation" "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" ) @@ -376,3 +379,26 @@ func (c *Cfg) UnmarshalYAML(b []byte) error { return nil } + +// GenerateProjectName generates a default project name based on the current directory name. +func (c *Cfg) GenerateProjectName(projectName string) error { + + // Assign a default project name + if projectName == "" { + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current directory: %w", err) + } + projectName = strings.ToLower(filepath.Base(dir)) + } + // Check if the project name is a valid k8s namespace (DNS 1123 label). + if err := validation.IsDNS1123Label(projectName); err != nil { + return fmt.Errorf("project name %q is invalid: %v", projectName, err) + } + + if err := c.SetProjectName(projectName); err != nil { + return fmt.Errorf("error setting project name: %w", err) + } + + return nil +} diff --git a/pkg/plugin/util/util_test.go b/pkg/plugin/util/util_test.go index d487fa22367..b1e40790b25 100644 --- a/pkg/plugin/util/util_test.go +++ b/pkg/plugin/util/util_test.go @@ -101,4 +101,62 @@ var _ = Describe("Cover plugin util helpers", func() { Expect(lines).To(Equal([]string{"noemptylines"})) }) }) + + Describe("HasFileContentWith", Ordered, func() { + + const ( + path = "testdata/PROJECT" + content = `# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html +domain: example.org +layout: +- go.kubebuilder.io/v4 +- helm.kubebuilder.io/v1-alpha +plugins: + helm.kubebuilder.io/v1-alpha: {} +repo: github.com/example/repo +version: "3" +` + ) + + BeforeAll(func() { + err := os.MkdirAll("testdata", 0o755) + Expect(err).NotTo(HaveOccurred()) + + if _, err = os.Stat(path); os.IsNotExist(err) { + err = os.WriteFile(path, []byte(content), 0o644) + Expect(err).NotTo(HaveOccurred()) + } + }) + + AfterAll(func() { + err := os.RemoveAll("testdata") + Expect(err).NotTo(HaveOccurred()) + }) + + It("should return true when file contains the expected content", func() { + content := "repo: github.com/example/repo" + found, err := HasFileContentWith(path, content) + Expect(err).NotTo(HaveOccurred()) + Expect(found).To(BeTrue()) + }) + + It("should return true when file contains multiline expected content", func() { + content := `plugins: + helm.kubebuilder.io/v1-alpha: {}` + found, err := HasFileContentWith(path, content) + Expect(err).NotTo(HaveOccurred()) + Expect(found).To(BeTrue()) + }) + + It("should return false when file does not contain the expected content", func() { + content := "nonExistentContent" + found, err := HasFileContentWith(path, content) + Expect(err).NotTo(HaveOccurred()) + Expect(found).To(BeFalse()) + }) + + }) }) diff --git a/pkg/plugins/common/kustomize/v2/init.go b/pkg/plugins/common/kustomize/v2/init.go index e4b7ba9bc49..a17e136c36c 100644 --- a/pkg/plugins/common/kustomize/v2/init.go +++ b/pkg/plugins/common/kustomize/v2/init.go @@ -18,14 +18,10 @@ package v2 import ( "fmt" - "os" - "path/filepath" - "strings" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v4/pkg/config" - "sigs.k8s.io/kubebuilder/v4/pkg/internal/validation" "sigs.k8s.io/kubebuilder/v4/pkg/machinery" "sigs.k8s.io/kubebuilder/v4/pkg/plugin" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds" @@ -69,20 +65,8 @@ func (p *initSubcommand) InjectConfig(c config.Config) error { } // Assign a default project name - if p.name == "" { - dir, err := os.Getwd() - if err != nil { - return fmt.Errorf("error getting current directory: %w", err) - } - p.name = strings.ToLower(filepath.Base(dir)) - } - // Check if the project name is a valid k8s namespace (DNS 1123 label). - if err := validation.IsDNS1123Label(p.name); err != nil { - return fmt.Errorf("project name %q is invalid: %v", p.name, err) - } - - if err := p.config.SetProjectName(p.name); err != nil { - return fmt.Errorf("error setting project name: %w", err) + if err := p.config.GenerateProjectName(p.name); err != nil { + return fmt.Errorf("error generating project name: %w", err) } return nil diff --git a/pkg/plugins/optional/helm/v1alpha/init.go b/pkg/plugins/optional/helm/v1alpha/init.go index c09d0051334..3a556c155a5 100644 --- a/pkg/plugins/optional/helm/v1alpha/init.go +++ b/pkg/plugins/optional/helm/v1alpha/init.go @@ -19,6 +19,7 @@ package v1alpha import ( "fmt" + "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v4/pkg/config" "sigs.k8s.io/kubebuilder/v4/pkg/machinery" "sigs.k8s.io/kubebuilder/v4/pkg/plugin" @@ -29,6 +30,9 @@ var _ plugin.InitSubcommand = &initSubcommand{} type initSubcommand struct { config config.Config + + // config options + name string } func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { @@ -41,8 +45,18 @@ func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta * `, cliMeta.CommandName, plugin.KeyFor(Plugin{})) } +func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { + fs.StringVar(&p.name, "project-name", "", "name of this project") +} + func (p *initSubcommand) InjectConfig(c config.Config) error { p.config = c + + // Assign a default project name + if err := p.config.GenerateProjectName(p.name); err != nil { + return fmt.Errorf("error generating project name: %w", err) + } + return nil } diff --git a/test/e2e/helm/e2e_suite_test.go b/test/e2e/helm/e2e_suite_test.go new file mode 100644 index 00000000000..a2be5998ea0 --- /dev/null +++ b/test/e2e/helm/e2e_suite_test.go @@ -0,0 +1,32 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helm + +import ( + "fmt" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// Run e2e tests using the Ginkgo runner. +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + _, _ = fmt.Fprintf(GinkgoWriter, "Starting helm plugin kubebuilder suite\n") + RunSpecs(t, "Kubebuilder helm plugin e2e suite") +} diff --git a/test/e2e/helm/generate_test.go b/test/e2e/helm/generate_test.go new file mode 100644 index 00000000000..c8f738526ef --- /dev/null +++ b/test/e2e/helm/generate_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helm + +import ( + "path/filepath" + + pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" + + . "github.com/onsi/ginkgo/v2" + + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v4/test/e2e/utils" +) + +var _ = Describe("kubebuilder", func() { + Context("plugin helm/v1-alpha", func() { + var kbc *utils.TestContext + + BeforeEach(func() { + var err error + kbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, "GO111MODULE=on") + Expect(err).NotTo(HaveOccurred()) + Expect(kbc.Prepare()).To(Succeed()) + }) + + AfterEach(func() { + kbc.Destroy() + }) + + It("should generate a runnable project with helm plugin", func() { + GenerateProject(kbc) + }) + }) +}) + +// GenerateProject implements a helm/v1(-alpha) plugin project defined by a TestContext. +func GenerateProject(kbc *utils.TestContext) { + var err error + + By("initializing a project") + err = kbc.Init( + "--plugins", "helm.kubebuilder.io/v1-alpha", + ) + Expect(err).NotTo(HaveOccurred(), "Failed to initialize project") + + fileContainsExpr, err := pluginutil.HasFileContentWith( + filepath.Join(kbc.Dir, "PROJECT"), + `helm.kubebuilder.io/v1-alpha: {}`) + Expect(err).NotTo(HaveOccurred(), "Failed to edit sum rate for custom metrics") + Expect(fileContainsExpr).To(BeTrue(), "Failed to find helm plugin in PROJECT file") + + fileContainsExpr, err = pluginutil.HasFileContentWith( + filepath.Join(kbc.Dir, "PROJECT"), + "projectName: e2e-"+kbc.TestSuffix) + Expect(err).NotTo(HaveOccurred(), "Failed to edit sum rate for custom metrics") + Expect(fileContainsExpr).To(BeTrue(), "Failed to find projectName in PROJECT file") + + fileContainsExpr, err = pluginutil.HasFileContentWith( + filepath.Join(kbc.Dir, "dist", "chart", "Chart.yaml"), + "name: e2e-"+kbc.TestSuffix) + Expect(err).NotTo(HaveOccurred(), "Failed to edit sum rate for custom metrics") + Expect(fileContainsExpr).To(BeTrue(), "Failed to find name in Chart.yaml file") + + fileContainsExpr, err = pluginutil.HasFileContentWith( + filepath.Join(kbc.Dir, "dist", "chart", "templates", "manager", "manager.yaml"), + `metadata: + name: e2e-`+kbc.TestSuffix) + Expect(err).NotTo(HaveOccurred(), "Failed to edit sum rate for custom metrics") + Expect(fileContainsExpr).To(BeTrue(), "Failed to find name in helm template manager.yaml file") + +} diff --git a/test/e2e/setup.sh b/test/e2e/setup.sh index b023a791cda..58040791cd1 100755 --- a/test/e2e/setup.sh +++ b/test/e2e/setup.sh @@ -65,6 +65,7 @@ function test_cluster { kind load docker-image --name $KIND_CLUSTER busybox:1.36.1 go test $(dirname "$0")/grafana $flags -timeout 30m + go test $(dirname "$0")/helm $flags -timeout 30m go test $(dirname "$0")/deployimage $flags -timeout 30m go test $(dirname "$0")/v4 $flags -timeout 30m go test $(dirname "$0")/alphagenerate $flags -timeout 30m