Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/config/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

@camilamacedo86 camilamacedo86 Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!

We don't need this change because the project name is already tracked in the config, and there's no need to add a new method to the interface for it.

Just to clarify:

layout:
- go.kubebuilder.io/v4
projectName: project-v4 <- HERE WE HAVE THE PROJECT name

Hope that makes sense! 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but if you do a kubebuilder init --plugins=helm/v1-alpha in an empty directory there is no project name and a project-name isn't generated. And that's why the generated helm-charts are wrong.

My thought was not duplicating code we already have in kustomize init (https://github.com/kubernetes-sigs/kubebuilder/pull/4901/files#diff-c81dda43edfad86b44d0309ad6026ae67301b5e2e6da69cf6f3cd59d3c7b89d4L72-L85) and do the same in helm init as well.

but sure, i can revert that change and copy the same steps from kustomize init to helm init (beside the domain-stuff: https://github.com/kubernetes-sigs/kubebuilder/pull/4901/files#diff-c81dda43edfad86b44d0309ad6026ae67301b5e2e6da69cf6f3cd59d3c7b89d4L67-L69)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just saw your comment #4901 (comment)


// GetPluginChain returns the plugin chain.
// This method was introduced in project version 3.
Expand Down
26 changes: 26 additions & 0 deletions pkg/config/v3/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
}
58 changes: 58 additions & 0 deletions pkg/plugin/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
})

})
})
20 changes: 2 additions & 18 deletions pkg/plugins/common/kustomize/v2/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions pkg/plugins/optional/helm/v1alpha/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) {
Expand All @@ -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
}

Expand Down
32 changes: 32 additions & 0 deletions test/e2e/helm/e2e_suite_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
87 changes: 87 additions & 0 deletions test/e2e/helm/generate_test.go
Original file line number Diff line number Diff line change
@@ -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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the effort here!

Just a heads-up — this isn't quite accurate.
To properly test this, we need to mock a full project setup, which means running the following steps:

  • kubebuilder init
  • kubebuilder create api
  • kubebuilder create webhook
  • Uncomment kustomize config if needed
  • Then call the Helm plugin (e.g., kubebuilder edit --plugins helm)

This sequence is necessary to simulate a real-world use case.

✅ Here's an example of the full project setup being mocked:
https://github.com/kubernetes-sigs/kubebuilder/blob/master/test/e2e/v4/generate_test.go#L32-L68

📦 And here’s how we properly test Helm support after that setup:
https://github.com/kubernetes-sigs/kubebuilder/blob/master/test/e2e/v4/plugin_cluster_test.go#L158-L181

Let me know if you'd like help aligning this test with the above pattern 🙌

)
Expect(err).NotTo(HaveOccurred(), "Failed to initialize project")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for raising this — I think I now understand your point about whether we should support helm in the init interface or only in edit.

We definitely need to review this.

Technically, you can run kubebuilder init --plugins=go/v4,helm/v1-alpha1, which includes Helm during project initialization — but the Helm-specific content wouldn't be fully or properly populated at that stage.

That’s because Helm relies on the Go project being scaffolded first.

Originally, the intention was to allow us to automatically update Helm-related files using edit, since the default layout is always called in the plugin chain.

For example, if we init a project with plugins A, B, and C — when we later call create api, all those plugins will be executed in the chain automatically.

So with that in mind, it seems clearer now that having a helm init doesn't really make sense.

I think the right path is to remove the init implementation for Helm and keep only edit. Thanks again for bringing this up — I now understand the confusion you raised earlier on Slack. Would you mind opening an issue for this? And would you be interested in working on it? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the right path is to remove the init implementation for Helm and keep only edit. Thanks again for bringing this up — I now understand the confusion you raised earlier on Slack. Would you mind opening an issue for this? And would you be interested in working on it? 🙂

Thanks for that. That was exactly what i wanted to know 😅
I'm definitely open to implement this.

Will raise a new issue and with that we can close my previous issue #4899 afterwards

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removal-request for helm init created #4902


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")

}
1 change: 1 addition & 0 deletions test/e2e/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down