Skip to content

Commit 5ed6496

Browse files
authored
Merge pull request #4914 from mercedes-benz/add_helm_edit_e2e_tests
🌱 helm(e2e): test helm plugin integration
2 parents 9d0611f + 98c60e7 commit 5ed6496

File tree

6 files changed

+283
-3
lines changed

6 files changed

+283
-3
lines changed

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ require (
1313
golang.org/x/mod v0.26.0
1414
golang.org/x/text v0.27.0
1515
golang.org/x/tools v0.35.0
16+
helm.sh/helm/v3 v3.18.4
1617
sigs.k8s.io/yaml v1.5.0
1718
)
1819

1920
require (
20-
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
21+
github.com/Masterminds/semver/v3 v3.3.0 // indirect
2122
github.com/go-logr/logr v1.4.2 // indirect
2223
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
2324
github.com/google/go-cmp v0.7.0 // indirect
2425
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
2526
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2627
github.com/kr/pretty v0.3.1 // indirect
27-
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
28+
github.com/pkg/errors v0.9.1 // indirect
2829
github.com/rogpeppe/go-internal v1.14.1 // indirect
29-
github.com/stretchr/testify v1.10.0 // indirect
3030
go.uber.org/automaxprocs v1.6.0 // indirect
3131
go.yaml.in/yaml/v2 v2.4.2 // indirect
3232
golang.org/x/net v0.42.0 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
2+
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
3+
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
4+
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
15
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
26
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
37
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -28,6 +32,8 @@ github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOT
2832
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
2933
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
3034
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
35+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
36+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
3137
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3238
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
3339
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -81,5 +87,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
8187
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8288
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
8389
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
90+
helm.sh/helm/v3 v3.18.4 h1:pNhnHM3nAmDrxz6/UC+hfjDY4yeDATQCka2/87hkZXQ=
91+
helm.sh/helm/v3 v3.18.4/go.mod h1:WVnwKARAw01iEdjpEkP7Ii1tT1pTPYfM1HsakFKM3LI=
8492
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
8593
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

pkg/plugin/util/util_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,60 @@ var _ = Describe("Cover plugin util helpers", func() {
101101
Expect(lines).To(Equal([]string{"noemptylines"}))
102102
})
103103
})
104+
105+
Describe("HasFileContentWith", Ordered, func() {
106+
const (
107+
path = "testdata/PROJECT"
108+
content = `# Code generated by tool. DO NOT EDIT.
109+
# This file is used to track the info used to scaffold your project
110+
# and allow the plugins properly work.
111+
# More info: https://book.kubebuilder.io/reference/project-config.html
112+
domain: example.org
113+
layout:
114+
- go.kubebuilder.io/v4
115+
- helm.kubebuilder.io/v1-alpha
116+
plugins:
117+
helm.kubebuilder.io/v1-alpha: {}
118+
repo: github.com/example/repo
119+
version: "3"
120+
`
121+
)
122+
123+
BeforeAll(func() {
124+
err := os.MkdirAll("testdata", 0o755)
125+
Expect(err).NotTo(HaveOccurred())
126+
127+
if _, err = os.Stat(path); os.IsNotExist(err) {
128+
err = os.WriteFile(path, []byte(content), 0o644)
129+
Expect(err).NotTo(HaveOccurred())
130+
}
131+
})
132+
133+
AfterAll(func() {
134+
err := os.RemoveAll("testdata")
135+
Expect(err).NotTo(HaveOccurred())
136+
})
137+
138+
It("should return true when file contains the expected content", func() {
139+
content := "repo: github.com/example/repo"
140+
found, err := HasFileContentWith(path, content)
141+
Expect(err).NotTo(HaveOccurred())
142+
Expect(found).To(BeTrue())
143+
})
144+
145+
It("should return true when file contains multiline expected content", func() {
146+
content := `plugins:
147+
helm.kubebuilder.io/v1-alpha: {}`
148+
found, err := HasFileContentWith(path, content)
149+
Expect(err).NotTo(HaveOccurred())
150+
Expect(found).To(BeTrue())
151+
})
152+
153+
It("should return false when file does not contain the expected content", func() {
154+
content := "nonExistentContent"
155+
found, err := HasFileContentWith(path, content)
156+
Expect(err).NotTo(HaveOccurred())
157+
Expect(found).To(BeFalse())
158+
})
159+
})
104160
})

test/e2e/helm/e2e_suite_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package helm
15+
16+
import (
17+
"fmt"
18+
"testing"
19+
20+
. "github.com/onsi/ginkgo/v2"
21+
. "github.com/onsi/gomega"
22+
)
23+
24+
// Run e2e tests using the Ginkgo runner.
25+
func TestE2E(t *testing.T) {
26+
RegisterFailHandler(Fail)
27+
_, _ = fmt.Fprintf(GinkgoWriter, "Starting helm plugin kubebuilder suite\n")
28+
RunSpecs(t, "Kubebuilder helm plugin e2e suite")
29+
}

test/e2e/helm/generate_test.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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 helm
18+
19+
import (
20+
"path/filepath"
21+
22+
"github.com/spf13/afero"
23+
helmChartLoader "helm.sh/helm/v3/pkg/chart/loader"
24+
"sigs.k8s.io/kubebuilder/v4/pkg/config"
25+
"sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml"
26+
"sigs.k8s.io/kubebuilder/v4/pkg/machinery"
27+
pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
28+
helmv1alpha "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha"
29+
"sigs.k8s.io/kubebuilder/v4/test/e2e/utils"
30+
31+
. "github.com/onsi/ginkgo/v2"
32+
. "github.com/onsi/gomega"
33+
)
34+
35+
var _ = Describe("kubebuilder", func() {
36+
Context("plugin helm/v1-alpha", func() {
37+
var kbc *utils.TestContext
38+
39+
BeforeEach(func() {
40+
var err error
41+
kbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, "GO111MODULE=on")
42+
Expect(err).NotTo(HaveOccurred())
43+
Expect(kbc.Prepare()).To(Succeed())
44+
})
45+
46+
AfterEach(func() {
47+
kbc.Destroy()
48+
})
49+
50+
It("should extend an initialed project with helm plugin", func() {
51+
initTheProject(kbc)
52+
53+
By("extend the project by adding helm plugin")
54+
err := kbc.Edit(
55+
"--plugins", "helm.kubebuilder.io/v1-alpha",
56+
)
57+
Expect(err).NotTo(HaveOccurred(), "Failed to edit the project")
58+
59+
ensureCommonHelmFilesContent(kbc, false)
60+
})
61+
62+
// This test is to ensure that the helm plugin can be added to a project
63+
// that has already been initialized with the go/v4 plugin.
64+
// As the project is getting extended with webhooks,
65+
// it is needed to run the `kubebuilder edit --plugins helm.kubebuilder.io/v1-alpha` command
66+
// with ` --force` again to ensure that the webhooks are enabled in the
67+
// values.yaml file.
68+
It("should extend an initialized project with helm plugin and webhooks", func() {
69+
initTheProject(kbc)
70+
71+
By("extend the project by adding helm plugin")
72+
err := kbc.Edit(
73+
"--plugins", "helm.kubebuilder.io/v1-alpha",
74+
)
75+
Expect(err).NotTo(HaveOccurred(), "Failed to edit the project")
76+
77+
ensureCommonHelmFilesContent(kbc, false)
78+
extendProjectWithWebhooks(kbc)
79+
80+
// after creating webhooks, we want to have the webhooks enabled
81+
// in the values.yaml file, so we need to run `kubebuilder edit`
82+
// with the --force flag for the helm plugin.
83+
By("re-edit the project after creating webhooks")
84+
err = kbc.Edit(
85+
"--plugins", "helm.kubebuilder.io/v1-alpha", "--force",
86+
)
87+
Expect(err).NotTo(HaveOccurred(), "Failed to edit the project")
88+
89+
ensureCommonHelmFilesContent(kbc, true)
90+
})
91+
})
92+
})
93+
94+
// ensureCommonHelmFilesContent tests common helm-chart files which got
95+
// generated by the helm/v1(-alpha) plugin
96+
func ensureCommonHelmFilesContent(kbc *utils.TestContext, webhookEnabled bool) {
97+
var helmConfig helmv1alpha.Plugin
98+
projectConfig := getConfigFromProjectFile(filepath.Join(kbc.Dir, "PROJECT"))
99+
100+
By("decoding the helm plugin configuration")
101+
err := projectConfig.DecodePluginConfig("helm.kubebuilder.io/v1-alpha", &helmConfig)
102+
Expect(err).NotTo(HaveOccurred(), "Failed to decode Helm plugin configuration")
103+
104+
// loading the generated helm chart
105+
chart, err := helmChartLoader.LoadDir(filepath.Join(kbc.Dir, "dist", "chart"))
106+
Expect(err).NotTo(HaveOccurred(), "Failed to load helm chart")
107+
108+
// validating the helm chart metadata (Chart.yaml)
109+
err = chart.Validate()
110+
Expect(err).NotTo(HaveOccurred(), "Failed to validate helm chart")
111+
112+
// expect the chart-name equal to the name of the PROJECT
113+
Expect(chart.Name()).To(Equal("e2e-"+kbc.TestSuffix), "Chart name doesn't match")
114+
115+
// expecting the existence of a manager.yaml file
116+
var matchedFiles int
117+
for _, templateFile := range chart.Templates {
118+
switch templateFile.Name {
119+
case "templates/manager/manager.yaml":
120+
matchedFiles++
121+
default:
122+
matchedFiles += 0
123+
}
124+
}
125+
126+
Expect(matchedFiles).To(BeNumerically("==", 1))
127+
128+
// check if webhooks are enabled in the Chart.yaml
129+
if webhookEnabled {
130+
isEnabled := chart.Values["webhook"].(map[string]interface{})["enable"]
131+
Expect(isEnabled).To(Equal(webhookEnabled), "webhook isn't enabled in the Chart.yaml")
132+
}
133+
}
134+
135+
// extendProjectWithWebhooks is creating API and scaffolding webhooks in the project
136+
func extendProjectWithWebhooks(kbc *utils.TestContext) {
137+
By("creating API definition")
138+
err := kbc.CreateAPI(
139+
"--group", kbc.Group,
140+
"--version", kbc.Version,
141+
"--kind", kbc.Kind,
142+
"--namespaced",
143+
"--resource",
144+
"--controller",
145+
"--make=false",
146+
)
147+
Expect(err).NotTo(HaveOccurred(), "Failed to create API")
148+
149+
By("scaffolding mutating and validating webhooks")
150+
err = kbc.CreateWebhook(
151+
"--group", kbc.Group,
152+
"--version", kbc.Version,
153+
"--kind", kbc.Kind,
154+
"--defaulting",
155+
"--programmatic-validation",
156+
"--make=false",
157+
)
158+
Expect(err).NotTo(HaveOccurred(), "Failed to scaffolding mutating webhook")
159+
160+
By("run make manifests")
161+
Expect(kbc.Make("manifests")).To(Succeed())
162+
}
163+
164+
// initTheProject initializes a project with the go/v4 plugin and sets the domain.
165+
func initTheProject(kbc *utils.TestContext) {
166+
By("initializing a project")
167+
err := kbc.Init(
168+
"--plugins", "go/v4",
169+
"--project-version", "3",
170+
"--domain", kbc.Domain,
171+
)
172+
Expect(err).NotTo(HaveOccurred(), "Failed to initialize project")
173+
}
174+
175+
func getConfigFromProjectFile(projectFilePath string) config.Config {
176+
By("loading the PROJECT configuration")
177+
fs := afero.NewOsFs()
178+
store := yaml.New(machinery.Filesystem{FS: fs})
179+
err := store.LoadFrom(projectFilePath)
180+
Expect(err).NotTo(HaveOccurred(), "Failed to load PROJECT configuration")
181+
182+
cfg := store.Config()
183+
return cfg
184+
}

test/features.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ pushd . >/dev/null
2828
header_text "Running Grafana Plugin E2E tests"
2929
go test "$(dirname "$0")/e2e/grafana" ${flags:-} -timeout 30m
3030

31+
header_text "Running Helm Plugin E2E tests"
32+
go test "$(dirname "$0")/e2e/helm" ${flags:-} -timeout 30m
33+
3134
header_text "Running Alpha Generate Command E2E tests"
3235
go test "$(dirname "$0")/e2e/alphagenerate" ${flags:-} -timeout 30m
3336

0 commit comments

Comments
 (0)