[0-9]+)|(?P(?:alpha|beta|rc)[0-9]+))?$`
)
-var (
- goVerRegexp = regexp.MustCompile(goVerPattern)
-)
+var goVerRegexp = regexp.MustCompile(goVerPattern)
// GoVersion describes a Go version.
type GoVersion struct {
@@ -66,18 +64,18 @@ func (v *GoVersion) parse(verStr string) error {
v.major, err = strconv.Atoi(m[1])
if err != nil {
- return fmt.Errorf("error parsing major version '%s': %s", m[1], err)
+ return fmt.Errorf("error parsing major version %q: %w", m[1], err)
}
v.minor, err = strconv.Atoi(m[2])
if err != nil {
- return fmt.Errorf("error parsing minor version '%s': %s", m[2], err)
+ return fmt.Errorf("error parsing minor version %q: %w", m[2], err)
}
if m[3] != "" {
v.patch, err = strconv.Atoi(m[3])
if err != nil {
- return fmt.Errorf("error parsing patch version '%s': %s", m[2], err)
+ return fmt.Errorf("error parsing patch version %q: %w", m[2], err)
}
}
@@ -125,15 +123,15 @@ func (v GoVersion) Compare(other GoVersion) int {
}
// ValidateGoVersion verifies that Go is installed and the current go version is supported by a plugin.
-func ValidateGoVersion(min, max GoVersion) error {
- err := fetchAndCheckGoVersion(min, max)
+func ValidateGoVersion(minVersion, maxVersion GoVersion) error {
+ err := fetchAndCheckGoVersion(minVersion, maxVersion)
if err != nil {
- return fmt.Errorf("%s. You can skip this check using the --skip-go-version-check flag", err)
+ return fmt.Errorf("you can skip this check using the --skip-go-version-check flag: %w", err)
}
return nil
}
-func fetchAndCheckGoVersion(min, max GoVersion) error {
+func fetchAndCheckGoVersion(minVersion, maxVersion GoVersion) error {
cmd := exec.Command("go", "version")
out, err := cmd.Output()
if err != nil {
@@ -145,20 +143,20 @@ func fetchAndCheckGoVersion(min, max GoVersion) error {
return fmt.Errorf("found invalid Go version: %q", string(out))
}
goVer := split[2]
- if err := checkGoVersion(goVer, min, max); err != nil {
- return fmt.Errorf("go version '%s' is incompatible because '%s'", goVer, err)
+ if err := checkGoVersion(goVer, minVersion, maxVersion); err != nil {
+ return fmt.Errorf("go version %q is incompatible: %w", goVer, err)
}
return nil
}
-func checkGoVersion(verStr string, min, max GoVersion) error {
+func checkGoVersion(verStr string, minVersion, maxVersion GoVersion) error {
var version GoVersion
if err := version.parse(verStr); err != nil {
return err
}
- if version.Compare(min) < 0 || version.Compare(max) >= 0 {
- return fmt.Errorf("plugin requires %s <= version < %s", min, max)
+ if version.Compare(minVersion) < 0 || version.Compare(maxVersion) >= 0 {
+ return fmt.Errorf("plugin requires %q <= version < %q", minVersion, maxVersion)
}
return nil
diff --git a/pkg/plugins/golang/go_version_test.go b/pkg/plugins/golang/go_version_test.go
index 17c45db8660..4153dded316 100644
--- a/pkg/plugins/golang/go_version_test.go
+++ b/pkg/plugins/golang/go_version_test.go
@@ -17,6 +17,7 @@ limitations under the License.
package golang
import (
+ "errors"
"sort"
. "github.com/onsi/ginkgo/v2"
@@ -24,6 +25,34 @@ import (
)
var _ = Describe("GoVersion", func() {
+ Context("String", func() {
+ It("patch is not empty", func() {
+ v := GoVersion{major: 1, minor: 1, patch: 1}
+ Expect(v.String()).To(Equal("go1.1.1"))
+ })
+ It("preRelease is not empty", func() {
+ v := GoVersion{major: 1, minor: 1, prerelease: "-alpha"}
+ Expect(v.String()).To(Equal("go1.1-alpha"))
+ })
+ It("default", func() {
+ v := GoVersion{major: 1, minor: 1}
+ Expect(v.String()).To(Equal("go1.1"))
+ })
+ })
+
+ Context("MustParse", func() {
+ It("succeeds", func() {
+ v := GoVersion{major: 1, minor: 1, patch: 1}
+ Expect(MustParse("go1.1.1")).To(Equal(v))
+ })
+ It("panics", func() {
+ triggerPanic := func() {
+ MustParse("go1.a")
+ }
+ Expect(triggerPanic).To(PanicWith(errors.New("invalid version string")))
+ })
+ })
+
Context("parse", func() {
var v GoVersion
@@ -80,6 +109,11 @@ var _ = Describe("GoVersion", func() {
Context("Compare", func() {
// Test Compare() by sorting a list.
var (
+ versions []GoVersion
+ sortedVersions []GoVersion
+ )
+
+ BeforeEach(func() {
versions = []GoVersion{
{major: 1, minor: 15, prerelease: "rc2"},
{major: 1, minor: 15, patch: 1},
@@ -113,7 +147,7 @@ var _ = Describe("GoVersion", func() {
{major: 1, minor: 16},
{major: 2, minor: 0},
}
- )
+ })
It("sorts a valid list of versions correctly", func() {
sort.Slice(versions, func(i int, j int) bool {
@@ -124,9 +158,34 @@ var _ = Describe("GoVersion", func() {
})
})
+var _ = Describe("ValidateGoVersion", func() {
+ DescribeTable("should return no error for valid/supported go versions", func(minVersion, maxVersion GoVersion) {
+ Expect(ValidateGoVersion(minVersion, maxVersion)).To(Succeed())
+ },
+ Entry("for minVersion: 1.1.1 and maxVersion: 2000.1.1", GoVersion{major: 1, minor: 1, patch: 1},
+ GoVersion{major: 2000, minor: 1, patch: 1}),
+ Entry("for minVersion: 1.1.1 and maxVersion: 1.2000.2000", GoVersion{major: 1, minor: 1, patch: 1},
+ GoVersion{major: 1, minor: 2000, patch: 1}),
+ )
+
+ DescribeTable("should return error for invalid/unsupported go versions", func(minVersion, maxVersion GoVersion) {
+ Expect(ValidateGoVersion(minVersion, maxVersion)).NotTo(Succeed())
+ },
+ Entry("for invalid min and maxVersions", GoVersion{major: 2, minor: 2, patch: 2},
+ GoVersion{major: 1, minor: 1, patch: 1}),
+ )
+})
+
var _ = Describe("checkGoVersion", func() {
- goVerMin := MustParse("go1.13")
- goVerMax := MustParse("go2.0alpha1")
+ var (
+ goVerMin GoVersion
+ goVerMax GoVersion
+ )
+
+ BeforeEach(func() {
+ goVerMin = MustParse("go1.13")
+ goVerMax = MustParse("go2.0alpha1")
+ })
DescribeTable("should return no error for supported go versions",
func(version string) { Expect(checkGoVersion(version, goVerMin, goVerMax)).To(Succeed()) },
@@ -197,6 +256,11 @@ var _ = Describe("checkGoVersion", func() {
Entry("for go 1.18.1", "go1.18.1"),
Entry("for go.1.19", "go1.19"),
Entry("for go.1.19.1", "go1.19.1"),
+ Entry("for go.1.20", "go1.20"),
+ Entry("for go.1.21", "go1.21"),
+ Entry("for go.1.22", "go1.22"),
+ Entry("for go.1.23", "go1.23"),
+ Entry("for go.1.24", "go1.24"),
)
DescribeTable("should return an error for non-supported go versions",
diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go
index b6a7338ee93..0b28536a3ee 100644
--- a/pkg/plugins/golang/options.go
+++ b/pkg/plugins/golang/options.go
@@ -19,48 +19,47 @@ package golang
import (
"path"
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/resource"
)
-var (
- coreGroups = map[string]string{
- "admission": "k8s.io",
- "admissionregistration": "k8s.io",
- "apps": "",
- "auditregistration": "k8s.io",
- "apiextensions": "k8s.io",
- "authentication": "k8s.io",
- "authorization": "k8s.io",
- "autoscaling": "",
- "batch": "",
- "certificates": "k8s.io",
- "coordination": "k8s.io",
- "core": "",
- "events": "k8s.io",
- "extensions": "",
- "imagepolicy": "k8s.io",
- "networking": "k8s.io",
- "node": "k8s.io",
- "metrics": "k8s.io",
- "policy": "",
- "rbac.authorization": "k8s.io",
- "scheduling": "k8s.io",
- "setting": "k8s.io",
- "storage": "k8s.io",
- }
-)
+var coreGroups = map[string]string{
+ "admission": "k8s.io",
+ "admissionregistration": "k8s.io",
+ "apps": "",
+ "auditregistration": "k8s.io",
+ "apiextensions": "k8s.io",
+ "authentication": "k8s.io",
+ "authorization": "k8s.io",
+ "autoscaling": "",
+ "batch": "",
+ "certificates": "k8s.io",
+ "coordination": "k8s.io",
+ "core": "",
+ "events": "k8s.io",
+ "extensions": "",
+ "imagepolicy": "k8s.io",
+ "networking": "k8s.io",
+ "node": "k8s.io",
+ "metrics": "k8s.io",
+ "policy": "",
+ "rbac.authorization": "k8s.io",
+ "scheduling": "k8s.io",
+ "setting": "k8s.io",
+ "storage": "k8s.io",
+}
// Options contains the information required to build a new resource.Resource.
type Options struct {
// Plural is the resource's kind plural form.
Plural string
- // CRDVersion is the CustomResourceDefinition API version that will be used for the resource.
- CRDVersion string
- // WebhookVersion is the {Validating,Mutating}WebhookConfiguration API version that will be used for the resource.
- WebhookVersion string
+ // ExternalAPIPath allows to inform a path for APIs not defined in the project
+ ExternalAPIPath string
+
+ // ExternalAPIPath allows to inform the resource domain to build the Qualified Group
+ // to generate the RBAC markers
+ ExternalAPIDomain string
// Namespaced is true if the resource should be namespaced.
Namespaced bool
@@ -71,6 +70,15 @@ type Options struct {
DoDefaulting bool
DoValidation bool
DoConversion bool
+
+ // Spoke versions for conversion webhook
+ Spoke []string
+
+ // DefaultingPath is the custom path for the defaulting/mutating webhook
+ DefaultingPath string
+
+ // ValidationPath is the custom path for the validation webhook
+ ValidationPath string
}
// UpdateResource updates the provided resource with the options
@@ -81,8 +89,9 @@ func (opts Options) UpdateResource(res *resource.Resource, c config.Config) {
if opts.DoAPI {
res.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup())
+
res.API = &resource.API{
- CRDVersion: opts.CRDVersion,
+ CRDVersion: "v1",
Namespaced: opts.Namespaced,
}
}
@@ -93,36 +102,50 @@ func (opts Options) UpdateResource(res *resource.Resource, c config.Config) {
if opts.DoDefaulting || opts.DoValidation || opts.DoConversion {
res.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup())
- res.Webhooks.WebhookVersion = opts.WebhookVersion
+
+ res.Webhooks.WebhookVersion = "v1"
if opts.DoDefaulting {
res.Webhooks.Defaulting = true
+ if opts.DefaultingPath != "" {
+ res.Webhooks.DefaultingPath = opts.DefaultingPath
+ }
}
if opts.DoValidation {
res.Webhooks.Validation = true
+ if opts.ValidationPath != "" {
+ res.Webhooks.ValidationPath = opts.ValidationPath
+ }
}
if opts.DoConversion {
res.Webhooks.Conversion = true
+ res.Webhooks.Spoke = opts.Spoke
}
}
+ if len(opts.ExternalAPIPath) > 0 {
+ res.External = true
+ }
+
// domain and path may need to be changed in case we are referring to a builtin core resource:
// - Check if we are scaffolding the resource now => project resource
// - Check if we already scaffolded the resource => project resource
// - Check if the resource group is a well-known core group => builtin core resource
// - In any other case, default to => project resource
- // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath
if !opts.DoAPI {
var alreadyHasAPI bool
- if c.GetVersion().Compare(cfgv2.Version) == 0 {
- alreadyHasAPI = c.HasResource(res.GVK)
- } else {
- loadedRes, err := c.GetResource(res.GVK)
- alreadyHasAPI = err == nil && loadedRes.HasAPI()
- }
+ loadedRes, err := c.GetResource(res.GVK)
+ alreadyHasAPI = err == nil && loadedRes.HasAPI()
if !alreadyHasAPI {
- if domain, found := coreGroups[res.Group]; found {
- res.Domain = domain
- res.Path = path.Join("k8s.io", "api", res.Group, res.Version)
+ if res.External {
+ res.Path = opts.ExternalAPIPath
+ res.Domain = opts.ExternalAPIDomain
+ } else {
+ // Handle core types
+ if domain, found := coreGroups[res.Group]; found {
+ res.Core = true
+ res.Domain = domain
+ res.Path = path.Join("k8s.io", "api", res.Group, res.Version)
+ }
}
}
}
diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go
index df87ee20748..49f6fc43daa 100644
--- a/pkg/plugins/golang/options_test.go
+++ b/pkg/plugins/golang/options_test.go
@@ -22,10 +22,9 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2"
- cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/resource"
)
var _ = Describe("Options", func() {
@@ -36,7 +35,13 @@ var _ = Describe("Options", func() {
version = "v1"
kind = "FirstMate"
)
+
var (
+ gvk resource.GVK
+ cfg config.Config
+ )
+
+ BeforeEach(func() {
gvk = resource.GVK{
Group: group,
Domain: domain,
@@ -44,10 +49,6 @@ var _ = Describe("Options", func() {
Kind: kind,
}
- cfg config.Config
- )
-
- BeforeEach(func() {
cfg = cfgv3.New()
_ = cfg.SetRepository("test")
})
@@ -77,17 +78,18 @@ var _ = Describe("Options", func() {
if options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion {
if multiGroup {
Expect(res.Path).To(Equal(
- path.Join(cfg.GetRepository(), "apis", gvk.Group, gvk.Version)))
+ path.Join(cfg.GetRepository(), "api", gvk.Group, gvk.Version)))
} else {
Expect(res.Path).To(Equal(path.Join(cfg.GetRepository(), "api", gvk.Version)))
}
+ } else if len(options.ExternalAPIPath) > 0 {
+ Expect(res.Path).To(Equal("testPath"))
} else {
// Core-resources have a path despite not having an API/Webhook but they are not tested here
Expect(res.Path).To(Equal(""))
}
Expect(res.API).NotTo(BeNil())
if options.DoAPI {
- Expect(res.API.CRDVersion).To(Equal(options.CRDVersion))
Expect(res.API.Namespaced).To(Equal(options.Namespaced))
Expect(res.API.IsEmpty()).To(BeFalse())
} else {
@@ -96,14 +98,20 @@ var _ = Describe("Options", func() {
Expect(res.Controller).To(Equal(options.DoController))
Expect(res.Webhooks).NotTo(BeNil())
if options.DoDefaulting || options.DoValidation || options.DoConversion {
- Expect(res.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion))
Expect(res.Webhooks.Defaulting).To(Equal(options.DoDefaulting))
Expect(res.Webhooks.Validation).To(Equal(options.DoValidation))
Expect(res.Webhooks.Conversion).To(Equal(options.DoConversion))
+ Expect(res.Webhooks.Spoke).To(Equal(options.Spoke))
Expect(res.Webhooks.IsEmpty()).To(BeFalse())
} else {
Expect(res.Webhooks.IsEmpty()).To(BeTrue())
}
+
+ if len(options.ExternalAPIPath) > 0 {
+ Expect(res.External).To(BeTrue())
+ Expect(res.Domain).To(Equal("test.io"))
+ }
+
Expect(res.QualifiedGroup()).To(Equal(gvk.Group + "." + gvk.Domain))
Expect(res.PackageName()).To(Equal(gvk.Group))
Expect(res.ImportAlias()).To(Equal(gvk.Group + gvk.Version))
@@ -111,10 +119,10 @@ var _ = Describe("Options", func() {
},
Entry("when updating nothing", Options{}),
Entry("when updating the plural", Options{Plural: "mates"}),
- Entry("when updating the API", Options{DoAPI: true, CRDVersion: "v1", Namespaced: true}),
Entry("when updating the Controller", Options{DoController: true}),
- Entry("when updating Webhooks",
- Options{WebhookVersion: "v1", DoDefaulting: true, DoValidation: true, DoConversion: true}),
+ Entry("when updating with External API Path", Options{ExternalAPIPath: "testPath", ExternalAPIDomain: "test.io"}),
+ Entry("when updating the API with setting webhooks params",
+ Options{DoAPI: true, DoDefaulting: true, DoValidation: true, DoConversion: true}),
)
DescribeTable("should use core apis",
@@ -156,7 +164,7 @@ var _ = Describe("Options", func() {
// the `HasAPI` method of the resource obtained with `GetResource` will always return false.
// Instead, the existence of a resource in the list means the API was scaffolded.
func(group, qualified string) {
- cfg = cfgv2.New()
+ cfg = cfgv3.New()
_ = cfg.SetRepository("test")
options := Options{}
diff --git a/pkg/plugins/golang/repository.go b/pkg/plugins/golang/repository.go
index 47debda41c3..e815e5dce4c 100644
--- a/pkg/plugins/golang/repository.go
+++ b/pkg/plugins/golang/repository.go
@@ -18,6 +18,7 @@ package golang
import (
"encoding/json"
+ "errors"
"fmt"
"os"
"os/exec"
@@ -39,14 +40,15 @@ func findGoModulePath() (string, error) {
cmd.Env = append(cmd.Env, os.Environ()...)
out, err := cmd.Output()
if err != nil {
- if exitErr, isExitErr := err.(*exec.ExitError); isExitErr {
+ var exitErr *exec.ExitError
+ if errors.As(err, &exitErr) {
err = fmt.Errorf("%s", string(exitErr.Stderr))
}
return "", err
}
mod := goMod{}
- if err := json.Unmarshal(out, &mod); err != nil {
- return "", err
+ if err = json.Unmarshal(out, &mod); err != nil {
+ return "", fmt.Errorf("failed to unmarshal go.mod: %w", err)
}
return mod.Module.Path, nil
}
@@ -77,12 +79,13 @@ func FindCurrentRepo() (string, error) {
cmd := exec.Command("go", "mod", "init")
cmd.Env = append(cmd.Env, os.Environ()...)
if _, err := cmd.Output(); err != nil {
- if exitErr, isExitErr := err.(*exec.ExitError); isExitErr {
+ var exitErr *exec.ExitError
+ if errors.As(err, &exitErr) {
err = fmt.Errorf("%s", string(exitErr.Stderr))
}
// give up, let the user figure it out
return "", fmt.Errorf("could not determine repository path from module data, "+
- "package data, or by initializing a module: %v", err)
+ "package data, or by initializing a module: %w", err)
}
//nolint:errcheck
defer os.Remove("go.mod") // clean up after ourselves
diff --git a/pkg/plugins/golang/repository_test.go b/pkg/plugins/golang/repository_test.go
new file mode 100644
index 00000000000..ee86a29a12d
--- /dev/null
+++ b/pkg/plugins/golang/repository_test.go
@@ -0,0 +1,111 @@
+/*
+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 golang
+
+import (
+ "os"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("golang:repository", func() {
+ var (
+ tmpDir string
+ oldDir string
+ )
+
+ BeforeEach(func() {
+ var err error
+ tmpDir, err = os.MkdirTemp("", "repo-test")
+ Expect(err).NotTo(HaveOccurred())
+ oldDir, err = os.Getwd()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(os.Chdir(tmpDir)).To(Succeed())
+ })
+
+ AfterEach(func() {
+ Expect(os.Chdir(oldDir)).To(Succeed())
+ Expect(os.RemoveAll(tmpDir)).To(Succeed())
+ })
+
+ When("go.mod exists", func() {
+ BeforeEach(func() {
+ // Simulate `go mod edit -json` output by writing a go.mod file and using go commands
+ Expect(os.WriteFile("go.mod", []byte("module github.com/example/repo\n"), 0o644)).To(Succeed())
+ })
+
+ It("findGoModulePath returns the module path", func() {
+ path, err := findGoModulePath()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(path).To(Equal("github.com/example/repo"))
+ })
+
+ It("FindCurrentRepo returns the module path", func() {
+ path, err := FindCurrentRepo()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(path).To(Equal("github.com/example/repo"))
+ })
+ })
+
+ When("go.mod does not exist", func() {
+ It("findGoModulePath returns error", func() {
+ got, err := findGoModulePath()
+ Expect(err).To(HaveOccurred())
+ Expect(got).To(Equal(""))
+ })
+
+ It("FindCurrentRepo tries to init a module and returns the path or a helpful error", func() {
+ path, err := FindCurrentRepo()
+ if err != nil {
+ Expect(path).To(Equal(""))
+ Expect(err.Error()).To(ContainSubstring("could not determine repository path"))
+ } else {
+ Expect(path).NotTo(BeEmpty())
+ }
+ })
+ })
+
+ When("go mod command fails with exec.ExitError", func() {
+ var origPath string
+
+ BeforeEach(func() {
+ // Move go binary out of PATH to force exec error
+ origPath = os.Getenv("PATH")
+ // Set PATH to empty so "go" cannot be found
+ Expect(os.Setenv("PATH", "")).To(Succeed())
+ })
+
+ AfterEach(func() {
+ Expect(os.Setenv("PATH", origPath)).To(Succeed())
+ })
+
+ It("findGoModulePath returns error with stderr message", func() {
+ got, err := findGoModulePath()
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).NotTo(BeEmpty())
+ Expect(got).To(Equal(""))
+ })
+
+ It("FindCurrentRepo returns error with stderr message", func() {
+ got, err := FindCurrentRepo()
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("could not determine repository path"))
+ Expect(got).To(Equal(""))
+ })
+ })
+})
diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go
deleted file mode 100644
index 88ccd3d188b..00000000000
--- a/pkg/plugins/golang/v2/api.go
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
-Copyright 2020 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.
-*/
-
-//go:deprecated This package has been deprecated
-package v2
-
-import (
- "bufio"
- "errors"
- "fmt"
- "os"
-
- "github.com/spf13/pflag"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
- goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds"
-)
-
-var _ plugin.CreateAPISubcommand = &createAPISubcommand{}
-
-type createAPISubcommand struct {
- config config.Config
-
- options *goPlugin.Options
-
- resource *resource.Resource
-
- // Check if we have to scaffold resource and/or controller
- resourceFlag *pflag.Flag
- controllerFlag *pflag.Flag
-
- // force indicates that the resource should be created even if it already exists
- force bool
-
- // runMake indicates whether to run make or not after scaffolding APIs
- runMake bool
-}
-
-func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- subcmdMeta.Description = `Scaffold a Kubernetes API by writing a Resource definition and/or a Controller.
-
-If information about whether the resource and controller should be scaffolded
-was not explicitly provided, it will prompt the user if they should be.
-
-After the scaffold is written, the dependencies will be updated and
-make generate will be run.
-`
- subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate
- %[1]s create api --group ship --version v1beta1 --kind Frigate
-
- # Edit the API Scheme
- nano api/v1beta1/frigate_types.go
-
- # Edit the Controller
- nano controllers/frigate/frigate_controller.go
-
- # Edit the Controller Test
- nano controllers/frigate/frigate_controller_test.go
-
- # Generate the manifests
- make manifests
-
- # Install CRDs into the Kubernetes cluster using kubectl apply
- make install
-
- # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
- make run
-`, cliMeta.CommandName)
-}
-
-func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {
- fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files")
-
- fs.BoolVar(&p.force, "force", false,
- "attempt to create resource even if it already exists")
-
- p.options = &goPlugin.Options{CRDVersion: "v1beta1"}
- // p.options.Plural can be set to specify an irregular plural form
-
- fs.BoolVar(&p.options.DoAPI, "resource", true,
- "if set, generate the resource without prompting the user")
- p.resourceFlag = fs.Lookup("resource")
- fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced")
-
- fs.BoolVar(&p.options.DoController, "controller", true,
- "if set, generate the controller without prompting the user")
- p.controllerFlag = fs.Lookup("controller")
-}
-
-func (p *createAPISubcommand) InjectConfig(c config.Config) error {
- p.config = c
-
- return nil
-}
-
-func (p *createAPISubcommand) InjectResource(res *resource.Resource) error {
- p.resource = res
-
- if p.resource.Group == "" {
- return fmt.Errorf("group cannot be empty")
- }
-
- // Ask for API and Controller if not specified
- reader := bufio.NewReader(os.Stdin)
- if !p.resourceFlag.Changed {
- fmt.Println("Create Resource [y/n]")
- p.options.DoAPI = util.YesNo(reader)
- }
- if !p.controllerFlag.Changed {
- fmt.Println("Create Controller [y/n]")
- p.options.DoController = util.YesNo(reader)
- }
-
- p.options.UpdateResource(p.resource, p.config)
-
- if err := p.resource.Validate(); err != nil {
- return err
- }
-
- // In case we want to scaffold a resource API we need to do some checks
- if p.options.DoAPI {
- // Check that resource doesn't have the API scaffolded or flag force was set
- if res, err := p.config.GetResource(p.resource.GVK); err == nil && res.HasAPI() && !p.force {
- return errors.New("API resource already exists")
- }
-
- // Check that the provided group can be added to the project
- if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) {
- return fmt.Errorf("multiple groups are not allowed by default, to enable multi-group visit %s",
- "https://kubebuilder.io/migration/multi-group.html")
- }
- }
-
- return nil
-}
-
-func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {
- scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force)
- scaffolder.InjectFS(fs)
- return scaffolder.Scaffold()
-}
-
-func (p *createAPISubcommand) PostScaffold() error {
- err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
- if err != nil {
- return err
- }
-
- if p.runMake && p.resource.HasAPI() {
- err = util.RunCmd("Running make", "make", "generate")
- if err != nil {
- return err
- }
- fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests \n")
- }
-
- return nil
-}
diff --git a/pkg/plugins/golang/v2/edit.go b/pkg/plugins/golang/v2/edit.go
deleted file mode 100644
index d8e04f39443..00000000000
--- a/pkg/plugins/golang/v2/edit.go
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-Copyright 2020 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.
-*/
-
-//go:deprecated This package has been deprecated
-package v2
-
-import (
- "fmt"
-
- "github.com/spf13/pflag"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds"
-)
-
-var _ plugin.EditSubcommand = &editSubcommand{}
-
-type editSubcommand struct {
- config config.Config
-
- multigroup bool
-}
-
-func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- subcmdMeta.Description = `This command will edit the project configuration.
-Features supported:
- - Toggle between single or multi group projects.
-`
- subcmdMeta.Examples = fmt.Sprintf(` # Enable the multigroup layout
- %[1]s edit --multigroup
-
- # Disable the multigroup layout
- %[1]s edit --multigroup=false
-`, cliMeta.CommandName)
-}
-
-func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {
- fs.BoolVar(&p.multigroup, "multigroup", false, "enable or disable multigroup layout")
-}
-
-func (p *editSubcommand) InjectConfig(c config.Config) error {
- p.config = c
-
- return nil
-}
-
-func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {
- scaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup)
- scaffolder.InjectFS(fs)
- return scaffolder.Scaffold()
-}
diff --git a/pkg/plugins/golang/v2/init.go b/pkg/plugins/golang/v2/init.go
deleted file mode 100644
index bafe378c359..00000000000
--- a/pkg/plugins/golang/v2/init.go
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
-Copyright 2020 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.
-*/
-
-//go:deprecated This package has been deprecated
-package v2
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/spf13/pflag"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2"
- "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds"
-)
-
-// Variables and function to check Go version requirements.
-var (
- goVerMin = golang.MustParse("go1.13")
- goVerMax = golang.MustParse("go2.0alpha1")
-)
-
-var _ plugin.InitSubcommand = &initSubcommand{}
-
-type initSubcommand struct {
- config config.Config
-
- // For help text.
- commandName string
-
- // boilerplate options
- license string
- owner string
-
- // config options
- domain string
- repo string
- name string
-
- // flags
- fetchDeps bool
- skipGoVersionCheck bool
-}
-
-func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- p.commandName = cliMeta.CommandName
-
- subcmdMeta.Description = `Initialize a new project including the following files:
- - a "go.mod" with project dependencies
- - a "PROJECT" file that stores project configuration
- - a "Makefile" with several useful make targets for the project
- - several YAML files for project deployment under the "config" directory
- - a "main.go" file that creates the manager that will run the project controllers
-`
- subcmdMeta.Examples = fmt.Sprintf(` # Initialize a new project with your domain and name in copyright
- %[1]s init --plugins go/v2 --domain example.org --owner "Your name"
-
- # Initialize a new project defining a specific project version
- %[1]s init --plugins go/v2 --project-version 2
-`, cliMeta.CommandName)
-}
-
-func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {
- fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check",
- false, "if specified, skip checking the Go version")
-
- // dependency args
- fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "ensure dependencies are downloaded")
-
- // boilerplate args
- fs.StringVar(&p.license, "license", "apache2",
- "license to use to boilerplate, may be one of 'apache2', 'none'")
- fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright")
-
- // project args
- fs.StringVar(&p.domain, "domain", "my.domain", "domain for groups")
- fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+
- "defaults to the go package of the current working directory.")
- fs.StringVar(&p.name, "project-name", "", "name of this project")
-}
-
-func (p *initSubcommand) InjectConfig(c config.Config) error {
- p.config = c
-
- if err := p.config.SetDomain(p.domain); err != nil {
- return err
- }
-
- // Try to guess repository if flag is not set.
- if p.repo == "" {
- repoPath, err := golang.FindCurrentRepo()
- if err != nil {
- return fmt.Errorf("error finding current repository: %v", err)
- }
- p.repo = repoPath
- }
- if err := p.config.SetRepository(p.repo); err != nil {
- return err
- }
-
- if p.config.GetVersion().Compare(cfgv2.Version) > 0 {
- // Assign a default project name
- if p.name == "" {
- dir, err := os.Getwd()
- if err != nil {
- return fmt.Errorf("error getting current directory: %v", 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 (%s) is invalid: %v", p.name, err)
- }
- if err := p.config.SetProjectName(p.name); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (p *initSubcommand) PreScaffold(machinery.Filesystem) error {
- // Ensure Go version is in the allowed range if check not turned off.
- if !p.skipGoVersionCheck {
- if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {
- scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner)
- scaffolder.InjectFS(fs)
- err := scaffolder.Scaffold()
- if err != nil {
- return err
- }
-
- if !p.fetchDeps {
- fmt.Println("Skipping fetching dependencies.")
- return nil
- }
-
- // Ensure that we are pinning controller-runtime version
- // xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997
- err = util.RunCmd("Get controller runtime", "go", "get",
- "sigs.k8s.io/controller-runtime@"+scaffolds.ControllerRuntimeVersion)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (p *initSubcommand) PostScaffold() error {
- err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
- if err != nil {
- return err
- }
-
- fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName)
- return nil
-}
diff --git a/pkg/plugins/golang/v2/plugin.go b/pkg/plugins/golang/v2/plugin.go
deleted file mode 100644
index 894a660dd1f..00000000000
--- a/pkg/plugins/golang/v2/plugin.go
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-Copyright 2020 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.
-*/
-
-//go:deprecated This package has been deprecated
-package v2
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2"
- cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
-)
-
-const pluginName = "go." + plugins.DefaultNameQualifier
-
-var (
- pluginVersion = plugin.Version{Number: 2}
- supportedProjectVersions = []config.Version{cfgv2.Version, cfgv3.Version}
-)
-
-var _ plugin.Full = Plugin{}
-
-// Plugin implements the plugin.Full interface
-type Plugin struct {
- initSubcommand
- createAPISubcommand
- createWebhookSubcommand
- editSubcommand
-}
-
-// Name returns the name of the plugin
-func (Plugin) Name() string { return pluginName }
-
-// Version returns the version of the plugin
-func (Plugin) Version() plugin.Version { return pluginVersion }
-
-// SupportedProjectVersions returns an array with all project versions supported by the plugin
-func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }
-
-// GetInitSubcommand will return the subcommand which is responsible for initializing and common scaffolding
-func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand }
-
-// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis
-func (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand }
-
-// GetCreateWebhookSubcommand will return the subcommand which is responsible for scaffolding webhooks
-func (p Plugin) GetCreateWebhookSubcommand() plugin.CreateWebhookSubcommand {
- return &p.createWebhookSubcommand
-}
-
-// GetEditSubcommand will return the subcommand which is responsible for editing the scaffold of the project
-func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }
-
-func (p Plugin) DeprecationWarning() string {
- return "This version is deprecated and is no longer scaffolded by default since `28 Apr 2021`." +
- "The `go/v2` plugin cannot scaffold projects in which CRDs and/or Webhooks have a `v1` API version." +
- "Be aware that v1beta1 API for CRDs and Webhooks was deprecated on Kubernetes 1.16 and are" +
- "removed as of the Kubernetes 1.22 release. Therefore, since this plugin cannot produce projects that" +
- "work on Kubernetes versions >= 1.22, it is recommended to upgrade your project " +
- "to the latest versions available."
-}
diff --git a/pkg/plugins/golang/v2/scaffolds/api.go b/pkg/plugins/golang/v2/scaffolds/api.go
deleted file mode 100644
index 46d7f20fc42..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/api.go
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
-Copyright 2019 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 scaffolds
-
-import (
- "fmt"
-
- "github.com/spf13/afero"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack"
-)
-
-var _ plugins.Scaffolder = &apiScaffolder{}
-
-// apiScaffolder contains configuration for generating scaffolding for Go type
-// representing the API and controller that implements the behavior for the API.
-type apiScaffolder struct {
- config config.Config
- resource resource.Resource
-
- // fs is the filesystem that will be used by the scaffolder
- fs machinery.Filesystem
-
- // force indicates whether to scaffold controller files even if it exists or not
- force bool
-}
-
-// NewAPIScaffolder returns a new Scaffolder for API/controller creation operations
-func NewAPIScaffolder(config config.Config, res resource.Resource, force bool) plugins.Scaffolder {
- return &apiScaffolder{
- config: config,
- resource: res,
- force: force,
- }
-}
-
-// InjectFS implements cmdutil.Scaffolder
-func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) {
- s.fs = fs
-}
-
-// Scaffold implements cmdutil.Scaffolder
-func (s *apiScaffolder) Scaffold() error {
- fmt.Println("Writing scaffold for you to edit...")
-
- // Load the boilerplate
- boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath)
- if err != nil {
- return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err)
- }
-
- // Initialize the machinery.Scaffold that will write the files to disk
- scaffold := machinery.NewScaffold(s.fs,
- machinery.WithConfig(s.config),
- machinery.WithBoilerplate(string(boilerplate)),
- machinery.WithResource(&s.resource),
- )
-
- // Keep track of these values before the update
- doAPI := s.resource.HasAPI()
- doController := s.resource.HasController()
-
- // Project version v2 only tracked GVK triplets of each resource.
- // As they were only tracked when the API was scaffolded, the presence of a
- // resource in the config file was used in webhook creation to verify that
- // the API had been scaffolded previously. From project version v3 onwards
- // this information is stored in the API field of the resource, so we can
- // update the resources except for project version 2 when no API was scaffolded.
- if doAPI || s.config.GetVersion().Compare(cfgv2.Version) == 1 {
- if err := s.config.UpdateResource(s.resource); err != nil {
- return fmt.Errorf("error updating resource: %w", err)
- }
- }
-
- if doAPI {
- if err := scaffold.Execute(
- &api.Types{Force: s.force},
- &api.Group{},
- &samples.CRDSample{Force: s.force},
- &rbac.CRDEditorRole{},
- &rbac.CRDViewerRole{},
- &patches.EnableWebhookPatch{},
- &patches.EnableCAInjectionPatch{},
- ); err != nil {
- return fmt.Errorf("error scaffolding APIs: %w", err)
- }
-
- if err := scaffold.Execute(
- &crd.Kustomization{},
- &crd.KustomizeConfig{},
- ); err != nil {
- return fmt.Errorf("error scaffolding kustomization: %v", err)
- }
- }
-
- if doController {
- if err := scaffold.Execute(
- &controllers.SuiteTest{Force: s.force},
- &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force},
- ); err != nil {
- return fmt.Errorf("error scaffolding controller: %v", err)
- }
- }
-
- if err := scaffold.Execute(
- &templates.MainUpdater{WireResource: doAPI, WireController: doController},
- ); err != nil {
- return fmt.Errorf("error updating main.go: %v", err)
- }
-
- return nil
-}
diff --git a/pkg/plugins/golang/v2/scaffolds/doc.go b/pkg/plugins/golang/v2/scaffolds/doc.go
deleted file mode 100644
index d969260e58b..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/doc.go
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
-Copyright 2018 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 scaffolds contains libraries for scaffolding code to use with controller-runtime
-package scaffolds
diff --git a/pkg/plugins/golang/v2/scaffolds/edit.go b/pkg/plugins/golang/v2/scaffolds/edit.go
deleted file mode 100644
index 0e4f39751c2..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/edit.go
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
-Copyright 2020 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 scaffolds
-
-import (
- "fmt"
- "strings"
-
- "github.com/spf13/afero"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
-)
-
-var _ plugins.Scaffolder = &editScaffolder{}
-
-type editScaffolder struct {
- config config.Config
- multigroup bool
-
- // fs is the filesystem that will be used by the scaffolder
- fs machinery.Filesystem
-}
-
-// NewEditScaffolder returns a new Scaffolder for configuration edit operations
-func NewEditScaffolder(config config.Config, multigroup bool) plugins.Scaffolder {
- return &editScaffolder{
- config: config,
- multigroup: multigroup,
- }
-}
-
-// InjectFS implements cmdutil.Scaffolder
-func (s *editScaffolder) InjectFS(fs machinery.Filesystem) {
- s.fs = fs
-}
-
-// Scaffold implements cmdutil.Scaffolder
-func (s *editScaffolder) Scaffold() error {
- filename := "Dockerfile"
- bs, err := afero.ReadFile(s.fs.FS, filename)
- if err != nil {
- return err
- }
- str := string(bs)
-
- // update dockerfile
- if s.multigroup {
- str, err = ensureExistAndReplace(
- str,
- "COPY api/ api/",
- `COPY apis/ apis/`)
- } else {
- str, err = ensureExistAndReplace(
- str,
- "COPY apis/ apis/",
- `COPY api/ api/`)
- }
-
- // Ignore the error encountered, if the file is already in desired format.
- if err != nil && s.multigroup != s.config.IsMultiGroup() {
- return err
- }
-
- if s.multigroup {
- _ = s.config.SetMultiGroup()
- } else {
- _ = s.config.ClearMultiGroup()
- }
-
- // Check if the str is not empty, because when the file is already in desired format it will return empty string
- // because there is nothing to replace.
- if str != "" {
- // TODO: instead of writing it directly, we should use the scaffolding machinery for consistency
- return afero.WriteFile(s.fs.FS, filename, []byte(str), 0644)
- }
-
- return nil
-}
-
-func ensureExistAndReplace(input, match, replace string) (string, error) {
- if !strings.Contains(input, match) {
- return "", fmt.Errorf("can't find %q", match)
- }
- return strings.Replace(input, match, replace, -1), nil
-}
diff --git a/pkg/plugins/golang/v2/scaffolds/init.go b/pkg/plugins/golang/v2/scaffolds/init.go
deleted file mode 100644
index fd40cb0bc14..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/init.go
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
-Copyright 2019 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 scaffolds
-
-import (
- "fmt"
-
- "github.com/spf13/afero"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack"
-)
-
-const (
- // ControllerRuntimeVersion is the kubernetes-sigs/controller-runtime version to be used in the project
- ControllerRuntimeVersion = "v0.6.4"
- // ControllerToolsVersion is the kubernetes-sigs/controller-tools version to be used in the project
- ControllerToolsVersion = "v0.3.0"
- // KustomizeVersion is the kubernetes-sigs/kustomize version to be used in the project
- // @Deprecate: KustomizeVersion came from the Kustomize plugin since go/v3
- // Go/v2 plugin exist only to ensure the backwards compatibility with the old scaffold
- // produced with kubebuilder >= 2.0.0 < 3.0.0. It does not take advantage of the plugin system
- // (see that the PROJECT file does not have the layout of the plugin and uses the old/legacy
- // version/schema.) This plugin will be deprecated with go/v4-alpha and can be removed
- // when go/v4 stable is published.
- KustomizeVersion = "v3.5.4"
-
- imageName = "controller:latest"
-)
-
-var _ plugins.Scaffolder = &initScaffolder{}
-
-type initScaffolder struct {
- config config.Config
- boilerplatePath string
- license string
- owner string
-
- // fs is the filesystem that will be used by the scaffolder
- fs machinery.Filesystem
-}
-
-// NewInitScaffolder returns a new Scaffolder for project initialization operations
-func NewInitScaffolder(config config.Config, license, owner string) plugins.Scaffolder {
- return &initScaffolder{
- config: config,
- boilerplatePath: hack.DefaultBoilerplatePath,
- license: license,
- owner: owner,
- }
-}
-
-// InjectFS implements cmdutil.Scaffolder
-func (s *initScaffolder) InjectFS(fs machinery.Filesystem) {
- s.fs = fs
-}
-
-// Scaffold implements cmdutil.Scaffolder
-func (s *initScaffolder) Scaffold() error {
- fmt.Println("Writing scaffold for you to edit...")
-
- // Initialize the machinery.Scaffold that will write the boilerplate file to disk
- // The boilerplate file needs to be scaffolded as a separate step as it is going to
- // be used by the rest of the files, even those scaffolded in this command call.
- scaffold := machinery.NewScaffold(s.fs,
- machinery.WithConfig(s.config),
- )
-
- bpFile := &hack.Boilerplate{
- License: s.license,
- Owner: s.owner,
- }
- bpFile.Path = s.boilerplatePath
- if err := scaffold.Execute(bpFile); err != nil {
- return err
- }
-
- boilerplate, err := afero.ReadFile(s.fs.FS, s.boilerplatePath)
- if err != nil {
- return err
- }
-
- // Initialize the machinery.Scaffold that will write the files to disk
- scaffold = machinery.NewScaffold(s.fs,
- machinery.WithConfig(s.config),
- machinery.WithBoilerplate(string(boilerplate)),
- )
-
- return scaffold.Execute(
- &rbac.Kustomization{},
- &rbac.AuthProxyRole{},
- &rbac.AuthProxyRoleBinding{},
- &rbac.AuthProxyService{},
- &rbac.AuthProxyClientRole{},
- &rbac.RoleBinding{},
- &rbac.LeaderElectionRole{},
- &rbac.LeaderElectionRoleBinding{},
- &manager.Kustomization{},
- &manager.Config{Image: imageName},
- &templates.Main{},
- &templates.GoMod{ControllerRuntimeVersion: ControllerRuntimeVersion},
- &templates.GitIgnore{},
- &templates.Makefile{
- Image: imageName,
- BoilerplatePath: s.boilerplatePath,
- ControllerToolsVersion: ControllerToolsVersion,
- KustomizeVersion: KustomizeVersion,
- },
- &templates.Dockerfile{},
- &kdefault.Kustomization{},
- &kdefault.ManagerAuthProxyPatch{},
- &kdefault.ManagerWebhookPatch{},
- &kdefault.WebhookCAInjectionPatch{},
- &webhook.Kustomization{},
- &webhook.KustomizeConfig{},
- &webhook.Service{},
- &prometheus.Kustomization{},
- &prometheus.Monitor{},
- &certmanager.Certificate{},
- &certmanager.Kustomization{},
- &certmanager.KustomizeConfig{},
- )
-}
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go
deleted file mode 100644
index ba9f21f6acc..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
-Copyright 2018 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 api
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Group{}
-
-// Group scaffolds the file that defines the registration methods for a certain group and version
-type Group struct {
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Group) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- f.Path = filepath.Join("apis", "%[group]", "%[version]", "groupversion_info.go")
- } else {
- f.Path = filepath.Join("api", "%[version]", "groupversion_info.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = groupTemplate
-
- return nil
-}
-
-//nolint:lll
-const groupTemplate = `{{ .Boilerplate }}
-
-// Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group
-//+kubebuilder:object:generate=true
-//+groupName={{ .Resource.QualifiedGroup }}
-package {{ .Resource.Version }}
-
-import (
- "k8s.io/apimachinery/pkg/runtime/schema"
- "sigs.k8s.io/controller-runtime/pkg/scheme"
-)
-
-var (
- // GroupVersion is group version used to register these objects
- GroupVersion = schema.GroupVersion{Group: "{{ .Resource.QualifiedGroup }}", Version: "{{ .Resource.Version }}"}
-
- // SchemeBuilder is used to add go types to the GroupVersionKind scheme
- SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
-
- // AddToScheme adds the types in this group-version to the given scheme.
- AddToScheme = SchemeBuilder.AddToScheme
-)
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go
deleted file mode 100644
index d80e5f33b93..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
-Copyright 2018 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 api
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Types{}
-
-// Types scaffolds the file that defines the schema for a CRD
-// nolint:maligned
-type Types struct {
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- Force bool
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Types) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- f.Path = filepath.Join("apis", "%[group]", "%[version]", "%[kind]_types.go")
- } else {
- f.Path = filepath.Join("api", "%[version]", "%[kind]_types.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
- fmt.Println(f.Path)
-
- f.TemplateBody = typesTemplate
-
- if f.Force {
- f.IfExistsAction = machinery.OverwriteFile
- } else {
- f.IfExistsAction = machinery.Error
- }
-
- return nil
-}
-
-const typesTemplate = `{{ .Boilerplate }}
-
-package {{ .Resource.Version }}
-
-import (
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
-// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
-
-// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }}
-type {{ .Resource.Kind }}Spec struct {
- // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
- // Important: Run "make" to regenerate code after modifying this file
-
- // Foo is an example field of {{ .Resource.Kind }}. Edit {{ lower .Resource.Kind }}_types.go to remove/update
- Foo string ` + "`" + `json:"foo,omitempty"` + "`" + `
-}
-
-// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}
-type {{ .Resource.Kind }}Status struct {
- // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
- // Important: Run "make" to regenerate code after modifying this file
-}
-
-//+kubebuilder:object:root=true
-//+kubebuilder:subresource:status
-{{- if not .Resource.API.Namespaced }}
-//+kubebuilder:resource:scope=Cluster
-{{- end }}
-
-// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API
-type {{ .Resource.Kind }} struct {
- metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + `
- metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty"` + "`" + `
-
- Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec,omitempty"` + "`" + `
- Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty"` + "`" + `
-}
-
-//+kubebuilder:object:root=true
-
-// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }}
-type {{ .Resource.Kind }}List struct {
- metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + `
- metav1.ListMeta ` + "`" + `json:"metadata,omitempty"` + "`" + `
- Items []{{ .Resource.Kind }} ` + "`" + `json:"items"` + "`" + `
-}
-
-func init() {
- SchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{})
-}
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go
deleted file mode 100644
index 5def3986d32..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
-Copyright 2019 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 api
-
-import (
- "fmt"
- "path/filepath"
- "strings"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Webhook{}
-
-// Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource
-type Webhook struct { // nolint:maligned
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- // Is the Group domain for the Resource replacing '.' with '-'
- QualifiedGroupWithDash string
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Webhook) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- f.Path = filepath.Join("apis", "%[group]", "%[version]", "%[kind]_webhook.go")
- } else {
- f.Path = filepath.Join("api", "%[version]", "%[kind]_webhook.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
- fmt.Println(f.Path)
-
- webhookTemplate := webhookTemplate
- if f.Resource.HasDefaultingWebhook() {
- webhookTemplate = webhookTemplate + defaultingWebhookTemplate
- }
- if f.Resource.HasValidationWebhook() {
- webhookTemplate = webhookTemplate + validatingWebhookTemplate
- }
- f.TemplateBody = webhookTemplate
-
- f.IfExistsAction = machinery.Error
-
- f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1)
-
- return nil
-}
-
-const (
- webhookTemplate = `{{ .Boilerplate }}
-
-package {{ .Resource.Version }}
-
-import (
- ctrl "sigs.k8s.io/controller-runtime"
- logf "sigs.k8s.io/controller-runtime/pkg/log"
- {{- if .Resource.HasValidationWebhook }}
- "k8s.io/apimachinery/pkg/runtime"
- {{- end }}
- {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }}
- "sigs.k8s.io/controller-runtime/pkg/webhook"
- {{- end }}
-)
-
-// log is for logging in this package.
-var {{ lower .Resource.Kind }}log = logf.Log.WithName("{{ lower .Resource.Kind }}-resource")
-
-func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error {
- return ctrl.NewWebhookManagedBy(mgr).
- For(r).
- Complete()
-}
-
-// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
-`
-
- //nolint:lll
- defaultingWebhookTemplate = `
-//+kubebuilder:webhook:path=/mutate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}.kb.io
-
-var _ webhook.Defaulter = &{{ .Resource.Kind }}{}
-
-// Default implements webhook.Defaulter so a webhook will be registered for the type
-func (r *{{ .Resource.Kind }}) Default() {
- {{ lower .Resource.Kind }}log.Info("default", "name", r.Name)
-
- // TODO(user): fill in your defaulting logic.
-}
-`
- //nolint:lll
- validatingWebhookTemplate = `
-// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
-//+kubebuilder:webhook:verbs=create;update,path=/validate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}.kb.io
-
-var _ webhook.Validator = &{{ .Resource.Kind }}{}
-
-// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
-func (r *{{ .Resource.Kind }}) ValidateCreate() error {
- {{ lower .Resource.Kind }}log.Info("validate create", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object creation.
- return nil
-}
-
-// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
-func (r *{{ .Resource.Kind }}) ValidateUpdate(old runtime.Object) error {
- {{ lower .Resource.Kind }}log.Info("validate update", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object update.
- return nil
-}
-
-// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
-func (r *{{ .Resource.Kind }}) ValidateDelete() error {
- {{ lower .Resource.Kind }}log.Info("validate delete", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object deletion.
- return nil
-}
-`
-)
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go
deleted file mode 100644
index 4d17b8dff60..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
-Copyright 2019 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 certmanager
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Certificate{}
-
-// Certificate scaffolds a file that defines the issuer CR and the certificate CR
-type Certificate struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Certificate) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "certmanager", "certificate.yaml")
- }
-
- f.TemplateBody = certManagerTemplate
-
- return nil
-}
-
-const certManagerTemplate = `# The following manifests contain a self-signed issuer CR and a certificate CR.
-# More document can be found at https://docs.cert-manager.io
-# WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for
-# breaking changes
-apiVersion: cert-manager.io/v1alpha2
-kind: Issuer
-metadata:
- name: selfsigned-issuer
- namespace: system
-spec:
- selfSigned: {}
----
-apiVersion: cert-manager.io/v1alpha2
-kind: Certificate
-metadata:
- name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml
- namespace: system
-spec:
- # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize
- dnsNames:
- - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc
- - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local
- issuerRef:
- kind: Issuer
- name: selfsigned-issuer
- secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go
deleted file mode 100644
index 78b2c5e9484..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-Copyright 2019 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 certmanager
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Kustomization{}
-
-// Kustomization scaffolds a file that defines the kustomization scheme for the certmanager folder
-type Kustomization struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Kustomization) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "certmanager", "kustomization.yaml")
- }
-
- f.TemplateBody = kustomizationTemplate
-
- return nil
-}
-
-const kustomizationTemplate = `resources:
-- certificate.yaml
-
-configurations:
-- kustomizeconfig.yaml
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go
deleted file mode 100644
index e7dbcf8986d..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-Copyright 2019 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 certmanager
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &KustomizeConfig{}
-
-// KustomizeConfig scaffolds a file that configures the kustomization for the certmanager folder
-type KustomizeConfig struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *KustomizeConfig) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "certmanager", "kustomizeconfig.yaml")
- }
-
- f.TemplateBody = kustomizeConfigTemplate
-
- return nil
-}
-
-//nolint:lll
-const kustomizeConfigTemplate = `# This configuration is for teaching kustomize how to update name ref and var substitution
-nameReference:
-- kind: Issuer
- group: cert-manager.io
- fieldSpecs:
- - kind: Certificate
- group: cert-manager.io
- path: spec/issuerRef/name
-
-varReference:
-- kind: Certificate
- group: cert-manager.io
- path: spec/commonName
-- kind: Certificate
- group: cert-manager.io
- path: spec/dnsNames
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go
deleted file mode 100644
index c0e20efdf8e..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
-Copyright 2019 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 crd
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var (
- _ machinery.Template = &Kustomization{}
- _ machinery.Inserter = &Kustomization{}
-)
-
-// Kustomization scaffolds a file that defines the kustomization scheme for the crd folder
-type Kustomization struct {
- machinery.TemplateMixin
- machinery.ResourceMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Kustomization) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "crd", "kustomization.yaml")
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = fmt.Sprintf(kustomizationTemplate,
- machinery.NewMarkerFor(f.Path, resourceMarker),
- machinery.NewMarkerFor(f.Path, webhookPatchMarker),
- machinery.NewMarkerFor(f.Path, caInjectionPatchMarker),
- )
-
- return nil
-}
-
-const (
- resourceMarker = "crdkustomizeresource"
- webhookPatchMarker = "crdkustomizewebhookpatch"
- caInjectionPatchMarker = "crdkustomizecainjectionpatch"
-)
-
-// GetMarkers implements file.Inserter
-func (f *Kustomization) GetMarkers() []machinery.Marker {
- return []machinery.Marker{
- machinery.NewMarkerFor(f.Path, resourceMarker),
- machinery.NewMarkerFor(f.Path, webhookPatchMarker),
- machinery.NewMarkerFor(f.Path, caInjectionPatchMarker),
- }
-}
-
-const (
- resourceCodeFragment = `- bases/%s_%s.yaml
-`
- webhookPatchCodeFragment = `#- patches/webhook_in_%s.yaml
-`
- caInjectionPatchCodeFragment = `#- patches/cainjection_in_%s.yaml
-`
-)
-
-// GetCodeFragments implements file.Inserter
-func (f *Kustomization) GetCodeFragments() machinery.CodeFragmentsMap {
- fragments := make(machinery.CodeFragmentsMap, 3)
-
- // Generate resource code fragments
- res := make([]string, 0)
- res = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.QualifiedGroup(), f.Resource.Plural))
-
- // Generate resource code fragments
- webhookPatch := make([]string, 0)
- webhookPatch = append(webhookPatch, fmt.Sprintf(webhookPatchCodeFragment, f.Resource.Plural))
-
- // Generate resource code fragments
- caInjectionPatch := make([]string, 0)
- caInjectionPatch = append(caInjectionPatch, fmt.Sprintf(caInjectionPatchCodeFragment, f.Resource.Plural))
-
- // Only store code fragments in the map if the slices are non-empty
- if len(res) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, resourceMarker)] = res
- }
- if len(webhookPatch) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, webhookPatchMarker)] = webhookPatch
- }
- if len(caInjectionPatch) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, caInjectionPatchMarker)] = caInjectionPatch
- }
-
- return fragments
-}
-
-var kustomizationTemplate = `# This kustomization.yaml is not intended to be run by itself,
-# since it depends on service name and namespace that are out of this kustomize package.
-# It should be run by config/default
-resources:
-%s
-
-patchesStrategicMerge:
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
-# patches here are for enabling the conversion webhook for each CRD
-%s
-
-# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
-# patches here are for enabling the CA injection for each CRD
-%s
-
-# the following config is for teaching kustomize how to do kustomization for CRDs.
-configurations:
-- kustomizeconfig.yaml
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go
deleted file mode 100644
index 0013026f81c..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-Copyright 2019 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 crd
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &KustomizeConfig{}
-
-// KustomizeConfig scaffolds a file that configures the kustomization for the crd folder
-type KustomizeConfig struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *KustomizeConfig) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "crd", "kustomizeconfig.yaml")
- }
-
- f.TemplateBody = kustomizeConfigTemplate
-
- return nil
-}
-
-//nolint:lll
-const kustomizeConfigTemplate = `# This file is for teaching kustomize how to substitute name and namespace reference in CRD
-nameReference:
-- kind: Service
- version: v1
- fieldSpecs:
- - kind: CustomResourceDefinition
- group: apiextensions.k8s.io
- path: spec/conversion/webhookClientConfig/service/name
-
-namespace:
-- kind: CustomResourceDefinition
- group: apiextensions.k8s.io
- path: spec/conversion/webhookClientConfig/service/namespace
- create: false
-
-varReference:
-- path: metadata/annotations
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go
deleted file mode 100644
index 376a058a01c..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-Copyright 2019 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 patches
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &EnableCAInjectionPatch{}
-
-// EnableCAInjectionPatch scaffolds a file that defines the patch that injects CA into the CRD
-type EnableCAInjectionPatch struct {
- machinery.TemplateMixin
- machinery.ResourceMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *EnableCAInjectionPatch) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "crd", "patches", "cainjection_in_%[plural].yaml")
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = enableCAInjectionPatchTemplate
-
- return nil
-}
-
-const enableCAInjectionPatchTemplate = `# The following patch adds a directive for certmanager to inject CA into the CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
-kind: CustomResourceDefinition
-metadata:
- annotations:
- cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
- name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }}
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go
deleted file mode 100644
index 1472028b627..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-Copyright 2019 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 patches
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &EnableWebhookPatch{}
-
-// EnableWebhookPatch scaffolds a file that defines the patch that enables conversion webhook for the CRD
-type EnableWebhookPatch struct {
- machinery.TemplateMixin
- machinery.ResourceMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *EnableWebhookPatch) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "crd", "patches", "webhook_in_%[plural].yaml")
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = enableWebhookPatchTemplate
-
- return nil
-}
-
-const enableWebhookPatchTemplate = `# The following patch enables conversion webhook for CRD
-# CRD conversion requires k8s 1.13 or later.
-apiVersion: apiextensions.k8s.io/v1beta1
-kind: CustomResourceDefinition
-metadata:
- name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }}
-spec:
- conversion:
- strategy: Webhook
- webhookClientConfig:
- # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
- # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
- caBundle: Cg==
- service:
- namespace: system
- name: webhook-service
- path: /convert
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go
deleted file mode 100644
index 3cee6b8595d..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-Copyright 2019 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 kdefault
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &WebhookCAInjectionPatch{}
-
-// WebhookCAInjectionPatch scaffolds a file that defines the patch that adds annotation to webhooks
-type WebhookCAInjectionPatch struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *WebhookCAInjectionPatch) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "default", "webhookcainjection_patch.yaml")
- }
-
- f.TemplateBody = injectCAPatchTemplate
-
- f.IfExistsAction = machinery.Error
-
- return nil
-}
-
-const injectCAPatchTemplate = `# This patch add annotation to admission webhook config and
-# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize.
-apiVersion: admissionregistration.k8s.io/v1beta1
-kind: MutatingWebhookConfiguration
-metadata:
- name: mutating-webhook-configuration
- annotations:
- cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
----
-apiVersion: admissionregistration.k8s.io/v1beta1
-kind: ValidatingWebhookConfiguration
-metadata:
- name: validating-webhook-configuration
- annotations:
- cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go
deleted file mode 100644
index 68ecc351cd7..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
-Copyright 2018 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 kdefault
-
-import (
- "os"
- "path/filepath"
- "strings"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Kustomization{}
-
-// Kustomization scaffolds a file that defines the kustomization scheme for the default overlay folder
-type Kustomization struct {
- machinery.TemplateMixin
- machinery.ProjectNameMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Kustomization) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "default", "kustomization.yaml")
- }
-
- f.TemplateBody = kustomizeTemplate
-
- f.IfExistsAction = machinery.Error
-
- if f.ProjectName == "" {
- // Use directory name as project name, which will be empty if the project version is < v3.
- dir, err := os.Getwd()
- if err != nil {
- return err
- }
- f.ProjectName = strings.ToLower(filepath.Base(dir))
- }
-
- return nil
-}
-
-const kustomizeTemplate = `# Adds namespace to all resources.
-namespace: {{ .ProjectName }}-system
-
-# Value of this field is prepended to the
-# names of all resources, e.g. a deployment named
-# "wordpress" becomes "alices-wordpress".
-# Note that it should also match with the prefix (text before '-') of the namespace
-# field above.
-namePrefix: {{ .ProjectName }}-
-
-# Labels to add to all resources and selectors.
-#commonLabels:
-# someName: someValue
-
-bases:
-- ../crd
-- ../rbac
-- ../manager
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
-# crd/kustomization.yaml
-#- ../webhook
-# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
-#- ../certmanager
-# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
-#- ../prometheus
-
-patchesStrategicMerge:
- # Protect the /metrics endpoint by putting it behind auth.
- # If you want your controller-manager to expose the /metrics
- # endpoint w/o any authn/z, please comment the following line.
-- manager_auth_proxy_patch.yaml
-
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
-# crd/kustomization.yaml
-#- manager_webhook_patch.yaml
-
-# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
-# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
-# 'CERTMANAGER' needs to be enabled to use ca injection
-#- webhookcainjection_patch.yaml
-
-# the following config is for teaching kustomize how to do var substitution
-vars:
-# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
-#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
-# objref:
-# kind: Certificate
-# group: cert-manager.io
-# version: v1alpha2
-# name: serving-cert # this name should match the one in certificate.yaml
-# fieldref:
-# fieldpath: metadata.namespace
-#- name: CERTIFICATE_NAME
-# objref:
-# kind: Certificate
-# group: cert-manager.io
-# version: v1alpha2
-# name: serving-cert # this name should match the one in certificate.yaml
-#- name: SERVICE_NAMESPACE # namespace of the service
-# objref:
-# kind: Service
-# version: v1
-# name: webhook-service
-# fieldref:
-# fieldpath: metadata.namespace
-#- name: SERVICE_NAME
-# objref:
-# kind: Service
-# version: v1
-# name: webhook-service
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go
deleted file mode 100644
index 25ce44b1282..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-Copyright 2018 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 kdefault
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &ManagerAuthProxyPatch{}
-
-// ManagerAuthProxyPatch scaffolds a file that defines the patch that enables prometheus metrics for the manager
-type ManagerAuthProxyPatch struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *ManagerAuthProxyPatch) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "default", "manager_auth_proxy_patch.yaml")
- }
-
- f.TemplateBody = kustomizeAuthProxyPatchTemplate
-
- f.IfExistsAction = machinery.Error
-
- return nil
-}
-
-const kustomizeAuthProxyPatchTemplate = `# This patch inject a sidecar container which is a HTTP proxy for the
-# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: controller-manager
- namespace: system
-spec:
- template:
- spec:
- containers:
- - name: kube-rbac-proxy
- image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0
- args:
- - "--secure-listen-address=0.0.0.0:8443"
- - "--upstream=http://127.0.0.1:8080/"
- - "--logtostderr=true"
- - "--v=10"
- ports:
- - containerPort: 8443
- name: https
- - name: manager
- args:
- - "--metrics-addr=127.0.0.1:8080"
- - "--enable-leader-election"
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go
deleted file mode 100644
index a9e0844bf18..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-Copyright 2019 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 kdefault
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &ManagerWebhookPatch{}
-
-// ManagerWebhookPatch scaffolds a file that defines the patch that enables webhooks on the manager
-type ManagerWebhookPatch struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *ManagerWebhookPatch) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "default", "manager_webhook_patch.yaml")
- }
-
- f.TemplateBody = managerWebhookPatchTemplate
-
- return nil
-}
-
-const managerWebhookPatchTemplate = `apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: controller-manager
- namespace: system
-spec:
- template:
- spec:
- containers:
- - name: manager
- ports:
- - containerPort: 9443
- name: webhook-server
- protocol: TCP
- volumeMounts:
- - mountPath: /tmp/k8s-webhook-server/serving-certs
- name: cert
- readOnly: true
- volumes:
- - name: cert
- secret:
- defaultMode: 420
- secretName: webhook-server-cert
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go
deleted file mode 100644
index 1e08e134923..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
-Copyright 2018 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 manager
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Config{}
-
-// Config scaffolds a file that defines the namespace and the manager deployment
-type Config struct {
- machinery.TemplateMixin
-
- // Image is controller manager image name
- Image string
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Config) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "manager", "manager.yaml")
- }
-
- f.TemplateBody = configTemplate
-
- return nil
-}
-
-const configTemplate = `apiVersion: v1
-kind: Namespace
-metadata:
- labels:
- control-plane: controller-manager
- name: system
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: controller-manager
- namespace: system
- labels:
- control-plane: controller-manager
-spec:
- selector:
- matchLabels:
- control-plane: controller-manager
- replicas: 1
- template:
- metadata:
- labels:
- control-plane: controller-manager
- spec:
- containers:
- - command:
- - /manager
- args:
- - --enable-leader-election
- image: {{ .Image }}
- name: manager
- resources:
- limits:
- cpu: 100m
- memory: 30Mi
- requests:
- cpu: 100m
- memory: 20Mi
- terminationGracePeriodSeconds: 10
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go
deleted file mode 100644
index f8d5ecd7ec9..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-Copyright 2019 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 manager
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Kustomization{}
-
-// Kustomization scaffolds a file that defines the kustomization scheme for the manager folder
-type Kustomization struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Kustomization) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "manager", "kustomization.yaml")
- }
-
- f.TemplateBody = kustomizeManagerTemplate
-
- f.IfExistsAction = machinery.Error
-
- return nil
-}
-
-const kustomizeManagerTemplate = `resources:
-- manager.yaml
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go
deleted file mode 100644
index c271a6a3dbb..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
-Copyright 2019 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 prometheus
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Kustomization{}
-
-// Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder
-type Kustomization struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Kustomization) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "prometheus", "kustomization.yaml")
- }
-
- f.TemplateBody = kustomizationTemplate
-
- return nil
-}
-
-const kustomizationTemplate = `resources:
-- monitor.yaml
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go
deleted file mode 100644
index 261282075ec..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
-Copyright 2020 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 prometheus
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Monitor{}
-
-// Monitor scaffolds a file that defines the prometheus service monitor
-type Monitor struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Monitor) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "prometheus", "monitor.yaml")
- }
-
- f.TemplateBody = serviceMonitorTemplate
-
- return nil
-}
-
-const serviceMonitorTemplate = `
-# Prometheus Monitor Service (Metrics)
-apiVersion: monitoring.coreos.com/v1
-kind: ServiceMonitor
-metadata:
- labels:
- control-plane: controller-manager
- name: controller-manager-metrics-monitor
- namespace: system
-spec:
- endpoints:
- - path: /metrics
- port: https
- scheme: https
- bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
- tlsConfig:
- insecureSkipVerify: true
- selector:
- matchLabels:
- control-plane: controller-manager
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go
deleted file mode 100644
index 1eee0af2031..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-Copyright 2018 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &AuthProxyClientRole{}
-
-// AuthProxyClientRole scaffolds a file that defines the role for the metrics reader
-type AuthProxyClientRole struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *AuthProxyClientRole) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "auth_proxy_client_clusterrole.yaml")
- }
-
- f.TemplateBody = clientClusterRoleTemplate
-
- return nil
-}
-
-const clientClusterRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
- name: metrics-reader
-rules:
-- nonResourceURLs: ["/metrics"]
- verbs: ["get"]
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go
deleted file mode 100644
index df22ef8dc39..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-Copyright 2018 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &AuthProxyRole{}
-
-// AuthProxyRole scaffolds a file that defines the role for the auth proxy
-type AuthProxyRole struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *AuthProxyRole) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "auth_proxy_role.yaml")
- }
-
- f.TemplateBody = proxyRoleTemplate
-
- return nil
-}
-
-const proxyRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
- name: proxy-role
-rules:
-- apiGroups: ["authentication.k8s.io"]
- resources:
- - tokenreviews
- verbs: ["create"]
-- apiGroups: ["authorization.k8s.io"]
- resources:
- - subjectaccessreviews
- verbs: ["create"]
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go
deleted file mode 100644
index eafc45f6ee9..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-Copyright 2018 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &AuthProxyRoleBinding{}
-
-// AuthProxyRoleBinding scaffolds a file that defines the role binding for the auth proxy
-type AuthProxyRoleBinding struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *AuthProxyRoleBinding) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "auth_proxy_role_binding.yaml")
- }
-
- f.TemplateBody = proxyRoleBindinggTemplate
-
- return nil
-}
-
-const proxyRoleBindinggTemplate = `apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
- name: proxy-rolebinding
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: proxy-role
-subjects:
-- kind: ServiceAccount
- name: default
- namespace: system
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go
deleted file mode 100644
index 6287d360ebb..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-Copyright 2018 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &AuthProxyService{}
-
-// AuthProxyService scaffolds a file that defines the service for the auth proxy
-type AuthProxyService struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *AuthProxyService) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "auth_proxy_service.yaml")
- }
-
- f.TemplateBody = authProxyServiceTemplate
-
- return nil
-}
-
-const authProxyServiceTemplate = `apiVersion: v1
-kind: Service
-metadata:
- labels:
- control-plane: controller-manager
- name: controller-manager-metrics-service
- namespace: system
-spec:
- ports:
- - name: https
- port: 8443
- targetPort: https
- selector:
- control-plane: controller-manager
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go
deleted file mode 100644
index 7024549629d..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
-Copyright 2018 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &CRDEditorRole{}
-
-// CRDEditorRole scaffolds a file that defines the role that allows to edit plurals
-type CRDEditorRole struct {
- machinery.TemplateMixin
- machinery.ResourceMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *CRDEditorRole) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "%[kind]_editor_role.yaml")
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = crdRoleEditorTemplate
-
- return nil
-}
-
-const crdRoleEditorTemplate = `# permissions for end users to edit {{ .Resource.Plural }}.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
- name: {{ lower .Resource.Kind }}-editor-role
-rules:
-- apiGroups:
- - {{ .Resource.QualifiedGroup }}
- resources:
- - {{ .Resource.Plural }}
- verbs:
- - create
- - delete
- - get
- - list
- - patch
- - update
- - watch
-- apiGroups:
- - {{ .Resource.QualifiedGroup }}
- resources:
- - {{ .Resource.Plural }}/status
- verbs:
- - get
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go
deleted file mode 100644
index 74177476661..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-Copyright 2018 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &CRDViewerRole{}
-
-// CRDViewerRole scaffolds a file that defines the role that allows to view plurals
-type CRDViewerRole struct {
- machinery.TemplateMixin
- machinery.ResourceMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *CRDViewerRole) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "%[kind]_viewer_role.yaml")
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = crdRoleViewerTemplate
-
- return nil
-}
-
-const crdRoleViewerTemplate = `# permissions for end users to view {{ .Resource.Plural }}.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
- name: {{ lower .Resource.Kind }}-viewer-role
-rules:
-- apiGroups:
- - {{ .Resource.QualifiedGroup }}
- resources:
- - {{ .Resource.Plural }}
- verbs:
- - get
- - list
- - watch
-- apiGroups:
- - {{ .Resource.QualifiedGroup }}
- resources:
- - {{ .Resource.Plural }}/status
- verbs:
- - get
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go
deleted file mode 100644
index f5b164e5b79..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-Copyright 2019 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Kustomization{}
-
-// Kustomization scaffolds a file that defines the kustomization scheme for the rbac folder
-type Kustomization struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Kustomization) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "kustomization.yaml")
- }
-
- f.TemplateBody = kustomizeRBACTemplate
-
- f.IfExistsAction = machinery.Error
-
- return nil
-}
-
-const kustomizeRBACTemplate = `resources:
-- role.yaml
-- role_binding.yaml
-- leader_election_role.yaml
-- leader_election_role_binding.yaml
-# Comment the following 4 lines if you want to disable
-# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
-# which protects your /metrics endpoint.
-- auth_proxy_service.yaml
-- auth_proxy_role.yaml
-- auth_proxy_role_binding.yaml
-- auth_proxy_client_clusterrole.yaml
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go
deleted file mode 100644
index 6de4d48b784..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
-Copyright 2018 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &LeaderElectionRole{}
-
-// LeaderElectionRole scaffolds a file that defines the role that allows leader election
-type LeaderElectionRole struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *LeaderElectionRole) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "leader_election_role.yaml")
- }
-
- f.TemplateBody = leaderElectionRoleTemplate
-
- return nil
-}
-
-const leaderElectionRoleTemplate = `# permissions to do leader election.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
- name: leader-election-role
-rules:
-- apiGroups:
- - ""
- resources:
- - configmaps
- verbs:
- - get
- - list
- - watch
- - create
- - update
- - patch
- - delete
-- apiGroups:
- - ""
- resources:
- - configmaps/status
- verbs:
- - get
- - update
- - patch
-- apiGroups:
- - ""
- resources:
- - events
- verbs:
- - create
- - patch
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go
deleted file mode 100644
index 9dd75b7ff6b..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-Copyright 2018 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &LeaderElectionRoleBinding{}
-
-// LeaderElectionRoleBinding scaffolds a file that defines the role binding that allows leader election
-type LeaderElectionRoleBinding struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *LeaderElectionRoleBinding) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "leader_election_role_binding.yaml")
- }
-
- f.TemplateBody = leaderElectionRoleBindingTemplate
-
- return nil
-}
-
-const leaderElectionRoleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
- name: leader-election-rolebinding
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: Role
- name: leader-election-role
-subjects:
-- kind: ServiceAccount
- name: default
- namespace: system
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go
deleted file mode 100644
index 0cc6687e8c3..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-Copyright 2019 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 rbac
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &RoleBinding{}
-
-// RoleBinding scaffolds a file that defines the role binding for the manager
-type RoleBinding struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *RoleBinding) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "rbac", "role_binding.yaml")
- }
-
- f.TemplateBody = managerBindingTemplate
-
- return nil
-}
-
-const managerBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
- name: manager-rolebinding
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: manager-role
-subjects:
-- kind: ServiceAccount
- name: default
- namespace: system
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go
deleted file mode 100644
index 4a8eee252f8..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
-Copyright 2018 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 samples
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &CRDSample{}
-
-// CRDSample scaffolds a file that defines a sample manifest for the CRD
-type CRDSample struct {
- machinery.TemplateMixin
- machinery.ResourceMixin
-
- Force bool
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *CRDSample) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "samples", "%[group]_%[version]_%[kind].yaml")
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- if f.Force {
- f.IfExistsAction = machinery.OverwriteFile
- } else {
- f.IfExistsAction = machinery.Error
- }
-
- f.TemplateBody = crdSampleTemplate
-
- return nil
-}
-
-const crdSampleTemplate = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }}
-kind: {{ .Resource.Kind }}
-metadata:
- name: {{ lower .Resource.Kind }}-sample
-spec:
- # TODO(user): Add fields here
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go
deleted file mode 100644
index 7157dd8380c..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
-Copyright 2019 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 webhook
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Kustomization{}
-
-// Kustomization scaffolds a file that defines the kustomization scheme for the webhook folder
-type Kustomization struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Kustomization) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "webhook", "kustomization.yaml")
- }
-
- f.TemplateBody = kustomizeWebhookTemplate
-
- f.IfExistsAction = machinery.Error
-
- return nil
-}
-
-const kustomizeWebhookTemplate = `resources:
-- manifests.yaml
-- service.yaml
-
-configurations:
-- kustomizeconfig.yaml
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go
deleted file mode 100644
index ac2c92cc89e..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-Copyright 2019 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 webhook
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &KustomizeConfig{}
-
-// KustomizeConfig scaffolds a file that configures the kustomization for the webhook folder
-type KustomizeConfig struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *KustomizeConfig) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "webhook", "kustomizeconfig.yaml")
- }
-
- f.TemplateBody = kustomizeConfigWebhookTemplate
-
- f.IfExistsAction = machinery.Error
-
- return nil
-}
-
-//nolint:lll
-const kustomizeConfigWebhookTemplate = `# the following config is for teaching kustomize where to look at when substituting vars.
-# It requires kustomize v2.1.0 or newer to work properly.
-nameReference:
-- kind: Service
- version: v1
- fieldSpecs:
- - kind: MutatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/name
- - kind: ValidatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/name
-
-namespace:
-- kind: MutatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/namespace
- create: true
-- kind: ValidatingWebhookConfiguration
- group: admissionregistration.k8s.io
- path: webhooks/clientConfig/service/namespace
- create: true
-
-varReference:
-- path: metadata/annotations
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go
deleted file mode 100644
index 7783aa136c4..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-Copyright 2019 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 webhook
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Service{}
-
-// Service scaffolds a file that defines the webhook service
-type Service struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Service) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join("config", "webhook", "service.yaml")
- }
-
- f.TemplateBody = serviceTemplate
-
- f.IfExistsAction = machinery.Error
-
- return nil
-}
-
-const serviceTemplate = `
-apiVersion: v1
-kind: Service
-metadata:
- name: webhook-service
- namespace: system
-spec:
- ports:
- - port: 443
- targetPort: 9443
- selector:
- control-plane: controller-manager
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go
deleted file mode 100644
index 23569f72593..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
-Copyright 2018 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 controllers
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Controller{}
-
-// Controller scaffolds the file that defines the controller for a CRD or a builtin resource
-// nolint:maligned
-type Controller struct {
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- ControllerRuntimeVersion string
-
- Force bool
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Controller) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller.go")
- } else {
- f.Path = filepath.Join("controllers", "%[kind]_controller.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
- fmt.Println(f.Path)
-
- f.TemplateBody = controllerTemplate
-
- if f.Force {
- f.IfExistsAction = machinery.OverwriteFile
- } else {
- f.IfExistsAction = machinery.Error
- }
-
- return nil
-}
-
-//nolint:lll
-const controllerTemplate = `{{ .Boilerplate }}
-
-package controllers
-
-import (
- "context"
- "github.com/go-logr/logr"
- "k8s.io/apimachinery/pkg/runtime"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/client"
- {{ if not (isEmptyStr .Resource.Path) -}}
- {{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
- {{- end }}
-)
-
-// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object
-type {{ .Resource.Kind }}Reconciler struct {
- client.Client
- Log logr.Logger
- Scheme *runtime.Scheme
-}
-
-//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete
-//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch
-
-// Reconcile is part of the main kubernetes reconciliation loop which aims to
-// move the current state of the cluster closer to the desired state.
-// TODO(user): Modify the Reconcile function to compare the state specified by
-// the {{ .Resource.Kind }} object against the actual cluster state, and then
-// perform operations to make the cluster state reflect the state specified by
-// the user.
-//
-// For more details, check Reconcile and its Result here:
-// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/reconcile
-func (r *{{ .Resource.Kind }}Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
- _ = context.Background()
- _ = r.Log.WithValues("{{ .Resource.Kind | lower }}", req.NamespacedName)
-
- // TODO(user): your logic here
-
- return ctrl.Result{}, nil
-}
-
-// SetupWithManager sets up the controller with the Manager.
-func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {
- return ctrl.NewControllerManagedBy(mgr).
- {{ if not (isEmptyStr .Resource.Path) -}}
- For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}).
- {{- else -}}
- // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument
- // For().
- {{- end }}
- Complete(r)
-}
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go
deleted file mode 100644
index d8a0b975050..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
-Copyright 2020 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 controllers
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &SuiteTest{}
-var _ machinery.Inserter = &SuiteTest{}
-
-// SuiteTest scaffolds the file that sets up the controller tests
-// nolint:maligned
-type SuiteTest struct {
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- // CRDDirectoryRelativePath define the Path for the CRD
- CRDDirectoryRelativePath string
-
- Force bool
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *SuiteTest) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- f.Path = filepath.Join("controllers", "%[group]", "suite_test.go")
- } else {
- f.Path = filepath.Join("controllers", "suite_test.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate,
- machinery.NewMarkerFor(f.Path, importMarker),
- machinery.NewMarkerFor(f.Path, addSchemeMarker),
- )
-
- // If is multigroup the path needs to be ../../ since it has
- // the group dir.
- f.CRDDirectoryRelativePath = `".."`
- if f.MultiGroup {
- f.CRDDirectoryRelativePath = `"..", ".."`
- }
-
- if f.Force {
- f.IfExistsAction = machinery.OverwriteFile
- }
-
- return nil
-}
-
-const (
- importMarker = "imports"
- addSchemeMarker = "scheme"
-)
-
-// GetMarkers implements file.Inserter
-func (f *SuiteTest) GetMarkers() []machinery.Marker {
- return []machinery.Marker{
- machinery.NewMarkerFor(f.Path, importMarker),
- machinery.NewMarkerFor(f.Path, addSchemeMarker),
- }
-}
-
-const (
- apiImportCodeFragment = `%s "%s"
-`
- addschemeCodeFragment = `err = %s.AddToScheme(scheme.Scheme)
-Expect(err).NotTo(HaveOccurred())
-
-`
-)
-
-// GetCodeFragments implements file.Inserter
-func (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap {
- fragments := make(machinery.CodeFragmentsMap, 2)
-
- // Generate import code fragments
- imports := make([]string, 0)
- if f.Resource.Path != "" {
- imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
- }
-
- // Generate add scheme code fragments
- addScheme := make([]string, 0)
- if f.Resource.Path != "" {
- addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))
- }
-
- // Only store code fragments in the map if the slices are non-empty
- if len(imports) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports
- }
- if len(addScheme) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme
- }
-
- return fragments
-}
-
-const controllerSuiteTestTemplate = `{{ .Boilerplate }}
-
-package controllers
-
-import (
- "path/filepath"
- "testing"
- . "github.com/onsi/ginkgo"
- . "github.com/onsi/gomega"
- "k8s.io/client-go/kubernetes/scheme"
- "k8s.io/client-go/rest"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/envtest"
- "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
- logf "sigs.k8s.io/controller-runtime/pkg/log"
- "sigs.k8s.io/controller-runtime/pkg/log/zap"
- %s
-)
-
-// These tests use Ginkgo (BDD-style Go testing framework). Refer to
-// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
-
-var cfg *rest.Config
-var k8sClient client.Client
-var testEnv *envtest.Environment
-
-func TestAPIs(t *testing.T) {
- RegisterFailHandler(Fail)
-
- RunSpecsWithDefaultAndCustomReporters(t,
- "Controller Suite",
- []Reporter{printer.NewlineReporter{}})
-}
-
-var _ = BeforeSuite(func(done Done) {
- logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
-
- By("bootstrapping test environment")
- testEnv = &envtest.Environment{
- CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")},
- }
-
- var err error
- // cfg is defined in this file globally.
- cfg, err = testEnv.Start()
- Expect(err).ToNot(HaveOccurred())
- Expect(cfg).ToNot(BeNil())
-
- %s
-
- k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
- Expect(err).ToNot(HaveOccurred())
- Expect(k8sClient).ToNot(BeNil())
-
- close(done)
-}, 60)
-
-var _ = AfterSuite(func() {
- By("tearing down the test environment")
- err := testEnv.Stop()
- Expect(err).ToNot(HaveOccurred())
-})
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go
deleted file mode 100644
index 38e4c47167c..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-Copyright 2019 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 templates
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Dockerfile{}
-
-// Dockerfile scaffolds a file that defines the containerized build process
-type Dockerfile struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Dockerfile) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = "Dockerfile"
- }
-
- f.TemplateBody = dockerfileTemplate
-
- return nil
-}
-
-const dockerfileTemplate = `# Build the manager binary
-FROM golang:1.13 as builder
-
-WORKDIR /workspace
-# Copy the Go Modules manifests
-COPY go.mod go.mod
-COPY go.sum go.sum
-# cache deps before building and copying source so that we don't need to re-download as much
-# and so that source changes don't invalidate our downloaded layer
-RUN go mod download
-
-# Copy the go source
-COPY main.go main.go
-COPY api/ api/
-COPY controllers/ controllers/
-
-# Build
-RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go
-
-# Use distroless as minimal base image to package the manager binary
-# Refer to https://github.com/GoogleContainerTools/distroless for more details
-FROM gcr.io/distroless/static:nonroot
-WORKDIR /
-COPY --from=builder /workspace/manager .
-USER nonroot:nonroot
-
-ENTRYPOINT ["/manager"]
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go
deleted file mode 100644
index a60f46d6760..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-Copyright 2018 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 templates
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &GitIgnore{}
-
-// GitIgnore scaffolds a file that defines which files should be ignored by git
-type GitIgnore struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *GitIgnore) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = ".gitignore"
- }
-
- f.TemplateBody = gitignoreTemplate
-
- return nil
-}
-
-const gitignoreTemplate = `
-# Binaries for programs and plugins
-*.exe
-*.exe~
-*.dll
-*.so
-*.dylib
-bin
-
-# Test binary, build with ` + "`go test -c`" + `
-*.test
-
-# Output of the go coverage tool, specifically when used with LiteIDE
-*.out
-
-# Kubernetes Generated files - skip generated files, except for vendored files
-
-!vendor/**/zz_generated.*
-
-# editor and IDE paraphernalia
-.idea
-*.swp
-*.swo
-*~
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go
deleted file mode 100644
index e369bc98567..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-Copyright 2019 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 templates
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &GoMod{}
-
-// GoMod scaffolds a file that defines the project dependencies
-type GoMod struct {
- machinery.TemplateMixin
- machinery.RepositoryMixin
-
- ControllerRuntimeVersion string
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *GoMod) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = "go.mod"
- }
-
- f.TemplateBody = goModTemplate
-
- f.IfExistsAction = machinery.OverwriteFile
-
- return nil
-}
-
-const goModTemplate = `
-module {{ .Repo }}
-
-go 1.13
-
-require (
- sigs.k8s.io/controller-runtime {{ .ControllerRuntimeVersion }}
-)
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go
deleted file mode 100644
index 1d07c79b45a..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
-Copyright 2018 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 hack
-
-import (
- "fmt"
- "path/filepath"
- "time"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-// DefaultBoilerplatePath is the default path to the boilerplate file
-var DefaultBoilerplatePath = filepath.Join("hack", "boilerplate.go.txt")
-
-var _ machinery.Template = &Boilerplate{}
-
-// Boilerplate scaffolds a file that defines the common header for the rest of the files
-type Boilerplate struct {
- machinery.TemplateMixin
- machinery.BoilerplateMixin
-
- // License is the License type to write
- License string
-
- // Licenses maps License types to their actual string
- Licenses map[string]string
-
- // Owner is the copyright owner - e.g. "The Kubernetes Authors"
- Owner string
-
- // Year is the copyright year
- Year string
-}
-
-// Validate implements file.RequiresValidation
-func (f Boilerplate) Validate() error {
- if f.License == "" {
- // A default license will be set later
- } else if _, found := knownLicenses[f.License]; found {
- // One of the know licenses
- } else if _, found := f.Licenses[f.License]; found {
- // A map containing the requested license was also provided
- } else {
- return fmt.Errorf("unknown specified license %s", f.License)
- }
-
- return nil
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Boilerplate) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = DefaultBoilerplatePath
- }
-
- if f.License == "" {
- f.License = "apache2"
- }
-
- if f.Licenses == nil {
- f.Licenses = make(map[string]string, len(knownLicenses))
- }
-
- for key, value := range knownLicenses {
- if _, hasLicense := f.Licenses[key]; !hasLicense {
- f.Licenses[key] = value
- }
- }
-
- if f.Year == "" {
- f.Year = fmt.Sprintf("%v", time.Now().Year())
- }
-
- // Boilerplate given
- if len(f.Boilerplate) > 0 {
- f.TemplateBody = f.Boilerplate
- return nil
- }
-
- f.TemplateBody = boilerplateTemplate
-
- return nil
-}
-
-const boilerplateTemplate = `/*
-{{ if .Owner -}}
-Copyright {{ .Year }} {{ .Owner }}.
-{{- else -}}
-Copyright {{ .Year }}.
-{{- end }}
-{{ index .Licenses .License }}*/`
-
-var knownLicenses = map[string]string{
- "apache2": apache2,
- "none": "",
-}
-
-const apache2 = `
-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.
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go
deleted file mode 100644
index c2bd6928d85..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
-Copyright 2019 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 templates
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-const defaultMainPath = "main.go"
-
-var _ machinery.Template = &Main{}
-
-// Main scaffolds a file that defines the controller manager entry point
-type Main struct {
- machinery.TemplateMixin
- machinery.BoilerplateMixin
- machinery.DomainMixin
- machinery.RepositoryMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Main) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join(defaultMainPath)
- }
-
- f.TemplateBody = fmt.Sprintf(mainTemplate,
- machinery.NewMarkerFor(f.Path, importMarker),
- machinery.NewMarkerFor(f.Path, addSchemeMarker),
- machinery.NewMarkerFor(f.Path, setupMarker),
- )
-
- return nil
-}
-
-var _ machinery.Inserter = &MainUpdater{}
-
-// MainUpdater updates main.go to run Controllers
-type MainUpdater struct { //nolint:maligned
- machinery.RepositoryMixin
- machinery.MultiGroupMixin
- machinery.ResourceMixin
-
- // Flags to indicate which parts need to be included when updating the file
- WireResource, WireController, WireWebhook bool
-}
-
-// GetPath implements file.Builder
-func (*MainUpdater) GetPath() string {
- return defaultMainPath
-}
-
-// GetIfExistsAction implements file.Builder
-func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction {
- return machinery.OverwriteFile
-}
-
-const (
- importMarker = "imports"
- addSchemeMarker = "scheme"
- setupMarker = "builder"
-)
-
-// GetMarkers implements file.Inserter
-func (f *MainUpdater) GetMarkers() []machinery.Marker {
- return []machinery.Marker{
- machinery.NewMarkerFor(defaultMainPath, importMarker),
- machinery.NewMarkerFor(defaultMainPath, addSchemeMarker),
- machinery.NewMarkerFor(defaultMainPath, setupMarker),
- }
-}
-
-const (
- apiImportCodeFragment = `%s "%s"
-`
- controllerImportCodeFragment = `"%s/controllers"
-`
- multiGroupControllerImportCodeFragment = `%scontroller "%s/controllers/%s"
-`
- addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme))
-`
- reconcilerSetupCodeFragment = `if err = (&controllers.%sReconciler{
- Client: mgr.GetClient(),
- Log: ctrl.Log.WithName("controllers").WithName("%s"),
- Scheme: mgr.GetScheme(),
- }).SetupWithManager(mgr); err != nil {
- setupLog.Error(err, "unable to create controller", "controller", "%s")
- os.Exit(1)
- }
-`
- multiGroupReconcilerSetupCodeFragment = `if err = (&%scontroller.%sReconciler{
- Client: mgr.GetClient(),
- Log: ctrl.Log.WithName("controllers").WithName("%s"),
- Scheme: mgr.GetScheme(),
- }).SetupWithManager(mgr); err != nil {
- setupLog.Error(err, "unable to create controller", "controller", "%s")
- os.Exit(1)
- }
-`
- webhookSetupCodeFragment = `if err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil {
- setupLog.Error(err, "unable to create webhook", "webhook", "%s")
- os.Exit(1)
- }
-`
-)
-
-// GetCodeFragments implements file.Inserter
-func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap {
- fragments := make(machinery.CodeFragmentsMap, 3)
-
- // If resource is not being provided we are creating the file, not updating it
- if f.Resource == nil {
- return fragments
- }
-
- // Generate import code fragments
- imports := make([]string, 0)
- if f.WireResource {
- imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
- }
-
- if f.WireController {
- if !f.MultiGroup {
- imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo))
- } else {
- imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment,
- f.Resource.PackageName(), f.Repo, f.Resource.Group))
- }
- }
-
- // Generate add scheme code fragments
- addScheme := make([]string, 0)
- if f.WireResource {
- addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))
- }
-
- // Generate setup code fragments
- setup := make([]string, 0)
- if f.WireController {
- if !f.MultiGroup {
- setup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment,
- f.Resource.Kind, f.Resource.Kind, f.Resource.Kind))
- } else {
- setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment,
- f.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind, f.Resource.Kind))
- }
- }
- if f.WireWebhook {
- setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment,
- f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind))
- }
-
- // Only store code fragments in the map if the slices are non-empty
- if len(imports) != 0 {
- fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports
- }
- if len(addScheme) != 0 {
- fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme
- }
- if len(setup) != 0 {
- fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup
- }
-
- return fragments
-}
-
-var mainTemplate = `{{ .Boilerplate }}
-
-package main
-
-import (
- "flag"
- "os"
- "k8s.io/apimachinery/pkg/runtime"
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
- clientgoscheme "k8s.io/client-go/kubernetes/scheme"
- _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/log/zap"
- %s
-)
-
-var (
- scheme = runtime.NewScheme()
- setupLog = ctrl.Log.WithName("setup")
-)
-
-func init() {
- utilruntime.Must(clientgoscheme.AddToScheme(scheme))
-
- %s
-}
-
-func main() {
- var metricsAddr string
- var enableLeaderElection bool
- flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
- flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
- "Enable leader election for controller manager. " +
- "Enabling this will ensure there is only one active controller manager.")
- flag.Parse()
-
- ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
-
- mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
- Scheme: scheme,
- MetricsBindAddress: metricsAddr,
- Port: 9443,
- LeaderElection: enableLeaderElection,
- LeaderElectionID: "{{ hashFNV .Repo }}.{{ .Domain }}",
- })
- if err != nil {
- setupLog.Error(err, "unable to start manager")
- os.Exit(1)
- }
-
- %s
-
- setupLog.Info("starting manager")
- if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
- setupLog.Error(err, "problem running manager")
- os.Exit(1)
- }
-}
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go
deleted file mode 100644
index 5aedcb17f0e..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
-Copyright 2019 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 templates
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Makefile{}
-
-// Makefile scaffolds a file that defines project management CLI commands
-type Makefile struct {
- machinery.TemplateMixin
-
- // Image is controller manager image name
- Image string
- // BoilerplatePath is the path to the boilerplate file
- BoilerplatePath string
- // Controller tools version to use in the project
- ControllerToolsVersion string
- // Kustomize version to use in the project
- KustomizeVersion string
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Makefile) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = "Makefile"
- }
-
- f.TemplateBody = makefileTemplate
-
- f.IfExistsAction = machinery.Error
-
- if f.Image == "" {
- f.Image = "controller:latest"
- }
-
- return nil
-}
-
-//nolint:lll
-const makefileTemplate = `
-# Image URL to use all building/pushing image targets
-IMG ?= {{ .Image }}
-# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
-CRD_OPTIONS ?= "crd:trivialVersions=true"
-
-# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
-ifeq (,$(shell go env GOBIN))
-GOBIN=$(shell go env GOPATH)/bin
-else
-GOBIN=$(shell go env GOBIN)
-endif
-
-.PHONY: all
-all: build
-
-##@ General
-
-# The help target prints out all targets with their descriptions organized
-# beneath their categories. The categories are represented by '##@' and the
-# target descriptions by '##'. The awk commands is responsible for reading the
-# entire set of makefiles included in this invocation, looking for lines of the
-# file as xyz: ## something, and then pretty-format the target and help. Then,
-# if there's a line with ##@ something, that gets pretty-printed as a category.
-# More info on the usage of ANSI control characters for terminal formatting:
-# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
-# More info on the awk command:
-# http://linuxcommand.org/lc3_adv_awk.php
-
-.PHONY: help
-help: ## Display this help.
- @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
-
-##@ Development
-
-.PHONY: manifests
-manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
- $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
-
-.PHONY: generate
-generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
- $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..."
-
-.PHONY: fmt
-fmt: ## Run go fmt against code.
- go fmt ./...
-
-.PHONY: vet
-vet: ## Run go vet against code.
- go vet ./...
-
-.PHONY: test
-test: manifests generate fmt vet ## Run tests.
- go test ./... -coverprofile cover.out
-
-##@ Build
-
-.PHONY: build
-build: generate fmt vet ## Build manager binary.
- go build -o bin/manager main.go
-
-# Backwards compatibility
-.PHONY: manager
-manager: build ## Build manager binary (alias for build target).
-
-.PHONY: run
-run: manifests generate fmt vet ## Run a controller from your host.
- go run ./main.go
-
-.PHONY: docker-build
-docker-build: test ## Build docker image with the manager.
- docker build -t ${IMG} .
-
-.PHONY: docker-push
-docker-push: ## Push docker image with the manager.
- docker push ${IMG}
-
-##@ Deployment
-
-ifndef ignore-not-found
- ignore-not-found = false
-endif
-
-.PHONY: install
-install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
- $(KUSTOMIZE) build config/crd | kubectl apply -f -
-
-.PHONY: uninstall
-uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
- $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
-
-.PHONY: deploy
-deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
- cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
- $(KUSTOMIZE) build config/default | kubectl apply -f -
-
-.PHONY: undeploy
-undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
- $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
-
-##@ Build Dependencies
-
-## Location to install dependencies to
-LOCALBIN ?= $(shell pwd)/bin
-$(LOCALBIN): ## Ensure that the directory exists
- mkdir -p $(LOCALBIN)
-
-## Tool Binaries
-KUSTOMIZE ?= $(LOCALBIN)/kustomize
-CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
-
-## Tool Versions
-KUSTOMIZE_VERSION ?= {{ .KustomizeVersion }}
-CONTROLLER_TOOLS_VERSION ?= {{ .ControllerToolsVersion }}
-
-KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
-.PHONY: kustomize
-kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
-$(KUSTOMIZE): $(LOCALBIN)
- test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); }
-
-.PHONY: controller-gen
-controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
-$(CONTROLLER_GEN): $(LOCALBIN)
- test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
-`
diff --git a/pkg/plugins/golang/v2/scaffolds/webhook.go b/pkg/plugins/golang/v2/scaffolds/webhook.go
deleted file mode 100644
index d3cef711cad..00000000000
--- a/pkg/plugins/golang/v2/scaffolds/webhook.go
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
-Copyright 2020 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 scaffolds
-
-import (
- "fmt"
-
- "github.com/spf13/afero"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack"
-)
-
-var _ plugins.Scaffolder = &webhookScaffolder{}
-
-type webhookScaffolder struct {
- config config.Config
- resource resource.Resource
-
- // fs is the filesystem that will be used by the scaffolder
- fs machinery.Filesystem
-}
-
-// NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations
-func NewWebhookScaffolder(config config.Config, resource resource.Resource) plugins.Scaffolder {
- return &webhookScaffolder{
- config: config,
- resource: resource,
- }
-}
-
-// InjectFS implements cmdutil.Scaffolder
-func (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) {
- s.fs = fs
-}
-
-// Scaffold implements cmdutil.Scaffolder
-func (s *webhookScaffolder) Scaffold() error {
- fmt.Println("Writing scaffold for you to edit...")
-
- // Load the boilerplate
- boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath)
- if err != nil {
- return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err)
- }
-
- // Initialize the machinery.Scaffold that will write the files to disk
- scaffold := machinery.NewScaffold(s.fs,
- machinery.WithConfig(s.config),
- machinery.WithBoilerplate(string(boilerplate)),
- machinery.WithResource(&s.resource),
- )
-
- if err := s.config.UpdateResource(s.resource); err != nil {
- return fmt.Errorf("error updating resource: %w", err)
- }
-
- if err := scaffold.Execute(
- &api.Webhook{},
- &templates.MainUpdater{WireWebhook: true},
- ); err != nil {
- return err
- }
-
- if s.resource.HasConversionWebhook() {
- fmt.Println(`Webhook server has been set up for you.
-You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`)
- }
-
- return nil
-}
diff --git a/pkg/plugins/golang/v2/webhook.go b/pkg/plugins/golang/v2/webhook.go
deleted file mode 100644
index 02a7ae91eab..00000000000
--- a/pkg/plugins/golang/v2/webhook.go
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
-Copyright 2020 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.
-*/
-
-//go:deprecated This package has been deprecated
-package v2
-
-import (
- "fmt"
-
- "github.com/spf13/pflag"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds"
-)
-
-var _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{}
-
-type createWebhookSubcommand struct {
- config config.Config
- // For help text.
- commandName string
-
- options *goPlugin.Options
-
- resource *resource.Resource
-}
-
-func (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- p.commandName = cliMeta.CommandName
-
- subcmdMeta.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting,
-validating and/or conversion webhooks.
-`
- subcmdMeta.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for Group: ship, Version: v1beta1
- # and Kind: Frigate
- %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation
-
- # Create conversion webhook for Group: ship, Version: v1beta1
- # and Kind: Frigate
- %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion
-`, cliMeta.CommandName)
-}
-
-func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) {
- p.options = &goPlugin.Options{WebhookVersion: "v1beta1"}
-
- fs.StringVar(&p.options.Plural, "resource", "", "resource irregular plural form")
-
- fs.BoolVar(&p.options.DoDefaulting, "defaulting", false,
- "if set, scaffold the defaulting webhook")
- fs.BoolVar(&p.options.DoValidation, "programmatic-validation", false,
- "if set, scaffold the validating webhook")
- fs.BoolVar(&p.options.DoConversion, "conversion", false,
- "if set, scaffold the conversion webhook")
-}
-
-func (p *createWebhookSubcommand) InjectConfig(c config.Config) error {
- p.config = c
-
- return nil
-}
-
-func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error {
- p.resource = res
-
- if p.resource.Group == "" {
- return fmt.Errorf("group cannot be empty")
- }
-
- p.options.UpdateResource(p.resource, p.config)
-
- if err := p.resource.Validate(); err != nil {
- return err
- }
-
- if !p.resource.HasDefaultingWebhook() && !p.resource.HasValidationWebhook() && !p.resource.HasConversionWebhook() {
- return fmt.Errorf("%s create webhook requires at least one of --defaulting,"+
- " --programmatic-validation and --conversion to be true", p.commandName)
- }
-
- // check if resource exist to create webhook
- if p.config.GetVersion().Compare(cfgv2.Version) == 0 {
- if !p.config.HasResource(p.resource.GVK) {
- return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName)
- }
- } else {
- if r, err := p.config.GetResource(p.resource.GVK); err != nil {
- return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName)
- } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() {
- return fmt.Errorf("webhook resource already exists")
- }
- }
-
- return nil
-}
-
-func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error {
- scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource)
- scaffolder.InjectFS(fs)
- return scaffolder.Scaffold()
-}
diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go
deleted file mode 100644
index 30b81ca7f30..00000000000
--- a/pkg/plugins/golang/v3/api.go
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
-Copyright 2020 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 v3
-
-import (
- "bufio"
- "errors"
- "fmt"
- "os"
-
- "github.com/spf13/pflag"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
- goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds"
-)
-
-const (
- // defaultCRDVersion is the default CRD API version to scaffold.
- defaultCRDVersion = "v1"
-)
-
-// DefaultMainPath is default file path of main.go
-const DefaultMainPath = "main.go"
-
-var _ plugin.CreateAPISubcommand = &createAPISubcommand{}
-
-type createAPISubcommand struct {
- config config.Config
-
- options *goPlugin.Options
-
- resource *resource.Resource
-
- // Check if we have to scaffold resource and/or controller
- resourceFlag *pflag.Flag
- controllerFlag *pflag.Flag
-
- // force indicates that the resource should be created even if it already exists
- force bool
-
- // runMake indicates whether to run make or not after scaffolding APIs
- runMake bool
-}
-
-func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- subcmdMeta.Description = `Scaffold a Kubernetes API by writing a Resource definition and/or a Controller.
-
-If information about whether the resource and controller should be scaffolded
-was not explicitly provided, it will prompt the user if they should be.
-
-After the scaffold is written, the dependencies will be updated and
-make generate will be run.
-`
- subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate
- %[1]s create api --group ship --version v1beta1 --kind Frigate
-
- # Edit the API Scheme
- nano api/v1beta1/frigate_types.go
-
- # Edit the Controller
- nano controllers/frigate/frigate_controller.go
-
- # Edit the Controller Test
- nano controllers/frigate/frigate_controller_test.go
-
- # Generate the manifests
- make manifests
-
- # Install CRDs into the Kubernetes cluster using kubectl apply
- make install
-
- # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
- make run
-`, cliMeta.CommandName)
-}
-
-func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {
- fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files")
-
- fs.BoolVar(&p.force, "force", false,
- "attempt to create resource even if it already exists")
-
- p.options = &goPlugin.Options{}
-
- fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form")
-
- fs.BoolVar(&p.options.DoAPI, "resource", true,
- "if set, generate the resource without prompting the user")
- p.resourceFlag = fs.Lookup("resource")
- fs.StringVar(&p.options.CRDVersion, "crd-version", defaultCRDVersion,
- "version of CustomResourceDefinition to scaffold. Options: [v1, v1beta1]")
- fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced")
-
- fs.BoolVar(&p.options.DoController, "controller", true,
- "if set, generate the controller without prompting the user")
- p.controllerFlag = fs.Lookup("controller")
-
- // (not required raise an error in this case)
- // nolint:errcheck,gosec
- fs.MarkDeprecated("crd-version", deprecateMsg)
-}
-
-func (p *createAPISubcommand) InjectConfig(c config.Config) error {
- p.config = c
-
- return nil
-}
-
-func (p *createAPISubcommand) InjectResource(res *resource.Resource) error {
- p.resource = res
-
- // TODO: re-evaluate whether y/n input still makes sense. We should probably always
- // scaffold the resource and controller.
- // Ask for API and Controller if not specified
- reader := bufio.NewReader(os.Stdin)
- if !p.resourceFlag.Changed {
- fmt.Println("Create Resource [y/n]")
- p.options.DoAPI = util.YesNo(reader)
- }
- if !p.controllerFlag.Changed {
- fmt.Println("Create Controller [y/n]")
- p.options.DoController = util.YesNo(reader)
- }
-
- p.options.UpdateResource(p.resource, p.config)
-
- if err := p.resource.Validate(); err != nil {
- return err
- }
-
- // In case we want to scaffold a resource API we need to do some checks
- if p.options.DoAPI {
- // Check that resource doesn't have the API scaffolded or flag force was set
- if r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force {
- return errors.New("API resource already exists")
- }
-
- // Check that the provided group can be added to the project
- if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) {
- return fmt.Errorf("multiple groups are not allowed by default, " +
- "to enable multi-group visit https://kubebuilder.io/migration/multi-group.html")
- }
-
- // Check CRDVersion against all other CRDVersions in p.config for compatibility.
- if util.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) {
- return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q",
- p.resource.API.CRDVersion)
- }
- }
-
- return nil
-}
-
-func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error {
- // check if main.go is present in the root directory
- if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) {
- return fmt.Errorf("%s file should present in the root directory", DefaultMainPath)
- }
-
- return nil
-}
-
-func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {
- scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force)
- scaffolder.InjectFS(fs)
- return scaffolder.Scaffold()
-}
-
-func (p *createAPISubcommand) PostScaffold() error {
-
- // Update the makefile to allow generate Webhooks to ensure backwards compatibility
- // todo: it should be removed for go/v4
- // nolint:lll,gosec
- if p.resource.API.CRDVersion == "v1beta1" {
- if err := applyScaffoldCustomizationsForVbeta1(); err != nil {
- return err
- }
- }
-
- err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
- if err != nil {
- return err
- }
- if p.runMake && p.resource.HasAPI() {
- err = util.RunCmd("Running make", "make", "generate")
- if err != nil {
- return err
- }
- fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n")
- }
-
- return nil
-}
diff --git a/pkg/plugins/golang/v3/commons.go b/pkg/plugins/golang/v3/commons.go
deleted file mode 100644
index 91508ab9544..00000000000
--- a/pkg/plugins/golang/v3/commons.go
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
-Copyright 2022 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 v3
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- log "github.com/sirupsen/logrus"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds"
-)
-
-const deprecateMsg = "The v1beta1 API version for CRDs and Webhooks are deprecated and are no longer supported since " +
- "the Kubernetes release 1.22. This flag no longer required to exist in future releases. Also, we would like to " +
- "recommend you no longer use these API versions." +
- "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22"
-
-// Update the makefile to allow generate CRDs/Webhooks with v1beta1 to ensure backwards compatibility
-// nolint:lll,gosec
-func applyScaffoldCustomizationsForVbeta1() error {
- makefilePath := filepath.Join("Makefile")
- bs, err := os.ReadFile(makefilePath)
- if err != nil {
- return err
- }
- if !strings.Contains(string(bs), "CRD_OPTIONS") {
-
- log.Warn("The v1beta1 API version for CRDs and Webhooks are deprecated and are no longer supported " +
- "since the Kubernetes release 1.22. In order to help you out use these versions" +
- "we will need to try to update the Makefile and go.mod files of this project. Please," +
- "ensure that these changes were done accordingly with your customizations.\n" +
- "Also, we would like to recommend you no longer use these API versions." +
- "More info: https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-22")
-
- const makefileTarget = `$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases`
- const makefileTargetForV1beta1 = `$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases`
-
- if err := util.ReplaceInFile("Makefile", makefileTarget, makefileTargetForV1beta1); err != nil {
- fmt.Printf("unable to update the makefile to allow the usage of v1beta1: %s", err)
- }
-
- const makegentarget = `manifests: controller-gen`
- const makegenV1beta1Options = `# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
-CRD_OPTIONS ?= "crd:crdVersions={v1beta1},trivialVersions=true,preserveUnknownFields=false"
-manifests: controller-gen`
-
- if err := util.ReplaceInFile("Makefile", makegentarget, makegenV1beta1Options); err != nil {
- log.Warnf("unable to update the Makefile with %s: %s", makegenV1beta1Options, err)
- }
-
- // latest version of controller-tools where v1beta1 is supported
- const controllerToolsVersionForVBeta1 = "v0.6.2"
- if err := util.ReplaceInFile("Makefile",
- fmt.Sprintf("CONTROLLER_TOOLS_VERSION ?= %s",
- scaffolds.ControllerToolsVersion),
- fmt.Sprintf("CONTROLLER_TOOLS_VERSION ?= %s",
- controllerToolsVersionForVBeta1)); err != nil {
- log.Warnf("unable to update the Makefile with %s: %s", fmt.Sprintf("controller-gen@%s",
- controllerToolsVersionForVBeta1), err)
- }
-
- if err := util.ReplaceInFile("Makefile",
- "ENVTEST_K8S_VERSION = 1.25.0",
- "ENVTEST_K8S_VERSION = 1.21"); err != nil {
- log.Warnf("unable to update the Makefile with %s: %s", "ENVTEST_K8S_VERSION = 1.21", err)
- }
-
- // DO NOT UPDATE THIS VERSION
- // Note that this implementation will update the go.mod to downgrade the versions for those that are
- // compatible v1beta1 CRD/Webhooks k8s core APIs if/when a user tries to create an API with
- // create api [options] crd-version=v1beta1. The flag/feature is deprecated. however, to ensure that backwards
- // compatible we must introduce this logic. Also, note that when we bump the k8s dependencies we need to
- // ensure that the following replacements will be done accordingly to downgrade the versions.
- // The next version of the Golang base plugin (go/v4-alpha) no longer provide this feature.
- const controllerRuntimeVersionForVBeta1 = "v0.9.2"
-
- if err := util.ReplaceInFile("go.mod",
- fmt.Sprintf("sigs.k8s.io/controller-runtime %s", scaffolds.ControllerRuntimeVersion),
- fmt.Sprintf("sigs.k8s.io/controller-runtime %s", controllerRuntimeVersionForVBeta1)); err != nil {
- log.Warnf("unable to update the go.mod with sigs.k8s.io/controller-runtime %s: %s",
- controllerRuntimeVersionForVBeta1, err)
- }
-
- if err := util.ReplaceInFile("go.mod",
- "k8s.io/api v0.24.2",
- "k8s.io/api v0.21.2"); err != nil {
- log.Warnf("unable to update the go.mod with k8s.io/api v0.21.2: %s", err)
- }
-
- if err := util.ReplaceInFile("go.mod",
- "k8s.io/apimachinery v0.24.2",
- "k8s.io/apimachinery v0.21.2"); err != nil {
- log.Warnf("unable to update the go.mod with k8s.io/apimachinery v0.21.2: %s", err)
- }
-
- if err := util.ReplaceInFile("go.mod",
- "k8s.io/client-go v0.24.2",
- "k8s.io/client-go v0.21.2"); err != nil {
- log.Warnf("unable to update the go.mod with k8s.io/client-go v0.21.2: %s", err)
- }
-
- // During the scaffolding phase, this gets added to go.mod file, running go mod tidy bumps back
- // the version from 21.2 to the latest
- if err := util.ReplaceInFile("go.mod",
- "k8s.io/api v0.24.2",
- "k8s.io/api v0.21.2"); err != nil {
- log.Warnf("unable to update the go.mod with k8s.io/api v0.21.2: %s", err)
- }
-
- if err := util.ReplaceInFile("go.mod",
- "k8s.io/apiextensions-apiserver v0.24.2",
- "k8s.io/apiextensions-apiserver v0.21.2"); err != nil {
- log.Warnf("unable to update the go.mod with k8s.io/apiextensions-apiserver v0.21.2: %s", err)
- }
-
- if err := util.ReplaceInFile("go.mod",
- "k8s.io/component-base v0.24.2",
- "k8s.io/component-base v0.21.2"); err != nil {
- log.Warnf("unable to update the go.mod with k8s.io/component-base v0.21.2: %s", err)
- }
-
- // Cannot use v1+ unless controller runtime is v0.11
- if err := util.ReplaceInFile("go.mod",
- "github.com/go-logr/logr v1.2.0",
- "github.com/go-logr/logr v0.4.0"); err != nil {
- log.Warnf("unable to update the go.mod with github.com/go-logr/logr v0.4.0: %s", err)
- }
-
- if err := util.ReplaceInFile("go.mod",
- "github.com/go-logr/zapr v1.2.0",
- "github.com/go-logr/zapr v0.4.0"); err != nil {
- log.Warnf("unable to update the go.mod with github.com/go-logr/zapr v0.4.0: %s", err)
- }
-
- if err := util.ReplaceInFile("go.mod",
- "k8s.io/klog/v2 v2.60.1",
- "k8s.io/klog/v2 v2.9.0"); err != nil {
- log.Warnf("unable to update the go.mod with k8s.io/klog/v2 v2.9.0: %s", err)
- }
-
- // Due to some indirect dependency changes with a bump in k8s packages from 0.23.x --> 0.24.x we need to
- // clear out all indirect dependencies before we run `go mod tidy` so that the above changes get resolved correctly
- if err := util.ReplaceRegexInFile("go.mod", `(require \(\n(\t.* \/\/ indirect\n)+\))`, ""); err != nil {
- log.Warnf("unable to update the go.mod indirect dependencies: %s", err)
- }
-
- err = util.RunCmd("Update dependencies", "go", "mod", "tidy")
- if err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/pkg/plugins/golang/v3/edit.go b/pkg/plugins/golang/v3/edit.go
deleted file mode 100644
index b75e7339bd6..00000000000
--- a/pkg/plugins/golang/v3/edit.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-Copyright 2020 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 v3
-
-import (
- "fmt"
-
- "github.com/spf13/pflag"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds"
-)
-
-var _ plugin.EditSubcommand = &editSubcommand{}
-
-type editSubcommand struct {
- config config.Config
-
- multigroup bool
-}
-
-func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- subcmdMeta.Description = `This command will edit the project configuration.
-Features supported:
- - Toggle between single or multi group projects.
-`
- subcmdMeta.Examples = fmt.Sprintf(` # Enable the multigroup layout
- %[1]s edit --multigroup
-
- # Disable the multigroup layout
- %[1]s edit --multigroup=false
-`, cliMeta.CommandName)
-}
-
-func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {
- fs.BoolVar(&p.multigroup, "multigroup", false, "enable or disable multigroup layout")
-}
-
-func (p *editSubcommand) InjectConfig(c config.Config) error {
- p.config = c
-
- return nil
-}
-
-func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {
- scaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup)
- scaffolder.InjectFS(fs)
- return scaffolder.Scaffold()
-}
diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go
deleted file mode 100644
index 9417fc43c2e..00000000000
--- a/pkg/plugins/golang/v3/init.go
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
-Copyright 2020 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 v3
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "unicode"
-
- "github.com/spf13/pflag"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds"
-)
-
-// Variables and function to check Go version requirements.
-var (
- goVerMin = golang.MustParse("go1.19.0")
- goVerMax = golang.MustParse("go2.0alpha1")
-)
-
-var _ plugin.InitSubcommand = &initSubcommand{}
-
-type initSubcommand struct {
- config config.Config
- // For help text.
- commandName string
-
- // boilerplate options
- license string
- owner string
-
- // go config options
- repo string
-
- // flags
- fetchDeps bool
- skipGoVersionCheck bool
-}
-
-func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- p.commandName = cliMeta.CommandName
-
- subcmdMeta.Description = `Initialize a new project including the following files:
- - a "go.mod" with project dependencies
- - a "PROJECT" file that stores project configuration
- - a "Makefile" with several useful make targets for the project
- - several YAML files for project deployment under the "config" directory
- - a "main.go" file that creates the manager that will run the project controllers
-`
- subcmdMeta.Examples = fmt.Sprintf(` # Initialize a new project with your domain and name in copyright
- %[1]s init --plugins go/v3 --domain example.org --owner "Your name"
-
- # Initialize a new project defining a specific project version
- %[1]s init --plugins go/v3 --project-version 3
-`, cliMeta.CommandName)
-}
-
-func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {
- fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check",
- false, "if specified, skip checking the Go version")
-
- // dependency args
- fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "ensure dependencies are downloaded")
-
- // boilerplate args
- fs.StringVar(&p.license, "license", "apache2",
- "license to use to boilerplate, may be one of 'apache2', 'none'")
- fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright")
-
- // project args
- fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+
- "defaults to the go package of the current working directory.")
-}
-
-func (p *initSubcommand) InjectConfig(c config.Config) error {
- p.config = c
-
- // Try to guess repository if flag is not set.
- if p.repo == "" {
- repoPath, err := golang.FindCurrentRepo()
- if err != nil {
- return fmt.Errorf("error finding current repository: %v", err)
- }
- p.repo = repoPath
- }
-
- return p.config.SetRepository(p.repo)
-}
-
-func (p *initSubcommand) PreScaffold(machinery.Filesystem) error {
- // Ensure Go version is in the allowed range if check not turned off.
- if !p.skipGoVersionCheck {
- if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil {
- return err
- }
- }
-
- // Check if the current directory has not files or directories which does not allow to init the project
- return checkDir()
-}
-
-func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {
- scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner)
- scaffolder.InjectFS(fs)
- err := scaffolder.Scaffold()
- if err != nil {
- return err
- }
-
- if !p.fetchDeps {
- fmt.Println("Skipping fetching dependencies.")
- return nil
- }
-
- // Ensure that we are pinning controller-runtime version
- // xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997
- err = util.RunCmd("Get controller runtime", "go", "get",
- "sigs.k8s.io/controller-runtime@"+scaffolds.ControllerRuntimeVersion)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (p *initSubcommand) PostScaffold() error {
- err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
- if err != nil {
- return err
- }
-
- fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName)
- return nil
-}
-
-// checkDir will return error if the current directory has files which are not allowed.
-// Note that, it is expected that the directory to scaffold the project is cleaned.
-// Otherwise, it might face issues to do the scaffold.
-func checkDir() error {
- err := filepath.Walk(".",
- func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- // Allow directory trees starting with '.'
- if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." {
- return filepath.SkipDir
- }
- // Allow files starting with '.'
- if strings.HasPrefix(info.Name(), ".") {
- return nil
- }
- // Allow files ending with '.md' extension
- if strings.HasSuffix(info.Name(), ".md") && !info.IsDir() {
- return nil
- }
- // Allow capitalized files except PROJECT
- isCapitalized := true
- for _, l := range info.Name() {
- if !unicode.IsUpper(l) {
- isCapitalized = false
- break
- }
- }
- if isCapitalized && info.Name() != "PROJECT" {
- return nil
- }
- // Allow files in the following list
- allowedFiles := []string{
- "go.mod", // user might run `go mod init` instead of providing the `--flag` at init
- "go.sum", // auto-generated file related to go.mod
- }
- for _, allowedFile := range allowedFiles {
- if info.Name() == allowedFile {
- return nil
- }
- }
- // Do not allow any other file
- return fmt.Errorf(
- "target directory is not empty (only %s, files and directories with the prefix \".\", "+
- "files with the suffix \".md\" or capitalized files name are allowed); "+
- "found existing file %q", strings.Join(allowedFiles, ", "), path)
- })
- if err != nil {
- return err
- }
- return nil
-}
diff --git a/pkg/plugins/golang/v3/plugin.go b/pkg/plugins/golang/v3/plugin.go
deleted file mode 100644
index 3ad89267e4b..00000000000
--- a/pkg/plugins/golang/v3/plugin.go
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
-Copyright 2020 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 v3
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
-)
-
-const pluginName = "base." + golang.DefaultNameQualifier
-
-var (
- pluginVersion = plugin.Version{Number: 3}
- supportedProjectVersions = []config.Version{cfgv3.Version}
-)
-
-var _ plugin.Full = Plugin{}
-
-// Plugin implements the plugin.Full interface
-type Plugin struct {
- initSubcommand
- createAPISubcommand
- createWebhookSubcommand
- editSubcommand
-}
-
-// Name returns the name of the plugin
-func (Plugin) Name() string { return pluginName }
-
-// Version returns the version of the plugin
-func (Plugin) Version() plugin.Version { return pluginVersion }
-
-// SupportedProjectVersions returns an array with all project versions supported by the plugin
-func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }
-
-// GetInitSubcommand will return the subcommand which is responsible for initializing and common scaffolding
-func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand }
-
-// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis
-func (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand }
-
-// GetCreateWebhookSubcommand will return the subcommand which is responsible for scaffolding webhooks
-func (p Plugin) GetCreateWebhookSubcommand() plugin.CreateWebhookSubcommand {
- return &p.createWebhookSubcommand
-}
-
-// GetEditSubcommand will return the subcommand which is responsible for editing the scaffold of the project
-func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }
diff --git a/pkg/plugins/golang/v3/scaffolds/api.go b/pkg/plugins/golang/v3/scaffolds/api.go
deleted file mode 100644
index fe9ad41da08..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/api.go
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
-Copyright 2020 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 scaffolds
-
-import (
- "fmt"
-
- "github.com/spf13/afero"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack"
-)
-
-var _ plugins.Scaffolder = &apiScaffolder{}
-
-// apiScaffolder contains configuration for generating scaffolding for Go type
-// representing the API and controller that implements the behavior for the API.
-type apiScaffolder struct {
- config config.Config
- resource resource.Resource
-
- // fs is the filesystem that will be used by the scaffolder
- fs machinery.Filesystem
-
- // force indicates whether to scaffold controller files even if it exists or not
- force bool
-}
-
-// NewAPIScaffolder returns a new Scaffolder for API/controller creation operations
-func NewAPIScaffolder(config config.Config, res resource.Resource, force bool) plugins.Scaffolder {
- return &apiScaffolder{
- config: config,
- resource: res,
- force: force,
- }
-}
-
-// InjectFS implements cmdutil.Scaffolder
-func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) {
- s.fs = fs
-}
-
-// Scaffold implements cmdutil.Scaffolder
-func (s *apiScaffolder) Scaffold() error {
- fmt.Println("Writing scaffold for you to edit...")
-
- // Load the boilerplate
- boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath)
- if err != nil {
- return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err)
- }
-
- // Initialize the machinery.Scaffold that will write the files to disk
- scaffold := machinery.NewScaffold(s.fs,
- machinery.WithConfig(s.config),
- machinery.WithBoilerplate(string(boilerplate)),
- machinery.WithResource(&s.resource),
- )
-
- // Keep track of these values before the update
- doAPI := s.resource.HasAPI()
- doController := s.resource.HasController()
-
- if err := s.config.UpdateResource(s.resource); err != nil {
- return fmt.Errorf("error updating resource: %w", err)
- }
-
- if doAPI {
- if err := scaffold.Execute(
- &api.Types{Force: s.force},
- &api.Group{},
- ); err != nil {
- return fmt.Errorf("error scaffolding APIs: %v", err)
- }
- }
-
- if doController {
- if err := scaffold.Execute(
- &controllers.SuiteTest{Force: s.force},
- &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force},
- ); err != nil {
- return fmt.Errorf("error scaffolding controller: %v", err)
- }
- }
-
- if err := scaffold.Execute(
- &templates.MainUpdater{WireResource: doAPI, WireController: doController},
- ); err != nil {
- return fmt.Errorf("error updating main.go: %v", err)
- }
-
- return nil
-}
diff --git a/pkg/plugins/golang/v3/scaffolds/doc.go b/pkg/plugins/golang/v3/scaffolds/doc.go
deleted file mode 100644
index 71ae0484cf4..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/doc.go
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
-Copyright 2020 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 scaffolds contains libraries for scaffolding code to use with controller-runtime
-package scaffolds
diff --git a/pkg/plugins/golang/v3/scaffolds/edit.go b/pkg/plugins/golang/v3/scaffolds/edit.go
deleted file mode 100644
index e099a8a7b16..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/edit.go
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
-Copyright 2020 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 scaffolds
-
-import (
- "fmt"
- "strings"
-
- "github.com/spf13/afero"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
-)
-
-var _ plugins.Scaffolder = &editScaffolder{}
-
-type editScaffolder struct {
- config config.Config
- multigroup bool
-
- // fs is the filesystem that will be used by the scaffolder
- fs machinery.Filesystem
-}
-
-// NewEditScaffolder returns a new Scaffolder for configuration edit operations
-func NewEditScaffolder(config config.Config, multigroup bool) plugins.Scaffolder {
- return &editScaffolder{
- config: config,
- multigroup: multigroup,
- }
-}
-
-// InjectFS implements cmdutil.Scaffolder
-func (s *editScaffolder) InjectFS(fs machinery.Filesystem) {
- s.fs = fs
-}
-
-// Scaffold implements cmdutil.Scaffolder
-func (s *editScaffolder) Scaffold() error {
- filename := "Dockerfile"
- bs, err := afero.ReadFile(s.fs.FS, filename)
- if err != nil {
- return err
- }
- str := string(bs)
-
- // update dockerfile
- if s.multigroup {
- str, err = ensureExistAndReplace(
- str,
- "COPY api/ api/",
- `COPY apis/ apis/`)
-
- } else {
- str, err = ensureExistAndReplace(
- str,
- "COPY apis/ apis/",
- `COPY api/ api/`)
- }
-
- // Ignore the error encountered, if the file is already in desired format.
- if err != nil && s.multigroup != s.config.IsMultiGroup() {
- return err
- }
-
- if s.multigroup {
- _ = s.config.SetMultiGroup()
- } else {
- _ = s.config.ClearMultiGroup()
- }
-
- // Check if the str is not empty, because when the file is already in desired format it will return empty string
- // because there is nothing to replace.
- if str != "" {
- // TODO: instead of writing it directly, we should use the scaffolding machinery for consistency
- return afero.WriteFile(s.fs.FS, filename, []byte(str), 0644)
- }
-
- return nil
-}
-
-func ensureExistAndReplace(input, match, replace string) (string, error) {
- if !strings.Contains(input, match) {
- return "", fmt.Errorf("can't find %q", match)
- }
- return strings.Replace(input, match, replace, -1), nil
-}
diff --git a/pkg/plugins/golang/v3/scaffolds/init.go b/pkg/plugins/golang/v3/scaffolds/init.go
deleted file mode 100644
index 43a6ab95478..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/init.go
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
-Copyright 2020 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 scaffolds
-
-import (
- "fmt"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
-
- "github.com/spf13/afero"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
- kustomizecommonv1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v1"
- kustomizecommonv2alpha "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v2-alpha"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack"
-)
-
-const (
- // ControllerRuntimeVersion is the kubernetes-sigs/controller-runtime version to be used in the project
- ControllerRuntimeVersion = "v0.13.0"
- // ControllerToolsVersion is the kubernetes-sigs/controller-tools version to be used in the project
- ControllerToolsVersion = "v0.10.0"
-
- imageName = "controller:latest"
-)
-
-var _ plugins.Scaffolder = &initScaffolder{}
-
-var kustomizeVersion string
-
-type initScaffolder struct {
- config config.Config
- boilerplatePath string
- license string
- owner string
-
- // fs is the filesystem that will be used by the scaffolder
- fs machinery.Filesystem
-}
-
-// NewInitScaffolder returns a new Scaffolder for project initialization operations
-func NewInitScaffolder(config config.Config, license, owner string) plugins.Scaffolder {
- return &initScaffolder{
- config: config,
- boilerplatePath: hack.DefaultBoilerplatePath,
- license: license,
- owner: owner,
- }
-}
-
-// InjectFS implements cmdutil.Scaffolder
-func (s *initScaffolder) InjectFS(fs machinery.Filesystem) {
- s.fs = fs
-}
-
-// Scaffold implements cmdutil.Scaffolder
-func (s *initScaffolder) Scaffold() error {
- fmt.Println("Writing scaffold for you to edit...")
-
- // Initialize the machinery.Scaffold that will write the boilerplate file to disk
- // The boilerplate file needs to be scaffolded as a separate step as it is going to
- // be used by the rest of the files, even those scaffolded in this command call.
- scaffold := machinery.NewScaffold(s.fs,
- machinery.WithConfig(s.config),
- )
-
- bpFile := &hack.Boilerplate{
- License: s.license,
- Owner: s.owner,
- }
- bpFile.Path = s.boilerplatePath
- if err := scaffold.Execute(bpFile); err != nil {
- return err
- }
-
- boilerplate, err := afero.ReadFile(s.fs.FS, s.boilerplatePath)
- if err != nil {
- return err
- }
-
- // Initialize the machinery.Scaffold that will write the files to disk
- scaffold = machinery.NewScaffold(s.fs,
- machinery.WithConfig(s.config),
- machinery.WithBoilerplate(string(boilerplate)),
- )
-
- // If the KustomizeV2 was used to do the scaffold then
- // we need to ensure that we use its supported Kustomize Version
- // in order to support it
- kustomizeVersion = kustomizecommonv1.KustomizeVersion
- kustomizev2 := kustomizecommonv2alpha.Plugin{}
- gov4alpha := "go.kubebuilder.io/v4-alpha"
- pluginKeyForKustomizeV2 := plugin.KeyFor(kustomizev2)
-
- for _, pluginKey := range s.config.GetPluginChain() {
- if pluginKey == pluginKeyForKustomizeV2 || pluginKey == gov4alpha {
- kustomizeVersion = kustomizecommonv2alpha.KustomizeVersion
- break
- }
- }
-
- return scaffold.Execute(
- &templates.Main{},
- &templates.GoMod{
- ControllerRuntimeVersion: ControllerRuntimeVersion,
- },
- &templates.GitIgnore{},
- &templates.Makefile{
- Image: imageName,
- BoilerplatePath: s.boilerplatePath,
- ControllerToolsVersion: ControllerToolsVersion,
- KustomizeVersion: kustomizeVersion,
- ControllerRuntimeVersion: ControllerRuntimeVersion,
- },
- &templates.Dockerfile{},
- &templates.DockerIgnore{},
- &templates.Readme{},
- )
-}
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go
deleted file mode 100644
index 8c93af689a0..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
-Copyright 2020 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 api
-
-import (
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Group{}
-
-// Group scaffolds the file that defines the registration methods for a certain group and version
-type Group struct {
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Group) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- if f.Resource.Group != "" {
- f.Path = filepath.Join("apis", "%[group]", "%[version]", "groupversion_info.go")
- } else {
- f.Path = filepath.Join("apis", "%[version]", "groupversion_info.go")
- }
- } else {
- f.Path = filepath.Join("api", "%[version]", "groupversion_info.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = groupTemplate
-
- return nil
-}
-
-//nolint:lll
-const groupTemplate = `{{ .Boilerplate }}
-
-// Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group
-//+kubebuilder:object:generate=true
-//+groupName={{ .Resource.QualifiedGroup }}
-package {{ .Resource.Version }}
-
-import (
- "k8s.io/apimachinery/pkg/runtime/schema"
- "sigs.k8s.io/controller-runtime/pkg/scheme"
-)
-
-var (
- // GroupVersion is group version used to register these objects
- GroupVersion = schema.GroupVersion{Group: "{{ .Resource.QualifiedGroup }}", Version: "{{ .Resource.Version }}"}
-
- // SchemeBuilder is used to add go types to the GroupVersionKind scheme
- SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
-
- // AddToScheme adds the types in this group-version to the given scheme.
- AddToScheme = SchemeBuilder.AddToScheme
-)
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go
deleted file mode 100644
index a76e3cb1391..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
-Copyright 2020 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 api
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Types{}
-
-// Types scaffolds the file that defines the schema for a CRD
-// nolint:maligned
-type Types struct {
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- Force bool
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Types) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- if f.Resource.Group != "" {
- f.Path = filepath.Join("apis", "%[group]", "%[version]", "%[kind]_types.go")
- } else {
- f.Path = filepath.Join("apis", "%[version]", "%[kind]_types.go")
- }
- } else {
- f.Path = filepath.Join("api", "%[version]", "%[kind]_types.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
- fmt.Println(f.Path)
-
- f.TemplateBody = typesTemplate
-
- if f.Force {
- f.IfExistsAction = machinery.OverwriteFile
- } else {
- f.IfExistsAction = machinery.Error
- }
-
- return nil
-}
-
-const typesTemplate = `{{ .Boilerplate }}
-
-package {{ .Resource.Version }}
-
-import (
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
-// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
-
-// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }}
-type {{ .Resource.Kind }}Spec struct {
- // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
- // Important: Run "make" to regenerate code after modifying this file
-
- // Foo is an example field of {{ .Resource.Kind }}. Edit {{ lower .Resource.Kind }}_types.go to remove/update
- Foo string ` + "`" + `json:"foo,omitempty"` + "`" + `
-}
-
-// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}
-type {{ .Resource.Kind }}Status struct {
- // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
- // Important: Run "make" to regenerate code after modifying this file
-}
-
-//+kubebuilder:object:root=true
-//+kubebuilder:subresource:status
-{{- if and (not .Resource.API.Namespaced) (not .Resource.IsRegularPlural) }}
-//+kubebuilder:resource:path={{ .Resource.Plural }},scope=Cluster
-{{- else if not .Resource.API.Namespaced }}
-//+kubebuilder:resource:scope=Cluster
-{{- else if not .Resource.IsRegularPlural }}
-//+kubebuilder:resource:path={{ .Resource.Plural }}
-{{- end }}
-
-// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API
-type {{ .Resource.Kind }} struct {
- metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + `
- metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty"` + "`" + `
-
- Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec,omitempty"` + "`" + `
- Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty"` + "`" + `
-}
-
-//+kubebuilder:object:root=true
-
-// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }}
-type {{ .Resource.Kind }}List struct {
- metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + `
- metav1.ListMeta ` + "`" + `json:"metadata,omitempty"` + "`" + `
- Items []{{ .Resource.Kind }} ` + "`" + `json:"items"` + "`" + `
-}
-
-func init() {
- SchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{})
-}
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go
deleted file mode 100644
index b441e908085..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
-Copyright 2020 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 api
-
-import (
- "fmt"
- "path/filepath"
- "strings"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Webhook{}
-
-// Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource
-type Webhook struct { // nolint:maligned
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- // Is the Group domain for the Resource replacing '.' with '-'
- QualifiedGroupWithDash string
-
- // Define value for AdmissionReviewVersions marker
- AdmissionReviewVersions string
-
- Force bool
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Webhook) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- if f.Resource.Group != "" {
- f.Path = filepath.Join("apis", "%[group]", "%[version]", "%[kind]_webhook.go")
- } else {
- f.Path = filepath.Join("apis", "%[version]", "%[kind]_webhook.go")
- }
- } else {
- f.Path = filepath.Join("api", "%[version]", "%[kind]_webhook.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
- fmt.Println(f.Path)
-
- webhookTemplate := webhookTemplate
- if f.Resource.HasDefaultingWebhook() {
- webhookTemplate = webhookTemplate + defaultingWebhookTemplate
- }
- if f.Resource.HasValidationWebhook() {
- webhookTemplate = webhookTemplate + validatingWebhookTemplate
- }
- f.TemplateBody = webhookTemplate
-
- if f.Force {
- f.IfExistsAction = machinery.OverwriteFile
- } else {
- f.IfExistsAction = machinery.Error
- }
-
- f.AdmissionReviewVersions = "v1"
- if f.Resource.Webhooks.WebhookVersion == "v1beta1" {
- f.AdmissionReviewVersions = "{v1,v1beta1}"
- }
-
- f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1)
-
- return nil
-}
-
-const (
- webhookTemplate = `{{ .Boilerplate }}
-
-package {{ .Resource.Version }}
-
-import (
- ctrl "sigs.k8s.io/controller-runtime"
- logf "sigs.k8s.io/controller-runtime/pkg/log"
- {{- if .Resource.HasValidationWebhook }}
- "k8s.io/apimachinery/pkg/runtime"
- {{- end }}
- {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }}
- "sigs.k8s.io/controller-runtime/pkg/webhook"
- {{- end }}
-)
-
-// log is for logging in this package.
-var {{ lower .Resource.Kind }}log = logf.Log.WithName("{{ lower .Resource.Kind }}-resource")
-
-func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error {
- return ctrl.NewWebhookManagedBy(mgr).
- For(r).
- Complete()
-}
-
-// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
-`
-
- //nolint:lll
- defaultingWebhookTemplate = `
-//+kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/mutate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}
-
-var _ webhook.Defaulter = &{{ .Resource.Kind }}{}
-
-// Default implements webhook.Defaulter so a webhook will be registered for the type
-func (r *{{ .Resource.Kind }}) Default() {
- {{ lower .Resource.Kind }}log.Info("default", "name", r.Name)
-
- // TODO(user): fill in your defaulting logic.
-}
-`
-
- //nolint:lll
- validatingWebhookTemplate = `
-// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
-//+kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/validate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}
-
-var _ webhook.Validator = &{{ .Resource.Kind }}{}
-
-// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
-func (r *{{ .Resource.Kind }}) ValidateCreate() error {
- {{ lower .Resource.Kind }}log.Info("validate create", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object creation.
- return nil
-}
-
-// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
-func (r *{{ .Resource.Kind }}) ValidateUpdate(old runtime.Object) error {
- {{ lower .Resource.Kind }}log.Info("validate update", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object update.
- return nil
-}
-
-// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
-func (r *{{ .Resource.Kind }}) ValidateDelete() error {
- {{ lower .Resource.Kind }}log.Info("validate delete", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object deletion.
- return nil
-}
-`
-)
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go
deleted file mode 100644
index 9ab368fd661..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
-Copyright 2022 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 api
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &WebhookSuite{}
-var _ machinery.Inserter = &WebhookSuite{}
-
-// WebhookSuite scaffolds the file that sets up the webhook tests
-type WebhookSuite struct { //nolint:maligned
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- // todo: currently is not possible to know if an API was or not scaffolded. We can fix it when #1826 be addressed
- WireResource bool
-
- // BaseDirectoryRelativePath define the Path for the base directory when it is multigroup
- BaseDirectoryRelativePath string
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *WebhookSuite) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup {
- if f.Resource.Group != "" {
- f.Path = filepath.Join("apis", "%[group]", "%[version]", "webhook_suite_test.go")
- } else {
- f.Path = filepath.Join("apis", "%[version]", "webhook_suite_test.go")
- }
- } else {
- f.Path = filepath.Join("api", "%[version]", "webhook_suite_test.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplate,
- machinery.NewMarkerFor(f.Path, importMarker),
- admissionImportAlias,
- machinery.NewMarkerFor(f.Path, addSchemeMarker),
- machinery.NewMarkerFor(f.Path, addWebhookManagerMarker),
- "%s",
- "%d",
- )
-
- // If is multigroup the path needs to be ../../.. since it has the group dir.
- f.BaseDirectoryRelativePath = `"..", ".."`
- if f.MultiGroup && f.Resource.Group != "" {
- f.BaseDirectoryRelativePath = `"..", "..",".."`
- }
-
- return nil
-}
-
-const (
- // TODO: admission webhook versions should be based on the input of the user. For More Info #1664
- admissionImportAlias = "admissionv1beta1"
- admissionPath = "k8s.io/api/admission/v1beta1"
- importMarker = "imports"
- addWebhookManagerMarker = "webhook"
- addSchemeMarker = "scheme"
-)
-
-// GetMarkers implements file.Inserter
-func (f *WebhookSuite) GetMarkers() []machinery.Marker {
- return []machinery.Marker{
- machinery.NewMarkerFor(f.Path, importMarker),
- machinery.NewMarkerFor(f.Path, addSchemeMarker),
- machinery.NewMarkerFor(f.Path, addWebhookManagerMarker),
- }
-}
-
-const (
- apiImportCodeFragment = `%s "%s"
-`
-
- addWebhookManagerCodeFragment = `err = (&%s{}).SetupWebhookWithManager(mgr)
-Expect(err).NotTo(HaveOccurred())
-
-`
-)
-
-// GetCodeFragments implements file.Inserter
-func (f *WebhookSuite) GetCodeFragments() machinery.CodeFragmentsMap {
- fragments := make(machinery.CodeFragmentsMap, 3)
-
- // Generate import code fragments
- imports := make([]string, 0)
- imports = append(imports, fmt.Sprintf(apiImportCodeFragment, admissionImportAlias, admissionPath))
-
- // Generate add scheme code fragments
- addScheme := make([]string, 0)
-
- // Generate add webhookManager code fragments
- addWebhookManager := make([]string, 0)
- addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragment, f.Resource.Kind))
-
- // Only store code fragments in the map if the slices are non-empty
- if len(addWebhookManager) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, addWebhookManagerMarker)] = addWebhookManager
- }
- if len(imports) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports
- }
- if len(addScheme) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme
- }
-
- return fragments
-}
-
-const webhookTestSuiteTemplate = `{{ .Boilerplate }}
-
-package {{ .Resource.Version }}
-
-import (
- "context"
- "path/filepath"
- "testing"
- "fmt"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
- %s
- "k8s.io/client-go/kubernetes/scheme"
- "k8s.io/client-go/rest"
- "k8s.io/apimachinery/pkg/runtime"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/envtest"
- "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
- logf "sigs.k8s.io/controller-runtime/pkg/log"
- "sigs.k8s.io/controller-runtime/pkg/log/zap"
-)
-
-// These tests use Ginkgo (BDD-style Go testing framework). Refer to
-// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
-
-var cfg *rest.Config
-var k8sClient client.Client
-var testEnv *envtest.Environment
-var ctx context.Context
-var cancel context.CancelFunc
-
-func TestAPIs(t *testing.T) {
- RegisterFailHandler(Fail)
-
- RunSpecs(t, "Webhook Suite")
-}
-
-var _ = BeforeSuite(func() {
- logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
-
- ctx, cancel = context.WithCancel(context.TODO())
-
- By("bootstrapping test environment")
- testEnv = &envtest.Environment{
- CRDDirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "crd", "bases")},
- ErrorIfCRDPathMissing: {{ .WireResource }},
- WebhookInstallOptions: envtest.WebhookInstallOptions{
- Paths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "webhook")},
- },
- }
-
- var err error
- // cfg is defined in this file globally.
- cfg, err = testEnv.Start()
- Expect(err).NotTo(HaveOccurred())
- Expect(cfg).NotTo(BeNil())
-
- scheme := runtime.NewScheme()
- err = AddToScheme(scheme)
- Expect(err).NotTo(HaveOccurred())
-
- err = %s.AddToScheme(scheme)
- Expect(err).NotTo(HaveOccurred())
-
- %s
-
- k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
- Expect(err).NotTo(HaveOccurred())
- Expect(k8sClient).NotTo(BeNil())
-
- // start webhook server using Manager
- webhookInstallOptions := &testEnv.WebhookInstallOptions
- mgr, err := ctrl.NewManager(cfg, ctrl.Options{
- Scheme: scheme,
- Host: webhookInstallOptions.LocalServingHost,
- Port: webhookInstallOptions.LocalServingPort,
- CertDir: webhookInstallOptions.LocalServingCertDir,
- LeaderElection: false,
- MetricsBindAddress: "0",
- })
- Expect(err).NotTo(HaveOccurred())
-
- %s
-
- go func() {
- defer GinkgoRecover()
- err = mgr.Start(ctx)
- Expect(err).NotTo(HaveOccurred())
- }()
-
- // wait for the webhook server to get ready
- dialer := &net.Dialer{Timeout: time.Second}
- addrPort := fmt.Sprintf("%s:%s", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)
- Eventually(func() error {
- conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true})
- if err != nil {
- return err
- }
- conn.Close()
- return nil
- }).Should(Succeed())
-
-})
-
-var _ = AfterSuite(func() {
- cancel()
- By("tearing down the test environment")
- err := testEnv.Stop()
- Expect(err).NotTo(HaveOccurred())
-})
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go
deleted file mode 100644
index f575c03fea2..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
-Copyright 2020 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 controllers
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Controller{}
-
-// Controller scaffolds the file that defines the controller for a CRD or a builtin resource
-// nolint:maligned
-type Controller struct {
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- ControllerRuntimeVersion string
-
- Force bool
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Controller) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup && f.Resource.Group != "" {
- f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller.go")
- } else {
- f.Path = filepath.Join("controllers", "%[kind]_controller.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
- fmt.Println(f.Path)
-
- f.TemplateBody = controllerTemplate
-
- if f.Force {
- f.IfExistsAction = machinery.OverwriteFile
- } else {
- f.IfExistsAction = machinery.Error
- }
-
- return nil
-}
-
-//nolint:lll
-const controllerTemplate = `{{ .Boilerplate }}
-
-package {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}controllers{{ end }}
-
-import (
- "context"
- "k8s.io/apimachinery/pkg/runtime"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/log"
- {{ if not (isEmptyStr .Resource.Path) -}}
- {{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
- {{- end }}
-)
-
-// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object
-type {{ .Resource.Kind }}Reconciler struct {
- client.Client
- Scheme *runtime.Scheme
-}
-
-//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete
-//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch
-//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/finalizers,verbs=update
-
-// Reconcile is part of the main kubernetes reconciliation loop which aims to
-// move the current state of the cluster closer to the desired state.
-// TODO(user): Modify the Reconcile function to compare the state specified by
-// the {{ .Resource.Kind }} object against the actual cluster state, and then
-// perform operations to make the cluster state reflect the state specified by
-// the user.
-//
-// For more details, check Reconcile and its Result here:
-// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/reconcile
-func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
- _ = log.FromContext(ctx)
-
- // TODO(user): your logic here
-
- return ctrl.Result{}, nil
-}
-
-// SetupWithManager sets up the controller with the Manager.
-func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {
- return ctrl.NewControllerManagedBy(mgr).
- {{ if not (isEmptyStr .Resource.Path) -}}
- For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}).
- {{- else -}}
- // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument
- // For().
- {{- end }}
- Complete(r)
-}
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go
deleted file mode 100644
index 2577add2f1f..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
-Copyright 2020 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 controllers
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &SuiteTest{}
-var _ machinery.Inserter = &SuiteTest{}
-
-// SuiteTest scaffolds the file that sets up the controller tests
-// nolint:maligned
-type SuiteTest struct {
- machinery.TemplateMixin
- machinery.MultiGroupMixin
- machinery.BoilerplateMixin
- machinery.ResourceMixin
-
- // CRDDirectoryRelativePath define the Path for the CRD
- CRDDirectoryRelativePath string
-
- Force bool
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *SuiteTest) SetTemplateDefaults() error {
- if f.Path == "" {
- if f.MultiGroup && f.Resource.Group != "" {
- f.Path = filepath.Join("controllers", "%[group]", "suite_test.go")
- } else {
- f.Path = filepath.Join("controllers", "suite_test.go")
- }
- }
- f.Path = f.Resource.Replacer().Replace(f.Path)
-
- f.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate,
- machinery.NewMarkerFor(f.Path, importMarker),
- machinery.NewMarkerFor(f.Path, addSchemeMarker),
- )
-
- // If is multigroup the path needs to be ../../ since it has
- // the group dir.
- f.CRDDirectoryRelativePath = `".."`
- if f.MultiGroup && f.Resource.Group != "" {
- f.CRDDirectoryRelativePath = `"..", ".."`
- }
-
- if f.Force {
- f.IfExistsAction = machinery.OverwriteFile
- }
-
- return nil
-}
-
-const (
- importMarker = "imports"
- addSchemeMarker = "scheme"
-)
-
-// GetMarkers implements file.Inserter
-func (f *SuiteTest) GetMarkers() []machinery.Marker {
- return []machinery.Marker{
- machinery.NewMarkerFor(f.Path, importMarker),
- machinery.NewMarkerFor(f.Path, addSchemeMarker),
- }
-}
-
-const (
- apiImportCodeFragment = `%s "%s"
-`
- addschemeCodeFragment = `err = %s.AddToScheme(scheme.Scheme)
-Expect(err).NotTo(HaveOccurred())
-
-`
-)
-
-// GetCodeFragments implements file.Inserter
-func (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap {
- fragments := make(machinery.CodeFragmentsMap, 2)
-
- // Generate import code fragments
- imports := make([]string, 0)
- if f.Resource.Path != "" {
- imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
- }
-
- // Generate add scheme code fragments
- addScheme := make([]string, 0)
- if f.Resource.Path != "" {
- addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))
- }
-
- // Only store code fragments in the map if the slices are non-empty
- if len(imports) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports
- }
- if len(addScheme) != 0 {
- fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme
- }
-
- return fragments
-}
-
-const controllerSuiteTestTemplate = `{{ .Boilerplate }}
-
-{{if and .MultiGroup .Resource.Group }}
-package {{ .Resource.PackageName }}
-{{else}}
-package controllers
-{{end}}
-
-import (
- "path/filepath"
- "testing"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-
- "k8s.io/client-go/kubernetes/scheme"
- "k8s.io/client-go/rest"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/envtest"
- "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
- logf "sigs.k8s.io/controller-runtime/pkg/log"
- "sigs.k8s.io/controller-runtime/pkg/log/zap"
- %s
-)
-
-// These tests use Ginkgo (BDD-style Go testing framework). Refer to
-// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
-
-var cfg *rest.Config
-var k8sClient client.Client
-var testEnv *envtest.Environment
-
-func TestAPIs(t *testing.T) {
- RegisterFailHandler(Fail)
-
- RunSpecs(t, "Controller Suite")
-}
-
-var _ = BeforeSuite(func() {
- logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
-
- By("bootstrapping test environment")
- testEnv = &envtest.Environment{
- CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")},
- ErrorIfCRDPathMissing: {{ .Resource.HasAPI }},
- }
-
- var err error
- // cfg is defined in this file globally.
- cfg, err = testEnv.Start()
- Expect(err).NotTo(HaveOccurred())
- Expect(cfg).NotTo(BeNil())
-
- %s
-
- k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
- Expect(err).NotTo(HaveOccurred())
- Expect(k8sClient).NotTo(BeNil())
-
-})
-
-var _ = AfterSuite(func() {
- By("tearing down the test environment")
- err := testEnv.Stop()
- Expect(err).NotTo(HaveOccurred())
-})
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go
deleted file mode 100644
index 1d1c1b53630..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
-Copyright 2020 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 templates
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Dockerfile{}
-
-// Dockerfile scaffolds a file that defines the containerized build process
-type Dockerfile struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Dockerfile) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = "Dockerfile"
- }
-
- f.TemplateBody = dockerfileTemplate
-
- return nil
-}
-
-const dockerfileTemplate = `# Build the manager binary
-FROM golang:1.19 as builder
-ARG TARGETOS
-ARG TARGETARCH
-
-WORKDIR /workspace
-# Copy the Go Modules manifests
-COPY go.mod go.mod
-COPY go.sum go.sum
-# cache deps before building and copying source so that we don't need to re-download as much
-# and so that source changes don't invalidate our downloaded layer
-RUN go mod download
-
-# Copy the go source
-COPY main.go main.go
-COPY api/ api/
-COPY controllers/ controllers/
-
-# Build
-# the GOARCH has not a default value to allow the binary be built according to the host where the command
-# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
-# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
-# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
-RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go
-
-# Use distroless as minimal base image to package the manager binary
-# Refer to https://github.com/GoogleContainerTools/distroless for more details
-FROM gcr.io/distroless/static:nonroot
-WORKDIR /
-COPY --from=builder /workspace/manager .
-USER 65532:65532
-
-ENTRYPOINT ["/manager"]
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go
deleted file mode 100644
index 8b194d955f6..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-Copyright 2020 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 templates
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &GitIgnore{}
-
-// GitIgnore scaffolds a file that defines which files should be ignored by git
-type GitIgnore struct {
- machinery.TemplateMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *GitIgnore) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = ".gitignore"
- }
-
- f.TemplateBody = gitignoreTemplate
-
- return nil
-}
-
-const gitignoreTemplate = `
-# Binaries for programs and plugins
-*.exe
-*.exe~
-*.dll
-*.so
-*.dylib
-bin
-testbin/*
-Dockerfile.cross
-
-# Test binary, build with ` + "`go test -c`" + `
-*.test
-
-# Output of the go coverage tool, specifically when used with LiteIDE
-*.out
-
-# Kubernetes Generated files - skip generated files, except for vendored files
-
-!vendor/**/zz_generated.*
-
-# editor and IDE paraphernalia
-.idea
-*.swp
-*.swo
-*~
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go
deleted file mode 100644
index 0191ede9702..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-Copyright 2020 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 templates
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &GoMod{}
-
-// GoMod scaffolds a file that defines the project dependencies
-type GoMod struct {
- machinery.TemplateMixin
- machinery.RepositoryMixin
-
- ControllerRuntimeVersion string
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *GoMod) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = "go.mod"
- }
-
- f.TemplateBody = goModTemplate
-
- f.IfExistsAction = machinery.OverwriteFile
-
- return nil
-}
-
-const goModTemplate = `
-module {{ .Repo }}
-
-go 1.19
-
-require (
- sigs.k8s.io/controller-runtime {{ .ControllerRuntimeVersion }}
-)
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go
deleted file mode 100644
index 3efbe592a54..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
-Copyright 2020 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 hack
-
-import (
- "fmt"
- "path/filepath"
- "time"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-// DefaultBoilerplatePath is the default path to the boilerplate file
-var DefaultBoilerplatePath = filepath.Join("hack", "boilerplate.go.txt")
-
-var _ machinery.Template = &Boilerplate{}
-
-// Boilerplate scaffolds a file that defines the common header for the rest of the files
-type Boilerplate struct {
- machinery.TemplateMixin
- machinery.BoilerplateMixin
-
- // License is the License type to write
- License string
-
- // Licenses maps License types to their actual string
- Licenses map[string]string
-
- // Owner is the copyright owner - e.g. "The Kubernetes Authors"
- Owner string
-
- // Year is the copyright year
- Year string
-}
-
-// Validate implements file.RequiresValidation
-func (f Boilerplate) Validate() error {
- if f.License == "" {
- // A default license will be set later
- } else if _, found := knownLicenses[f.License]; found {
- // One of the know licenses
- } else if _, found := f.Licenses[f.License]; found {
- // A map containing the requested license was also provided
- } else {
- return fmt.Errorf("unknown specified license %s", f.License)
- }
-
- return nil
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Boilerplate) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = DefaultBoilerplatePath
- }
-
- if f.License == "" {
- f.License = "apache2"
- }
-
- if f.Licenses == nil {
- f.Licenses = make(map[string]string, len(knownLicenses))
- }
-
- for key, value := range knownLicenses {
- if _, hasLicense := f.Licenses[key]; !hasLicense {
- f.Licenses[key] = value
- }
- }
-
- if f.Year == "" {
- f.Year = fmt.Sprintf("%v", time.Now().Year())
- }
-
- // Boilerplate given
- if len(f.Boilerplate) > 0 {
- f.TemplateBody = f.Boilerplate
- return nil
- }
-
- f.TemplateBody = boilerplateTemplate
-
- return nil
-}
-
-const boilerplateTemplate = `/*
-{{ if .Owner -}}
-Copyright {{ .Year }} {{ .Owner }}.
-{{- else -}}
-Copyright {{ .Year }}.
-{{- end }}
-{{ index .Licenses .License }}*/`
-
-var knownLicenses = map[string]string{
- "apache2": apache2,
- "none": "",
-}
-
-const apache2 = `
-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.
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go
deleted file mode 100644
index e450584fbba..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
-Copyright 2020 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 templates
-
-import (
- "fmt"
- "path/filepath"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-const defaultMainPath = "main.go"
-
-var _ machinery.Template = &Main{}
-
-// Main scaffolds a file that defines the controller manager entry point
-type Main struct {
- machinery.TemplateMixin
- machinery.BoilerplateMixin
- machinery.DomainMixin
- machinery.RepositoryMixin
- machinery.ComponentConfigMixin
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Main) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = filepath.Join(defaultMainPath)
- }
-
- f.TemplateBody = fmt.Sprintf(mainTemplate,
- machinery.NewMarkerFor(f.Path, importMarker),
- machinery.NewMarkerFor(f.Path, addSchemeMarker),
- machinery.NewMarkerFor(f.Path, setupMarker),
- )
-
- return nil
-}
-
-var _ machinery.Inserter = &MainUpdater{}
-
-// MainUpdater updates main.go to run Controllers
-type MainUpdater struct { //nolint:maligned
- machinery.RepositoryMixin
- machinery.MultiGroupMixin
- machinery.ResourceMixin
-
- // Flags to indicate which parts need to be included when updating the file
- WireResource, WireController, WireWebhook bool
-}
-
-// GetPath implements file.Builder
-func (*MainUpdater) GetPath() string {
- return defaultMainPath
-}
-
-// GetIfExistsAction implements file.Builder
-func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction {
- return machinery.OverwriteFile
-}
-
-const (
- importMarker = "imports"
- addSchemeMarker = "scheme"
- setupMarker = "builder"
-)
-
-// GetMarkers implements file.Inserter
-func (f *MainUpdater) GetMarkers() []machinery.Marker {
- return []machinery.Marker{
- machinery.NewMarkerFor(defaultMainPath, importMarker),
- machinery.NewMarkerFor(defaultMainPath, addSchemeMarker),
- machinery.NewMarkerFor(defaultMainPath, setupMarker),
- }
-}
-
-const (
- apiImportCodeFragment = `%s "%s"
-`
- controllerImportCodeFragment = `"%s/controllers"
-`
- multiGroupControllerImportCodeFragment = `%scontrollers "%s/controllers/%s"
-`
- addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme))
-`
- reconcilerSetupCodeFragment = `if err = (&controllers.%sReconciler{
- Client: mgr.GetClient(),
- Scheme: mgr.GetScheme(),
- }).SetupWithManager(mgr); err != nil {
- setupLog.Error(err, "unable to create controller", "controller", "%s")
- os.Exit(1)
- }
-`
- multiGroupReconcilerSetupCodeFragment = `if err = (&%scontrollers.%sReconciler{
- Client: mgr.GetClient(),
- Scheme: mgr.GetScheme(),
- }).SetupWithManager(mgr); err != nil {
- setupLog.Error(err, "unable to create controller", "controller", "%s")
- os.Exit(1)
- }
-`
- webhookSetupCodeFragment = `if err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil {
- setupLog.Error(err, "unable to create webhook", "webhook", "%s")
- os.Exit(1)
- }
-`
-)
-
-// GetCodeFragments implements file.Inserter
-func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap {
- fragments := make(machinery.CodeFragmentsMap, 3)
-
- // If resource is not being provided we are creating the file, not updating it
- if f.Resource == nil {
- return fragments
- }
-
- // Generate import code fragments
- imports := make([]string, 0)
- if f.WireResource {
- imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
- }
-
- if f.WireController {
- if !f.MultiGroup || f.Resource.Group == "" {
- imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo))
- } else {
- imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment,
- f.Resource.PackageName(), f.Repo, f.Resource.Group))
- }
- }
-
- // Generate add scheme code fragments
- addScheme := make([]string, 0)
- if f.WireResource {
- addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))
- }
-
- // Generate setup code fragments
- setup := make([]string, 0)
- if f.WireController {
- if !f.MultiGroup || f.Resource.Group == "" {
- setup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment,
- f.Resource.Kind, f.Resource.Kind))
- } else {
- setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment,
- f.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind))
- }
- }
- if f.WireWebhook {
- setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment,
- f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind))
- }
-
- // Only store code fragments in the map if the slices are non-empty
- if len(imports) != 0 {
- fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports
- }
- if len(addScheme) != 0 {
- fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme
- }
- if len(setup) != 0 {
- fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup
- }
-
- return fragments
-}
-
-var mainTemplate = `{{ .Boilerplate }}
-
-package main
-
-import (
- "flag"
- "os"
-
- // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
- // to ensure that exec-entrypoint and run can make use of them.
- _ "k8s.io/client-go/plugin/pkg/client/auth"
-
- "k8s.io/apimachinery/pkg/runtime"
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
- clientgoscheme "k8s.io/client-go/kubernetes/scheme"
- ctrl "sigs.k8s.io/controller-runtime"
- "sigs.k8s.io/controller-runtime/pkg/log/zap"
- "sigs.k8s.io/controller-runtime/pkg/healthz"
- %s
-)
-
-var (
- scheme = runtime.NewScheme()
- setupLog = ctrl.Log.WithName("setup")
-)
-
-func init() {
- utilruntime.Must(clientgoscheme.AddToScheme(scheme))
-
- %s
-}
-
-func main() {
-{{- if not .ComponentConfig }}
- var metricsAddr string
- var enableLeaderElection bool
- var probeAddr string
- flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
- flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
- flag.BoolVar(&enableLeaderElection, "leader-elect", false,
- "Enable leader election for controller manager. " +
- "Enabling this will ensure there is only one active controller manager.")
-{{- else }}
- var configFile string
- flag.StringVar(&configFile, "config", "",
- "The controller will load its initial configuration from this file. " +
- "Omit this flag to use the default configuration values. " +
- "Command-line flags override configuration from this file.")
-{{- end }}
- opts := zap.Options{
- Development: true,
- }
- opts.BindFlags(flag.CommandLine)
- flag.Parse()
-
- ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
-
-{{ if not .ComponentConfig }}
- mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
- Scheme: scheme,
- MetricsBindAddress: metricsAddr,
- Port: 9443,
- HealthProbeBindAddress: probeAddr,
- LeaderElection: enableLeaderElection,
- LeaderElectionID: "{{ hashFNV .Repo }}.{{ .Domain }}",
- // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
- // when the Manager ends. This requires the binary to immediately end when the
- // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
- // speeds up voluntary leader transitions as the new leader don't have to wait
- // LeaseDuration time first.
- //
- // In the default scaffold provided, the program ends immediately after
- // the manager stops, so would be fine to enable this option. However,
- // if you are doing or is intended to do any operation such as perform cleanups
- // after the manager stops then its usage might be unsafe.
- // LeaderElectionReleaseOnCancel: true,
- })
-{{- else }}
- var err error
- options := ctrl.Options{Scheme: scheme}
- if configFile != "" {
- options, err = options.AndFrom(ctrl.ConfigFile().AtPath(configFile))
- if err != nil {
- setupLog.Error(err, "unable to load the config file")
- os.Exit(1)
- }
- }
-
- mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options)
-{{- end }}
- if err != nil {
- setupLog.Error(err, "unable to start manager")
- os.Exit(1)
- }
-
- %s
-
- if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
- setupLog.Error(err, "unable to set up health check")
- os.Exit(1)
- }
- if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
- setupLog.Error(err, "unable to set up ready check")
- os.Exit(1)
- }
-
- setupLog.Info("starting manager")
- if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
- setupLog.Error(err, "problem running manager")
- os.Exit(1)
- }
-}
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go
deleted file mode 100644
index b8593c7f1cb..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
-Copyright 2020 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 templates
-
-import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Makefile{}
-
-// Makefile scaffolds a file that defines project management CLI commands
-type Makefile struct {
- machinery.TemplateMixin
- machinery.ComponentConfigMixin
- machinery.ProjectNameMixin
-
- // Image is controller manager image name
- Image string
- // BoilerplatePath is the path to the boilerplate file
- BoilerplatePath string
- // Controller tools version to use in the project
- ControllerToolsVersion string
- // Kustomize version to use in the project
- KustomizeVersion string
- // ControllerRuntimeVersion version to be used to download the envtest setup script
- ControllerRuntimeVersion string
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Makefile) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = "Makefile"
- }
-
- f.TemplateBody = makefileTemplate
-
- f.IfExistsAction = machinery.Error
-
- if f.Image == "" {
- f.Image = "controller:latest"
- }
-
- return nil
-}
-
-//nolint:lll
-const makefileTemplate = `
-# Image URL to use all building/pushing image targets
-IMG ?= {{ .Image }}
-# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
-ENVTEST_K8S_VERSION = 1.25.0
-
-# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
-ifeq (,$(shell go env GOBIN))
-GOBIN=$(shell go env GOPATH)/bin
-else
-GOBIN=$(shell go env GOBIN)
-endif
-
-# Setting SHELL to bash allows bash commands to be executed by recipes.
-# Options are set to exit when a recipe line exits non-zero or a piped command fails.
-SHELL = /usr/bin/env bash -o pipefail
-.SHELLFLAGS = -ec
-
-.PHONY: all
-all: build
-
-##@ General
-
-# The help target prints out all targets with their descriptions organized
-# beneath their categories. The categories are represented by '##@' and the
-# target descriptions by '##'. The awk commands is responsible for reading the
-# entire set of makefiles included in this invocation, looking for lines of the
-# file as xyz: ## something, and then pretty-format the target and help. Then,
-# if there's a line with ##@ something, that gets pretty-printed as a category.
-# More info on the usage of ANSI control characters for terminal formatting:
-# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
-# More info on the awk command:
-# http://linuxcommand.org/lc3_adv_awk.php
-
-.PHONY: help
-help: ## Display this help.
- @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
-
-##@ Development
-
-.PHONY: manifests
-manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
- $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
-
-.PHONY: generate
-generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
- $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..."
-
-.PHONY: fmt
-fmt: ## Run go fmt against code.
- go fmt ./...
-
-.PHONY: vet
-vet: ## Run go vet against code.
- go vet ./...
-
-.PHONY: test
-test: manifests generate fmt vet envtest ## Run tests.
- KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out
-
-##@ Build
-
-.PHONY: build
-build: manifests generate fmt vet ## Build manager binary.
- go build -o bin/manager main.go
-
-.PHONY: run
-run: manifests generate fmt vet ## Run a controller from your host.
- go run ./main.go
-
-# If you wish built the manager image targeting other platforms you can use the --platform flag.
-# (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it.
-# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
-.PHONY: docker-build
-docker-build: test ## Build docker image with the manager.
- docker build -t ${IMG} .
-
-.PHONY: docker-push
-docker-push: ## Push docker image with the manager.
- docker push ${IMG}
-
-# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple
-# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
-# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/
-# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/
-# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> than the export will fail)
-# To properly provided solutions that supports more than one platform you should use this option.
-PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
-.PHONY: docker-buildx
-docker-buildx: test ## Build and push docker image for the manager for cross-platform support
- # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
- sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
- - docker buildx create --name project-v3-builder
- docker buildx use project-v3-builder
- - docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross
- - docker buildx rm project-v3-builder
- rm Dockerfile.cross
-
-##@ Deployment
-
-ifndef ignore-not-found
- ignore-not-found = false
-endif
-
-.PHONY: install
-install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
- $(KUSTOMIZE) build config/crd | kubectl apply -f -
-
-.PHONY: uninstall
-uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
- $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
-
-.PHONY: deploy
-deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
- cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
- $(KUSTOMIZE) build config/default | kubectl apply -f -
-
-.PHONY: undeploy
-undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
- $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
-
-##@ Build Dependencies
-
-## Location to install dependencies to
-LOCALBIN ?= $(shell pwd)/bin
-$(LOCALBIN):
- mkdir -p $(LOCALBIN)
-
-## Tool Binaries
-KUSTOMIZE ?= $(LOCALBIN)/kustomize
-CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
-ENVTEST ?= $(LOCALBIN)/setup-envtest
-
-## Tool Versions
-KUSTOMIZE_VERSION ?= {{ .KustomizeVersion }}
-CONTROLLER_TOOLS_VERSION ?= {{ .ControllerToolsVersion }}
-
-KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
-.PHONY: kustomize
-kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
-$(KUSTOMIZE): $(LOCALBIN)
- test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); }
-
-.PHONY: controller-gen
-controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
-$(CONTROLLER_GEN): $(LOCALBIN)
- test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
-
-.PHONY: envtest
-envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
-$(ENVTEST): $(LOCALBIN)
- test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
-`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/readme.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/readme.go
deleted file mode 100644
index 64d541bf170..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/readme.go
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
-Copyright 2022 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 templates
-
-import (
- "fmt"
- "strings"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
-)
-
-var _ machinery.Template = &Readme{}
-
-// Readme scaffolds a README.md file
-type Readme struct {
- machinery.TemplateMixin
- machinery.BoilerplateMixin
- machinery.ProjectNameMixin
-
- License string
-}
-
-// SetTemplateDefaults implements file.Template
-func (f *Readme) SetTemplateDefaults() error {
- if f.Path == "" {
- f.Path = "README.md"
- }
-
- f.License = strings.Replace(
- strings.Replace(f.Boilerplate, "/*", "", 1),
- "*/", "", 1)
-
- f.TemplateBody = fmt.Sprintf(readmeFileTemplate,
- codeFence("kubectl apply -f config/samples/"),
- codeFence("make docker-build docker-push IMG=/{{ .ProjectName }}:tag"),
- codeFence("make deploy IMG=/{{ .ProjectName }}:tag"),
- codeFence("make uninstall"),
- codeFence("make undeploy"),
- codeFence("make install"),
- codeFence("make run"),
- codeFence("make manifests"))
-
- return nil
-}
-
-//nolint:lll
-const readmeFileTemplate = `# {{ .ProjectName }}
-// TODO(user): Add simple overview of use/purpose
-
-## Description
-// TODO(user): An in-depth paragraph about your project and overview of use
-
-## Getting Started
-You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster.
-**Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster ` + "`kubectl cluster-info`" + ` shows).
-
-### Running on the cluster
-1. Install Instances of Custom Resources:
-
-%s
-
-2. Build and push your image to the location specified by ` + "`IMG`" + `:
-
-%s
-
-3. Deploy the controller to the cluster with the image specified by ` + "`IMG`" + `:
-
-%s
-
-### Uninstall CRDs
-To delete the CRDs from the cluster:
-
-%s
-
-### Undeploy controller
-UnDeploy the controller to the cluster:
-
-%s
-
-## Contributing
-// TODO(user): Add detailed information on how you would like others to contribute to this project
-
-### How it works
-This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/)
-
-It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/)
-which provides a reconcile function responsible for synchronizing resources untile the desired state is reached on the cluster
-
-### Test It Out
-1. Install the CRDs into the cluster:
-
-%s
-
-2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running):
-
-%s
-
-**NOTE:** You can also run this in one step by running: ` + "`make install run`" + `
-
-### Modifying the API definitions
-If you are editing the API definitions, generate the manifests such as CRs or CRDs using:
-
-%s
-
-**NOTE:** Run ` + "`make --help`" + ` for more information on all potential ` + "`make`" + ` targets
-
-More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
-
-## License
-{{ .License }}
-`
-
-func codeFence(code string) string {
- return "```sh" + "\n" + code + "\n" + "```"
-}
diff --git a/pkg/plugins/golang/v3/scaffolds/webhook.go b/pkg/plugins/golang/v3/scaffolds/webhook.go
deleted file mode 100644
index 5ca51c39844..00000000000
--- a/pkg/plugins/golang/v3/scaffolds/webhook.go
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
-Copyright 2020 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 scaffolds
-
-import (
- "fmt"
-
- "github.com/spf13/afero"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack"
-)
-
-var _ plugins.Scaffolder = &webhookScaffolder{}
-
-type webhookScaffolder struct {
- config config.Config
- resource resource.Resource
-
- // fs is the filesystem that will be used by the scaffolder
- fs machinery.Filesystem
-
- // force indicates whether to scaffold controller files even if it exists or not
- force bool
-}
-
-// NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations
-func NewWebhookScaffolder(config config.Config, resource resource.Resource, force bool) plugins.Scaffolder {
- return &webhookScaffolder{
- config: config,
- resource: resource,
- force: force,
- }
-}
-
-// InjectFS implements cmdutil.Scaffolder
-func (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) {
- s.fs = fs
-}
-
-// Scaffold implements cmdutil.Scaffolder
-func (s *webhookScaffolder) Scaffold() error {
- fmt.Println("Writing scaffold for you to edit...")
-
- // Load the boilerplate
- boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath)
- if err != nil {
- return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err)
- }
-
- // Initialize the machinery.Scaffold that will write the files to disk
- scaffold := machinery.NewScaffold(s.fs,
- machinery.WithConfig(s.config),
- machinery.WithBoilerplate(string(boilerplate)),
- machinery.WithResource(&s.resource),
- )
-
- // Keep track of these values before the update
- doDefaulting := s.resource.HasDefaultingWebhook()
- doValidation := s.resource.HasValidationWebhook()
- doConversion := s.resource.HasConversionWebhook()
-
- if err := s.config.UpdateResource(s.resource); err != nil {
- return fmt.Errorf("error updating resource: %w", err)
- }
-
- if err := scaffold.Execute(
- &api.Webhook{Force: s.force},
- &templates.MainUpdater{WireWebhook: true},
- ); err != nil {
- return err
- }
-
- if doConversion {
- fmt.Println(`Webhook server has been set up for you.
-You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`)
- }
-
- // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest.
- if doDefaulting || doValidation {
- if err := scaffold.Execute(
- &api.WebhookSuite{},
- ); err != nil {
- return err
- }
- }
-
- return nil
-}
diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go
deleted file mode 100644
index 1baba22aaba..00000000000
--- a/pkg/plugins/golang/v3/webhook.go
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
-Copyright 2020 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 v3
-
-import (
- "fmt"
-
- "github.com/spf13/pflag"
-
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- pluginutil "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
- goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds"
-)
-
-// defaultWebhookVersion is the default mutating/validating webhook config API version to scaffold.
-const defaultWebhookVersion = "v1"
-
-var _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{}
-
-type createWebhookSubcommand struct {
- config config.Config
- // For help text.
- commandName string
-
- options *goPlugin.Options
-
- resource *resource.Resource
-
- // force indicates that the resource should be created even if it already exists
- force bool
-}
-
-func (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- p.commandName = cliMeta.CommandName
-
- subcmdMeta.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting,
-validating and/or conversion webhooks.
-`
- subcmdMeta.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for Group: ship, Version: v1beta1
- # and Kind: Frigate
- %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation
-
- # Create conversion webhook for Group: ship, Version: v1beta1
- # and Kind: Frigate
- %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion
-`, cliMeta.CommandName)
-}
-
-func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) {
- p.options = &goPlugin.Options{}
-
- fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form")
-
- fs.StringVar(&p.options.WebhookVersion, "webhook-version", defaultWebhookVersion,
- "version of {Mutating,Validating}WebhookConfigurations to scaffold. Options: [v1, v1beta1]")
- fs.BoolVar(&p.options.DoDefaulting, "defaulting", false,
- "if set, scaffold the defaulting webhook")
- fs.BoolVar(&p.options.DoValidation, "programmatic-validation", false,
- "if set, scaffold the validating webhook")
- fs.BoolVar(&p.options.DoConversion, "conversion", false,
- "if set, scaffold the conversion webhook")
-
- fs.BoolVar(&p.force, "force", false,
- "attempt to create resource even if it already exists")
-
- // (not required raise an error in this case)
- // nolint:errcheck,gosec
- fs.MarkDeprecated("webhook-version", deprecateMsg)
-}
-
-func (p *createWebhookSubcommand) InjectConfig(c config.Config) error {
- p.config = c
-
- return nil
-}
-
-func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error {
- p.resource = res
-
- p.options.UpdateResource(p.resource, p.config)
-
- if err := p.resource.Validate(); err != nil {
- return err
- }
-
- if !p.resource.HasDefaultingWebhook() && !p.resource.HasValidationWebhook() && !p.resource.HasConversionWebhook() {
- return fmt.Errorf("%s create webhook requires at least one of --defaulting,"+
- " --programmatic-validation and --conversion to be true", p.commandName)
- }
-
- // check if resource exist to create webhook
- if r, err := p.config.GetResource(p.resource.GVK); err != nil {
- return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName)
- } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() && !p.force {
- return fmt.Errorf("webhook resource already exists")
- }
-
- if pluginutil.HasDifferentWebhookVersion(p.config, p.resource.Webhooks.WebhookVersion) {
- return fmt.Errorf("only one webhook version can be used for all resources, cannot add %q",
- p.resource.Webhooks.WebhookVersion)
- }
-
- return nil
-}
-
-func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error {
- scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force)
- scaffolder.InjectFS(fs)
- return scaffolder.Scaffold()
-}
-
-func (p *createWebhookSubcommand) PostScaffold() error {
- if p.resource.Webhooks.WebhookVersion == "v1beta1" {
- if err := applyScaffoldCustomizationsForVbeta1(); err != nil {
- return err
- }
- }
-
- err := pluginutil.RunCmd("Update dependencies", "go", "mod", "tidy")
- if err != nil {
- return err
- }
-
- err = pluginutil.RunCmd("Running make", "make", "generate")
- if err != nil {
- return err
- }
- fmt.Print("Next: implement your new Webhook and generate the manifests with:\n$ make manifests\n")
-
- return nil
-}
diff --git a/pkg/plugins/golang/v4/api.go b/pkg/plugins/golang/v4/api.go
new file mode 100644
index 00000000000..3747dc9de7d
--- /dev/null
+++ b/pkg/plugins/golang/v4/api.go
@@ -0,0 +1,204 @@
+/*
+Copyright 2022 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 v4
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ log "log/slog"
+ "os"
+
+ "github.com/spf13/pflag"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/resource"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
+ goPlugin "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds"
+)
+
+// DefaultMainPath is default file path of main.go
+const DefaultMainPath = "cmd/main.go"
+
+var _ plugin.CreateAPISubcommand = &createAPISubcommand{}
+
+type createAPISubcommand struct {
+ config config.Config
+
+ options *goPlugin.Options
+
+ resource *resource.Resource
+
+ // Check if we have to scaffold resource and/or controller
+ resourceFlag *pflag.Flag
+ controllerFlag *pflag.Flag
+
+ // force indicates that the resource should be created even if it already exists
+ force bool
+
+ // runMake indicates whether to run make or not after scaffolding APIs
+ runMake bool
+}
+
+func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
+ subcmdMeta.Description = `Scaffold a Kubernetes API by writing a Resource definition and/or a Controller.
+
+If information about whether the resource and controller should be scaffolded
+was not explicitly provided, it will prompt the user if they should be.
+
+After the scaffold is written, the dependencies will be updated and
+make generate will be run.
+`
+ subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate
+ %[1]s create api --group ship --version v1beta1 --kind Frigate
+
+ # Edit the API Scheme
+
+ nano api/v1beta1/frigate_types.go
+
+ # Edit the Controller
+ nano internal/controller/frigate/frigate_controller.go
+
+ # Edit the Controller Test
+ nano internal/controller/frigate/frigate_controller_test.go
+
+ # Generate the manifests
+ make manifests
+
+ # Install CRDs into the Kubernetes cluster using kubectl apply
+ make install
+
+ # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
+ make run
+`, cliMeta.CommandName)
+}
+
+func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {
+ fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files")
+
+ fs.BoolVar(&p.force, "force", false,
+ "attempt to create resource even if it already exists")
+
+ p.options = &goPlugin.Options{}
+
+ fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form")
+
+ fs.BoolVar(&p.options.DoAPI, "resource", true,
+ "if set, generate the resource without prompting the user")
+ p.resourceFlag = fs.Lookup("resource")
+ fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced")
+
+ fs.BoolVar(&p.options.DoController, "controller", true,
+ "if set, generate the controller without prompting the user")
+ p.controllerFlag = fs.Lookup("controller")
+
+ fs.StringVar(&p.options.ExternalAPIPath, "external-api-path", "",
+ "Specify the Go package import path for the external API. This is used to scaffold controllers for resources "+
+ "defined outside this project (e.g., github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1).")
+
+ fs.StringVar(&p.options.ExternalAPIDomain, "external-api-domain", "",
+ "Specify the domain name for the external API. This domain is used to generate accurate RBAC "+
+ "markers and permissions for the external resources (e.g., cert-manager.io).")
+}
+
+func (p *createAPISubcommand) InjectConfig(c config.Config) error {
+ p.config = c
+ return nil
+}
+
+func (p *createAPISubcommand) InjectResource(res *resource.Resource) error {
+ p.resource = res
+
+ reader := bufio.NewReader(os.Stdin)
+ if !p.resourceFlag.Changed {
+ log.Info("Create Resource [y/n]")
+ p.options.DoAPI = util.YesNo(reader)
+ }
+ if !p.controllerFlag.Changed {
+ log.Info("Create Controller [y/n]")
+ p.options.DoController = util.YesNo(reader)
+ }
+
+ // Ensure that external API options cannot be used when creating an API in the project.
+ if p.options.DoAPI {
+ if len(p.options.ExternalAPIPath) != 0 || len(p.options.ExternalAPIDomain) != 0 {
+ return errors.New("cannot use '--external-api-path' or '--external-api-domain' " +
+ "when creating an API in the project with '--resource=true'. " +
+ "Use '--resource=false' when referencing an external API")
+ }
+ }
+
+ p.options.UpdateResource(p.resource, p.config)
+
+ if err := p.resource.Validate(); err != nil {
+ return fmt.Errorf("error validating resource: %w", err)
+ }
+
+ // In case we want to scaffold a resource API we need to do some checks
+ if p.options.DoAPI {
+ // Check that resource doesn't have the API scaffolded or flag force was set
+ if r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force {
+ return errors.New("API resource already exists")
+ }
+
+ // Check that the provided group can be added to the project
+ if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) {
+ return fmt.Errorf("multiple groups are not allowed by default, " +
+ "to enable multi-group visit https://kubebuilder.io/migration/multi-group.html")
+ }
+ }
+
+ return nil
+}
+
+func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error {
+ // check if main.go is present in the root directory
+ if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) {
+ return fmt.Errorf("%s file should present in the root directory", DefaultMainPath)
+ }
+
+ return nil
+}
+
+func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {
+ scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force)
+ scaffolder.InjectFS(fs)
+ if err := scaffolder.Scaffold(); err != nil {
+ return fmt.Errorf("error scaffolding API: %w", err)
+ }
+
+ return nil
+}
+
+func (p *createAPISubcommand) PostScaffold() error {
+ err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
+ if err != nil {
+ return fmt.Errorf("error updating go dependencies: %w", err)
+ }
+ if p.runMake && p.resource.HasAPI() {
+ err = util.RunCmd("Running make", "make", "generate")
+ if err != nil {
+ return fmt.Errorf("error running make generate: %w", err)
+ }
+ fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n")
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/golang/v4/edit.go b/pkg/plugins/golang/v4/edit.go
new file mode 100644
index 00000000000..d60567bf150
--- /dev/null
+++ b/pkg/plugins/golang/v4/edit.go
@@ -0,0 +1,69 @@
+/*
+Copyright 2022 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 v4
+
+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"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds"
+)
+
+var _ plugin.EditSubcommand = &editSubcommand{}
+
+type editSubcommand struct {
+ config config.Config
+
+ multigroup bool
+}
+
+func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
+ subcmdMeta.Description = `This command will edit the project configuration.
+Features supported:
+ - Toggle between single or multi group projects.
+`
+ subcmdMeta.Examples = fmt.Sprintf(` # Enable the multigroup layout
+ %[1]s edit --multigroup
+
+ # Disable the multigroup layout
+ %[1]s edit --multigroup=false
+`, cliMeta.CommandName)
+}
+
+func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {
+ fs.BoolVar(&p.multigroup, "multigroup", false, "enable or disable multigroup layout")
+}
+
+func (p *editSubcommand) InjectConfig(c config.Config) error {
+ p.config = c
+
+ return nil
+}
+
+func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {
+ scaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup)
+ scaffolder.InjectFS(fs)
+ if err := scaffolder.Scaffold(); err != nil {
+ return fmt.Errorf("failed to edit scaffold: %w", err)
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/golang/v4/init.go b/pkg/plugins/golang/v4/init.go
new file mode 100644
index 00000000000..278f5f3bdf3
--- /dev/null
+++ b/pkg/plugins/golang/v4/init.go
@@ -0,0 +1,214 @@
+/*
+Copyright 2022 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 v4
+
+import (
+ "fmt"
+ log "log/slog"
+ "os"
+ "path/filepath"
+ "strings"
+ "unicode"
+
+ "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"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds"
+)
+
+// Variables and function to check Go version requirements.
+var (
+ goVerMin = golang.MustParse("go1.23.0")
+ goVerMax = golang.MustParse("go2.0alpha1")
+)
+
+var _ plugin.InitSubcommand = &initSubcommand{}
+
+type initSubcommand struct {
+ config config.Config
+ // For help text.
+ commandName string
+
+ // boilerplate options
+ license string
+ owner string
+
+ // go config options
+ repo string
+
+ // flags
+ fetchDeps bool
+ skipGoVersionCheck bool
+}
+
+func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
+ p.commandName = cliMeta.CommandName
+
+ subcmdMeta.Description = `Initialize a new project including the following files:
+ - a "go.mod" with project dependencies
+ - a "PROJECT" file that stores project configuration
+ - a "Makefile" with several useful make targets for the project
+ - several YAML files for project deployment under the "config" directory
+ - a "cmd/main.go" file that creates the manager that will run the project controllers
+`
+ subcmdMeta.Examples = fmt.Sprintf(` # Initialize a new project with your domain and name in copyright
+ %[1]s init --plugins go/v4 --domain example.org --owner "Your name"
+
+ # Initialize a new project defining a specific project version
+ %[1]s init --plugins go/v4 --project-version 3
+`, cliMeta.CommandName)
+}
+
+func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {
+ fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check",
+ false, "if specified, skip checking the Go version")
+
+ // dependency args
+ fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "ensure dependencies are downloaded")
+
+ // boilerplate args
+ fs.StringVar(&p.license, "license", "apache2",
+ "license to use to boilerplate, may be one of 'apache2', 'none'")
+ fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright")
+
+ // project args
+ fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+
+ "defaults to the go package of the current working directory.")
+}
+
+func (p *initSubcommand) InjectConfig(c config.Config) error {
+ p.config = c
+
+ // Try to guess repository if flag is not set.
+ if p.repo == "" {
+ repoPath, err := golang.FindCurrentRepo()
+ if err != nil {
+ return fmt.Errorf("error finding current repository: %w", err)
+ }
+ p.repo = repoPath
+ }
+
+ if err := p.config.SetRepository(p.repo); err != nil {
+ return fmt.Errorf("error setting repository: %w", err)
+ }
+
+ return nil
+}
+
+func (p *initSubcommand) PreScaffold(machinery.Filesystem) error {
+ // Ensure Go version is in the allowed range if check not turned off.
+ if !p.skipGoVersionCheck {
+ if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil {
+ return fmt.Errorf("error validating go version: %w", err)
+ }
+ }
+
+ // Check if the current directory has no files or directories which does not allow to init the project
+ return checkDir()
+}
+
+func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {
+ scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner, p.commandName)
+ scaffolder.InjectFS(fs)
+ if err := scaffolder.Scaffold(); err != nil {
+ return fmt.Errorf("error scaffolding init plugin: %w", err)
+ }
+
+ if !p.fetchDeps {
+ log.Info("Skipping fetching dependencies.")
+ return nil
+ }
+
+ // Ensure that we are pinning controller-runtime version
+ // xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997
+ err := util.RunCmd("Get controller runtime", "go", "get",
+ "sigs.k8s.io/controller-runtime@"+scaffolds.ControllerRuntimeVersion)
+ if err != nil {
+ return fmt.Errorf("error getting controller-runtime version: %w", err)
+ }
+
+ return nil
+}
+
+func (p *initSubcommand) PostScaffold() error {
+ err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
+ if err != nil {
+ return fmt.Errorf("error updating go dependencies: %w", err)
+ }
+
+ fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName)
+ return nil
+}
+
+// checkDir will return error if the current directory has files which are not allowed.
+// Note that, it is expected that the directory to scaffold the project is cleaned.
+// Otherwise, it might face issues to do the scaffold.
+func checkDir() error {
+ err := filepath.Walk(".",
+ func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return fmt.Errorf("error walking path %q: %w", path, err)
+ }
+ // Allow directory trees starting with '.'
+ if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." {
+ return filepath.SkipDir
+ }
+ // Allow files starting with '.'
+ if strings.HasPrefix(info.Name(), ".") {
+ return nil
+ }
+ // Allow files ending with '.md' extension
+ if strings.HasSuffix(info.Name(), ".md") && !info.IsDir() {
+ return nil
+ }
+ // Allow capitalized files except PROJECT
+ isCapitalized := true
+ for _, l := range info.Name() {
+ if !unicode.IsUpper(l) {
+ isCapitalized = false
+ break
+ }
+ }
+ if isCapitalized && info.Name() != "PROJECT" {
+ return nil
+ }
+ disallowedExtensions := []string{
+ ".go",
+ ".yaml",
+ ".mod",
+ ".sum",
+ }
+ // Deny files with .go or .yaml or .mod or .sum extensions
+ for _, ext := range disallowedExtensions {
+ if strings.HasSuffix(info.Name(), ext) {
+ return nil
+ }
+ }
+ // Do not allow any other file
+ return fmt.Errorf("target directory is not empty and contains a disallowed file %q. "+
+ "files with the following extensions [%s] are not allowed to avoid conflicts with the tooling",
+ path, strings.Join(disallowedExtensions, ", "))
+ })
+ if err != nil {
+ return fmt.Errorf("error walking directory: %w", err)
+ }
+ return nil
+}
diff --git a/pkg/plugins/golang/v4/plugin.go b/pkg/plugins/golang/v4/plugin.go
new file mode 100644
index 00000000000..19baeef40f0
--- /dev/null
+++ b/pkg/plugins/golang/v4/plugin.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2022 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 v4
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/stage"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang"
+)
+
+const pluginName = "base." + golang.DefaultNameQualifier
+
+var (
+ pluginVersion = plugin.Version{Number: 4, Stage: stage.Stable}
+ supportedProjectVersions = []config.Version{cfgv3.Version}
+)
+
+var _ plugin.Full = Plugin{}
+
+// Plugin implements the plugin.Full interface
+type Plugin struct {
+ initSubcommand
+ createAPISubcommand
+ createWebhookSubcommand
+ editSubcommand
+}
+
+// Name returns the name of the plugin
+func (Plugin) Name() string { return pluginName }
+
+// Version returns the version of the plugin
+func (Plugin) Version() plugin.Version { return pluginVersion }
+
+// SupportedProjectVersions returns an array with all project versions supported by the plugin
+func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }
+
+// GetInitSubcommand will return the subcommand which is responsible for initializing and common scaffolding
+func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand }
+
+// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis
+func (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand }
+
+// GetCreateWebhookSubcommand will return the subcommand which is responsible for scaffolding webhooks
+func (p Plugin) GetCreateWebhookSubcommand() plugin.CreateWebhookSubcommand {
+ return &p.createWebhookSubcommand
+}
+
+// GetEditSubcommand will return the subcommand which is responsible for editing the scaffold of the project
+func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }
+
+// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated
+func (p Plugin) DeprecationWarning() string {
+ return ""
+}
diff --git a/pkg/plugins/golang/v4/scaffolds/api.go b/pkg/plugins/golang/v4/scaffolds/api.go
new file mode 100644
index 00000000000..aa8c708417a
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/api.go
@@ -0,0 +1,125 @@
+/*
+Copyright 2022 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 scaffolds
+
+import (
+ "errors"
+ "fmt"
+ log "log/slog"
+
+ "github.com/spf13/afero"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/resource"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/api"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/hack"
+)
+
+var _ plugins.Scaffolder = &apiScaffolder{}
+
+// apiScaffolder contains configuration for generating scaffolding for Go type
+// representing the API and controller that implements the behavior for the API.
+type apiScaffolder struct {
+ config config.Config
+ resource resource.Resource
+
+ // fs is the filesystem that will be used by the scaffolder
+ fs machinery.Filesystem
+
+ // force indicates whether to scaffold controller files even if it exists or not
+ force bool
+}
+
+// NewAPIScaffolder returns a new Scaffolder for API/controller creation operations
+func NewAPIScaffolder(cfg config.Config, res resource.Resource, force bool) plugins.Scaffolder {
+ return &apiScaffolder{
+ config: cfg,
+ resource: res,
+ force: force,
+ }
+}
+
+// InjectFS implements cmdutil.Scaffolder
+func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) {
+ s.fs = fs
+}
+
+// Scaffold implements cmdutil.Scaffolder
+func (s *apiScaffolder) Scaffold() error {
+ log.Info("Writing scaffold for you to edit...")
+
+ // Load the boilerplate
+ boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath)
+ if err != nil {
+ if errors.Is(err, afero.ErrFileNotFound) {
+ log.Warn("Unable to find boilerplate file."+
+ "This file is used to generate the license header in the project.\n"+
+ "Note that controller-gen will also use this. Therefore, ensure that you "+
+ "add the license file or configure your project accordingly.",
+ "file_path", hack.DefaultBoilerplatePath, "error", err)
+ boilerplate = []byte("")
+ } else {
+ return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err)
+ }
+ }
+
+ // Initialize the machinery.Scaffold that will write the files to disk
+ scaffold := machinery.NewScaffold(s.fs,
+ machinery.WithConfig(s.config),
+ machinery.WithBoilerplate(string(boilerplate)),
+ machinery.WithResource(&s.resource),
+ )
+
+ // Keep track of these values before the update
+ doAPI := s.resource.HasAPI()
+ doController := s.resource.HasController()
+
+ if err := s.config.UpdateResource(s.resource); err != nil {
+ return fmt.Errorf("error updating resource: %w", err)
+ }
+
+ if doAPI {
+ if err := scaffold.Execute(
+ &api.Types{Force: s.force},
+ &api.Group{},
+ ); err != nil {
+ return fmt.Errorf("error scaffolding APIs: %w", err)
+ }
+ }
+
+ if doController {
+ if err := scaffold.Execute(
+ &controllers.SuiteTest{Force: s.force},
+ &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force},
+ &controllers.ControllerTest{Force: s.force, DoAPI: doAPI},
+ ); err != nil {
+ return fmt.Errorf("error scaffolding controller: %w", err)
+ }
+ }
+
+ if err := scaffold.Execute(
+ &cmd.MainUpdater{WireResource: doAPI, WireController: doController},
+ ); err != nil {
+ return fmt.Errorf("error updating cmd/main.go: %w", err)
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/golang/v4/scaffolds/doc.go b/pkg/plugins/golang/v4/scaffolds/doc.go
new file mode 100644
index 00000000000..81757e9692b
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/doc.go
@@ -0,0 +1,18 @@
+/*
+Copyright 2022 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 scaffolds contains libraries for scaffolding code to use with controller-runtime
+package scaffolds
diff --git a/pkg/plugins/golang/v4/scaffolds/edit.go b/pkg/plugins/golang/v4/scaffolds/edit.go
new file mode 100644
index 00000000000..fcc4b791194
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/edit.go
@@ -0,0 +1,77 @@
+/*
+Copyright 2022 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 scaffolds
+
+import (
+ "fmt"
+
+ "github.com/spf13/afero"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+)
+
+var _ plugins.Scaffolder = &editScaffolder{}
+
+type editScaffolder struct {
+ config config.Config
+ multigroup bool
+
+ // fs is the filesystem that will be used by the scaffolder
+ fs machinery.Filesystem
+}
+
+// NewEditScaffolder returns a new Scaffolder for configuration edit operations
+func NewEditScaffolder(cfg config.Config, multigroup bool) plugins.Scaffolder {
+ return &editScaffolder{
+ config: cfg,
+ multigroup: multigroup,
+ }
+}
+
+// InjectFS implements cmdutil.Scaffolder
+func (s *editScaffolder) InjectFS(fs machinery.Filesystem) {
+ s.fs = fs
+}
+
+// Scaffold implements cmdutil.Scaffolder
+func (s *editScaffolder) Scaffold() error {
+ filename := "Dockerfile"
+ bs, err := afero.ReadFile(s.fs.FS, filename)
+ if err != nil {
+ return fmt.Errorf("error reading %q: %w", filename, err)
+ }
+ str := string(bs)
+
+ if s.multigroup {
+ _ = s.config.SetMultiGroup()
+ } else {
+ _ = s.config.ClearMultiGroup()
+ }
+
+ // Check if the str is not empty, because when the file is already in desired format it will return empty string
+ // because there is nothing to replace.
+ if str != "" {
+ // TODO: instead of writing it directly, we should use the scaffolding machinery for consistency
+ if err = afero.WriteFile(s.fs.FS, filename, []byte(str), 0o644); err != nil {
+ return fmt.Errorf("error writing %q: %w", filename, err)
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/golang/v4/scaffolds/init.go b/pkg/plugins/golang/v4/scaffolds/init.go
new file mode 100644
index 00000000000..6f78d0a5cfa
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/init.go
@@ -0,0 +1,196 @@
+/*
+Copyright 2022 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 scaffolds
+
+import (
+ "errors"
+ "fmt"
+ log "log/slog"
+ "strings"
+
+ "github.com/spf13/afero"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+ kustomizecommonv2 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/github"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/hack"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils"
+)
+
+const (
+ // GolangciLintVersion is the golangci-lint version to be used in the project
+ GolangciLintVersion = "v2.5.0"
+ // ControllerRuntimeVersion is the kubernetes-sigs/controller-runtime version to be used in the project
+ ControllerRuntimeVersion = "v0.22.4"
+ // ControllerToolsVersion is the kubernetes-sigs/controller-tools version to be used in the project
+ ControllerToolsVersion = "v0.19.0"
+
+ imageName = "controller:latest"
+)
+
+var _ plugins.Scaffolder = &initScaffolder{}
+
+var kustomizeVersion string
+
+type initScaffolder struct {
+ config config.Config
+ boilerplatePath string
+ license string
+ owner string
+ commandName string
+
+ // fs is the filesystem that will be used by the scaffolder
+ fs machinery.Filesystem
+}
+
+// NewInitScaffolder returns a new Scaffolder for project initialization operations
+func NewInitScaffolder(cfg config.Config, license, owner, commandName string) plugins.Scaffolder {
+ return &initScaffolder{
+ config: cfg,
+ boilerplatePath: hack.DefaultBoilerplatePath,
+ license: license,
+ owner: owner,
+ commandName: commandName,
+ }
+}
+
+// InjectFS implements cmdutil.Scaffolder
+func (s *initScaffolder) InjectFS(fs machinery.Filesystem) {
+ s.fs = fs
+}
+
+// getControllerRuntimeReleaseBranch converts the ControllerRuntime semantic versioning string to a
+// release branch string. Example input: "v0.17.0" -> Output: "release-0.17"
+func getControllerRuntimeReleaseBranch() string {
+ v := strings.TrimPrefix(ControllerRuntimeVersion, "v")
+ tmp := strings.Split(v, ".")
+
+ if len(tmp) < 2 {
+ fmt.Println("Invalid version format. Expected at least major and minor version numbers.")
+ return ""
+ }
+ releaseBranch := fmt.Sprintf("release-%s.%s", tmp[0], tmp[1])
+ return releaseBranch
+}
+
+// Scaffold implements cmdutil.Scaffolder
+func (s *initScaffolder) Scaffold() error {
+ log.Info("Writing scaffold for you to edit...")
+
+ // Initialize the machinery.Scaffold that will write the boilerplate file to disk
+ // The boilerplate file needs to be scaffolded as a separate step as it is going to
+ // be used by the rest of the files, even those scaffolded in this command call.
+ scaffold := machinery.NewScaffold(s.fs,
+ machinery.WithConfig(s.config),
+ )
+
+ if s.license != "none" {
+ bpFile := &hack.Boilerplate{
+ License: s.license,
+ Owner: s.owner,
+ }
+ bpFile.Path = s.boilerplatePath
+ if err := scaffold.Execute(bpFile); err != nil {
+ return fmt.Errorf("failed to execute boilerplate: %w", err)
+ }
+
+ boilerplate, err := afero.ReadFile(s.fs.FS, s.boilerplatePath)
+ if err != nil {
+ if errors.Is(err, afero.ErrFileNotFound) {
+ log.Warn("Unable to find boilerplate file. "+
+ "This file is used to generate the license header in the project. "+
+ "Note that controller-gen will also use this. Therefore, ensure that you "+
+ "add the license file or configure your project accordingly.",
+ "file_path", s.boilerplatePath,
+ "error", err)
+ boilerplate = []byte("")
+ } else {
+ return fmt.Errorf("unable to load boilerplate: %w", err)
+ }
+ }
+ // Initialize the machinery.Scaffold that will write the files to disk
+ scaffold = machinery.NewScaffold(s.fs,
+ machinery.WithConfig(s.config),
+ machinery.WithBoilerplate(string(boilerplate)),
+ )
+ } else {
+ s.boilerplatePath = ""
+ // Initialize the machinery.Scaffold without boilerplate
+ scaffold = machinery.NewScaffold(s.fs,
+ machinery.WithConfig(s.config),
+ )
+ }
+
+ // If the KustomizeV2 was used to do the scaffold then
+ // we need to ensure that we use its supported Kustomize Version
+ // in order to support it
+ kustomizev2 := kustomizecommonv2.Plugin{}
+ gov4 := "go.kubebuilder.io/v4"
+ pluginKeyForKustomizeV2 := plugin.KeyFor(kustomizev2)
+
+ for _, pluginKey := range s.config.GetPluginChain() {
+ if pluginKey == pluginKeyForKustomizeV2 || pluginKey == gov4 {
+ kustomizeVersion = kustomizecommonv2.KustomizeVersion
+ break
+ }
+ }
+
+ err := scaffold.Execute(
+ &cmd.Main{
+ ControllerRuntimeVersion: ControllerRuntimeVersion,
+ },
+ &templates.GoMod{
+ ControllerRuntimeVersion: ControllerRuntimeVersion,
+ },
+ &templates.GitIgnore{},
+ &templates.Makefile{
+ Image: imageName,
+ BoilerplatePath: s.boilerplatePath,
+ ControllerToolsVersion: ControllerToolsVersion,
+ KustomizeVersion: kustomizeVersion,
+ GolangciLintVersion: GolangciLintVersion,
+ ControllerRuntimeVersion: ControllerRuntimeVersion,
+ EnvtestVersion: getControllerRuntimeReleaseBranch(),
+ },
+ &templates.Dockerfile{},
+ &templates.DockerIgnore{},
+ &templates.Readme{CommandName: s.commandName},
+ &templates.Golangci{},
+ &e2e.Test{},
+ &e2e.WebhookTestUpdater{WireWebhook: false},
+ &e2e.SuiteTest{},
+ &github.E2eTestCi{},
+ &github.TestCi{},
+ &github.LintCi{
+ GolangciLintVersion: GolangciLintVersion,
+ },
+ &utils.Utils{},
+ &templates.DevContainer{},
+ &templates.DevContainerPostInstallScript{},
+ )
+ if err != nil {
+ return fmt.Errorf("failed to execute init scaffold: %w", err)
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go
new file mode 100644
index 00000000000..817722b461b
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go
@@ -0,0 +1,76 @@
+/*
+Copyright 2022 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 api
+
+import (
+ log "log/slog"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Group{}
+
+// Group scaffolds the file that defines the registration methods for a certain group and version
+type Group struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Group) SetTemplateDefaults() error {
+ if f.Path == "" {
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join("api", "%[group]", "%[version]", "groupversion_info.go")
+ } else {
+ f.Path = filepath.Join("api", "%[version]", "groupversion_info.go")
+ }
+ }
+
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+ f.TemplateBody = groupTemplate
+
+ return nil
+}
+
+//nolint:lll
+const groupTemplate = `{{ .Boilerplate }}
+
+// Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group.
+// +kubebuilder:object:generate=true
+// +groupName={{ .Resource.QualifiedGroup }}
+package {{ .Resource.Version }}
+
+import (
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+ // GroupVersion is group version used to register these objects.
+ GroupVersion = schema.GroupVersion{Group: "{{ .Resource.QualifiedGroup }}", Version: "{{ .Resource.Version }}"}
+
+ // SchemeBuilder is used to add go types to the GroupVersionKind scheme.
+ SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+ // AddToScheme adds the types in this group-version to the given scheme.
+ AddToScheme = SchemeBuilder.AddToScheme
+)
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go
new file mode 100644
index 00000000000..bb46111b2f3
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2022 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 api
+
+import (
+ log "log/slog"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Hub{}
+
+// Hub scaffolds the file that defines hub
+//
+
+type Hub struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+
+ Force bool
+}
+
+// SetTemplateDefaults implements file.Template
+func (f *Hub) SetTemplateDefaults() error {
+ if f.Path == "" {
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join("api", "%[group]", "%[version]", "%[kind]_conversion.go")
+ } else {
+ f.Path = filepath.Join("api", "%[version]", "%[kind]_conversion.go")
+ }
+ }
+
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+
+ f.TemplateBody = hubTemplate
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ } else {
+ f.IfExistsAction = machinery.SkipFile
+ }
+
+ return nil
+}
+
+const hubTemplate = `{{ .Boilerplate }}
+
+package {{ .Resource.Version }}
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+
+// Hub marks this type as a conversion hub.
+func (*{{ .Resource.Kind }}) Hub() {}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go
new file mode 100644
index 00000000000..74c67df8b1d
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go
@@ -0,0 +1,108 @@
+/*
+Copyright 2022 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 api
+
+import (
+ log "log/slog"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Spoke{}
+
+// Spoke scaffolds the file that defines spoke version conversion
+type Spoke struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+
+ Force bool
+ SpokeVersion string
+}
+
+// SetTemplateDefaults implements file.Template
+func (f *Spoke) SetTemplateDefaults() error {
+ if f.Path == "" {
+ if f.MultiGroup && f.Resource.Group != "" {
+ // Use SpokeVersion for dynamic file path generation
+ f.Path = filepath.Join("api", f.Resource.Group, f.SpokeVersion, "%[kind]_conversion.go")
+ } else {
+ f.Path = filepath.Join("api", f.SpokeVersion, "%[kind]_conversion.go")
+ }
+ }
+
+ // Replace placeholders in the path
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info("Creating spoke conversion file", "path", f.Path)
+
+ f.TemplateBody = spokeTemplate
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ } else {
+ f.IfExistsAction = machinery.SkipFile
+ }
+
+ return nil
+}
+
+//nolint:lll
+const spokeTemplate = `{{ .Boilerplate }}
+
+package {{ .SpokeVersion }}
+
+import (
+ "log"
+
+ "sigs.k8s.io/controller-runtime/pkg/conversion"
+ {{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
+)
+
+// ConvertTo converts this {{ .Resource.Kind }} ({{ .SpokeVersion }}) to the Hub version ({{ .Resource.Version }}).
+func (src *{{ .Resource.Kind }}) ConvertTo(dstRaw conversion.Hub) error {
+ dst := dstRaw.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})
+ log.Printf("ConvertTo: Converting {{ .Resource.Kind }} from Spoke version {{ .SpokeVersion }} to Hub version {{ .Resource.Version }};" +
+ "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name)
+
+ // TODO(user): Implement conversion logic from {{ .SpokeVersion }} to {{ .Resource.Version }}
+ // Example: Copying Spec fields
+ // dst.Spec.Size = src.Spec.Replicas
+
+ // Copy ObjectMeta to preserve name, namespace, labels, etc.
+ dst.ObjectMeta = src.ObjectMeta
+
+ return nil
+}
+
+// ConvertFrom converts the Hub version ({{ .Resource.Version }}) to this {{ .Resource.Kind }} ({{ .SpokeVersion }}).
+func (dst *{{ .Resource.Kind }}) ConvertFrom(srcRaw conversion.Hub) error {
+ src := srcRaw.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})
+ log.Printf("ConvertFrom: Converting {{ .Resource.Kind }} from Hub version {{ .Resource.Version }} to Spoke version {{ .SpokeVersion }};" +
+ "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name)
+
+ // TODO(user): Implement conversion logic from {{ .Resource.Version }} to {{ .SpokeVersion }}
+ // Example: Copying Spec fields
+ // dst.Spec.Replicas = src.Spec.Size
+
+ // Copy ObjectMeta to preserve name, namespace, labels, etc.
+ dst.ObjectMeta = src.ObjectMeta
+
+ return nil
+}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go
new file mode 100644
index 00000000000..429b68192d6
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go
@@ -0,0 +1,150 @@
+/*
+Copyright 2022 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 api
+
+import (
+ log "log/slog"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Types{}
+
+// Types scaffolds the file that defines the schema for a CRD
+//
+
+type Types struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+
+ Force bool
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Types) SetTemplateDefaults() error {
+ if f.Path == "" {
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join("api", "%[group]", "%[version]", "%[kind]_types.go")
+ } else {
+ f.Path = filepath.Join("api", "%[version]", "%[kind]_types.go")
+ }
+ }
+
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+
+ f.TemplateBody = typesTemplate
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ } else {
+ f.IfExistsAction = machinery.Error
+ }
+
+ return nil
+}
+
+//nolint:lll
+const typesTemplate = `{{ .Boilerplate }}
+
+package {{ .Resource.Version }}
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }}
+type {{ .Resource.Kind }}Spec struct {
+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+ // The following markers will use OpenAPI v3 schema to validate the value
+ // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html
+
+ // foo is an example field of {{ .Resource.Kind }}. Edit {{ lower .Resource.Kind }}_types.go to remove/update
+ // +optional
+ Foo *string ` + "`" + `json:"foo,omitempty"` + "`" + `
+}
+
+// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}.
+type {{ .Resource.Kind }}Status struct {
+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+
+ // For Kubernetes API conventions, see:
+ // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
+
+ // conditions represent the current state of the {{ .Resource.Kind }} resource.
+ // Each condition has a unique type and reflects the status of a specific aspect of the resource.
+ //
+ // Standard condition types include:
+ // - "Available": the resource is fully functional
+ // - "Progressing": the resource is being created or updated
+ // - "Degraded": the resource failed to reach or maintain its desired state
+ //
+ // The status of each condition is one of True, False, or Unknown.
+ // +listType=map
+ // +listMapKey=type
+ // +optional
+ Conditions []metav1.Condition ` + "`" + `json:"conditions,omitempty"` + "`" + `
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+{{- if and (not .Resource.API.Namespaced) (not .Resource.IsRegularPlural) }}
+// +kubebuilder:resource:path={{ .Resource.Plural }},scope=Cluster
+{{- else if not .Resource.API.Namespaced }}
+// +kubebuilder:resource:scope=Cluster
+{{- else if not .Resource.IsRegularPlural }}
+// +kubebuilder:resource:path={{ .Resource.Plural }}
+{{- end }}
+
+// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API
+type {{ .Resource.Kind }} struct {
+ metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + `
+
+ // metadata is a standard object metadata
+ // +optional
+ metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty,omitzero"` + "`" + `
+
+ // spec defines the desired state of {{ .Resource.Kind }}
+ // +required
+ Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec"` + "`" + `
+
+ // status defines the observed state of {{ .Resource.Kind }}
+ // +optional
+ Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty,omitzero"` + "`" + `
+}
+
+// +kubebuilder:object:root=true
+
+// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }}
+type {{ .Resource.Kind }}List struct {
+ metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + `
+ metav1.ListMeta ` + "`" + `json:"metadata,omitempty"` + "`" + `
+ Items []{{ .Resource.Kind }} ` + "`" + `json:"items"` + "`" + `
+}
+
+func init() {
+ SchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{})
+}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go
new file mode 100644
index 00000000000..dedb30b6ae0
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go
@@ -0,0 +1,410 @@
+/*
+Copyright 2022 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 cmd
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+const defaultMainPath = "cmd/main.go"
+
+var _ machinery.Template = &Main{}
+
+// Main scaffolds a file that defines the controller manager entry point
+type Main struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+ machinery.DomainMixin
+ machinery.RepositoryMixin
+
+ ControllerRuntimeVersion string
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Main) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join(defaultMainPath)
+ }
+
+ f.TemplateBody = fmt.Sprintf(mainTemplate,
+ machinery.NewMarkerFor(f.Path, importMarker),
+ machinery.NewMarkerFor(f.Path, addSchemeMarker),
+ machinery.NewMarkerFor(f.Path, setupMarker),
+ )
+
+ return nil
+}
+
+var _ machinery.Inserter = &MainUpdater{}
+
+// MainUpdater updates cmd/main.go to run Controllers
+type MainUpdater struct {
+ machinery.RepositoryMixin
+ machinery.MultiGroupMixin
+ machinery.ResourceMixin
+
+ // Flags to indicate which parts need to be included when updating the file
+ WireResource, WireController, WireWebhook bool
+
+ // Deprecated - The flag should be removed from go/v5
+ // IsLegacyPath indicates if webhooks should be scaffolded under the API.
+ // Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback.
+ // This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path.
+ IsLegacyPath bool
+}
+
+// GetPath implements file.Builder
+func (*MainUpdater) GetPath() string {
+ return defaultMainPath
+}
+
+// GetIfExistsAction implements file.Builder
+func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction {
+ return machinery.OverwriteFile
+}
+
+const (
+ importMarker = "imports"
+ addSchemeMarker = "scheme"
+ setupMarker = "builder"
+)
+
+// GetMarkers implements file.Inserter
+func (f *MainUpdater) GetMarkers() []machinery.Marker {
+ return []machinery.Marker{
+ machinery.NewMarkerFor(defaultMainPath, importMarker),
+ machinery.NewMarkerFor(defaultMainPath, addSchemeMarker),
+ machinery.NewMarkerFor(defaultMainPath, setupMarker),
+ }
+}
+
+const (
+ apiImportCodeFragment = `%s "%s"
+`
+ controllerImportCodeFragment = `"%s/internal/controller"
+`
+ webhookImportCodeFragment = `%s "%s/internal/webhook/%s"
+`
+ multiGroupWebhookImportCodeFragment = `%s "%s/internal/webhook/%s/%s"
+`
+ multiGroupControllerImportCodeFragment = `%scontroller "%s/internal/controller/%s"
+`
+ addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme))
+`
+ reconcilerSetupCodeFragment = `if err := (&controller.%sReconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "%s")
+ os.Exit(1)
+ }
+`
+ multiGroupReconcilerSetupCodeFragment = `if err := (&%scontroller.%sReconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "%s")
+ os.Exit(1)
+ }
+`
+ webhookSetupCodeFragmentLegacy = `// nolint:goconst
+ if os.Getenv("ENABLE_WEBHOOKS") != "false" {
+ if err := (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create webhook", "webhook", "%s")
+ os.Exit(1)
+ }
+ }
+`
+
+ webhookSetupCodeFragment = `// nolint:goconst
+ if os.Getenv("ENABLE_WEBHOOKS") != "false" {
+ if err := %s.Setup%sWebhookWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create webhook", "webhook", "%s")
+ os.Exit(1)
+ }
+ }
+`
+)
+
+// GetCodeFragments implements file.Inserter
+func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap {
+ fragments := make(machinery.CodeFragmentsMap, 3)
+
+ // If resource is not being provided we are creating the file, not updating it
+ if f.Resource == nil {
+ return fragments
+ }
+
+ // Generate import code fragments
+ imports := make([]string, 0)
+ if f.WireResource || f.Resource.IsExternal() {
+ imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
+ }
+ if f.WireWebhook && !f.IsLegacyPath {
+ if !f.MultiGroup || f.Resource.Group == "" {
+ importPath := fmt.Sprintf("webhook%s", f.Resource.Version)
+ imports = append(imports, fmt.Sprintf(webhookImportCodeFragment, importPath, f.Repo, f.Resource.Version))
+ } else {
+ importPath := fmt.Sprintf("webhook%s", f.Resource.ImportAlias())
+ imports = append(imports, fmt.Sprintf(multiGroupWebhookImportCodeFragment, importPath,
+ f.Repo, f.Resource.Group, f.Resource.Version))
+ }
+ }
+
+ if f.WireController {
+ if !f.MultiGroup || f.Resource.Group == "" {
+ imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo))
+ } else {
+ imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment,
+ f.Resource.PackageName(), f.Repo, f.Resource.Group))
+ }
+ }
+
+ // Generate add scheme code fragments
+ addScheme := make([]string, 0)
+ if f.WireResource || f.Resource.IsExternal() {
+ addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))
+ }
+
+ // Generate setup code fragments
+ setup := make([]string, 0)
+ if f.WireController {
+ if !f.MultiGroup || f.Resource.Group == "" {
+ setup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment,
+ f.Resource.Kind, f.Resource.Kind))
+ } else {
+ setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment,
+ f.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind))
+ }
+ }
+ if f.WireWebhook {
+ if f.IsLegacyPath {
+ setup = append(setup, fmt.Sprintf(webhookSetupCodeFragmentLegacy,
+ f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind))
+ } else {
+ if !f.MultiGroup || f.Resource.Group == "" {
+ setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment,
+ "webhook"+f.Resource.Version, f.Resource.Kind, f.Resource.Kind))
+ } else {
+ setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment,
+ "webhook"+f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind))
+ }
+ }
+ }
+
+ // Only store code fragments in the map if the slices are non-empty
+ if len(imports) != 0 {
+ fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports
+ }
+ if len(addScheme) != 0 {
+ fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme
+ }
+ if len(setup) != 0 {
+ fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup
+ }
+
+ return fragments
+}
+
+//nolint:lll
+var mainTemplate = `{{ .Boilerplate }}
+
+package main
+
+import (
+ "crypto/tls"
+ "flag"
+ "os"
+
+ // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
+ // to ensure that exec-entrypoint and run can make use of them.
+ _ "k8s.io/client-go/plugin/pkg/client/auth"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+ "sigs.k8s.io/controller-runtime/pkg/healthz"
+ "sigs.k8s.io/controller-runtime/pkg/metrics/filters"
+ "sigs.k8s.io/controller-runtime/pkg/webhook"
+ metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
+ %s
+)
+
+var (
+ scheme = runtime.NewScheme()
+ setupLog = ctrl.Log.WithName("setup")
+)
+
+func init() {
+ utilruntime.Must(clientgoscheme.AddToScheme(scheme))
+
+ %s
+}
+
+// nolint:gocyclo
+func main() {
+ var metricsAddr string
+ var metricsCertPath, metricsCertName, metricsCertKey string
+ var webhookCertPath, webhookCertName, webhookCertKey string
+ var enableLeaderElection bool
+ var probeAddr string
+ var secureMetrics bool
+ var enableHTTP2 bool
+ var tlsOpts []func(*tls.Config)
+ flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. " +
+ "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
+ flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
+ flag.BoolVar(&enableLeaderElection, "leader-elect", false,
+ "Enable leader election for controller manager. " +
+ "Enabling this will ensure there is only one active controller manager.")
+ flag.BoolVar(&secureMetrics, "metrics-secure", true,
+ "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
+ flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
+ flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
+ flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
+ flag.StringVar(&metricsCertPath, "metrics-cert-path", "",
+ "The directory that contains the metrics server certificate.")
+ flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.")
+ flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.")
+ flag.BoolVar(&enableHTTP2, "enable-http2", false,
+ "If set, HTTP/2 will be enabled for the metrics and webhook servers")
+ opts := zap.Options{
+ Development: true,
+ }
+ opts.BindFlags(flag.CommandLine)
+ flag.Parse()
+
+ ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
+
+ // if the enable-http2 flag is false (the default), http/2 should be disabled
+ // due to its vulnerabilities. More specifically, disabling http/2 will
+ // prevent from being vulnerable to the HTTP/2 Stream Cancellation and
+ // Rapid Reset CVEs. For more information see:
+ // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
+ // - https://github.com/advisories/GHSA-4374-p667-p6c8
+ disableHTTP2 := func(c *tls.Config) {
+ setupLog.Info("disabling http/2")
+ c.NextProtos = []string{"http/1.1"}
+ }
+
+ if !enableHTTP2 {
+ tlsOpts = append(tlsOpts, disableHTTP2)
+ }
+
+ // Initial webhook TLS options
+ webhookTLSOpts := tlsOpts
+ webhookServerOptions := webhook.Options{
+ TLSOpts: webhookTLSOpts,
+ }
+
+ if len(webhookCertPath) > 0 {
+ setupLog.Info("Initializing webhook certificate watcher using provided certificates",
+ "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)
+
+ webhookServerOptions.CertDir = webhookCertPath
+ webhookServerOptions.CertName = webhookCertName
+ webhookServerOptions.KeyName = webhookCertKey
+ }
+
+ webhookServer := webhook.NewServer(webhookServerOptions)
+
+ // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
+ // More info:
+ // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/metrics/server
+ // - https://book.kubebuilder.io/reference/metrics.html
+ metricsServerOptions := metricsserver.Options{
+ BindAddress: metricsAddr,
+ SecureServing: secureMetrics,
+ TLSOpts: tlsOpts,
+ }
+
+ if secureMetrics {
+ // FilterProvider is used to protect the metrics endpoint with authn/authz.
+ // These configurations ensure that only authorized users and service accounts
+ // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
+ // https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/metrics/filters#WithAuthenticationAndAuthorization
+ metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
+ }
+
+ // If the certificate is not specified, controller-runtime will automatically
+ // generate self-signed certificates for the metrics server. While convenient for development and testing,
+ // this setup is not recommended for production.
+ //
+ // TODO(user): If you enable certManager, uncomment the following lines:
+ // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates
+ // managed by cert-manager for the metrics server.
+ // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.
+ if len(metricsCertPath) > 0 {
+ setupLog.Info("Initializing metrics certificate watcher using provided certificates",
+ "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey)
+
+ metricsServerOptions.CertDir = metricsCertPath
+ metricsServerOptions.CertName = metricsCertName
+ metricsServerOptions.KeyName = metricsCertKey
+ }
+
+ mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
+ Scheme: scheme,
+ Metrics: metricsServerOptions,
+ WebhookServer: webhookServer,
+ HealthProbeBindAddress: probeAddr,
+ LeaderElection: enableLeaderElection,
+ {{- if not .Domain }}
+ LeaderElectionID: "{{ hashFNV .Repo }}",
+ {{- else }}
+ LeaderElectionID: "{{ hashFNV .Repo }}.{{ .Domain }}",
+ {{- end }}
+ // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
+ // when the Manager ends. This requires the binary to immediately end when the
+ // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
+ // speeds up voluntary leader transitions as the new leader don't have to wait
+ // LeaseDuration time first.
+ //
+ // In the default scaffold provided, the program ends immediately after
+ // the manager stops, so would be fine to enable this option. However,
+ // if you are doing or is intended to do any operation such as perform cleanups
+ // after the manager stops then its usage might be unsafe.
+ // LeaderElectionReleaseOnCancel: true,
+ })
+ if err != nil {
+ setupLog.Error(err, "unable to start manager")
+ os.Exit(1)
+ }
+
+ %s
+
+ if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
+ setupLog.Error(err, "unable to set up health check")
+ os.Exit(1)
+ }
+ if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
+ setupLog.Error(err, "unable to set up ready check")
+ os.Exit(1)
+ }
+
+ setupLog.Info("starting manager")
+ if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
+ setupLog.Error(err, "problem running manager")
+ os.Exit(1)
+ }
+}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go
new file mode 100644
index 00000000000..640f64efb4c
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go
@@ -0,0 +1,125 @@
+/*
+Copyright 2022 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 controllers
+
+import (
+ log "log/slog"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Controller{}
+
+// Controller scaffolds the file that defines the controller for a CRD or a builtin resource
+//
+
+type Controller struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+
+ ControllerRuntimeVersion string
+
+ Force bool
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Controller) SetTemplateDefaults() error {
+ if f.Path == "" {
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join("internal", "controller", "%[group]", "%[kind]_controller.go")
+ } else {
+ f.Path = filepath.Join("internal", "controller", "%[kind]_controller.go")
+ }
+ }
+
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+
+ f.TemplateBody = controllerTemplate
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ } else {
+ f.IfExistsAction = machinery.Error
+ }
+
+ return nil
+}
+
+//nolint:lll
+const controllerTemplate = `{{ .Boilerplate }}
+
+package {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}controller{{ end }}
+
+import (
+ "context"
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ {{ if not (isEmptyStr .Resource.Path) -}}
+ {{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
+ {{- end }}
+)
+
+// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object
+type {{ .Resource.Kind }}Reconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// TODO(user): Modify the Reconcile function to compare the state specified by
+// the {{ .Resource.Kind }} object against the actual cluster state, and then
+// perform operations to make the cluster state reflect the state specified by
+// the user.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/reconcile
+func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ _ = logf.FromContext(ctx)
+
+ // TODO(user): your logic here
+
+ return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ {{ if not (isEmptyStr .Resource.Path) -}}
+ For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}).
+ {{- else -}}
+ // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument
+ // For().
+ {{- end }}
+ {{- if and (.MultiGroup) (not (isEmptyStr .Resource.Group)) }}
+ Named("{{ lower .Resource.Group }}-{{ lower .Resource.Kind }}").
+ {{- else }}
+ Named("{{ lower .Resource.Kind }}").
+ {{- end }}
+ Complete(r)
+}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go
new file mode 100644
index 00000000000..8ef84af7ff9
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go
@@ -0,0 +1,229 @@
+/*
+Copyright 2022 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 controllers
+
+import (
+ "fmt"
+ log "log/slog"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var (
+ _ machinery.Template = &SuiteTest{}
+ _ machinery.Inserter = &SuiteTest{}
+)
+
+// SuiteTest scaffolds the file that sets up the controller tests
+//
+
+type SuiteTest struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+
+ // CRDDirectoryRelativePath define the Path for the CRD
+ CRDDirectoryRelativePath string
+
+ Force bool
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *SuiteTest) SetTemplateDefaults() error {
+ if f.Path == "" {
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join("internal", "controller", "%[group]", "suite_test.go")
+ } else {
+ f.Path = filepath.Join("internal", "controller", "suite_test.go")
+ }
+ }
+
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+
+ f.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate,
+ machinery.NewMarkerFor(f.Path, importMarker),
+ machinery.NewMarkerFor(f.Path, addSchemeMarker),
+ )
+
+ // If is multigroup the path needs to be ../../ since it has
+ // the group dir.
+ f.CRDDirectoryRelativePath = `"..",".."`
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.CRDDirectoryRelativePath = `"..", "..",".."`
+ }
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ }
+
+ return nil
+}
+
+const (
+ importMarker = "imports"
+ addSchemeMarker = "scheme"
+)
+
+// GetMarkers implements file.Inserter
+func (f *SuiteTest) GetMarkers() []machinery.Marker {
+ return []machinery.Marker{
+ machinery.NewMarkerFor(f.Path, importMarker),
+ machinery.NewMarkerFor(f.Path, addSchemeMarker),
+ }
+}
+
+const (
+ apiImportCodeFragment = `%s "%s"
+`
+ addschemeCodeFragment = `err = %s.AddToScheme(scheme.Scheme)
+Expect(err).NotTo(HaveOccurred())
+
+`
+)
+
+// GetCodeFragments implements file.Inserter
+func (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap {
+ fragments := make(machinery.CodeFragmentsMap, 2)
+
+ // Generate import code fragments
+ imports := make([]string, 0)
+ if f.Resource.Path != "" {
+ imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
+ }
+
+ // Generate add scheme code fragments
+ addScheme := make([]string, 0)
+ if f.Resource.Path != "" {
+ addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))
+ }
+
+ // Only store code fragments in the map if the slices are non-empty
+ if len(imports) != 0 {
+ fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports
+ }
+ if len(addScheme) != 0 {
+ fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme
+ }
+
+ return fragments
+}
+
+const controllerSuiteTestTemplate = `{{ .Boilerplate }}
+
+{{if and .MultiGroup .Resource.Group }}
+package {{ .Resource.PackageName }}
+{{else}}
+package controller
+{{end}}
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ "k8s.io/client-go/kubernetes/scheme"
+ "k8s.io/client-go/rest"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+ %s
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var (
+ ctx context.Context
+ cancel context.CancelFunc
+ testEnv *envtest.Environment
+ cfg *rest.Config
+ k8sClient client.Client
+)
+
+func TestControllers(t *testing.T) {
+ RegisterFailHandler(Fail)
+
+ RunSpecs(t, "Controller Suite")
+}
+
+var _ = BeforeSuite(func() {
+ logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
+
+ ctx, cancel = context.WithCancel(context.TODO())
+
+ var err error
+ %s
+
+ By("bootstrapping test environment")
+ testEnv = &envtest.Environment{
+ CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")},
+ ErrorIfCRDPathMissing: {{ .Resource.HasAPI }},
+ }
+
+ // Retrieve the first found binary directory to allow running tests from IDEs
+ if getFirstFoundEnvTestBinaryDir() != "" {
+ testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()
+ }
+
+ // cfg is defined in this file globally.
+ cfg, err = testEnv.Start()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(cfg).NotTo(BeNil())
+
+ k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
+ Expect(err).NotTo(HaveOccurred())
+ Expect(k8sClient).NotTo(BeNil())
+})
+
+var _ = AfterSuite(func() {
+ By("tearing down the test environment")
+ cancel()
+ err := testEnv.Stop()
+ Expect(err).NotTo(HaveOccurred())
+})
+
+// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.
+// ENVTEST-based tests depend on specific binaries, usually located in paths set by
+// controller-runtime. When running tests directly (e.g., via an IDE) without using
+// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.
+//
+// This function streamlines the process by finding the required binaries, similar to
+// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are
+// properly set up, run 'make setup-envtest' beforehand.
+func getFirstFoundEnvTestBinaryDir() string {
+ basePath := filepath.Join({{ .CRDDirectoryRelativePath }}, "bin", "k8s")
+ entries, err := os.ReadDir(basePath)
+ if err != nil {
+ logf.Log.Error(err, "Failed to read directory", "path", basePath)
+ return ""
+ }
+ for _, entry := range entries {
+ if entry.IsDir() {
+ return filepath.Join(basePath, entry.Name())
+ }
+ }
+ return ""
+}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_test_template.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_test_template.go
new file mode 100644
index 00000000000..1df4f15dc6b
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_test_template.go
@@ -0,0 +1,147 @@
+/*
+Copyright 2022 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 controllers
+
+import (
+ log "log/slog"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &ControllerTest{}
+
+// ControllerTest scaffolds the file that sets up the controller unit tests
+//
+
+type ControllerTest struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+
+ Force bool
+
+ DoAPI bool
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *ControllerTest) SetTemplateDefaults() error {
+ if f.Path == "" {
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join("internal", "controller", "%[group]", "%[kind]_controller_test.go")
+ } else {
+ f.Path = filepath.Join("internal", "controller", "%[kind]_controller_test.go")
+ }
+ }
+
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+
+ f.TemplateBody = controllerTestTemplate
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ }
+
+ return nil
+}
+
+const controllerTestTemplate = `{{ .Boilerplate }}
+
+{{if and .MultiGroup .Resource.Group }}
+package {{ .Resource.PackageName }}
+{{else}}
+package controller
+{{end}}
+
+import (
+ {{ if .DoAPI -}}
+ "context"
+ {{- end }}
+ . "github.com/onsi/ginkgo/v2"
+ {{ if .DoAPI -}}
+
+ . "github.com/onsi/gomega"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/types"
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ {{ if not (isEmptyStr .Resource.Path) -}}
+ {{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
+ {{- end }}
+ {{- end }}
+)
+
+var _ = Describe("{{ .Resource.Kind }} Controller", func() {
+ Context("When reconciling a resource", func() {
+ {{ if .DoAPI -}}
+ const resourceName = "test-resource"
+
+ ctx := context.Background()
+
+ typeNamespacedName := types.NamespacedName{
+ Name: resourceName,
+ Namespace: "default", // TODO(user):Modify as needed
+ }
+ {{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
+
+ BeforeEach(func() {
+ By("creating the custom resource for the Kind {{ .Resource.Kind }}")
+ err := k8sClient.Get(ctx, typeNamespacedName, {{ lower .Resource.Kind }})
+ if err != nil && errors.IsNotFound(err) {
+ resource := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: resourceName,
+ Namespace: "default",
+ },
+ // TODO(user): Specify other spec details if needed.
+ }
+ Expect(k8sClient.Create(ctx, resource)).To(Succeed())
+ }
+ })
+
+ AfterEach(func() {
+ // TODO(user): Cleanup logic after each test, like removing the resource instance.
+ resource := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
+ err := k8sClient.Get(ctx, typeNamespacedName, resource)
+ Expect(err).NotTo(HaveOccurred())
+
+ By("Cleanup the specific resource instance {{ .Resource.Kind }}")
+ Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
+ })
+ {{- end }}
+ It("should successfully reconcile the resource", func() {
+ {{ if .DoAPI -}}
+ By("Reconciling the created resource")
+ controllerReconciler := &{{ .Resource.Kind }}Reconciler{
+ Client: k8sClient,
+ Scheme: k8sClient.Scheme(),
+ }
+
+ _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
+ NamespacedName: typeNamespacedName,
+ })
+ Expect(err).NotTo(HaveOccurred())
+ {{- end }}
+ // TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
+ // Example: If you expect a certain status condition after reconciliation, verify it here.
+ })
+ })
+})
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go
new file mode 100644
index 00000000000..2a22df963a0
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go
@@ -0,0 +1,110 @@
+/*
+Copyright 2024 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 templates
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+const devContainerTemplate = `{
+ "name": "Kubebuilder DevContainer",
+ "image": "golang:1.25",
+ "features": {
+ "ghcr.io/devcontainers/features/docker-in-docker:2": {},
+ "ghcr.io/devcontainers/features/git:1": {}
+ },
+
+ "runArgs": ["--network=host"],
+
+ "customizations": {
+ "vscode": {
+ "settings": {
+ "terminal.integrated.shell.linux": "/bin/bash"
+ },
+ "extensions": [
+ "ms-kubernetes-tools.vscode-kubernetes-tools",
+ "ms-azuretools.vscode-docker"
+ ]
+ }
+ },
+
+ "onCreateCommand": "bash .devcontainer/post-install.sh"
+}
+
+`
+
+const postInstallScript = `#!/bin/bash
+set -x
+
+curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)
+chmod +x ./kind
+mv ./kind /usr/local/bin/kind
+
+curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/linux/$(go env GOARCH)
+chmod +x kubebuilder
+mv kubebuilder /usr/local/bin/
+
+KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt)
+curl -LO "https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/$(go env GOARCH)/kubectl"
+chmod +x kubectl
+mv kubectl /usr/local/bin/kubectl
+
+docker network create -d=bridge --subnet=172.19.0.0/24 kind
+
+kind version
+kubebuilder version
+docker --version
+go version
+kubectl version --client
+`
+
+var (
+ _ machinery.Template = &DevContainer{}
+ _ machinery.Template = &DevContainerPostInstallScript{}
+)
+
+// DevContainer scaffoldds a `devcontainer.json` configurations file for creating Kubebuilder & Kind based DevContainer.
+type DevContainer struct {
+ machinery.TemplateMixin
+}
+
+// DevContainerPostInstallScript defines the scaffold that will be done with the post install script
+type DevContainerPostInstallScript struct {
+ machinery.TemplateMixin
+}
+
+// SetTemplateDefaults set defaults for this template
+func (f *DevContainer) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = ".devcontainer/devcontainer.json"
+ }
+
+ f.TemplateBody = devContainerTemplate
+
+ return nil
+}
+
+// SetTemplateDefaults set the defaults of this template
+func (f *DevContainerPostInstallScript) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = ".devcontainer/post-install.sh"
+ }
+
+ f.TemplateBody = postInstallScript
+
+ return nil
+}
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go
new file mode 100644
index 00000000000..0c8f64c0978
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2022 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 templates
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Dockerfile{}
+
+// Dockerfile scaffolds a file that defines the containerized build process
+type Dockerfile struct {
+ machinery.TemplateMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Dockerfile) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = "Dockerfile"
+ }
+
+ f.TemplateBody = dockerfileTemplate
+
+ return nil
+}
+
+const dockerfileTemplate = `# Build the manager binary
+FROM golang:1.25 AS builder
+ARG TARGETOS
+ARG TARGETARCH
+
+WORKDIR /workspace
+# Copy the Go Modules manifests
+COPY go.mod go.mod
+COPY go.sum go.sum
+# cache deps before building and copying source so that we don't need to re-download as much
+# and so that source changes don't invalidate our downloaded layer
+RUN go mod download
+
+# Copy the Go source (relies on .dockerignore to filter)
+COPY . .
+
+# Build
+# the GOARCH has no default value to allow the binary to be built according to the host where the command
+# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
+# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
+# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
+RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
+
+# Use distroless as minimal base image to package the manager binary
+# Refer to https://github.com/GoogleContainerTools/distroless for more details
+FROM gcr.io/distroless/static:nonroot
+WORKDIR /
+COPY --from=builder /workspace/manager .
+USER 65532:65532
+
+ENTRYPOINT ["/manager"]
+`
diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go
similarity index 76%
rename from pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go
rename to pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go
index 3826dc2b8a0..d95cc54e046 100644
--- a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go
@@ -1,5 +1,5 @@
/*
-Copyright 2020 The Kubernetes Authors.
+Copyright 2022 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.
@@ -17,7 +17,7 @@ limitations under the License.
package templates
import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
)
var _ machinery.Template = &DockerIgnore{}
@@ -27,7 +27,7 @@ type DockerIgnore struct {
machinery.TemplateMixin
}
-// SetTemplateDefaults implements file.Template
+// SetTemplateDefaults implements machinery.Template
func (f *DockerIgnore) SetTemplateDefaults() error {
if f.Path == "" {
f.Path = ".dockerignore"
@@ -39,7 +39,14 @@ func (f *DockerIgnore) SetTemplateDefaults() error {
}
const dockerignorefileTemplate = `# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
-# Ignore build and test binaries.
-bin/
-testbin/
+# Ignore everything by default and re-include only needed files
+**
+
+# Re-include Go source files (but not *_test.go)
+!**/*.go
+**/*_test.go
+
+# Re-include Go module files
+!go.mod
+!go.sum
`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/github/lint.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/lint.go
new file mode 100644
index 00000000000..730eba48ab4
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/lint.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2024 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 github
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &TestCi{}
+
+// LintCi scaffolds the GitHub Action to lint the project
+type LintCi struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+
+ // golangci-lint version to use in the project
+ GolangciLintVersion string
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *LintCi) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join(".github", "workflows", "lint.yml")
+ }
+
+ f.TemplateBody = lintCiTemplate
+
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+const lintCiTemplate = `name: Lint
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ lint:
+ name: Run on Ubuntu
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone the code
+ uses: actions/checkout@v4
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: go.mod
+
+ - name: Run linter
+ uses: golangci/golangci-lint-action@v8
+ with:
+ version: {{ .GolangciLintVersion }}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test-e2e.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test-e2e.go
new file mode 100644
index 00000000000..6404c0b6a04
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test-e2e.go
@@ -0,0 +1,79 @@
+/*
+Copyright 2024 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 github
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &E2eTestCi{}
+
+// E2eTestCi scaffolds the GitHub Action to call make test-e2e
+type E2eTestCi struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *E2eTestCi) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join(".github", "workflows", "test-e2e.yml")
+ }
+
+ f.TemplateBody = e2eTestCiTemplate
+
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+const e2eTestCiTemplate = `name: E2E Tests
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ test-e2e:
+ name: Run on Ubuntu
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone the code
+ uses: actions/checkout@v4
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: go.mod
+
+ - name: Install the latest version of kind
+ run: |
+ curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)
+ chmod +x ./kind
+ sudo mv ./kind /usr/local/bin/kind
+
+ - name: Verify kind installation
+ run: kind version
+
+ - name: Running Test e2e
+ run: |
+ go mod tidy
+ make test-e2e
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test.go
new file mode 100644
index 00000000000..8701bd8ffd9
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test.go
@@ -0,0 +1,69 @@
+/*
+Copyright 2024 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 github
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &TestCi{}
+
+// TestCi scaffolds the GitHub Action to call make test
+type TestCi struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *TestCi) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join(".github", "workflows", "test.yml")
+ }
+
+ f.TemplateBody = testCiTemplate
+
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+const testCiTemplate = `name: Tests
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ test:
+ name: Run on Ubuntu
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone the code
+ uses: actions/checkout@v4
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: go.mod
+
+ - name: Running Tests
+ run: |
+ go mod tidy
+ make test
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go
new file mode 100644
index 00000000000..029f8affa9f
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go
@@ -0,0 +1,71 @@
+/*
+Copyright 2022 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 templates
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &GitIgnore{}
+
+// GitIgnore scaffolds a file that defines which files should be ignored by git
+type GitIgnore struct {
+ machinery.TemplateMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *GitIgnore) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = ".gitignore"
+ }
+
+ f.TemplateBody = gitignoreTemplate
+
+ return nil
+}
+
+const gitignoreTemplate = `# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+bin/*
+Dockerfile.cross
+
+# Test binary, built with ` + "`go test -c`" + `
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Go workspace file
+go.work
+
+# Kubernetes Generated files - skip generated files, except for vendored files
+!vendor/**/zz_generated.*
+
+# editor and IDE paraphernalia
+.idea
+.vscode
+*.swp
+*.swo
+*~
+
+# Kubeconfig might contain secrets
+*.kubeconfig
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/golangci.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/golangci.go
new file mode 100644
index 00000000000..d2a1d1fc012
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/golangci.go
@@ -0,0 +1,96 @@
+/*
+Copyright 2022 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 templates
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Golangci{}
+
+// Golangci scaffolds a file which define Golangci rules
+type Golangci struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Golangci) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = ".golangci.yml"
+ }
+
+ f.TemplateBody = golangciTemplate
+
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+const golangciTemplate = `version: "2"
+run:
+ allow-parallel-runners: true
+linters:
+ default: none
+ enable:
+ - copyloopvar
+ - dupl
+ - errcheck
+ - ginkgolinter
+ - goconst
+ - gocyclo
+ - govet
+ - ineffassign
+ - lll
+ - misspell
+ - nakedret
+ - prealloc
+ - revive
+ - staticcheck
+ - unconvert
+ - unparam
+ - unused
+ settings:
+ revive:
+ rules:
+ - name: comment-spacings
+ - name: import-shadowing
+ exclusions:
+ generated: lax
+ rules:
+ - linters:
+ - lll
+ path: api/*
+ - linters:
+ - dupl
+ - lll
+ path: internal/*
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
+formatters:
+ enable:
+ - gofmt
+ - goimports
+ exclusions:
+ generated: lax
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go
new file mode 100644
index 00000000000..2485580f3b3
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2022 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 templates
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &GoMod{}
+
+// GoMod scaffolds a file that defines the project dependencies
+type GoMod struct {
+ machinery.TemplateMixin
+ machinery.RepositoryMixin
+
+ ControllerRuntimeVersion string
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *GoMod) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = "go.mod"
+ }
+
+ f.TemplateBody = goModTemplate
+
+ f.IfExistsAction = machinery.OverwriteFile
+
+ return nil
+}
+
+const goModTemplate = `module {{ .Repo }}
+
+go 1.24.5
+
+require (
+ sigs.k8s.io/controller-runtime {{ .ControllerRuntimeVersion }}
+)
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go
new file mode 100644
index 00000000000..b200fca0f90
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go
@@ -0,0 +1,122 @@
+/*
+Copyright 2022 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 hack
+
+import (
+ "fmt"
+ "path/filepath"
+ "time"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+// DefaultBoilerplatePath is the default path to the boilerplate file
+var DefaultBoilerplatePath = filepath.Join("hack", "boilerplate.go.txt")
+
+var _ machinery.Template = &Boilerplate{}
+
+// Boilerplate scaffolds a file that defines the common header for the rest of the files
+type Boilerplate struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+
+ // License is the License type to write
+ License string
+
+ // Licenses maps License types to their actual string
+ Licenses map[string]string
+
+ // Owner is the copyright owner - e.g. "The Kubernetes Authors"
+ Owner string
+
+ // Year is the copyright year
+ Year string
+}
+
+// Validate implements file.RequiresValidation
+func (f *Boilerplate) Validate() error {
+ if f.License != "" {
+ if _, foundKnown := knownLicenses[f.License]; !foundKnown {
+ if _, found := f.Licenses[f.License]; !found {
+ return fmt.Errorf("unknown specified license %s", f.License)
+ }
+ }
+ }
+ return nil
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Boilerplate) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = DefaultBoilerplatePath
+ }
+
+ if f.License == "" {
+ f.License = "apache2"
+ }
+
+ if f.Licenses == nil {
+ f.Licenses = make(map[string]string, len(knownLicenses))
+ }
+
+ for key, value := range knownLicenses {
+ if _, hasLicense := f.Licenses[key]; !hasLicense {
+ f.Licenses[key] = value
+ }
+ }
+
+ if f.Year == "" {
+ f.Year = fmt.Sprintf("%v", time.Now().Year())
+ }
+
+ // Boilerplate given
+ if len(f.Boilerplate) > 0 {
+ f.TemplateBody = f.Boilerplate
+ return nil
+ }
+
+ f.TemplateBody = boilerplateTemplate
+
+ return nil
+}
+
+const boilerplateTemplate = `/*
+{{ if .Owner -}}
+Copyright {{ .Year }} {{ .Owner }}.
+{{- else -}}
+Copyright {{ .Year }}.
+{{- end }}
+{{ index .Licenses .License }}*/`
+
+var knownLicenses = map[string]string{
+ "apache2": apache2,
+ "copyright": "",
+}
+
+const apache2 = `
+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.
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go
new file mode 100644
index 00000000000..4ee5fbc7ff7
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go
@@ -0,0 +1,330 @@
+/*
+Copyright 2022 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 templates
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Makefile{}
+
+// Makefile scaffolds a file that defines project management CLI commands
+type Makefile struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+
+ // Image is controller manager image name
+ Image string
+ // BoilerplatePath is the path to the boilerplate file
+ BoilerplatePath string
+ // Controller tools version to use in the project
+ ControllerToolsVersion string
+ // Kustomize version to use in the project
+ KustomizeVersion string
+ // golangci-lint version to use in the project
+ GolangciLintVersion string
+ // ControllerRuntimeVersion version to be used to download the envtest setup script
+ ControllerRuntimeVersion string
+ // EnvtestVersion store the name of the verions to be used to install setup-envtest
+ EnvtestVersion string
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Makefile) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = "Makefile"
+ }
+
+ f.TemplateBody = makefileTemplate
+
+ f.IfExistsAction = machinery.Error
+
+ if f.Image == "" {
+ f.Image = "controller:latest"
+ }
+
+ // TODO: Current workaround for setup-envtest compatibility
+ // Due to past instances where controller-runtime maintainers released
+ // versions without corresponding branches, directly relying on branches
+ // poses a risk of breaking the Kubebuilder chain. Such practices may
+ // change over time, potentially leading to compatibility issues. This
+ // approach, although not ideal, remains the best solution for ensuring
+ // compatibility with controller-runtime releases as of now. For more
+ // details on the quest for a more robust solution, refer to the issue
+ // raised in the controller-runtime repository: https://github.com/kubernetes-sigs/controller-runtime/issues/2744
+ if f.EnvtestVersion == "" {
+ f.EnvtestVersion = "latest"
+ }
+ return nil
+}
+
+//nolint:lll
+const makefileTemplate = `# Image URL to use all building/pushing image targets
+IMG ?= {{ .Image }}
+
+# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
+ifeq (,$(shell go env GOBIN))
+GOBIN=$(shell go env GOPATH)/bin
+else
+GOBIN=$(shell go env GOBIN)
+endif
+
+# CONTAINER_TOOL defines the container tool to be used for building images.
+# Be aware that the target commands are only tested with Docker which is
+# scaffolded by default. However, you might want to replace it to use other
+# tools. (i.e. podman)
+CONTAINER_TOOL ?= docker
+
+# Setting SHELL to bash allows bash commands to be executed by recipes.
+# Options are set to exit when a recipe line exits non-zero or a piped command fails.
+SHELL = /usr/bin/env bash -o pipefail
+.SHELLFLAGS = -ec
+
+.PHONY: all
+all: build
+
+##@ General
+
+# The help target prints out all targets with their descriptions organized
+# beneath their categories. The categories are represented by '##@' and the
+# target descriptions by '##'. The awk command is responsible for reading the
+# entire set of makefiles included in this invocation, looking for lines of the
+# file as xyz: ## something, and then pretty-format the target and help. Then,
+# if there's a line with ##@ something, that gets pretty-printed as a category.
+# More info on the usage of ANSI control characters for terminal formatting:
+# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
+# More info on the awk command:
+# http://linuxcommand.org/lc3_adv_awk.php
+
+.PHONY: help
+help: ## Display this help.
+ @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
+
+##@ Development
+
+.PHONY: manifests
+manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
+ "$(CONTROLLER_GEN)" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
+
+.PHONY: generate
+generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
+ {{ if .BoilerplatePath -}}
+ "$(CONTROLLER_GEN)" object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..."
+ {{- else -}}
+ "$(CONTROLLER_GEN)" object paths="./..."
+ {{- end }}
+
+.PHONY: fmt
+fmt: ## Run go fmt against code.
+ go fmt ./...
+
+.PHONY: vet
+vet: ## Run go vet against code.
+ go vet ./...
+
+.PHONY: test
+test: manifests generate fmt vet setup-envtest ## Run tests.
+ KUBEBUILDER_ASSETS="$(shell "$(ENVTEST)" use $(ENVTEST_K8S_VERSION) --bin-dir "$(LOCALBIN)" -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
+
+# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
+# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.
+# CertManager is installed by default; skip with:
+# - CERT_MANAGER_INSTALL_SKIP=true
+KIND_CLUSTER ?= {{ .ProjectName }}-test-e2e
+
+.PHONY: setup-test-e2e
+setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
+ @command -v $(KIND) >/dev/null 2>&1 || { \
+ echo "Kind is not installed. Please install Kind manually."; \
+ exit 1; \
+ }
+ @case "$$($(KIND) get clusters)" in \
+ *"$(KIND_CLUSTER)"*) \
+ echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \
+ *) \
+ echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \
+ $(KIND) create cluster --name $(KIND_CLUSTER) ;; \
+ esac
+
+.PHONY: test-e2e
+test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.
+ KIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -v -ginkgo.v
+ $(MAKE) cleanup-test-e2e
+
+.PHONY: cleanup-test-e2e
+cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests
+ @$(KIND) delete cluster --name $(KIND_CLUSTER)
+
+.PHONY: lint
+lint: golangci-lint ## Run golangci-lint linter
+ "$(GOLANGCI_LINT)" run
+
+.PHONY: lint-fix
+lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
+ "$(GOLANGCI_LINT)" run --fix
+
+.PHONY: lint-config
+lint-config: golangci-lint ## Verify golangci-lint linter configuration
+ "$(GOLANGCI_LINT)" config verify
+
+##@ Build
+
+.PHONY: build
+build: manifests generate fmt vet ## Build manager binary.
+ go build -o bin/manager cmd/main.go
+
+.PHONY: run
+run: manifests generate fmt vet ## Run a controller from your host.
+ go run ./cmd/main.go
+
+# If you wish to build the manager image targeting other platforms you can use the --platform flag.
+# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
+# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
+.PHONY: docker-build
+docker-build: ## Build docker image with the manager.
+ $(CONTAINER_TOOL) build -t ${IMG} .
+
+.PHONY: docker-push
+docker-push: ## Push docker image with the manager.
+ $(CONTAINER_TOOL) push ${IMG}
+
+# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
+# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
+# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
+# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
+# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail)
+# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
+PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
+.PHONY: docker-buildx
+docker-buildx: ## Build and push docker image for the manager for cross-platform support
+ # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
+ sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
+ - $(CONTAINER_TOOL) buildx create --name {{ .ProjectName }}-builder
+ $(CONTAINER_TOOL) buildx use {{ .ProjectName }}-builder
+ - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
+ - $(CONTAINER_TOOL) buildx rm {{ .ProjectName }}-builder
+ rm Dockerfile.cross
+
+.PHONY: build-installer
+build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.
+ mkdir -p dist
+ cd config/manager && "$(KUSTOMIZE)" edit set image controller=${IMG}
+ "$(KUSTOMIZE)" build config/default > dist/install.yaml
+
+##@ Deployment
+
+ifndef ignore-not-found
+ ignore-not-found = false
+endif
+
+.PHONY: install
+install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
+ @out="$$( "$(KUSTOMIZE)" build config/crd 2>/dev/null || true )"; \
+ if [ -n "$$out" ]; then echo "$$out" | "$(KUBECTL)" apply -f -; else echo "No CRDs to install; skipping."; fi
+
+.PHONY: uninstall
+uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
+ @out="$$( "$(KUSTOMIZE)" build config/crd 2>/dev/null || true )"; \
+ if [ -n "$$out" ]; then echo "$$out" | "$(KUBECTL)" delete --ignore-not-found=$(ignore-not-found) -f -; else echo "No CRDs to delete; skipping."; fi
+
+.PHONY: deploy
+deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
+ cd config/manager && "$(KUSTOMIZE)" edit set image controller=${IMG}
+ "$(KUSTOMIZE)" build config/default | "$(KUBECTL)" apply -f -
+
+.PHONY: undeploy
+undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
+ "$(KUSTOMIZE)" build config/default | "$(KUBECTL)" delete --ignore-not-found=$(ignore-not-found) -f -
+
+##@ Dependencies
+
+## Location to install dependencies to
+LOCALBIN ?= $(shell pwd)/bin
+$(LOCALBIN):
+ mkdir -p "$(LOCALBIN)"
+
+## Tool Binaries
+KUBECTL ?= kubectl
+KIND ?= kind
+KUSTOMIZE ?= $(LOCALBIN)/kustomize
+CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
+ENVTEST ?= $(LOCALBIN)/setup-envtest
+GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
+
+## Tool Versions
+KUSTOMIZE_VERSION ?= {{ .KustomizeVersion }}
+CONTROLLER_TOOLS_VERSION ?= {{ .ControllerToolsVersion }}
+
+#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)
+ENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \
+ [ -n "$$v" ] || { echo "Set ENVTEST_VERSION manually (controller-runtime replace has no tag)" >&2; exit 1; }; \
+ printf '%s\n' "$$v" | sed -E 's/^v?([0-9]+)\.([0-9]+).*/release-\1.\2/')
+
+#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
+ENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \
+ [ -n "$$v" ] || { echo "Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)" >&2; exit 1; }; \
+ printf '%s\n' "$$v" | sed -E 's/^v?[0-9]+\.([0-9]+).*/1.\1/')
+
+GOLANGCI_LINT_VERSION ?= {{ .GolangciLintVersion }}
+.PHONY: kustomize
+kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
+$(KUSTOMIZE): $(LOCALBIN)
+ $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))
+
+.PHONY: controller-gen
+controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
+$(CONTROLLER_GEN): $(LOCALBIN)
+ $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
+
+.PHONY: setup-envtest
+setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.
+ @echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..."
+ @"$(ENVTEST)" use $(ENVTEST_K8S_VERSION) --bin-dir "$(LOCALBIN)" -p path || { \
+ echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \
+ exit 1; \
+ }
+
+.PHONY: envtest
+envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
+$(ENVTEST): $(LOCALBIN)
+ $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
+
+.PHONY: golangci-lint
+golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
+$(GOLANGCI_LINT): $(LOCALBIN)
+ $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
+
+# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
+# $1 - target path with name of binary
+# $2 - package url which can be installed
+# $3 - specific version of package
+define go-install-tool
+@[ -f "$(1)-$(3)" ] && [ "$$(readlink -- "$(1)" 2>/dev/null)" = "$(1)-$(3)" ] || { \
+set -e; \
+package=$(2)@$(3) ;\
+echo "Downloading $${package}" ;\
+rm -f "$(1)" ;\
+GOBIN="$(LOCALBIN)" go install $${package} ;\
+mv "$(LOCALBIN)/$$(basename "$(1)")" "$(1)-$(3)" ;\
+} ;\
+ln -sf "$$(realpath "$(1)-$(3)")" "$(1)"
+endef
+
+define gomodver
+$(shell go list -m -f '{{"{{"}}if .Replace{{"}}"}}{{"{{"}}.Replace.Version{{"}}"}}{{"{{"}}else{{"}}"}}{{"{{"}}.Version{{"}}"}}{{"{{"}}end{{"}}"}}' $(1) 2>/dev/null)
+endef
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go
new file mode 100644
index 00000000000..5511c9be69a
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go
@@ -0,0 +1,172 @@
+/*
+Copyright 2022 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 templates
+
+import (
+ "fmt"
+ "strings"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Readme{}
+
+// Readme scaffolds a README.md file
+type Readme struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+ machinery.ProjectNameMixin
+
+ License string
+
+ // CommandName stores the name of the bin used
+ CommandName string
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Readme) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = "README.md"
+ }
+
+ f.License = strings.Replace(
+ strings.Replace(f.Boilerplate, "/*", "", 1),
+ "*/", "", 1)
+
+ f.TemplateBody = fmt.Sprintf(readmeFileTemplate,
+ codeFence("make docker-build docker-push IMG=/{{ .ProjectName }}:tag"),
+ codeFence("make install"),
+ codeFence("make deploy IMG=/{{ .ProjectName }}:tag"),
+ codeFence("kubectl apply -k config/samples/"),
+ codeFence("kubectl delete -k config/samples/"),
+ codeFence("make uninstall"),
+ codeFence("make undeploy"),
+ codeFence("make build-installer IMG=/{{ .ProjectName }}:tag"),
+ codeFence("kubectl apply -f https://raw.githubusercontent.com//{{ .ProjectName }}/"+
+ "/dist/install.yaml"),
+ codeFence(fmt.Sprintf("%s edit --plugins=helm/v2-alpha", f.CommandName)),
+ )
+
+ return nil
+}
+
+const readmeFileTemplate = `# {{ .ProjectName }}
+// TODO(user): Add simple overview of use/purpose
+
+## Description
+// TODO(user): An in-depth paragraph about your project and overview of use
+
+## Getting Started
+
+### Prerequisites
+- go version v1.24.0+
+- docker version 17.03+.
+- kubectl version v1.11.3+.
+- Access to a Kubernetes v1.11.3+ cluster.
+
+### To Deploy on the cluster
+**Build and push your image to the location specified by ` + "`IMG`" + `:**
+
+%s
+
+**NOTE:** This image ought to be published in the personal registry you specified.
+And it is required to have access to pull the image from the working environment.
+Make sure you have the proper permission to the registry if the above commands don’t work.
+
+**Install the CRDs into the cluster:**
+
+%s
+
+**Deploy the Manager to the cluster with the image specified by ` + "`IMG`" + `:**
+
+%s
+
+> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin
+privileges or be logged in as admin.
+
+**Create instances of your solution**
+You can apply the samples (examples) from the config/sample:
+
+%s
+
+>**NOTE**: Ensure that the samples has default values to test it out.
+
+### To Uninstall
+**Delete the instances (CRs) from the cluster:**
+
+%s
+
+**Delete the APIs(CRDs) from the cluster:**
+
+%s
+
+**UnDeploy the controller from the cluster:**
+
+%s
+
+## Project Distribution
+
+Following the options to release and provide this solution to the users.
+
+### By providing a bundle with all YAML files
+
+1. Build the installer for the image built and published in the registry:
+
+%s
+
+**NOTE:** The makefile target mentioned above generates an 'install.yaml'
+file in the dist directory. This file contains all the resources built
+with Kustomize, which are necessary to install this project without its
+dependencies.
+
+2. Using the installer
+
+Users can just run 'kubectl apply -f ' to install
+the project, i.e.:
+
+%s
+
+### By providing a Helm Chart
+
+1. Build the chart using the optional helm plugin
+
+%s
+
+2. See that a chart was generated under 'dist/chart', and users
+can obtain this solution from there.
+
+**NOTE:** If you change the project, you need to update the Helm Chart
+using the same command above to sync the latest changes. Furthermore,
+if you create webhooks, you need to use the above command with
+the '--force' flag and manually ensure that any custom configuration
+previously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'
+is manually re-applied afterwards.
+
+## Contributing
+// TODO(user): Add detailed information on how you would like others to contribute to this project
+
+**NOTE:** Run ` + "`make help`" + ` for more information on all potential ` + "`make`" + ` targets
+
+More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
+
+## License
+{{ .License }}
+`
+
+func codeFence(code string) string {
+ return "```sh" + "\n" + code + "\n" + "```"
+}
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/suite.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/suite.go
new file mode 100644
index 00000000000..6923e7ad87f
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/suite.go
@@ -0,0 +1,121 @@
+/*
+Copyright 2022 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 e2e
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &SuiteTest{}
+
+// SuiteTest scaffolds the files for the e2e tests
+type SuiteTest struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+ machinery.RepositoryMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *SuiteTest) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = "test/e2e/e2e_suite_test.go"
+ }
+
+ f.TemplateBody = suiteTestTemplate
+ return nil
+}
+
+var suiteTestTemplate = `//go:build e2e
+// +build e2e
+
+{{ .Boilerplate }}
+
+package e2e
+
+import (
+ "fmt"
+ "testing"
+ "os"
+ "os/exec"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ "{{ .Repo }}/test/utils"
+)
+
+var (
+ // Optional Environment Variables:
+ // - CERT_MANAGER_INSTALL_SKIP=true: Skips CertManager installation during test setup.
+ // These variables are useful if CertManager is already installed, avoiding
+ // re-installation and conflicts.
+ skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true"
+ // isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster
+ isCertManagerAlreadyInstalled = false
+
+ // projectImage is the name of the image which will be build and loaded
+ // with the code source changes to be tested.
+ projectImage = "example.com/{{ .ProjectName }}:v0.0.1"
+)
+
+// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated,
+// temporary environment to validate project changes with the purpose of being used in CI jobs.
+// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs
+// CertManager.
+func TestE2E(t *testing.T) {
+ RegisterFailHandler(Fail)
+ _, _ = fmt.Fprintf(GinkgoWriter, "Starting {{ .ProjectName }} integration test suite\n")
+ RunSpecs(t, "e2e suite")
+}
+
+var _ = BeforeSuite(func() {
+ By("building the manager(Operator) image")
+ cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage))
+ _, err := utils.Run(cmd)
+ ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image")
+
+ // TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is
+ // built and available before running the tests. Also, remove the following block.
+ By("loading the manager(Operator) image on Kind")
+ err = utils.LoadImageToKindClusterWithName(projectImage)
+ ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind")
+
+ // The tests-e2e are intended to run on a temporary cluster that is created and destroyed for testing.
+ // To prevent errors when tests run in environments with CertManager already installed,
+ // we check for its presence before execution.
+ // Setup CertManager before the suite if not skipped and if not already installed
+ if !skipCertManagerInstall {
+ By("checking if cert manager is installed already")
+ isCertManagerAlreadyInstalled = utils.IsCertManagerCRDsInstalled()
+ if !isCertManagerAlreadyInstalled {
+ _, _ = fmt.Fprintf(GinkgoWriter, "Installing CertManager...\n")
+ Expect(utils.InstallCertManager()).To(Succeed(), "Failed to install CertManager")
+ } else {
+ _, _ = fmt.Fprintf(GinkgoWriter, "WARNING: CertManager is already installed. Skipping installation...\n")
+ }
+ }
+})
+
+var _ = AfterSuite(func() {
+ // Teardown CertManager after the suite if not skipped and if it was not already installed
+ if !skipCertManagerInstall && !isCertManagerAlreadyInstalled {
+ _, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling CertManager...\n")
+ utils.UninstallCertManager()
+ }
+})
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/test.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/test.go
new file mode 100644
index 00000000000..a641e46e92e
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/test.go
@@ -0,0 +1,518 @@
+/*
+Copyright 2022 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 e2e
+
+import (
+ "bytes"
+ "fmt"
+ log "log/slog"
+ "os"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var (
+ _ machinery.Template = &Test{}
+ _ machinery.Inserter = &WebhookTestUpdater{}
+)
+
+const webhookChecksMarker = "e2e-webhooks-checks"
+
+// Test defines the basic setup for the e2e test
+type Test struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+ machinery.RepositoryMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults set defaults for this template
+func (f *Test) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("test", "e2e", "e2e_test.go")
+ }
+
+ // This is where the template body is defined with markers
+ f.TemplateBody = testCodeTemplate
+
+ return nil
+}
+
+// WebhookTestUpdater updates e2e_test.go to insert additional webhook validation tests
+type WebhookTestUpdater struct {
+ machinery.RepositoryMixin
+ machinery.ProjectNameMixin
+ machinery.ResourceMixin
+ WireWebhook bool
+}
+
+// GetPath implements file.Builder
+func (*WebhookTestUpdater) GetPath() string {
+ return filepath.Join("test", "e2e", "e2e_test.go")
+}
+
+// GetIfExistsAction implements file.Builder
+func (*WebhookTestUpdater) GetIfExistsAction() machinery.IfExistsAction {
+ return machinery.OverwriteFile // Ensures only the marker is replaced
+}
+
+// GetMarkers implements file.Inserter
+func (f *WebhookTestUpdater) GetMarkers() []machinery.Marker {
+ return []machinery.Marker{
+ machinery.NewMarkerFor(f.GetPath(), webhookChecksMarker),
+ }
+}
+
+// GetCodeFragments implements file.Inserter
+func (f *WebhookTestUpdater) GetCodeFragments() machinery.CodeFragmentsMap {
+ if !f.WireWebhook {
+ return nil
+ }
+
+ filePath := f.GetPath()
+
+ content, err := os.ReadFile(filePath)
+ if err != nil {
+ log.Warn("Unable to read file", "file", filePath, "error", err)
+ log.Warn("Webhook test code injection will be skipped for this file.")
+ log.Warn("This typically occurs when the file was removed and is missing.")
+ log.Warn("If you intend to scaffold webhook tests, ensure the file and its markers exist.")
+ return nil
+ }
+
+ codeFragments := machinery.CodeFragmentsMap{}
+ markers := f.GetMarkers()
+
+ for _, marker := range markers {
+ if !bytes.Contains(content, []byte(marker.String())) {
+ log.Warn("Marker not found in file, skipping webhook test code injection",
+ "marker", marker.String(),
+ "file_path", filePath)
+ continue // skip this marker
+ }
+
+ var fragments []string
+ fragments = append(fragments, webhookChecksFragment)
+
+ if f.Resource != nil && f.Resource.HasDefaultingWebhook() {
+ mutatingWebhookCode := fmt.Sprintf(mutatingWebhookChecksFragment, f.ProjectName)
+ fragments = append(fragments, mutatingWebhookCode)
+ }
+
+ if f.Resource != nil && f.Resource.HasValidationWebhook() {
+ validatingWebhookCode := fmt.Sprintf(validatingWebhookChecksFragment, f.ProjectName)
+ fragments = append(fragments, validatingWebhookCode)
+ }
+
+ if f.Resource != nil && f.Resource.HasConversionWebhook() {
+ conversionWebhookCode := fmt.Sprintf(
+ conversionWebhookChecksFragment,
+ f.Resource.Kind,
+ f.Resource.Plural+"."+f.Resource.Group+"."+f.Resource.Domain,
+ )
+ fragments = append(fragments, conversionWebhookCode)
+ }
+
+ codeFragments[marker] = fragments
+ }
+
+ if len(codeFragments) == 0 {
+ return nil
+ }
+
+ return codeFragments
+}
+
+const webhookChecksFragment = `It("should provisioned cert-manager", func() {
+ By("validating that cert-manager has the certificate Secret")
+ verifyCertManager := func(g Gomega) {
+ cmd := exec.Command("kubectl", "get", "secrets", "webhook-server-cert", "-n", namespace)
+ _, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred())
+ }
+ Eventually(verifyCertManager).Should(Succeed())
+})
+
+`
+
+const mutatingWebhookChecksFragment = `It("should have CA injection for mutating webhooks", func() {
+ By("checking CA injection for mutating webhooks")
+ verifyCAInjection := func(g Gomega) {
+ cmd := exec.Command("kubectl", "get",
+ "mutatingwebhookconfigurations.admissionregistration.k8s.io",
+ "%s-mutating-webhook-configuration",
+ "-o", "go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}")
+ mwhOutput, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred())
+ g.Expect(len(mwhOutput)).To(BeNumerically(">", 10))
+ }
+ Eventually(verifyCAInjection).Should(Succeed())
+})
+
+`
+
+const validatingWebhookChecksFragment = `It("should have CA injection for validating webhooks", func() {
+ By("checking CA injection for validating webhooks")
+ verifyCAInjection := func(g Gomega) {
+ cmd := exec.Command("kubectl", "get",
+ "validatingwebhookconfigurations.admissionregistration.k8s.io",
+ "%s-validating-webhook-configuration",
+ "-o", "go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}")
+ vwhOutput, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred())
+ g.Expect(len(vwhOutput)).To(BeNumerically(">", 10))
+ }
+ Eventually(verifyCAInjection).Should(Succeed())
+})
+
+`
+
+const conversionWebhookChecksFragment = `It("should have CA injection for %[1]s conversion webhook", func() {
+ By("checking CA injection for %[1]s conversion webhook")
+ verifyCAInjection := func(g Gomega) {
+ cmd := exec.Command("kubectl", "get",
+ "customresourcedefinitions.apiextensions.k8s.io",
+ "%[2]s",
+ "-o", "go-template={{ .spec.conversion.webhook.clientConfig.caBundle }}")
+ vwhOutput, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred())
+ g.Expect(len(vwhOutput)).To(BeNumerically(">", 10))
+ }
+ Eventually(verifyCAInjection).Should(Succeed())
+})
+
+`
+
+var testCodeTemplate = `//go:build e2e
+// +build e2e
+
+{{ .Boilerplate }}
+
+package e2e
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ "{{ .Repo }}/test/utils"
+)
+
+// namespace where the project is deployed in
+const namespace = "{{ .ProjectName }}-system"
+// serviceAccountName created for the project
+const serviceAccountName = "{{ .ProjectName }}-controller-manager"
+// metricsServiceName is the name of the metrics service of the project
+const metricsServiceName = "{{ .ProjectName }}-controller-manager-metrics-service"
+// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data
+const metricsRoleBindingName = "{{ .ProjectName }}-metrics-binding"
+
+var _ = Describe("Manager", Ordered, func() {
+ var controllerPodName string
+
+ // Before running the tests, set up the environment by creating the namespace,
+ // enforce the restricted security policy to the namespace, installing CRDs,
+ // and deploying the controller.
+ BeforeAll(func() {
+ By("creating manager namespace")
+ cmd := exec.Command("kubectl", "create", "ns", namespace)
+ _, err := utils.Run(cmd)
+ Expect(err).NotTo(HaveOccurred(), "Failed to create namespace")
+
+ By("labeling the namespace to enforce the restricted security policy")
+ cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace,
+ "pod-security.kubernetes.io/enforce=restricted")
+ _, err = utils.Run(cmd)
+ Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with restricted policy")
+
+ By("installing CRDs")
+ cmd = exec.Command("make", "install")
+ _, err = utils.Run(cmd)
+ Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs")
+
+ By("deploying the controller-manager")
+ cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectImage))
+ _, err = utils.Run(cmd)
+ Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager")
+ })
+
+ // After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,
+ // and deleting the namespace.
+ AfterAll(func() {
+ By("cleaning up the curl pod for metrics")
+ cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", namespace)
+ _, _ = utils.Run(cmd)
+
+ By("undeploying the controller-manager")
+ cmd = exec.Command("make", "undeploy")
+ _, _ = utils.Run(cmd)
+
+ By("uninstalling CRDs")
+ cmd = exec.Command("make", "uninstall")
+ _, _ = utils.Run(cmd)
+
+ By("removing manager namespace")
+ cmd = exec.Command("kubectl", "delete", "ns", namespace)
+ _, _ = utils.Run(cmd)
+ })
+
+ // After each test, check for failures and collect logs, events,
+ // and pod descriptions for debugging.
+ AfterEach(func() {
+ specReport := CurrentSpecReport()
+ if specReport.Failed() {
+ By("Fetching controller manager pod logs")
+ cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace)
+ controllerLogs, err := utils.Run(cmd)
+ if err == nil {
+ _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs)
+ } else {
+ _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err)
+ }
+
+ By("Fetching Kubernetes events")
+ cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp")
+ eventsOutput, err := utils.Run(cmd)
+ if err == nil {
+ _, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput)
+ } else {
+ _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err)
+ }
+
+ By("Fetching curl-metrics logs")
+ cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace)
+ metricsOutput, err := utils.Run(cmd)
+ if err == nil {
+ _, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput)
+ } else {
+ _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err)
+ }
+
+ By("Fetching controller manager pod description")
+ cmd = exec.Command("kubectl", "describe", "pod", controllerPodName, "-n", namespace)
+ podDescription, err := utils.Run(cmd)
+ if err == nil {
+ fmt.Println("Pod description:\n", podDescription)
+ } else {
+ fmt.Println("Failed to describe controller pod")
+ }
+ }
+ })
+
+ SetDefaultEventuallyTimeout(2 * time.Minute)
+ SetDefaultEventuallyPollingInterval(time.Second)
+
+ Context("Manager", func() {
+ It("should run successfully", func() {
+ By("validating that the controller-manager pod is running as expected")
+ verifyControllerUp := func(g Gomega) {
+ // Get the name of the controller-manager pod
+ cmd := exec.Command("kubectl", "get",
+ "pods", "-l", "control-plane=controller-manager",
+ "-o", "go-template={{"{{"}} range .items {{"}}"}}" +
+ "{{"{{"}} if not .metadata.deletionTimestamp {{"}}"}}" +
+ "{{"{{"}} .metadata.name {{"}}"}}"+
+ "{{"{{"}} \"\\n\" {{"}}"}}{{"{{"}} end {{"}}"}}{{"{{"}} end {{"}}"}}",
+ "-n", namespace,
+ )
+
+ podOutput, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve controller-manager pod information")
+ podNames := utils.GetNonEmptyLines(podOutput)
+ g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running")
+ controllerPodName = podNames[0]
+ g.Expect(controllerPodName).To(ContainSubstring("controller-manager"))
+
+ // Validate the pod's status
+ cmd = exec.Command("kubectl", "get",
+ "pods", controllerPodName, "-o", "jsonpath={.status.phase}",
+ "-n", namespace,
+ )
+ output, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred())
+ g.Expect(output).To(Equal("Running"), "Incorrect controller-manager pod status")
+ }
+ Eventually(verifyControllerUp).Should(Succeed())
+ })
+
+ It("should ensure the metrics endpoint is serving metrics", func() {
+ By("creating a ClusterRoleBinding for the service account to allow access to metrics")
+ cmd := exec.Command("kubectl", "create", "clusterrolebinding", metricsRoleBindingName,
+ "--clusterrole={{ .ProjectName}}-metrics-reader",
+ fmt.Sprintf("--serviceaccount=%s:%s", namespace, serviceAccountName),
+ )
+ _, err := utils.Run(cmd)
+ Expect(err).NotTo(HaveOccurred(), "Failed to create ClusterRoleBinding")
+
+ By("validating that the metrics service is available")
+ cmd = exec.Command("kubectl", "get", "service", metricsServiceName, "-n", namespace)
+ _, err = utils.Run(cmd)
+ Expect(err).NotTo(HaveOccurred(), "Metrics service should exist")
+
+ By("getting the service account token")
+ token, err := serviceAccountToken()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(token).NotTo(BeEmpty())
+
+ By("waiting for the metrics endpoint to be ready")
+ verifyMetricsEndpointReady := func(g Gomega) {
+ cmd := exec.Command("kubectl", "get", "endpoints", metricsServiceName, "-n", namespace)
+ output, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred())
+ g.Expect(output).To(ContainSubstring("8443"), "Metrics endpoint is not ready")
+ }
+ Eventually(verifyMetricsEndpointReady).Should(Succeed())
+
+ By("verifying that the controller manager is serving the metrics server")
+ verifyMetricsServerStarted := func(g Gomega) {
+ cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace)
+ output, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred())
+ g.Expect(output).To(ContainSubstring("controller-runtime.metrics\tServing metrics server"),
+ "Metrics server not yet started")
+ }
+ Eventually(verifyMetricsServerStarted).Should(Succeed())
+
+ By("creating the curl-metrics pod to access the metrics endpoint")
+ cmd = exec.Command("kubectl", "run", "curl-metrics", "--restart=Never",
+ "--namespace", namespace,
+ "--image=curlimages/curl:latest",
+ "--overrides",
+ fmt.Sprintf(` + "`" + `{
+ "spec": {
+ "containers": [{
+ "name": "curl",
+ "image": "curlimages/curl:latest",
+ "command": ["/bin/sh", "-c"],
+ "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"],
+ "securityContext": {
+ "readOnlyRootFilesystem": true,
+ "allowPrivilegeEscalation": false,
+ "capabilities": {
+ "drop": ["ALL"]
+ },
+ "runAsNonRoot": true,
+ "runAsUser": 1000,
+ "seccompProfile": {
+ "type": "RuntimeDefault"
+ }
+ }
+ }],
+ "serviceAccountName": "%s"
+ }
+ }` + "`" + `, token, metricsServiceName, namespace, serviceAccountName))
+ _, err = utils.Run(cmd)
+ Expect(err).NotTo(HaveOccurred(), "Failed to create curl-metrics pod")
+
+ By("waiting for the curl-metrics pod to complete.")
+ verifyCurlUp := func(g Gomega) {
+ cmd := exec.Command("kubectl", "get", "pods", "curl-metrics",
+ "-o", "jsonpath={.status.phase}",
+ "-n", namespace)
+ output, err := utils.Run(cmd)
+ g.Expect(err).NotTo(HaveOccurred())
+ g.Expect(output).To(Equal("Succeeded"), "curl pod in wrong status")
+ }
+ Eventually(verifyCurlUp, 5 * time.Minute).Should(Succeed())
+
+ By("getting the metrics by checking curl-metrics logs")
+ verifyMetricsAvailable := func(g Gomega) {
+ metricsOutput, err := getMetricsOutput()
+ g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod")
+ g.Expect(metricsOutput).NotTo(BeEmpty())
+ g.Expect(metricsOutput).To(ContainSubstring("< HTTP/1.1 200 OK"))
+ }
+ Eventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed())
+ })
+
+ // +kubebuilder:scaffold:e2e-webhooks-checks
+
+ // TODO: Customize the e2e test suite with scenarios specific to your project.
+ // Consider applying sample/CR(s) and check their status and/or verifying
+ // the reconciliation by using the metrics, i.e.:
+ // metricsOutput, err := getMetricsOutput()
+ // Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod")
+ // Expect(metricsOutput).To(ContainSubstring(
+ // fmt.Sprintf(` + "`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`" + `,
+ // strings.ToLower(),
+ // ))
+ })
+})
+
+// serviceAccountToken returns a token for the specified service account in the given namespace.
+// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request
+// and parsing the resulting token from the API response.
+func serviceAccountToken() (string, error) {
+ const tokenRequestRawString = ` + "`" + `{
+ "apiVersion": "authentication.k8s.io/v1",
+ "kind": "TokenRequest"
+ }` + "`" + `
+
+ // Temporary file to store the token request
+ secretName := fmt.Sprintf("%s-token-request", serviceAccountName)
+ tokenRequestFile := filepath.Join("/tmp", secretName)
+ err := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644))
+ if err != nil {
+ return "", err
+ }
+
+ var out string
+ verifyTokenCreation := func(g Gomega) {
+ // Execute kubectl command to create the token
+ cmd := exec.Command("kubectl", "create", "--raw", fmt.Sprintf(
+ "/api/v1/namespaces/%s/serviceaccounts/%s/token",
+ namespace,
+ serviceAccountName,
+ ), "-f", tokenRequestFile)
+
+ output, err := cmd.CombinedOutput()
+ g.Expect(err).NotTo(HaveOccurred())
+
+ // Parse the JSON output to extract the token
+ var token tokenRequest
+ err = json.Unmarshal(output, &token)
+ g.Expect(err).NotTo(HaveOccurred())
+
+ out = token.Status.Token
+ }
+ Eventually(verifyTokenCreation).Should(Succeed())
+
+ return out, err
+}
+
+// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.
+func getMetricsOutput() (string, error) {
+ By("getting the curl-metrics logs")
+ cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace)
+ return utils.Run(cmd)
+}
+
+// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,
+// containing only the token field that we need to extract.
+type tokenRequest struct {
+ Status struct {
+ Token string ` + "`json:\"token\"`" + `
+ } ` + "`json:\"status\"`" + `
+}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils/utils.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils/utils.go
new file mode 100644
index 00000000000..44866e5c3cb
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils/utils.go
@@ -0,0 +1,255 @@
+/*
+Copyright 2022 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 utils
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Utils{}
+
+// Utils define the template for the utils file
+type Utils struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults set the defaults for its template
+func (f *Utils) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = "test/utils/utils.go"
+ }
+
+ f.TemplateBody = utilsTemplate
+
+ return nil
+}
+
+var utilsTemplate = `{{ .Boilerplate }}
+
+package utils
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck
+)
+
+const (
+ certmanagerVersion = "v1.19.1"
+ certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml"
+
+ defaultKindBinary = "kind"
+ defaultKindCluster = "kind"
+)
+
+func warnError(err error) {
+ _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err)
+}
+
+// Run executes the provided command within this context
+func Run(cmd *exec.Cmd) (string, error) {
+ dir, _ := GetProjectDir()
+ cmd.Dir = dir
+
+ if err := os.Chdir(cmd.Dir); err != nil {
+ _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %q\n", err)
+ }
+
+ cmd.Env = append(os.Environ(), "GO111MODULE=on")
+ command := strings.Join(cmd.Args, " ")
+ _, _ = fmt.Fprintf(GinkgoWriter, "running: %q\n", command)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ return string(output), fmt.Errorf("%q failed with error %q: %w", command, string(output), err)
+ }
+
+ return string(output), nil
+}
+
+// UninstallCertManager uninstalls the cert manager
+func UninstallCertManager() {
+ url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
+ cmd := exec.Command("kubectl", "delete", "-f", url)
+ if _, err := Run(cmd); err != nil {
+ warnError(err)
+ }
+
+ // Delete leftover leases in kube-system (not cleaned by default)
+ kubeSystemLeases := []string{
+ "cert-manager-cainjector-leader-election",
+ "cert-manager-controller",
+ }
+ for _, lease := range kubeSystemLeases {
+ cmd = exec.Command("kubectl", "delete", "lease", lease,
+ "-n", "kube-system", "--ignore-not-found", "--force", "--grace-period=0")
+ if _, err := Run(cmd); err != nil {
+ warnError(err)
+ }
+ }
+}
+
+// InstallCertManager installs the cert manager bundle.
+func InstallCertManager() error {
+ url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
+ cmd := exec.Command("kubectl", "apply", "-f", url)
+ if _, err := Run(cmd); err != nil {
+ return err
+ }
+ // Wait for cert-manager-webhook to be ready, which can take time if cert-manager
+ // was re-installed after uninstalling on a cluster.
+ cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook",
+ "--for", "condition=Available",
+ "--namespace", "cert-manager",
+ "--timeout", "5m",
+ )
+
+ _, err := Run(cmd)
+ return err
+}
+
+// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed
+// by verifying the existence of key CRDs related to Cert Manager.
+func IsCertManagerCRDsInstalled() bool {
+ // List of common Cert Manager CRDs
+ certManagerCRDs := []string{
+ "certificates.cert-manager.io",
+ "issuers.cert-manager.io",
+ "clusterissuers.cert-manager.io",
+ "certificaterequests.cert-manager.io",
+ "orders.acme.cert-manager.io",
+ "challenges.acme.cert-manager.io",
+ }
+
+ // Execute the kubectl command to get all CRDs
+ cmd := exec.Command("kubectl", "get", "crds")
+ output, err := Run(cmd)
+ if err != nil {
+ return false
+ }
+
+ // Check if any of the Cert Manager CRDs are present
+ crdList := GetNonEmptyLines(output)
+ for _, crd := range certManagerCRDs {
+ for _, line := range crdList {
+ if strings.Contains(line, crd) {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+// LoadImageToKindClusterWithName loads a local docker image to the kind cluster
+func LoadImageToKindClusterWithName(name string) error {
+ cluster := defaultKindCluster
+ if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {
+ cluster = v
+ }
+ kindOptions := []string{"load", "docker-image", name, "--name", cluster}
+ kindBinary := defaultKindBinary
+ if v, ok := os.LookupEnv("KIND"); ok {
+ kindBinary = v
+ }
+ cmd := exec.Command(kindBinary, kindOptions...)
+ _, err := Run(cmd)
+ return err
+}
+
+// GetNonEmptyLines converts given command output string into individual objects
+// according to line breakers, and ignores the empty elements in it.
+func GetNonEmptyLines(output string) []string {
+ var res []string
+ elements := strings.Split(output, "\n")
+ for _, element := range elements {
+ if element != "" {
+ res = append(res, element)
+ }
+ }
+
+ return res
+}
+
+// GetProjectDir will return the directory where the project is
+func GetProjectDir() (string, error) {
+ wd, err := os.Getwd()
+ if err != nil {
+ return wd, fmt.Errorf("failed to get current working directory: %w", err)
+ }
+ wd = strings.ReplaceAll(wd, "/test/e2e", "")
+ return wd, nil
+}
+
+// UncommentCode searches for target in the file and remove the comment prefix
+// of the target content. The target content may span multiple lines.
+func UncommentCode(filename, target, prefix string) error {
+ // false positive
+ // nolint:gosec
+ content, err := os.ReadFile(filename)
+ if err != nil {
+ return fmt.Errorf("failed to read file %q: %w", filename, err)
+ }
+ strContent := string(content)
+
+ idx := strings.Index(strContent, target)
+ if idx < 0 {
+ return fmt.Errorf("unable to find the code %q to be uncomment", target)
+ }
+
+ out := new(bytes.Buffer)
+ _, err = out.Write(content[:idx])
+ if err != nil {
+ return fmt.Errorf("failed to write to output: %w", err)
+ }
+
+ scanner := bufio.NewScanner(bytes.NewBufferString(target))
+ if !scanner.Scan() {
+ return nil
+ }
+ for {
+ if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {
+ return fmt.Errorf("failed to write to output: %w", err)
+ }
+ // Avoid writing a newline in case the previous line was the last in target.
+ if !scanner.Scan() {
+ break
+ }
+ if _, err = out.WriteString("\n"); err != nil {
+ return fmt.Errorf("failed to write to output: %w", err)
+ }
+ }
+
+ if _, err = out.Write(content[idx+len(target):]); err != nil {
+ return fmt.Errorf("failed to write to output: %w", err)
+ }
+
+ // false positive
+ // nolint:gosec
+ if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil {
+ return fmt.Errorf("failed to write file %q: %w", filename, err)
+ }
+
+ return nil
+}
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go
new file mode 100644
index 00000000000..d7747295c3d
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go
@@ -0,0 +1,277 @@
+/*
+Copyright 2022 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 webhooks
+
+import (
+ log "log/slog"
+ "path/filepath"
+ "strings"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Webhook{}
+
+// Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource
+type Webhook struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+
+ // Is the Group domain for the Resource replacing '.' with '-'
+ QualifiedGroupWithDash string
+
+ // Define value for AdmissionReviewVersions marker
+ AdmissionReviewVersions string
+
+ Force bool
+
+ // Deprecated - The flag should be removed from go/v5
+ // IsLegacyPath indicates if webhooks should be scaffolded under the API.
+ // Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback.
+ // This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path.
+ IsLegacyPath bool
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *Webhook) SetTemplateDefaults() error {
+ if f.Path == "" {
+ // Deprecated: Remove me when remove go/v4
+ baseDir := "api"
+ if !f.IsLegacyPath {
+ baseDir = filepath.Join("internal", "webhook")
+ }
+
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join(baseDir, "%[group]", "%[version]", "%[kind]_webhook.go")
+ } else {
+ f.Path = filepath.Join(baseDir, "%[version]", "%[kind]_webhook.go")
+ }
+ }
+
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+
+ webhookTemplate := webhookTemplate
+ if f.Resource.HasDefaultingWebhook() {
+ webhookTemplate = webhookTemplate + defaultingWebhookTemplate
+ }
+ if f.Resource.HasValidationWebhook() {
+ webhookTemplate = webhookTemplate + validatingWebhookTemplate
+ }
+ f.TemplateBody = webhookTemplate
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ } else {
+ f.IfExistsAction = machinery.Error
+ }
+
+ f.AdmissionReviewVersions = "v1"
+ f.QualifiedGroupWithDash = strings.ReplaceAll(f.Resource.QualifiedGroup(), ".", "-")
+
+ return nil
+}
+
+const (
+ webhookTemplate = `{{ .Boilerplate }}
+
+package {{ .Resource.Version }}
+
+import (
+ {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }}
+ "context"
+ "fmt"
+ {{- end }}
+
+ ctrl "sigs.k8s.io/controller-runtime"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }}
+ "k8s.io/apimachinery/pkg/runtime"
+ "sigs.k8s.io/controller-runtime/pkg/webhook"
+ {{- end }}
+ {{- if .Resource.HasValidationWebhook }}
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+ {{- end }}
+ {{ if not .IsLegacyPath -}}
+ {{ if not (isEmptyStr .Resource.Path) -}}
+ {{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
+ {{- end }}
+ {{- end }}
+)
+
+// nolint:unused
+// log is for logging in this package.
+var {{ lower .Resource.Kind }}log = logf.Log.WithName("{{ lower .Resource.Kind }}-resource")
+
+{{- if .IsLegacyPath -}}
+// SetupWebhookWithManager will setup the manager to manage the webhooks.
+func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewWebhookManagedBy(mgr).
+ For(r).
+ {{- if .Resource.HasValidationWebhook }}
+ WithValidator(&{{ .Resource.Kind }}CustomValidator{}).
+ {{- if ne .Resource.Webhooks.ValidationPath "" }}
+ WithValidatorCustomPath("{{ .Resource.Webhooks.ValidationPath }}").
+ {{- end }}
+ {{- end }}
+ {{- if .Resource.HasDefaultingWebhook }}
+ WithDefaulter(&{{ .Resource.Kind }}CustomDefaulter{}).
+ {{- if ne .Resource.Webhooks.DefaultingPath "" }}
+ WithDefaulterCustomPath("{{ .Resource.Webhooks.DefaultingPath }}").
+ {{- end }}
+ {{- end }}
+ Complete()
+}
+{{- else }}
+// Setup{{ .Resource.Kind }}WebhookWithManager registers the webhook for {{ .Resource.Kind }} in the manager.
+func Setup{{ .Resource.Kind }}WebhookWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewWebhookManagedBy(mgr).
+ {{- if not (isEmptyStr .Resource.ImportAlias) -}}
+ For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}).
+ {{- else -}}
+ For(&{{ .Resource.Kind }}{}).
+ {{- end }}
+ {{- if .Resource.HasValidationWebhook }}
+ WithValidator(&{{ .Resource.Kind }}CustomValidator{}).
+ {{- if ne .Resource.Webhooks.ValidationPath "" }}
+ WithValidatorCustomPath("{{ .Resource.Webhooks.ValidationPath }}").
+ {{- end }}
+ {{- end }}
+ {{- if .Resource.HasDefaultingWebhook }}
+ WithDefaulter(&{{ .Resource.Kind }}CustomDefaulter{}).
+ {{- if ne .Resource.Webhooks.DefaultingPath "" }}
+ WithDefaulterCustomPath("{{ .Resource.Webhooks.DefaultingPath }}").
+ {{- end }}
+ {{- end }}
+ Complete()
+}
+{{- end }}
+
+// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+`
+
+ //nolint:lll
+ defaultingWebhookTemplate = `
+// +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}{{- if ne .Resource.Webhooks.DefaultingPath "" -}}path={{ .Resource.Webhooks.DefaultingPath }}{{- else -}}path=/mutate-{{ if and .Resource.Core (eq .Resource.QualifiedGroup "core") }}-{{ else }}{{ .QualifiedGroupWithDash }}-{{ end }}{{ .Resource.Version }}-{{ lower .Resource.Kind }}{{- end -}},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ if and .Resource.Core (eq .Resource.QualifiedGroup "core") }}""{{ else }}{{ .Resource.QualifiedGroup }}{{ end }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}-{{ .Resource.Version }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}
+
+{{ if .IsLegacyPath -}}
+// +kubebuilder:object:generate=false
+{{- end }}
+// {{ .Resource.Kind }}CustomDefaulter struct is responsible for setting default values on the custom resource of the
+// Kind {{ .Resource.Kind }} when those are created or updated.
+//
+// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
+// as it is used only for temporary operations and does not need to be deeply copied.
+type {{ .Resource.Kind }}CustomDefaulter struct {
+ // TODO(user): Add more fields as needed for defaulting
+}
+
+var _ webhook.CustomDefaulter = &{{ .Resource.Kind }}CustomDefaulter{}
+
+// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind {{ .Resource.Kind }}.
+func (d *{{ .Resource.Kind }}CustomDefaulter) Default(_ context.Context, obj runtime.Object) error {
+ {{- if .IsLegacyPath -}}
+ {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.Kind }})
+ {{- else }}
+ {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})
+ {{- end }}
+
+ if !ok {
+ return fmt.Errorf("expected an {{ .Resource.Kind }} object but got %T", obj)
+ }
+ {{ lower .Resource.Kind }}log.Info("Defaulting for {{ .Resource.Kind }}", "name", {{ lower .Resource.Kind }}.GetName())
+
+ // TODO(user): fill in your defaulting logic.
+
+ return nil
+}
+`
+
+ //nolint:lll
+ validatingWebhookTemplate = `
+// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
+// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.
+// +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}{{- if ne .Resource.Webhooks.ValidationPath "" -}}path={{ .Resource.Webhooks.ValidationPath }}{{- else -}}path=/validate-{{ if and .Resource.Core (eq .Resource.QualifiedGroup "core") }}-{{ else }}{{ .QualifiedGroupWithDash }}-{{ end }}{{ .Resource.Version }}-{{ lower .Resource.Kind }}{{- end -}},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ if and .Resource.Core (eq .Resource.QualifiedGroup "core") }}""{{ else }}{{ .Resource.QualifiedGroup }}{{ end }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}-{{ .Resource.Version }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}
+
+{{ if .IsLegacyPath -}}
+// +kubebuilder:object:generate=false
+{{- end }}
+// {{ .Resource.Kind }}CustomValidator struct is responsible for validating the {{ .Resource.Kind }} resource
+// when it is created, updated, or deleted.
+//
+// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
+// as this struct is used only for temporary operations and does not need to be deeply copied.
+type {{ .Resource.Kind }}CustomValidator struct{
+ // TODO(user): Add more fields as needed for validation
+}
+
+var _ webhook.CustomValidator = &{{ .Resource.Kind }}CustomValidator{}
+
+// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}.
+func (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
+ {{- if .IsLegacyPath -}}
+ {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.Kind }})
+ {{- else }}
+ {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})
+ {{- end }}
+ if !ok {
+ return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object but got %T", obj)
+ }
+ {{ lower .Resource.Kind }}log.Info("Validation for {{ .Resource.Kind }} upon creation", "name", {{ lower .Resource.Kind }}.GetName())
+
+ // TODO(user): fill in your validation logic upon object creation.
+
+ return nil, nil
+}
+
+// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}.
+func (v *{{ .Resource.Kind }}CustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
+ {{- if .IsLegacyPath -}}
+ {{ lower .Resource.Kind }}, ok := newObj.(*{{ .Resource.Kind }})
+ {{- else }}
+ {{ lower .Resource.Kind }}, ok := newObj.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})
+ {{- end }}
+ if !ok {
+ return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object for the newObj but got %T", newObj)
+ }
+ {{ lower .Resource.Kind }}log.Info("Validation for {{ .Resource.Kind }} upon update", "name", {{ lower .Resource.Kind }}.GetName())
+
+ // TODO(user): fill in your validation logic upon object update.
+
+ return nil, nil
+}
+
+// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}.
+func (v *{{ .Resource.Kind }}CustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
+ {{- if .IsLegacyPath -}}
+ {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.Kind }})
+ {{- else }}
+ {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})
+ {{- end }}
+ if !ok {
+ return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object but got %T", obj)
+ }
+ {{ lower .Resource.Kind }}log.Info("Validation for {{ .Resource.Kind }} upon deletion", "name", {{ lower .Resource.Kind }}.GetName())
+
+ // TODO(user): fill in your validation logic upon object deletion.
+
+ return nil, nil
+}
+`
+)
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go
new file mode 100644
index 00000000000..17d705c164d
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go
@@ -0,0 +1,456 @@
+/*
+Copyright 2022 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 webhooks
+
+import (
+ "fmt"
+ log "log/slog"
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var (
+ _ machinery.Template = &WebhookSuite{}
+ _ machinery.Inserter = &WebhookSuite{}
+)
+
+// WebhookSuite scaffolds the file that sets up the webhook tests
+type WebhookSuite struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+
+ // todo: currently is not possible to know if an API was or not scaffolded. We can fix it when #1826 be addressed
+ WireResource bool
+
+ // K8SVersion define the k8s version used to do the scaffold
+ // so that is possible retrieve the binaries
+ K8SVersion string
+
+ // BaseDirectoryRelativePath define the Path for the base directory when it is multigroup
+ BaseDirectoryRelativePath string
+
+ // Deprecated - The flag should be removed from go/v5
+ // IsLegacyPath indicates if webhooks should be scaffolded under the API.
+ // Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback.
+ // This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path.
+ IsLegacyPath bool
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *WebhookSuite) SetTemplateDefaults() error {
+ if f.Path == "" {
+ // Deprecated: Remove me when remove go/v4
+ baseDir := "api"
+ if !f.IsLegacyPath {
+ baseDir = filepath.Join("internal", "webhook")
+ }
+
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join(baseDir, "%[group]", "%[version]", "webhook_suite_test.go")
+ } else {
+ f.Path = filepath.Join(baseDir, "%[version]", "webhook_suite_test.go")
+ }
+ }
+
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+
+ if f.IsLegacyPath {
+ f.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplateLegacy,
+ machinery.NewMarkerFor(f.Path, importMarker),
+ admissionImportAlias,
+ machinery.NewMarkerFor(f.Path, addSchemeMarker),
+ machinery.NewMarkerFor(f.Path, addWebhookManagerMarker),
+ "%s",
+ "%d",
+ )
+ } else {
+ f.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplate,
+ machinery.NewMarkerFor(f.Path, importMarker),
+ f.Resource.ImportAlias(),
+ machinery.NewMarkerFor(f.Path, addSchemeMarker),
+ machinery.NewMarkerFor(f.Path, addWebhookManagerMarker),
+ "%s",
+ "%d",
+ )
+ }
+
+ if f.IsLegacyPath {
+ // If is multigroup the path needs to be ../../../ since it has the group dir.
+ f.BaseDirectoryRelativePath = `"..", ".."`
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.BaseDirectoryRelativePath = `"..", "..", ".."`
+ }
+ } else {
+ // If is multigroup the path needs to be ../../../../ since it has the group dir.
+ f.BaseDirectoryRelativePath = `"..", "..", ".."`
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.BaseDirectoryRelativePath = `"..", "..", "..", ".."`
+ }
+ }
+
+ return nil
+}
+
+const (
+ admissionImportAlias = "admissionv1"
+ admissionPath = "k8s.io/api/admission/v1"
+ importMarker = "imports"
+ addWebhookManagerMarker = "webhook"
+ addSchemeMarker = "scheme"
+)
+
+// GetMarkers implements file.Inserter
+func (f *WebhookSuite) GetMarkers() []machinery.Marker {
+ return []machinery.Marker{
+ machinery.NewMarkerFor(f.Path, importMarker),
+ machinery.NewMarkerFor(f.Path, addSchemeMarker),
+ machinery.NewMarkerFor(f.Path, addWebhookManagerMarker),
+ }
+}
+
+const (
+ apiImportCodeFragment = `%s "%s"
+`
+
+ // Deprecated - TODO: remove for go/v5
+ // addWebhookManagerCodeFragmentLegacy is for the path under API
+ addWebhookManagerCodeFragmentLegacy = `err = (&%s{}).SetupWebhookWithManager(mgr)
+Expect(err).NotTo(HaveOccurred())
+
+`
+
+ addWebhookManagerCodeFragment = `err = Setup%sWebhookWithManager(mgr)
+Expect(err).NotTo(HaveOccurred())
+
+`
+)
+
+// GetCodeFragments implements file.Inserter
+func (f *WebhookSuite) GetCodeFragments() machinery.CodeFragmentsMap {
+ fragments := make(machinery.CodeFragmentsMap, 3)
+
+ // Generate import code fragments
+ imports := make([]string, 0)
+
+ // Generate add scheme code fragments
+ addScheme := make([]string, 0)
+
+ // Generate add webhookManager code fragments
+ addWebhookManager := make([]string, 0)
+ if f.IsLegacyPath {
+ imports = append(imports, fmt.Sprintf(apiImportCodeFragment, admissionImportAlias, admissionPath))
+ addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragmentLegacy, f.Resource.Kind))
+ } else {
+ imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))
+ addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragment, f.Resource.Kind))
+ }
+
+ // Only store code fragments in the map if the slices are non-empty
+ if len(addWebhookManager) != 0 {
+ fragments[machinery.NewMarkerFor(f.Path, addWebhookManagerMarker)] = addWebhookManager
+ }
+ if len(imports) != 0 {
+ fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports
+ }
+ if len(addScheme) != 0 {
+ fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme
+ }
+
+ return fragments
+}
+
+const webhookTestSuiteTemplate = `{{ .Boilerplate }}
+
+package {{ .Resource.Version }}
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "net"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ "k8s.io/client-go/kubernetes/scheme"
+ "k8s.io/client-go/rest"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+ %s
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var (
+ ctx context.Context
+ cancel context.CancelFunc
+ k8sClient client.Client
+ cfg *rest.Config
+ testEnv *envtest.Environment
+)
+
+func TestAPIs(t *testing.T) {
+ RegisterFailHandler(Fail)
+
+ RunSpecs(t, "Webhook Suite")
+}
+
+var _ = BeforeSuite(func() {
+ logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
+
+ ctx, cancel = context.WithCancel(context.TODO())
+
+ var err error
+ err = %s.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ %s
+
+ By("bootstrapping test environment")
+ testEnv = &envtest.Environment{
+ CRDDirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "crd", "bases")},
+ ErrorIfCRDPathMissing: {{ .WireResource }},
+
+ WebhookInstallOptions: envtest.WebhookInstallOptions{
+ Paths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "webhook")},
+ },
+ }
+
+ // Retrieve the first found binary directory to allow running tests from IDEs
+ if getFirstFoundEnvTestBinaryDir() != "" {
+ testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()
+ }
+
+ // cfg is defined in this file globally.
+ cfg, err = testEnv.Start()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(cfg).NotTo(BeNil())
+
+ k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
+ Expect(err).NotTo(HaveOccurred())
+ Expect(k8sClient).NotTo(BeNil())
+
+ // start webhook server using Manager.
+ webhookInstallOptions := &testEnv.WebhookInstallOptions
+ mgr, err := ctrl.NewManager(cfg, ctrl.Options{
+ Scheme: scheme.Scheme,
+ WebhookServer: webhook.NewServer(webhook.Options{
+ Host: webhookInstallOptions.LocalServingHost,
+ Port: webhookInstallOptions.LocalServingPort,
+ CertDir: webhookInstallOptions.LocalServingCertDir,
+ }),
+ LeaderElection: false,
+ Metrics: metricsserver.Options{BindAddress: "0"},
+
+ })
+ Expect(err).NotTo(HaveOccurred())
+
+ %s
+
+ go func() {
+ defer GinkgoRecover()
+ err = mgr.Start(ctx)
+ Expect(err).NotTo(HaveOccurred())
+ }()
+
+ // wait for the webhook server to get ready.
+ dialer := &net.Dialer{Timeout: time.Second}
+ addrPort := fmt.Sprintf("%s:%s", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)
+ Eventually(func() error {
+ conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ return err
+ }
+
+ return conn.Close();
+ }).Should(Succeed())
+})
+
+var _ = AfterSuite(func() {
+ By("tearing down the test environment")
+ cancel()
+ err := testEnv.Stop()
+ Expect(err).NotTo(HaveOccurred())
+})
+
+// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.
+// ENVTEST-based tests depend on specific binaries, usually located in paths set by
+// controller-runtime. When running tests directly (e.g., via an IDE) without using
+// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.
+//
+// This function streamlines the process by finding the required binaries, similar to
+// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are
+// properly set up, run 'make setup-envtest' beforehand.
+func getFirstFoundEnvTestBinaryDir() string {
+ basePath := filepath.Join({{ .BaseDirectoryRelativePath }}, "bin", "k8s")
+ entries, err := os.ReadDir(basePath)
+ if err != nil {
+ logf.Log.Error(err, "Failed to read directory", "path", basePath)
+ return ""
+ }
+ for _, entry := range entries {
+ if entry.IsDir() {
+ return filepath.Join(basePath, entry.Name())
+ }
+ }
+ return ""
+}
+`
+
+const webhookTestSuiteTemplateLegacy = `{{ .Boilerplate }}
+
+package {{ .Resource.Version }}
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "net"
+ "path/filepath"
+ "testing"
+ "time"
+ "runtime"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ %s
+ "k8s.io/client-go/kubernetes/scheme"
+ "k8s.io/client-go/rest"
+ apimachineryruntime "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+ metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var (
+ cancel context.CancelFunc
+ cfg *rest.Config
+ ctx context.Context
+ k8sClient client.Client
+ testEnv *envtest.Environment
+)
+
+func TestAPIs(t *testing.T) {
+ RegisterFailHandler(Fail)
+
+ RunSpecs(t, "Webhook Suite")
+}
+
+var _ = BeforeSuite(func() {
+ logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
+
+ ctx, cancel = context.WithCancel(context.TODO())
+
+ By("bootstrapping test environment")
+ testEnv = &envtest.Environment{
+ CRDDirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "crd", "bases")},
+ ErrorIfCRDPathMissing: {{ .WireResource }},
+
+ // The BinaryAssetsDirectory is only required if you want to run the tests directly
+ // without call the makefile target test. If not informed it will look for the
+ // default path defined in controller-runtime which is /usr/local/kubebuilder/.
+ // Note that you must have the required binaries setup under the bin directory to perform
+ // the tests directly. When we run make test it will be setup and used automatically.
+ BinaryAssetsDirectory: filepath.Join({{ .BaseDirectoryRelativePath }}, "bin", "k8s",
+ fmt.Sprintf("{{ .K8SVersion }}-%%s-%%s", runtime.GOOS, runtime.GOARCH)),
+
+ WebhookInstallOptions: envtest.WebhookInstallOptions{
+ Paths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "webhook")},
+ },
+ }
+
+ var err error
+ // cfg is defined in this file globally.
+ cfg, err = testEnv.Start()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(cfg).NotTo(BeNil())
+
+ scheme := apimachineryruntime.NewScheme()
+ err = AddToScheme(scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = %s.AddToScheme(scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ %s
+
+ k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
+ Expect(err).NotTo(HaveOccurred())
+ Expect(k8sClient).NotTo(BeNil())
+
+ // start webhook server using Manager.
+ webhookInstallOptions := &testEnv.WebhookInstallOptions
+ mgr, err := ctrl.NewManager(cfg, ctrl.Options{
+ Scheme: scheme,
+ WebhookServer: webhook.NewServer(webhook.Options{
+ Host: webhookInstallOptions.LocalServingHost,
+ Port: webhookInstallOptions.LocalServingPort,
+ CertDir: webhookInstallOptions.LocalServingCertDir,
+ }),
+ LeaderElection: false,
+ Metrics: metricsserver.Options{BindAddress: "0"},
+
+ })
+ Expect(err).NotTo(HaveOccurred())
+
+ %s
+
+ go func() {
+ defer GinkgoRecover()
+ err = mgr.Start(ctx)
+ Expect(err).NotTo(HaveOccurred())
+ }()
+
+ // wait for the webhook server to get ready.
+ dialer := &net.Dialer{Timeout: time.Second}
+ addrPort := fmt.Sprintf("%s:%s", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)
+ Eventually(func() error {
+ conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ return err
+ }
+
+ return conn.Close();
+ }).Should(Succeed())
+})
+
+var _ = AfterSuite(func() {
+ By("tearing down the test environment")
+ cancel()
+ err := testEnv.Stop()
+ Expect(err).NotTo(HaveOccurred())
+})
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go
new file mode 100644
index 00000000000..db1c7fc79ab
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go
@@ -0,0 +1,221 @@
+/*
+Copyright 2022 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 webhooks
+
+import (
+ "fmt"
+ log "log/slog"
+ "path/filepath"
+ "strings"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &WebhookTest{}
+
+// WebhookTest scaffolds the file that sets up the webhook unit tests
+type WebhookTest struct {
+ machinery.TemplateMixin
+ machinery.MultiGroupMixin
+ machinery.BoilerplateMixin
+ machinery.ResourceMixin
+ machinery.IfNotExistsActionMixin
+
+ Force bool
+
+ // Deprecated - The flag should be removed from go/v5
+ // IsLegacyPath indicates if webhooks should be scaffolded under the API.
+ // Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback.
+ // This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path.
+ IsLegacyPath bool
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *WebhookTest) SetTemplateDefaults() error {
+ if f.Path == "" {
+ // Deprecated: Remove me when remove go/v4
+ const baseDir = "api"
+ pathAPI := baseDir
+ if !f.IsLegacyPath {
+ pathAPI = filepath.Join("internal", "webhook")
+ }
+
+ if f.MultiGroup && f.Resource.Group != "" {
+ f.Path = filepath.Join(pathAPI, "%[group]", "%[version]", "%[kind]_webhook_test.go")
+ } else {
+ f.Path = filepath.Join(pathAPI, "%[version]", "%[kind]_webhook_test.go")
+ }
+ }
+ f.Path = f.Resource.Replacer().Replace(f.Path)
+ log.Info(f.Path)
+
+ webhookTestTemplate := webhookTestTemplate
+ templates := make([]string, 0)
+ if f.Resource.HasDefaultingWebhook() {
+ templates = append(templates, defaultWebhookTestTemplate)
+ }
+ if f.Resource.HasValidationWebhook() {
+ templates = append(templates, validateWebhookTestTemplate)
+ }
+ if f.Resource.HasConversionWebhook() {
+ templates = append(templates, conversionWebhookTestTemplate)
+ }
+ f.TemplateBody = fmt.Sprintf(webhookTestTemplate, strings.Join(templates, "\n"))
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ }
+ f.IfNotExistsAction = machinery.IgnoreFile
+
+ return nil
+}
+
+const webhookTestTemplate = `{{ .Boilerplate }}
+
+package {{ .Resource.Version }}
+
+import (
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+
+ {{ if not .IsLegacyPath -}}
+ {{ if not (isEmptyStr .Resource.Path) -}}
+ {{ .Resource.ImportAlias }} "{{ .Resource.Path }}"
+ {{- end }}
+ {{- end }}
+ // TODO (user): Add any additional imports if needed
+)
+
+var _ = Describe("{{ .Resource.Kind }} Webhook", func() {
+ var (
+ {{- if .IsLegacyPath -}}
+ obj *{{ .Resource.Kind }}
+ {{- else }}
+ obj *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}
+ oldObj *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}
+ {{- if .Resource.HasValidationWebhook }}
+ validator {{ .Resource.Kind }}CustomValidator
+ {{- end }}
+ {{- if .Resource.HasDefaultingWebhook }}
+ defaulter {{ .Resource.Kind }}CustomDefaulter
+ {{- end }}
+ {{- end }}
+ )
+
+ BeforeEach(func() {
+ {{- if .IsLegacyPath -}}
+ obj = &{{ .Resource.Kind }}{}
+ {{- else }}
+ obj = &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
+ oldObj = &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
+ {{- if .Resource.HasValidationWebhook }}
+ validator = {{ .Resource.Kind }}CustomValidator{}
+ Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
+ {{- end }}
+ {{- if .Resource.HasDefaultingWebhook }}
+ defaulter = {{ .Resource.Kind }}CustomDefaulter{}
+ Expect(defaulter).NotTo(BeNil(), "Expected defaulter to be initialized")
+ {{- end }}
+ Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
+ {{- end }}
+ Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
+ // TODO (user): Add any setup logic common to all tests
+ })
+
+ AfterEach(func() {
+ // TODO (user): Add any teardown logic common to all tests
+ })
+
+ %s
+})
+`
+
+const conversionWebhookTestTemplate = `
+Context("When creating {{ .Resource.Kind }} under Conversion Webhook", func() {
+ // TODO (user): Add logic to convert the object to the desired version and verify the conversion
+ // Example:
+ // It("Should convert the object correctly", func() {
+ {{- if .IsLegacyPath -}}
+ // convertedObj := &{{ .Resource.Kind }}{}
+ {{- else }}
+ // convertedObj := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
+ {{- end }}
+ // Expect(obj.ConvertTo(convertedObj)).To(Succeed())
+ // Expect(convertedObj).ToNot(BeNil())
+ // })
+})
+`
+
+const validateWebhookTestTemplate = `
+Context("When creating or updating {{ .Resource.Kind }} under Validating Webhook", func() {
+ // TODO (user): Add logic for validating webhooks
+ // Example:
+ // It("Should deny creation if a required field is missing", func() {
+ // By("simulating an invalid creation scenario")
+ // obj.SomeRequiredField = ""
+ {{- if .IsLegacyPath -}}
+ // Expect(obj.ValidateCreate(ctx)).Error().To(HaveOccurred())
+ {{- else }}
+ // Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
+ {{- end }}
+ // })
+ //
+ // It("Should admit creation if all required fields are present", func() {
+ // By("simulating an invalid creation scenario")
+ // obj.SomeRequiredField = "valid_value"
+ {{- if .IsLegacyPath -}}
+ // Expect(obj.ValidateCreate(ctx)).To(BeNil())
+ {{- else }}
+ // Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
+ {{- end }}
+ // })
+ //
+ // It("Should validate updates correctly", func() {
+ // By("simulating a valid update scenario")
+ {{- if .IsLegacyPath -}}
+ // oldObj := &Captain{SomeRequiredField: "valid_value"}
+ // obj.SomeRequiredField = "updated_value"
+ // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil())
+ {{- else }}
+ // oldObj.SomeRequiredField = "updated_value"
+ // obj.SomeRequiredField = "updated_value"
+ // Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
+ {{- end }}
+ // })
+})
+`
+
+const defaultWebhookTestTemplate = `
+Context("When creating {{ .Resource.Kind }} under Defaulting Webhook", func() {
+ // TODO (user): Add logic for defaulting webhooks
+ // Example:
+ // It("Should apply defaults when a required field is empty", func() {
+ // By("simulating a scenario where defaults should be applied")
+ {{- if .IsLegacyPath -}}
+ // obj.SomeFieldWithDefault = ""
+ // Expect(obj.Default(ctx)).To(Succeed())
+ // Expect(obj.SomeFieldWithDefault).To(Equal("default_value"))
+ {{- else }}
+ // obj.SomeFieldWithDefault = ""
+ // By("calling the Default method to apply defaults")
+ // defaulter.Default(ctx, obj)
+ // By("checking that the default values are set")
+ // Expect(obj.SomeFieldWithDefault).To(Equal("default_value"))
+ {{- end }}
+ // })
+})
+`
diff --git a/pkg/plugins/golang/v4/scaffolds/webhook.go b/pkg/plugins/golang/v4/scaffolds/webhook.go
new file mode 100644
index 00000000000..efc83e7fcd2
--- /dev/null
+++ b/pkg/plugins/golang/v4/scaffolds/webhook.go
@@ -0,0 +1,170 @@
+/*
+Copyright 2022 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 scaffolds
+
+import (
+ "errors"
+ "fmt"
+ log "log/slog"
+ "strings"
+
+ "github.com/spf13/afero"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/resource"
+ pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/api"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/hack"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks"
+)
+
+var _ plugins.Scaffolder = &webhookScaffolder{}
+
+type webhookScaffolder struct {
+ config config.Config
+ resource resource.Resource
+
+ // fs is the filesystem that will be used by the scaffolder
+ fs machinery.Filesystem
+
+ // force indicates whether to scaffold controller files even if it exists or not
+ force bool
+
+ // Deprecated - TODO: remove it for go/v5
+ // isLegacy indicates that the resource should be created in the legacy path under the api
+ isLegacy bool
+}
+
+// NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations
+func NewWebhookScaffolder(cfg config.Config, res resource.Resource, force bool, isLegacy bool) plugins.Scaffolder {
+ return &webhookScaffolder{
+ config: cfg,
+ resource: res,
+ force: force,
+ isLegacy: isLegacy,
+ }
+}
+
+// InjectFS implements cmdutil.Scaffolder
+func (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) {
+ s.fs = fs
+}
+
+// Scaffold implements cmdutil.Scaffolder
+func (s *webhookScaffolder) Scaffold() error {
+ log.Info("Writing scaffold for you to edit...")
+
+ // Load the boilerplate
+ boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath)
+ if err != nil {
+ if errors.Is(err, afero.ErrFileNotFound) {
+ log.Warn("Unable to find boilerplate file."+
+ "This file is used to generate the license header in the project.\n"+
+ "Note that controller-gen will also use this. Therefore, ensure that you "+
+ "add the license file or configure your project accordingly.",
+ "file_path", hack.DefaultBoilerplatePath, "error", err)
+ boilerplate = []byte("")
+ } else {
+ return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err)
+ }
+ }
+
+ // Initialize the machinery.Scaffold that will write the files to disk
+ scaffold := machinery.NewScaffold(s.fs,
+ machinery.WithConfig(s.config),
+ machinery.WithBoilerplate(string(boilerplate)),
+ machinery.WithResource(&s.resource),
+ )
+
+ // Keep track of these values before the update
+ doDefaulting := s.resource.HasDefaultingWebhook()
+ doValidation := s.resource.HasValidationWebhook()
+ doConversion := s.resource.HasConversionWebhook()
+
+ if err = s.config.UpdateResource(s.resource); err != nil {
+ return fmt.Errorf("error updating resource: %w", err)
+ }
+
+ if err = scaffold.Execute(
+ &webhooks.Webhook{Force: s.force, IsLegacyPath: s.isLegacy},
+ &e2e.WebhookTestUpdater{WireWebhook: true},
+ &cmd.MainUpdater{WireWebhook: true, IsLegacyPath: s.isLegacy},
+ &webhooks.WebhookTest{Force: s.force, IsLegacyPath: s.isLegacy},
+ ); err != nil {
+ return fmt.Errorf("error updating webhook: %w", err)
+ }
+
+ if doConversion {
+ resourceFilePath := fmt.Sprintf("api/%s/%s_types.go",
+ s.resource.Version, strings.ToLower(s.resource.Kind))
+ if s.config.IsMultiGroup() {
+ resourceFilePath = fmt.Sprintf("api/%s/%s/%s_types.go",
+ s.resource.Group, s.resource.Version,
+ strings.ToLower(s.resource.Kind))
+ }
+
+ err = pluginutil.InsertCodeIfNotExist(resourceFilePath,
+ "// +kubebuilder:object:root=true",
+ "\n// +kubebuilder:storageversion")
+ if err != nil {
+ log.Error("Unable to insert storage version marker in file",
+ "marker", "// +kubebuilder:storageversion",
+ "file_path", resourceFilePath,
+ "error", err)
+ }
+
+ if err = scaffold.Execute(&api.Hub{Force: s.force}); err != nil {
+ return fmt.Errorf("error scaffold resource with hub: %w", err)
+ }
+
+ for _, spoke := range s.resource.Webhooks.Spoke {
+ log.Info("Scaffolding for spoke version", "version", spoke)
+ if err = scaffold.Execute(&api.Spoke{Force: s.force, SpokeVersion: spoke}); err != nil {
+ return fmt.Errorf("failed to scaffold spoke %s: %w", spoke, err)
+ }
+ }
+
+ log.Info(`Webhook server has been set up for you.
+You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`)
+ }
+
+ // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest.
+ if doDefaulting || doValidation {
+ if err = scaffold.Execute(&webhooks.WebhookSuite{IsLegacyPath: s.isLegacy}); err != nil {
+ return fmt.Errorf("error scaffold webhook suite: %w", err)
+ }
+ }
+
+ // TODO: remove for go/v5
+ if !s.isLegacy {
+ if hasInternalController, err := pluginutil.HasFileContentWith("Dockerfile", "internal/controller"); err != nil {
+ log.Error("Unable to read Dockerfile to check if webhook(s) will be properly copied", "error", err)
+ } else if hasInternalController {
+ log.Warn("Dockerfile is copying internal/controller. To allow copying webhooks, " +
+ "it will be edited, and `internal/controller` will be replaced by `internal/`.")
+
+ if err = pluginutil.ReplaceInFile("Dockerfile", "internal/controller", "internal/"); err != nil {
+ log.Error("Unable to replace \"internal/controller\" with \"internal/\" in the Dockerfile", "error", err)
+ }
+ }
+ }
+ return nil
+}
diff --git a/pkg/plugins/golang/v4/webhook.go b/pkg/plugins/golang/v4/webhook.go
new file mode 100644
index 00000000000..c51b5643022
--- /dev/null
+++ b/pkg/plugins/golang/v4/webhook.go
@@ -0,0 +1,230 @@
+/*
+Copyright 2022 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 v4
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/spf13/pflag"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/resource"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
+ goPlugin "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds"
+)
+
+var _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{}
+
+type createWebhookSubcommand struct {
+ config config.Config
+ // For help text.
+ commandName string
+
+ options *goPlugin.Options
+
+ resource *resource.Resource
+
+ // force indicates that the resource should be created even if it already exists
+ force bool
+
+ // Deprecated - TODO: remove it for go/v5
+ // isLegacyPath indicates that the resource should be created in the legacy path under the api
+ isLegacyPath bool
+
+ // runMake indicates whether to run make or not after scaffolding APIs
+ runMake bool
+}
+
+func (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
+ p.commandName = cliMeta.CommandName
+
+ subcmdMeta.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting,
+validating and/or conversion webhooks.
+`
+ subcmdMeta.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for Group: ship, Version: v1beta1
+ # and Kind: Frigate
+ %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation
+
+ # Create conversion webhook for Group: ship, Version: v1beta1
+ # and Kind: Frigate
+ %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion --spoke v1
+
+ # Create defaulting webhook with custom path for Group: ship, Version: v1beta1
+ # and Kind: Frigate
+ %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting \
+ --defaulting-path=/my-custom-mutate-path
+
+ # Create validation webhook with custom path for Group: ship, Version: v1beta1
+ # and Kind: Frigate
+ %[1]s create webhook --group ship --version v1beta1 --kind Frigate \
+ --programmatic-validation --validation-path=/my-custom-validate-path
+
+ # Create both defaulting and validation webhooks with different custom paths
+ %[1]s create webhook --group ship --version v1beta1 --kind Frigate \
+ --defaulting --programmatic-validation \
+ --defaulting-path=/custom-mutate --validation-path=/custom-validate
+`, cliMeta.CommandName)
+}
+
+func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) {
+ p.options = &goPlugin.Options{}
+
+ fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files")
+
+ fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form")
+
+ fs.BoolVar(&p.options.DoDefaulting, "defaulting", false,
+ "if set, scaffold the defaulting webhook")
+ fs.BoolVar(&p.options.DoValidation, "programmatic-validation", false,
+ "if set, scaffold the validating webhook")
+ fs.BoolVar(&p.options.DoConversion, "conversion", false,
+ "if set, scaffold the conversion webhook")
+
+ fs.StringSliceVar(&p.options.Spoke, "spoke",
+ nil,
+ "Comma-separated list of spoke versions to be added to the conversion webhook (e.g., --spoke v1,v2)")
+
+ fs.StringVar(&p.options.DefaultingPath, "defaulting-path", "",
+ "Custom path for the defaulting/mutating webhook (only valid with --defaulting)")
+
+ fs.StringVar(&p.options.ValidationPath, "validation-path", "",
+ "Custom path for the validation webhook (only valid with --programmatic-validation)")
+
+ // TODO: remove for go/v5
+ fs.BoolVar(&p.isLegacyPath, "legacy", false,
+ "[DEPRECATED] Attempts to create resource under the API directory (legacy path). "+
+ "This option will be removed in future versions.")
+
+ fs.StringVar(&p.options.ExternalAPIPath, "external-api-path", "",
+ "Specify the Go package import path for the external API. This is used to scaffold controllers for resources "+
+ "defined outside this project (e.g., github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1).")
+
+ fs.StringVar(&p.options.ExternalAPIDomain, "external-api-domain", "",
+ "Specify the domain name for the external API. This domain is used to generate accurate RBAC "+
+ "markers and permissions for the external resources (e.g., cert-manager.io).")
+
+ fs.BoolVar(&p.force, "force", false,
+ "attempt to create resource even if it already exists")
+}
+
+func (p *createWebhookSubcommand) InjectConfig(c config.Config) error {
+ p.config = c
+ return nil
+}
+
+func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error {
+ p.resource = res
+
+ if len(p.options.ExternalAPIPath) != 0 && len(p.options.ExternalAPIDomain) != 0 && p.isLegacyPath {
+ return errors.New("you cannot scaffold webhooks for external types using the legacy path")
+ }
+
+ for _, spoke := range p.options.Spoke {
+ spoke = strings.TrimSpace(spoke)
+ if !isValidVersion(spoke, res, p.config) {
+ return fmt.Errorf("invalid spoke version %q", spoke)
+ }
+ res.Webhooks.Spoke = append(res.Webhooks.Spoke, spoke)
+ }
+
+ // Validate path flags are only used with appropriate webhook types
+ if p.options.DefaultingPath != "" && !p.options.DoDefaulting {
+ return fmt.Errorf("--defaulting-path can only be used with --defaulting")
+ }
+ if p.options.ValidationPath != "" && !p.options.DoValidation {
+ return fmt.Errorf("--validation-path can only be used with --programmatic-validation")
+ }
+
+ p.options.UpdateResource(p.resource, p.config)
+
+ if err := p.resource.Validate(); err != nil {
+ return fmt.Errorf("error validating resource: %w", err)
+ }
+
+ if !p.resource.HasDefaultingWebhook() && !p.resource.HasValidationWebhook() && !p.resource.HasConversionWebhook() {
+ return fmt.Errorf("%s create webhook requires at least one of --defaulting,"+
+ " --programmatic-validation and --conversion to be true", p.commandName)
+ }
+
+ // check if resource exist to create webhook
+ resValue, err := p.config.GetResource(p.resource.GVK)
+ res = &resValue
+ if err != nil {
+ if !p.resource.External && !p.resource.Core {
+ return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName)
+ }
+ } else if res.Webhooks != nil && !res.Webhooks.IsEmpty() && !p.force {
+ // FIXME: This is a temporary fix to allow we move forward
+ // However, users should be able to call the command to create an webhook
+ // even if the resource already has one when the webhook is not of the same type.
+ return fmt.Errorf("webhook resource already exists")
+ }
+
+ return nil
+}
+
+func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error {
+ scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force, p.isLegacyPath)
+ scaffolder.InjectFS(fs)
+ if err := scaffolder.Scaffold(); err != nil {
+ return fmt.Errorf("failed to scaffold webhook: %w", err)
+ }
+
+ return nil
+}
+
+func (p *createWebhookSubcommand) PostScaffold() error {
+ err := pluginutil.RunCmd("Update dependencies", "go", "mod", "tidy")
+ if err != nil {
+ return fmt.Errorf("error updating go dependencies: %w", err)
+ }
+
+ if p.runMake {
+ err = pluginutil.RunCmd("Running make", "make", "generate")
+ if err != nil {
+ return fmt.Errorf("error running make generate: %w", err)
+ }
+ }
+
+ fmt.Print("Next: implement your new Webhook and generate the manifests with:\n$ make manifests\n")
+
+ return nil
+}
+
+// Helper function to validate spoke versions
+func isValidVersion(version string, res *resource.Resource, cfg config.Config) bool {
+ // Fetch all resources in the config
+ resources, err := cfg.GetResources()
+ if err != nil {
+ return false
+ }
+
+ // Iterate through resources and validate if the given version exists for the same Group and Kind
+ for _, r := range resources {
+ if r.Group == res.Group && r.Kind == res.Kind && r.Version == version {
+ return true
+ }
+ }
+
+ // If no matching version is found, return false
+ return false
+}
diff --git a/pkg/plugins/optional/autoupdate/v1alpha/edit.go b/pkg/plugins/optional/autoupdate/v1alpha/edit.go
new file mode 100644
index 00000000000..bfaa05d5bec
--- /dev/null
+++ b/pkg/plugins/optional/autoupdate/v1alpha/edit.go
@@ -0,0 +1,70 @@
+/*
+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 v1alpha
+
+import (
+ "fmt"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha/scaffolds"
+)
+
+var _ plugin.EditSubcommand = &editSubcommand{}
+
+type editSubcommand struct {
+ config config.Config
+}
+
+func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
+ subcmdMeta.Description = metaDataDescription
+
+ subcmdMeta.Examples = fmt.Sprintf(` # Edit a common project with this plugin
+ %[1]s edit --plugins=%[2]s
+`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))
+}
+
+func (p *editSubcommand) InjectConfig(c config.Config) error {
+ p.config = c
+ return nil
+}
+
+func (p *editSubcommand) PreScaffold(machinery.Filesystem) error {
+ if len(p.config.GetCliVersion()) == 0 {
+ return fmt.Errorf(
+ "you must manually upgrade your project to a version that records the CLI version in PROJECT (`cliVersion`) " +
+ "to allow the `alpha update` command to work properly before using this plugin.\n" +
+ "More info: https://book.kubebuilder.io/migrations",
+ )
+ }
+ return nil
+}
+
+func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {
+ if err := insertPluginMetaToConfig(p.config, pluginConfig{}); err != nil {
+ return fmt.Errorf("error inserting project plugin meta to configuration: %w", err)
+ }
+
+ scaffolder := scaffolds.NewInitScaffolder()
+ scaffolder.InjectFS(fs)
+ if err := scaffolder.Scaffold(); err != nil {
+ return fmt.Errorf("error scaffolding edit subcommand: %w", err)
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/optional/autoupdate/v1alpha/init.go b/pkg/plugins/optional/autoupdate/v1alpha/init.go
new file mode 100644
index 00000000000..1bc04ad0804
--- /dev/null
+++ b/pkg/plugins/optional/autoupdate/v1alpha/init.go
@@ -0,0 +1,59 @@
+/*
+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 v1alpha
+
+import (
+ "fmt"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha/scaffolds"
+)
+
+var _ plugin.InitSubcommand = &initSubcommand{}
+
+type initSubcommand struct {
+ config config.Config
+}
+
+func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
+ subcmdMeta.Description = metaDataDescription
+
+ subcmdMeta.Examples = fmt.Sprintf(` # Initialize a common project with this plugin
+ %[1]s init --plugins=%[2]s
+`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))
+}
+
+func (p *initSubcommand) InjectConfig(c config.Config) error {
+ p.config = c
+ return nil
+}
+
+func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {
+ if err := insertPluginMetaToConfig(p.config, pluginConfig{}); err != nil {
+ return fmt.Errorf("error inserting project plugin meta to configuration: %w", err)
+ }
+
+ scaffolder := scaffolds.NewInitScaffolder()
+ scaffolder.InjectFS(fs)
+ if err := scaffolder.Scaffold(); err != nil {
+ return fmt.Errorf("error scaffolding init subcommand: %w", err)
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/optional/autoupdate/v1alpha/plugin.go b/pkg/plugins/optional/autoupdate/v1alpha/plugin.go
new file mode 100644
index 00000000000..6ee4ba0f107
--- /dev/null
+++ b/pkg/plugins/optional/autoupdate/v1alpha/plugin.go
@@ -0,0 +1,111 @@
+/*
+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 v1alpha
+
+import (
+ "errors"
+ "fmt"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/stage"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+)
+
+//nolint:lll
+const metaDataDescription = `This plugin scaffolds a GitHub Action that helps you keep your project aligned with the latest Kubebuilder improvements. With a tiny amount of setup, you’ll receive **automatic issue notifications** whenever a new Kubebuilder release is available. Each issue includes a **compare link** so you can open a Pull Request with one click and review the changes safely.
+
+Under the hood, the workflow runs 'kubebuilder alpha update' using a **3-way merge strategy** to refresh your scaffold while preserving your code. It creates and pushes an update branch, then opens a GitHub **Issue** containing the PR URL you can use to review and merge.
+
+### How to set it up
+
+1) **Add the plugin**: Use the Kubebuilder CLI to scaffold the automation into your repo.
+2) **Review the workflow**: The file '.github/workflows/auto_update.yml' runs on a schedule to check for updates.
+3) **Permissions required** (via the built-in 'GITHUB_TOKEN'):
+ - **contents: write** — needed to create and push the update branch.
+ - **issues: write** — needed to create the tracking Issue with the PR link.
+4) **Protect your branches**: Enable **branch protection rules** so automated changes **cannot** be pushed directly. All updates must go through a Pull Request for review.`
+
+const pluginName = "autoupdate." + plugins.DefaultNameQualifier
+
+var (
+ pluginVersion = plugin.Version{Number: 1, Stage: stage.Alpha}
+ supportedProjectVersions = []config.Version{cfgv3.Version}
+)
+
+// Plugin implements the plugin.Full interface
+type Plugin struct {
+ editSubcommand
+ initSubcommand
+}
+
+var _ plugin.Init = Plugin{}
+
+type pluginConfig struct{}
+
+// Name returns the name of the plugin
+func (Plugin) Name() string { return pluginName }
+
+// Version returns the version of the Helm plugin
+func (Plugin) Version() plugin.Version { return pluginVersion }
+
+// SupportedProjectVersions returns an array with all project versions supported by the plugin
+func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }
+
+// GetEditSubcommand will return the subcommand which is responsible for adding and/or edit a autoupdate
+func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }
+
+// GetInitSubcommand will return the subcommand which is responsible for init autoupdate plugin
+func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand }
+
+// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated
+func (p Plugin) DeprecationWarning() string {
+ return ""
+}
+
+// insertPluginMetaToConfig will insert the metadata to the plugin configuration
+func insertPluginMetaToConfig(target config.Config, cfg pluginConfig) error {
+ key := plugin.GetPluginKeyForConfig(target.GetPluginChain(), Plugin{})
+ canonicalKey := plugin.KeyFor(Plugin{})
+
+ if err := target.DecodePluginConfig(key, &cfg); err != nil {
+ switch {
+ case errors.As(err, &config.UnsupportedFieldError{}):
+ return nil
+ case errors.As(err, &config.PluginKeyNotFoundError{}):
+ if key != canonicalKey {
+ if err2 := target.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {
+ if errors.As(err2, &config.UnsupportedFieldError{}) {
+ return nil
+ }
+ if !errors.As(err2, &config.PluginKeyNotFoundError{}) {
+ return fmt.Errorf("error decoding plugin configuration: %w", err2)
+ }
+ }
+ }
+ default:
+ return fmt.Errorf("error decoding plugin configuration: %w", err)
+ }
+ }
+
+ if err := target.EncodePluginConfig(key, cfg); err != nil {
+ return fmt.Errorf("error encoding plugin configuration: %w", err)
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/optional/autoupdate/v1alpha/scaffolds/init.go b/pkg/plugins/optional/autoupdate/v1alpha/scaffolds/init.go
new file mode 100644
index 00000000000..c36af8f2a8a
--- /dev/null
+++ b/pkg/plugins/optional/autoupdate/v1alpha/scaffolds/init.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2022 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 scaffolds
+
+import (
+ "fmt"
+ log "log/slog"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha/scaffolds/internal/github"
+)
+
+var _ plugins.Scaffolder = &initScaffolder{}
+
+type initScaffolder struct {
+ config config.Config
+
+ // fs is the filesystem that will be used by the scaffolder
+ fs machinery.Filesystem
+}
+
+// NewInitScaffolder returns a new Scaffolder for project initialization operations
+func NewInitScaffolder() plugins.Scaffolder {
+ return &initScaffolder{}
+}
+
+// InjectFS implements cmdutil.Scaffolder
+func (s *initScaffolder) InjectFS(fs machinery.Filesystem) {
+ s.fs = fs
+}
+
+// Scaffold implements cmdutil.Scaffolder
+func (s *initScaffolder) Scaffold() error {
+ log.Info("Writing scaffold for you to edit...")
+
+ scaffold := machinery.NewScaffold(s.fs,
+ machinery.WithConfig(s.config),
+ )
+
+ err := scaffold.Execute(
+ &github.AutoUpdate{},
+ )
+ if err != nil {
+ return fmt.Errorf("failed to execute init scaffold: %w", err)
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/optional/autoupdate/v1alpha/scaffolds/internal/github/auto_update.go b/pkg/plugins/optional/autoupdate/v1alpha/scaffolds/internal/github/auto_update.go
new file mode 100644
index 00000000000..80a50b768a2
--- /dev/null
+++ b/pkg/plugins/optional/autoupdate/v1alpha/scaffolds/internal/github/auto_update.go
@@ -0,0 +1,119 @@
+/*
+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 github
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &AutoUpdate{}
+
+// AutoUpdate scaffolds the GitHub Action to lint the project
+type AutoUpdate struct {
+ machinery.TemplateMixin
+ machinery.BoilerplateMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *AutoUpdate) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join(".github", "workflows", "auto_update.yml")
+ }
+
+ f.TemplateBody = autoUpdateTemplate
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+const autoUpdateTemplate = `name: Auto Update
+
+# The 'kubebuilder alpha update 'command requires write access to the repository to create a branch
+# with the update files and allow you to open a pull request using the link provided in the issue.
+# The branch created will be named in the format kubebuilder-update-from--to- by default.
+# To protect your codebase, please ensure that you have branch protection rules configured for your
+# main branches. This will guarantee that no one can bypass a review and push directly to a branch like 'main'.
+permissions:
+ contents: write
+ issues: write
+ models: read
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: "0 0 * * 2" # Every Tuesday at 00:00 UTC
+
+jobs:
+ auto-update:
+ runs-on: ubuntu-latest
+ env:
+ GH_TOKEN: {{ "${{ secrets.GITHUB_TOKEN }}" }}
+
+ # Step 1: Checkout the repository.
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ token: {{ "${{ secrets.GITHUB_TOKEN }}" }}
+ fetch-depth: 0
+
+ # Step 2: Configure Git to create commits with the GitHub Actions bot.
+ - name: Configure Git
+ run: |
+ git config --global user.name "github-actions[bot]"
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+
+ # Step 3: Set up Go environment.
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: stable
+
+ # Step 4: Install Kubebuilder.
+ - name: Install Kubebuilder
+ run: |
+ curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
+ chmod +x kubebuilder
+ sudo mv kubebuilder /usr/local/bin/
+ kubebuilder version
+
+ # Step 5: Install Models extension for GitHub CLI
+ - name: Install/upgrade gh-models extension
+ run: |
+ gh extension install github/gh-models --force
+ gh models --help >/dev/null
+
+ # Step 6: Run the Kubebuilder alpha update command.
+ # More info: https://kubebuilder.io/reference/commands/alpha_update
+ - name: Run kubebuilder alpha update
+ # Executes the update command with specified flags.
+ # --force: Completes the merge even if conflicts occur, leaving conflict markers.
+ # --push: Automatically pushes the resulting output branch to the 'origin' remote.
+ # --restore-path: Preserves specified paths (e.g., CI workflow files) when squashing.
+ # --open-gh-issue: Creates a GitHub Issue with a link for opening a PR for review.
+ # --use-gh-models: Adds an AI-generated comment to the created Issue with
+ # a short overview of the scaffold changes and conflict-resolution guidance (If Any).
+ run: |
+ kubebuilder alpha update \
+ --force \
+ --push \
+ --restore-path .github/workflows \
+ --open-gh-issue \
+ --use-gh-models
+`
diff --git a/pkg/plugins/optional/grafana/v1alpha/commons.go b/pkg/plugins/optional/grafana/v1alpha/commons.go
index a372a94e983..06761c95006 100644
--- a/pkg/plugins/optional/grafana/v1alpha/commons.go
+++ b/pkg/plugins/optional/grafana/v1alpha/commons.go
@@ -18,22 +18,39 @@ package v1alpha
import (
"errors"
+ "fmt"
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
)
+// InsertPluginMetaToConfig will insert the metadata to the plugin configuration
func InsertPluginMetaToConfig(target config.Config, cfg pluginConfig) error {
- err := target.DecodePluginConfig(pluginKey, cfg)
- if !errors.As(err, &config.UnsupportedFieldError{}) {
-
- if err != nil && !errors.As(err, &config.PluginKeyNotFoundError{}) {
- return err
- }
-
- if err = target.EncodePluginConfig(pluginKey, cfg); err != nil {
- return err
+ key := plugin.GetPluginKeyForConfig(target.GetPluginChain(), Plugin{})
+ canonicalKey := plugin.KeyFor(Plugin{})
+
+ if err := target.DecodePluginConfig(key, &cfg); err != nil {
+ switch {
+ case errors.As(err, &config.UnsupportedFieldError{}):
+ return nil
+ case errors.As(err, &config.PluginKeyNotFoundError{}):
+ if key != canonicalKey {
+ if err2 := target.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {
+ if errors.As(err2, &config.UnsupportedFieldError{}) {
+ return nil
+ }
+ if !errors.As(err2, &config.PluginKeyNotFoundError{}) {
+ return fmt.Errorf("error decoding plugin configuration: %w", err2)
+ }
+ }
+ }
+ default:
+ return fmt.Errorf("error decoding plugin configuration: %w", err)
}
+ }
+ if err := target.EncodePluginConfig(key, cfg); err != nil {
+ return fmt.Errorf("error encoding plugin configuration: %w", err)
}
return nil
diff --git a/pkg/plugins/optional/grafana/v1alpha/constants.go b/pkg/plugins/optional/grafana/v1alpha/constants.go
index 0388f3be2f2..402cecbafed 100644
--- a/pkg/plugins/optional/grafana/v1alpha/constants.go
+++ b/pkg/plugins/optional/grafana/v1alpha/constants.go
@@ -16,8 +16,8 @@ limitations under the License.
package v1alpha
-// nolint: lll
-const MetaDataDescription = `This command will add Grafana manifests to the project:
+//nolint:lll
+const metaDataDescription = `This command will add Grafana manifests to the project:
- A JSON file includes dashboard manifest that can be directly copied to Grafana Web UI.
('grafana/controller-runtime-metrics.json')
diff --git a/pkg/plugins/optional/grafana/v1alpha/edit.go b/pkg/plugins/optional/grafana/v1alpha/edit.go
index 2205e8df9f2..90c4180efb1 100644
--- a/pkg/plugins/optional/grafana/v1alpha/edit.go
+++ b/pkg/plugins/optional/grafana/v1alpha/edit.go
@@ -14,15 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+//nolint:dupl
package v1alpha
import (
"fmt"
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/optional/grafana/v1alpha/scaffolds"
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds"
)
var _ plugin.EditSubcommand = &editSubcommand{}
@@ -32,11 +33,11 @@ type editSubcommand struct {
}
func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- subcmdMeta.Description = MetaDataDescription
+ subcmdMeta.Description = metaDataDescription
subcmdMeta.Examples = fmt.Sprintf(` # Edit a common project with this plugin
- %[1]s edit --plugins=grafana.kubebuilder.io/v1-alpha
-`, cliMeta.CommandName)
+ %[1]s edit --plugins=%[2]s
+`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))
}
func (p *editSubcommand) InjectConfig(c config.Config) error {
@@ -46,10 +47,14 @@ func (p *editSubcommand) InjectConfig(c config.Config) error {
func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {
if err := InsertPluginMetaToConfig(p.config, pluginConfig{}); err != nil {
- return err
+ return fmt.Errorf("error inserting project plugin meta to configuration: %w", err)
}
scaffolder := scaffolds.NewEditScaffolder()
scaffolder.InjectFS(fs)
- return scaffolder.Scaffold()
+ if err := scaffolder.Scaffold(); err != nil {
+ return fmt.Errorf("error scaffolding edit subcommand: %w", err)
+ }
+
+ return nil
}
diff --git a/pkg/plugins/optional/grafana/v1alpha/init.go b/pkg/plugins/optional/grafana/v1alpha/init.go
index b824bfcb220..fa400fdf8f0 100644
--- a/pkg/plugins/optional/grafana/v1alpha/init.go
+++ b/pkg/plugins/optional/grafana/v1alpha/init.go
@@ -14,15 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+//nolint:dupl
package v1alpha
import (
"fmt"
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/optional/grafana/v1alpha/scaffolds"
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds"
)
var _ plugin.InitSubcommand = &initSubcommand{}
@@ -32,11 +33,11 @@ type initSubcommand struct {
}
func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
- subcmdMeta.Description = MetaDataDescription
+ subcmdMeta.Description = metaDataDescription
subcmdMeta.Examples = fmt.Sprintf(` # Initialize a common project with this plugin
- %[1]s init --plugins=grafana.kubebuilder.io/v1-alpha
-`, cliMeta.CommandName)
+ %[1]s init --plugins=%[2]s
+`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))
}
func (p *initSubcommand) InjectConfig(c config.Config) error {
@@ -46,10 +47,14 @@ func (p *initSubcommand) InjectConfig(c config.Config) error {
func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {
if err := InsertPluginMetaToConfig(p.config, pluginConfig{}); err != nil {
- return err
+ return fmt.Errorf("error inserting project plugin meta to configuration: %w", err)
}
scaffolder := scaffolds.NewInitScaffolder()
scaffolder.InjectFS(fs)
- return scaffolder.Scaffold()
+ if err := scaffolder.Scaffold(); err != nil {
+ return fmt.Errorf("error scaffolding init subcommand: %w", err)
+ }
+
+ return nil
}
diff --git a/pkg/plugins/optional/grafana/v1alpha/plugin.go b/pkg/plugins/optional/grafana/v1alpha/plugin.go
index d4f9ee4f7dd..7c56ea5fb57 100644
--- a/pkg/plugins/optional/grafana/v1alpha/plugin.go
+++ b/pkg/plugins/optional/grafana/v1alpha/plugin.go
@@ -17,11 +17,11 @@ limitations under the License.
package v1alpha
import (
- "sigs.k8s.io/kubebuilder/v3/pkg/config"
- cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3"
- "sigs.k8s.io/kubebuilder/v3/pkg/model/stage"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugin"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/stage"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
)
const pluginName = "grafana." + plugins.DefaultNameQualifier
@@ -29,7 +29,6 @@ const pluginName = "grafana." + plugins.DefaultNameQualifier
var (
pluginVersion = plugin.Version{Number: 1, Stage: stage.Alpha}
supportedProjectVersions = []config.Version{cfgv3.Version}
- pluginKey = plugin.KeyFor(Plugin{})
)
// Plugin implements the plugin.Full interface
@@ -38,9 +37,7 @@ type Plugin struct {
editSubcommand
}
-var (
- _ plugin.Init = Plugin{}
-)
+var _ plugin.Init = Plugin{}
// Name returns the name of the plugin
func (Plugin) Name() string { return pluginName }
@@ -58,3 +55,8 @@ func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcom
func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }
type pluginConfig struct{}
+
+// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated
+func (p Plugin) DeprecationWarning() string {
+ return ""
+}
diff --git a/pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go b/pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go
index 6cf8edf7d8b..132c229c0f2 100644
--- a/pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go
+++ b/pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go
@@ -19,14 +19,15 @@ package scaffolds
import (
"fmt"
"io"
+ log "log/slog"
"os"
"strings"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates"
-
"sigs.k8s.io/yaml"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates"
)
var _ plugins.Scaffolder = &editScaffolder{}
@@ -60,32 +61,35 @@ func loadConfig(configPath string) ([]templates.CustomMetricItem, error) {
return nil, nil
}
- // nolint:gosec
+ //nolint:gosec
f, err := os.Open(configPath)
if err != nil {
return nil, fmt.Errorf("error loading plugin config: %w", err)
}
items, err := configReader(f)
+ if err != nil {
+ return nil, fmt.Errorf("error reading config.yaml: %w", err)
+ }
- if err := f.Close(); err != nil {
+ if err = f.Close(); err != nil {
return nil, fmt.Errorf("could not close config.yaml: %w", err)
}
- return items, err
+ return items, nil
}
func configReader(reader io.Reader) ([]templates.CustomMetricItem, error) {
yamlFile, err := io.ReadAll(reader)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("error reading config.yaml: %w", err)
}
config := templates.CustomMetricsConfig{}
err = yaml.Unmarshal(yamlFile, &config)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("error parsing config.yaml: %w", err)
}
validatedMetricItems := validateCustomMetricItems(config.CustomMetrics)
@@ -95,7 +99,7 @@ func configReader(reader io.Reader) ([]templates.CustomMetricItem, error) {
func validateCustomMetricItems(rawItems []templates.CustomMetricItem) []templates.CustomMetricItem {
// 1. Filter items of missing `Metric` or `Type`
- filterResult := []templates.CustomMetricItem{}
+ var filterResult []templates.CustomMetricItem
for _, item := range rawItems {
if hasFields(item) {
filterResult = append(filterResult, item)
@@ -134,7 +138,7 @@ func fillMissingExpr(item templates.CustomMetricItem) templates.CustomMetricItem
case "counter":
item.Expr = "sum(rate(" + item.Metric + `{job=\"$job\", namespace=\"$namespace\"}[5m])) by (instance, pod)`
case "histogram":
- // nolint: lll
+ //nolint:lll
item.Expr = "histogram_quantile(0.90, sum by(instance, le) (rate(" + item.Metric + `{job=\"$job\", namespace=\"$namespace\"}[5m])))`
default: // gauge
item.Expr = item.Metric
@@ -160,14 +164,14 @@ func fillMissingUnit(item templates.CustomMetricItem) templates.CustomMetricItem
// Scaffold implements cmdutil.Scaffolder
func (s *editScaffolder) Scaffold() error {
- fmt.Println("Generating Grafana manifests to visualize controller status...")
+ log.Info("Generating Grafana manifests to visualize controller status...")
// Initialize the machinery.Scaffold that will write the files to disk
scaffold := machinery.NewScaffold(s.fs)
- configPath := string(configFilePath)
+ configPath := configFilePath
- var templatesBuilder = []machinery.Builder{
+ templatesBuilder := []machinery.Builder{
&templates.RuntimeManifest{},
&templates.ResourcesManifest{},
&templates.CustomMetricsConfigManifest{ConfigPath: configPath},
@@ -177,8 +181,12 @@ func (s *editScaffolder) Scaffold() error {
if err == nil && len(configItems) > 0 {
templatesBuilder = append(templatesBuilder, &templates.CustomMetricsDashManifest{Items: configItems})
} else if err != nil {
- fmt.Fprintf(os.Stderr, "Error on scaffolding manifest for custom metris:\n%v", err)
+ _, _ = fmt.Fprintf(os.Stderr, "Error on scaffolding manifest for custom metris:\n%v", err)
+ }
+
+ if err = scaffold.Execute(templatesBuilder...); err != nil {
+ return fmt.Errorf("error scaffolding Grafana manifests: %w", err)
}
- return scaffold.Execute(templatesBuilder...)
+ return nil
}
diff --git a/pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go b/pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go
index 74eb15c1c9b..f67ffb621e8 100644
--- a/pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go
+++ b/pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go
@@ -18,10 +18,11 @@ package scaffolds
import (
"fmt"
+ log "log/slog"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins"
- "sigs.k8s.io/kubebuilder/v3/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates"
)
var _ plugins.Scaffolder = &initScaffolder{}
@@ -43,14 +44,19 @@ func (s *initScaffolder) InjectFS(fs machinery.Filesystem) {
// Scaffold implements cmdutil.Scaffolder
func (s *initScaffolder) Scaffold() error {
- fmt.Println("Generating Grafana manifests to visualize controller status...")
+ log.Info("Generating Grafana manifests to visualize controller status...")
// Initialize the machinery.Scaffold that will write the files to disk
scaffold := machinery.NewScaffold(s.fs)
- return scaffold.Execute(
+ err := scaffold.Execute(
&templates.RuntimeManifest{},
&templates.ResourcesManifest{},
- &templates.CustomMetricsConfigManifest{ConfigPath: string(configFilePath)},
+ &templates.CustomMetricsConfigManifest{ConfigPath: configFilePath},
)
+ if err != nil {
+ return fmt.Errorf("error scaffolding Grafana memanifests: %w", err)
+ }
+
+ return nil
}
diff --git a/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom.go b/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom.go
index 2e4e0810ec7..faa8dcb73bc 100644
--- a/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom.go
+++ b/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom.go
@@ -17,18 +17,18 @@ limitations under the License.
package templates
import (
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
)
var _ machinery.Template = &CustomMetricsConfigManifest{}
-// Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder
+// CustomMetricsConfigManifest scaffolds a file that defines the kustomization scheme for the prometheus folder
type CustomMetricsConfigManifest struct {
machinery.TemplateMixin
ConfigPath string
}
-// SetTemplateDefaults implements file.Template
+// SetTemplateDefaults implements machinery.Template
func (f *CustomMetricsConfigManifest) SetTemplateDefaults() error {
f.Path = f.ConfigPath
@@ -39,7 +39,6 @@ func (f *CustomMetricsConfigManifest) SetTemplateDefaults() error {
return nil
}
-// nolint: lll
const customMetricsConfigTemplate = `---
customMetrics:
# - metric: # Raw custom metric (required)
diff --git a/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom_metrics.go b/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom_metrics.go
index 2a1d532dd24..9cdece75a2d 100644
--- a/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom_metrics.go
+++ b/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom_metrics.go
@@ -23,13 +23,15 @@ import (
"strings"
"text/template"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
)
+// CustomMetricsConfig represents the configuration for custom metrics
type CustomMetricsConfig struct {
CustomMetrics []CustomMetricItem `json:"customMetrics"`
}
+// CustomMetricItem defines the config items for the custom metrics
type CustomMetricItem struct {
Metric string `json:"metric"`
Type string `json:"type"`
@@ -39,14 +41,14 @@ type CustomMetricItem struct {
var _ machinery.Template = &CustomMetricsDashManifest{}
-// Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder
+// CustomMetricsDashManifest scaffolds a file that defines the kustomization scheme for the prometheus folder
type CustomMetricsDashManifest struct {
machinery.TemplateMixin
Items []CustomMetricItem
}
-// SetTemplateDefaults implements file.Template
+// SetTemplateDefaults implements machinery.Template
func (f *CustomMetricsDashManifest) SetTemplateDefaults() error {
if f.Path == "" {
f.Path = filepath.Join("grafana", "custom-metrics", "custom-metrics-dashboard.json")
@@ -82,7 +84,6 @@ func (f *CustomMetricsDashManifest) createTemplate() (string, error) {
return outputTmpl.String(), nil
}
-// nolint: lll
const customMetricsDashTemplate = `{
"__inputs": [
{
diff --git a/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/resources.go b/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/resources.go
index fa19c5de265..928593a2609 100644
--- a/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/resources.go
+++ b/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/resources.go
@@ -19,17 +19,17 @@ package templates
import (
"path/filepath"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
)
var _ machinery.Template = &ResourcesManifest{}
-// Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder
+// ResourcesManifest scaffolds a file that defines the kustomization scheme for the prometheus folder
type ResourcesManifest struct {
machinery.TemplateMixin
}
-// SetTemplateDefaults implements file.Template
+// SetTemplateDefaults implements machinery.Template
func (f *ResourcesManifest) SetTemplateDefaults() error {
if f.Path == "" {
f.Path = filepath.Join("grafana", "controller-resources-metrics.json")
@@ -45,7 +45,6 @@ func (f *ResourcesManifest) SetTemplateDefaults() error {
return nil
}
-// nolint: lll
const controllerResourcesTemplate = `{
"__inputs": [
{
diff --git a/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/runtime.go b/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/runtime.go
index 85321d63005..8973ac8fb45 100644
--- a/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/runtime.go
+++ b/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/runtime.go
@@ -19,17 +19,17 @@ package templates
import (
"path/filepath"
- "sigs.k8s.io/kubebuilder/v3/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
)
var _ machinery.Template = &RuntimeManifest{}
-// Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder
+// RuntimeManifest scaffolds a file that defines the kustomization scheme for the prometheus folder
type RuntimeManifest struct {
machinery.TemplateMixin
}
-// SetTemplateDefaults implements file.Template
+// SetTemplateDefaults implements machinery.Template
func (f *RuntimeManifest) SetTemplateDefaults() error {
if f.Path == "" {
f.Path = filepath.Join("grafana", "controller-runtime-metrics.json")
@@ -44,7 +44,7 @@ func (f *RuntimeManifest) SetTemplateDefaults() error {
return nil
}
-// nolint: lll
+//nolint:lll
const controllerRuntimeTemplate = `{
"__inputs": [
{
@@ -105,6 +105,62 @@ const controllerRuntimeTemplate = `{
"title": "Reconciliation Metrics",
"type": "row"
},
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "fieldConfig": {
+ "defaults": {
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "orange",
+ "value": 70
+ },
+ {
+ "color": "red",
+ "value": 85
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 3,
+ "x": 0,
+ "y": 1
+ },
+ "id": 24,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": ["lastNotNull"],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.5.3",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "exemplar": true,
+ "expr": "controller_runtime_active_workers{job=\"$job\", namespace=\"$namespace\"}",
+ "interval": "",
+ "legendFormat": "{{controller}} {{instance}}",
+ "refId": "A"
+ }
+ ],
+ "title": "Number of workers in use",
+ "type": "gauge"
+ },
{
"datasource": "${DS_PROMETHEUS}",
"description": "Total number of reconciliations per controller",
@@ -114,6 +170,8 @@ const controllerRuntimeTemplate = `{
"mode": "continuous-GrYlRd"
},
"custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
@@ -160,17 +218,18 @@ const controllerRuntimeTemplate = `{
"overrides": []
},
"gridPos": {
- "h": 7,
- "w": 12,
- "x": 0,
+ "h": 8,
+ "w": 11,
+ "x": 3,
"y": 1
},
"id": 7,
"options": {
"legend": {
"calcs": [],
- "displayMode": "list",
- "placement": "bottom"
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
},
"tooltip": {
"mode": "single",
@@ -201,6 +260,8 @@ const controllerRuntimeTemplate = `{
"mode": "continuous-GrYlRd"
},
"custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
@@ -247,17 +308,18 @@ const controllerRuntimeTemplate = `{
"overrides": []
},
"gridPos": {
- "h": 7,
- "w": 12,
- "x": 12,
+ "h": 8,
+ "w": 10,
+ "x": 14,
"y": 1
},
"id": 6,
"options": {
"legend": {
"calcs": [],
- "displayMode": "list",
- "placement": "bottom"
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
},
"tooltip": {
"mode": "single",
@@ -285,13 +347,69 @@ const controllerRuntimeTemplate = `{
"h": 1,
"w": 24,
"x": 0,
- "y": 8
+ "y": 9
},
"id": 11,
"panels": [],
"title": "Work Queue Metrics",
"type": "row"
},
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "fieldConfig": {
+ "defaults": {
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "orange",
+ "value": 70
+ },
+ {
+ "color": "red",
+ "value": 85
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 3,
+ "x": 0,
+ "y": 10
+ },
+ "id": 22,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": ["lastNotNull"],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.5.3",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "exemplar": true,
+ "expr": "workqueue_depth{job=\"$job\", namespace=\"$namespace\"}",
+ "interval": "",
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "WorkQueue Depth",
+ "type": "gauge"
+ },
{
"datasource": "${DS_PROMETHEUS}",
"description": "How long in seconds an item stays in workqueue before being requested",
@@ -301,6 +419,8 @@ const controllerRuntimeTemplate = `{
"mode": "palette-classic"
},
"custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
@@ -347,10 +467,10 @@ const controllerRuntimeTemplate = `{
"overrides": []
},
"gridPos": {
- "h": 7,
- "w": 12,
- "x": 0,
- "y": 9
+ "h": 8,
+ "w": 11,
+ "x": 3,
+ "y": 10
},
"id": 13,
"options": {
@@ -359,8 +479,9 @@ const controllerRuntimeTemplate = `{
"max",
"mean"
],
- "displayMode": "list",
- "placement": "right"
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
},
"tooltip": {
"mode": "single",
@@ -406,6 +527,8 @@ const controllerRuntimeTemplate = `{
"mode": "continuous-GrYlRd"
},
"custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
@@ -452,17 +575,18 @@ const controllerRuntimeTemplate = `{
"overrides": []
},
"gridPos": {
- "h": 7,
- "w": 12,
- "x": 12,
- "y": 9
+ "h": 8,
+ "w": 10,
+ "x": 14,
+ "y": 10
},
"id": 15,
"options": {
"legend": {
"calcs": [],
- "displayMode": "list",
- "placement": "bottom"
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
},
"tooltip": {
"mode": "single",
@@ -483,6 +607,64 @@ const controllerRuntimeTemplate = `{
"title": "Work Queue Add Rate",
"type": "timeseries"
},
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "How many seconds of work has done that is in progress and hasn't been observed by work_duration.\nLarge values indicate stuck threads.\nOne can deduce the number of stuck threads by observing the rate at which this increases.",
+ "fieldConfig": {
+ "defaults": {
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "orange",
+ "value": 70
+ },
+ {
+ "color": "red",
+ "value": 85
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 3,
+ "x": 0,
+ "y": 18
+ },
+ "id": 23,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": ["lastNotNull"],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.5.3",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "exemplar": true,
+ "expr": "rate(workqueue_unfinished_work_seconds{job=\"$job\", namespace=\"$namespace\"}[5m])",
+ "interval": "",
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "Unfinished Seconds",
+ "type": "gauge"
+ },
{
"datasource": "${DS_PROMETHEUS}",
"description": "How long in seconds processing an item from workqueue takes.",
@@ -492,6 +674,8 @@ const controllerRuntimeTemplate = `{
"mode": "palette-classic"
},
"custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
@@ -538,10 +722,10 @@ const controllerRuntimeTemplate = `{
"overrides": []
},
"gridPos": {
- "h": 7,
- "w": 12,
- "x": 0,
- "y": 16
+ "h": 9,
+ "w": 11,
+ "x": 3,
+ "y": 18
},
"id": 19,
"options": {
@@ -551,7 +735,8 @@ const controllerRuntimeTemplate = `{
"mean"
],
"displayMode": "table",
- "placement": "right"
+ "placement": "bottom",
+ "showLegend": true
},
"tooltip": {
"mode": "single",
@@ -598,6 +783,8 @@ const controllerRuntimeTemplate = `{
"mode": "continuous-GrYlRd"
},
"custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
@@ -644,17 +831,18 @@ const controllerRuntimeTemplate = `{
"overrides": []
},
"gridPos": {
- "h": 7,
- "w": 12,
- "x": 12,
- "y": 16
+ "h": 9,
+ "w": 10,
+ "x": 14,
+ "y": 18
},
"id": 17,
"options": {
"legend": {
"calcs": [],
- "displayMode": "list",
- "placement": "bottom"
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
},
"tooltip": {
"mode": "single",
diff --git a/pkg/plugins/optional/helm/v1alpha/commons.go b/pkg/plugins/optional/helm/v1alpha/commons.go
new file mode 100644
index 00000000000..ae898c39056
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/commons.go
@@ -0,0 +1,56 @@
+/*
+Copyright 2024 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 v1alpha
+
+import (
+ "errors"
+ "fmt"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+)
+
+func insertPluginMetaToConfig(target config.Config, cfg pluginConfig) error {
+ key := plugin.GetPluginKeyForConfig(target.GetPluginChain(), Plugin{})
+ canonicalKey := plugin.KeyFor(Plugin{})
+
+ if err := target.DecodePluginConfig(key, &cfg); err != nil {
+ switch {
+ case errors.As(err, &config.UnsupportedFieldError{}):
+ return nil
+ case errors.As(err, &config.PluginKeyNotFoundError{}):
+ if key != canonicalKey {
+ if err2 := target.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {
+ if errors.As(err2, &config.UnsupportedFieldError{}) {
+ return nil
+ }
+ if !errors.As(err2, &config.PluginKeyNotFoundError{}) {
+ return fmt.Errorf("error decoding plugin configuration: %w", err2)
+ }
+ }
+ }
+ default:
+ return fmt.Errorf("error decoding plugin configuration: %w", err)
+ }
+ }
+
+ if err := target.EncodePluginConfig(key, cfg); err != nil {
+ return fmt.Errorf("error encoding plugin config: %w", err)
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/optional/helm/v1alpha/edit.go b/pkg/plugins/optional/helm/v1alpha/edit.go
new file mode 100644
index 00000000000..e462eb8e6dd
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/edit.go
@@ -0,0 +1,143 @@
+/*
+Copyright 2024 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 v1alpha
+
+import (
+ "fmt"
+ log "log/slog"
+ "os"
+ "path/filepath"
+
+ "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"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds"
+)
+
+var _ plugin.EditSubcommand = &editSubcommand{}
+
+type editSubcommand struct {
+ config config.Config
+ force bool
+}
+
+//nolint:lll
+func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
+ subcmdMeta.Description = `Initialize or update a Helm chart to distribute the project under the dist/ directory.
+
+**NOTE** Before running the edit command, ensure you first execute 'make manifests' to regenerate
+the latest Helm chart with your most recent changes.`
+
+ subcmdMeta.Examples = fmt.Sprintf(`# Initialize or update a Helm chart to distribute the project under the dist/ directory
+ %[1]s edit --plugins=%[2]s
+
+# Update the Helm chart under the dist/ directory and overwrite all files
+ %[1]s edit --plugins=%[2]s --force
+
+**IMPORTANT**: If the "--force" flag is not used, the following files will not be updated to preserve your customizations:
+dist/chart/
+├── values.yaml
+└── templates/
+ └── manager/
+ └── manager.yaml
+
+The following files are never updated after their initial creation:
+ - chart/Chart.yaml
+ - chart/templates/_helpers.tpl
+ - chart/.helmignore
+
+All other files are updated without the usage of the '--force=true' flag
+when the edit option is used to ensure that the
+manifests in the chart align with the latest changes.
+`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))
+}
+
+func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {
+ fs.BoolVar(&p.force, "force", false, "if true, regenerates all the files")
+}
+
+func (p *editSubcommand) InjectConfig(c config.Config) error {
+ p.config = c
+ return nil
+}
+
+func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {
+ scaffolder := scaffolds.NewHelmScaffolder(p.config, p.force)
+ scaffolder.InjectFS(fs)
+ err := scaffolder.Scaffold()
+ if err != nil {
+ return fmt.Errorf("error scaffolding Helm chart: %w", err)
+ }
+
+ // Track the resources following a declarative approach
+ return insertPluginMetaToConfig(p.config, pluginConfig{})
+}
+
+// PostScaffold automatically uncomments cert-manager installation when webhooks are present
+func (p *editSubcommand) PostScaffold() error {
+ hasWebhooks := hasWebhooksWith(p.config)
+
+ if hasWebhooks {
+ workflowFile := filepath.Join(".github", "workflows", "test-chart.yml")
+ if _, err := os.Stat(workflowFile); err != nil {
+ log.Info(
+ "Workflow file not found, unable to uncomment cert-manager installation",
+ "error", err,
+ "file", workflowFile,
+ )
+ return nil
+ }
+ //nolint:lll
+ target := `
+# - name: Install cert-manager via Helm
+# run: |
+# helm repo add jetstack https://charts.jetstack.io
+# helm repo update
+# helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true
+#
+# - name: Wait for cert-manager to be ready
+# run: |
+# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager
+# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector
+# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook`
+ if err := util.UncommentCode(workflowFile, target, "#"); err != nil {
+ hasUncommented, errCheck := util.HasFileContentWith(workflowFile, "- name: Install cert-manager via Helm")
+ if !hasUncommented || errCheck != nil {
+ log.Warn("Failed to uncomment cert-manager installation in workflow file", "error", err, "file", workflowFile)
+ }
+ }
+ }
+ return nil
+}
+
+func hasWebhooksWith(c config.Config) bool {
+ resources, err := c.GetResources()
+ if err != nil {
+ return false
+ }
+
+ for _, res := range resources {
+ if res.HasDefaultingWebhook() || res.HasValidationWebhook() || res.HasConversionWebhook() {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/pkg/plugins/optional/helm/v1alpha/plugin.go b/pkg/plugins/optional/helm/v1alpha/plugin.go
new file mode 100644
index 00000000000..26ea303e1e6
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/plugin.go
@@ -0,0 +1,59 @@
+/*
+Copyright 2024 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 v1alpha
+
+import (
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3"
+ "sigs.k8s.io/kubebuilder/v4/pkg/model/stage"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+)
+
+const pluginName = "helm." + plugins.DefaultNameQualifier
+
+var (
+ pluginVersion = plugin.Version{Number: 1, Stage: stage.Alpha}
+ supportedProjectVersions = []config.Version{cfgv3.Version}
+)
+
+// Plugin implements the plugin.Full interface
+type Plugin struct {
+ editSubcommand
+}
+
+var _ plugin.Edit = Plugin{}
+
+type pluginConfig struct{}
+
+// Name returns the name of the plugin
+func (Plugin) Name() string { return pluginName }
+
+// Version returns the version of the Helm plugin
+func (Plugin) Version() plugin.Version { return pluginVersion }
+
+// SupportedProjectVersions returns an array with all project versions supported by the plugin
+func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }
+
+// GetEditSubcommand will return the subcommand which is responsible for adding and/or edit a helm chart
+func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }
+
+// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated
+func (p Plugin) DeprecationWarning() string {
+ return "helm/v1-alpha plugin is deprecated, use helm/v2-alpha instead which " +
+ "provides dynamic Helm chart generation from kustomize output"
+}
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/edit.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/edit.go
new file mode 100644
index 00000000000..f938598e877
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/edit.go
@@ -0,0 +1,586 @@
+/*
+Copyright 2024 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 scaffolds
+
+import (
+ "errors"
+ "fmt"
+ log "log/slog"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "sigs.k8s.io/yaml"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/config"
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins"
+ deployimagev1alpha1 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates"
+ charttemplates "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates"
+ templatescertmanager "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager"
+ templatesmetrics "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus"
+ templateswebhooks "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/github"
+)
+
+var _ plugins.Scaffolder = &editScaffolder{}
+
+type editScaffolder struct {
+ config config.Config
+
+ fs machinery.Filesystem
+
+ force bool
+}
+
+// NewHelmScaffolder returns a new Scaffolder for HelmPlugin
+func NewHelmScaffolder(cfg config.Config, force bool) plugins.Scaffolder {
+ return &editScaffolder{
+ config: cfg,
+ force: force,
+ }
+}
+
+// InjectFS implements cmdutil.Scaffolder
+func (s *editScaffolder) InjectFS(fs machinery.Filesystem) {
+ s.fs = fs
+}
+
+// Scaffold scaffolds the Helm chart with the necessary files.
+func (s *editScaffolder) Scaffold() error {
+ log.Info("Generating Helm Chart to distribute project")
+
+ imagesEnvVars := s.getDeployImagesEnvVars()
+
+ scaffold := machinery.NewScaffold(s.fs,
+ machinery.WithConfig(s.config),
+ )
+
+ // Found webhooks by looking at the config our scaffolds files
+ mutatingWebhooks, validatingWebhooks, err := s.extractWebhooksFromGeneratedFiles()
+ if err != nil {
+ return fmt.Errorf("failed to extract webhooks: %w", err)
+ }
+ hasWebhooks := hasWebhooksWith(s.config) || (len(mutatingWebhooks) > 0 && len(validatingWebhooks) > 0)
+
+ buildScaffold := []machinery.Builder{
+ &github.HelmChartCI{},
+ &templates.HelmChart{},
+ &templates.HelmValues{
+ HasWebhooks: hasWebhooks,
+ DeployImages: imagesEnvVars,
+ Force: s.force,
+ },
+ &templates.HelmIgnore{},
+ &charttemplates.HelmHelpers{},
+ &manager.Deployment{
+ Force: s.force,
+ DeployImages: len(imagesEnvVars) > 0,
+ HasWebhooks: hasWebhooks,
+ },
+ &templatescertmanager.Certificate{HasWebhooks: hasWebhooks},
+ &templatesmetrics.Service{},
+ &prometheus.Monitor{},
+ }
+
+ if len(mutatingWebhooks) > 0 || len(validatingWebhooks) > 0 {
+ buildScaffold = append(buildScaffold,
+ &templateswebhooks.Template{
+ MutatingWebhooks: mutatingWebhooks,
+ ValidatingWebhooks: validatingWebhooks,
+ },
+ )
+ }
+
+ if hasWebhooks {
+ buildScaffold = append(buildScaffold,
+ &templateswebhooks.Service{},
+ )
+ }
+
+ if err = scaffold.Execute(buildScaffold...); err != nil {
+ return fmt.Errorf("error scaffolding helm-chart manifests: %w", err)
+ }
+
+ // Copy relevant files from config/ to dist/chart/templates/
+ err = s.copyConfigFiles()
+ if err != nil {
+ return fmt.Errorf("failed to copy manifests from config to dist/chart/templates/: %w", err)
+ }
+
+ return nil
+}
+
+// getDeployImagesEnvVars will return the values to append the envvars for projects
+// which has the APIs scaffolded with DeployImage plugin
+func (s *editScaffolder) getDeployImagesEnvVars() map[string]string {
+ deployImages := make(map[string]string)
+
+ pluginConfig := struct {
+ Resources []struct {
+ Kind string `json:"kind"`
+ Options map[string]string `json:"options"`
+ } `json:"resources"`
+ }{}
+
+ key := plugin.GetPluginKeyForConfig(s.config.GetPluginChain(), deployimagev1alpha1.Plugin{})
+ canonicalKey := plugin.KeyFor(deployimagev1alpha1.Plugin{})
+
+ err := s.config.DecodePluginConfig(key, &pluginConfig)
+ if err != nil {
+ switch {
+ case errors.As(err, &config.UnsupportedFieldError{}):
+ return deployImages
+ case errors.As(err, &config.PluginKeyNotFoundError{}):
+ if key != canonicalKey {
+ if err2 := s.config.DecodePluginConfig(canonicalKey, &pluginConfig); err2 != nil {
+ if errors.As(err2, &config.UnsupportedFieldError{}) {
+ return deployImages
+ }
+ if !errors.As(err2, &config.PluginKeyNotFoundError{}) {
+ log.Warn("error decoding deploy-image configuration", "error", err2, "key", canonicalKey)
+ }
+ }
+ }
+ default:
+ log.Warn("error decoding deploy-image configuration", "error", err, "key", key)
+ }
+ }
+
+ for _, res := range pluginConfig.Resources {
+ image, ok := res.Options["image"]
+ if ok {
+ deployImages[strings.ToUpper(res.Kind)] = image
+ }
+ }
+ return deployImages
+}
+
+// extractWebhooksFromGeneratedFiles parses the files generated by controller-gen under
+// config/webhooks and created Mutating and Validating helper structures to
+// generate the webhook manifest for the helm-chart
+func (s *editScaffolder) extractWebhooksFromGeneratedFiles() (mutatingWebhooks []templateswebhooks.DataWebhook,
+ validatingWebhooks []templateswebhooks.DataWebhook, err error,
+) {
+ manifestFile := "config/webhook/manifests.yaml"
+
+ if _, err = os.Stat(manifestFile); os.IsNotExist(err) {
+ log.Info("webhook manifests were not found", "path", manifestFile)
+ return nil, nil, nil
+ }
+
+ content, err := os.ReadFile(manifestFile)
+ if err != nil {
+ return nil, nil,
+ fmt.Errorf("failed to read %q: %w", manifestFile, err)
+ }
+
+ docs := strings.Split(string(content), "---")
+ for _, doc := range docs {
+ var webhookConfig struct {
+ Kind string `yaml:"kind"`
+ Webhooks []struct {
+ Name string `yaml:"name"`
+ ClientConfig struct {
+ Service struct {
+ Name string `yaml:"name"`
+ Namespace string `yaml:"namespace"`
+ Path string `yaml:"path"`
+ } `yaml:"service"`
+ } `yaml:"clientConfig"`
+ Rules []templateswebhooks.DataWebhookRule `yaml:"rules"`
+ FailurePolicy string `yaml:"failurePolicy"`
+ SideEffects string `yaml:"sideEffects"`
+ AdmissionReviewVersions []string `yaml:"admissionReviewVersions"`
+ } `yaml:"webhooks"`
+ }
+
+ if err := yaml.Unmarshal([]byte(doc), &webhookConfig); err != nil {
+ log.Error("fail to unmarshalling webhook YAML", "error", err)
+ continue
+ }
+
+ for _, w := range webhookConfig.Webhooks {
+ for i := range w.Rules {
+ if len(w.Rules[i].APIGroups) == 0 {
+ w.Rules[i].APIGroups = []string{""}
+ }
+ }
+ webhook := templateswebhooks.DataWebhook{
+ Name: w.Name,
+ ServiceName: fmt.Sprintf("%s-webhook-service", s.config.GetProjectName()),
+ Path: w.ClientConfig.Service.Path,
+ FailurePolicy: w.FailurePolicy,
+ SideEffects: w.SideEffects,
+ AdmissionReviewVersions: w.AdmissionReviewVersions,
+ Rules: w.Rules,
+ }
+
+ switch webhookConfig.Kind {
+ case "MutatingWebhookConfiguration":
+ mutatingWebhooks = append(mutatingWebhooks, webhook)
+ case "ValidatingWebhookConfiguration":
+ validatingWebhooks = append(validatingWebhooks, webhook)
+ }
+ }
+ }
+
+ return mutatingWebhooks, validatingWebhooks, nil
+}
+
+// Helper function to copy files from config/ to dist/chart/templates/
+func (s *editScaffolder) copyConfigFiles() error {
+ configDirs := []struct {
+ SrcDir string
+ DestDir string
+ SubDir string
+ }{
+ {"config/rbac", "dist/chart/templates/rbac", "rbac"},
+ {"config/crd/bases", "dist/chart/templates/crd", "crd"},
+ {"config/network-policy", "dist/chart/templates/network-policy", "networkPolicy"},
+ }
+
+ for _, dir := range configDirs {
+ // Check if the source directory exists
+ if _, err := os.Stat(dir.SrcDir); os.IsNotExist(err) {
+ // Skip if the source directory does not exist
+ continue
+ }
+
+ files, err := filepath.Glob(filepath.Join(dir.SrcDir, "*.yaml"))
+ if err != nil {
+ return fmt.Errorf("failed finding files in %q: %w", dir.SrcDir, err)
+ }
+
+ // Skip processing if the directory is empty (no matching files)
+ if len(files) == 0 {
+ continue
+ }
+
+ // Ensure destination directory exists
+ if err := os.MkdirAll(dir.DestDir, 0o755); err != nil {
+ return fmt.Errorf("failed to create directory %q: %w", dir.DestDir, err)
+ }
+
+ for _, srcFile := range files {
+ destFile := filepath.Join(dir.DestDir, filepath.Base(srcFile))
+
+ hasConvertionalWebhook := false
+ if hasWebhooksWith(s.config) {
+ resources, err := s.config.GetResources()
+ if err != nil {
+ break
+ }
+ for _, res := range resources {
+ if res.HasConversionWebhook() {
+ hasConvertionalWebhook = true
+ break
+ }
+ }
+ }
+
+ err := copyFileWithHelmLogic(srcFile, destFile, dir.SubDir, s.config.GetProjectName(), hasConvertionalWebhook)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+// copyFileWithHelmLogic reads the source file, modifies the content for Helm, applies patches
+// to spec.conversion if applicable, and writes it to the destination
+func copyFileWithHelmLogic(srcFile, destFile, subDir, projectName string, hasConvertionalWebhook bool) error {
+ if _, err := os.Stat(srcFile); os.IsNotExist(err) {
+ log.Info("Source file does not exist", "source_file", srcFile)
+ return fmt.Errorf("source file does not exist %q: %w", srcFile, err)
+ }
+
+ content, err := os.ReadFile(srcFile)
+ if err != nil {
+ log.Info("Error reading source file", "source_file", srcFile)
+ return fmt.Errorf("failed to read file %q: %w", srcFile, err)
+ }
+
+ contentStr := string(content)
+
+ // Skip kustomization.yaml or kustomizeconfig.yaml files
+ if strings.HasSuffix(srcFile, "kustomization.yaml") ||
+ strings.HasSuffix(srcFile, "kustomizeconfig.yaml") {
+ return nil
+ }
+
+ // Apply RBAC-specific replacements
+ if subDir == "rbac" {
+ contentStr = strings.ReplaceAll(contentStr,
+ "name: controller-manager",
+ "name: {{ .Values.controllerManager.serviceAccountName }}")
+ contentStr = strings.Replace(contentStr,
+ "name: metrics-reader",
+ fmt.Sprintf("name: %s-metrics-reader", projectName), 1)
+
+ contentStr = strings.ReplaceAll(contentStr,
+ "name: metrics-auth-role",
+ fmt.Sprintf("name: %s-metrics-auth-role", projectName))
+ contentStr = strings.Replace(contentStr,
+ "name: metrics-auth-rolebinding",
+ fmt.Sprintf("name: %s-metrics-auth-rolebinding", projectName), 1)
+
+ if strings.Contains(contentStr, ".Values.controllerManager.serviceAccountName") &&
+ strings.Contains(contentStr, "kind: ServiceAccount") &&
+ !strings.Contains(contentStr, "RoleBinding") {
+ // The generated Service Account does not have the annotations field so we must add it.
+ contentStr = strings.Replace(contentStr,
+ "metadata:", `metadata:
+ {{- if and .Values.controllerManager.serviceAccount .Values.controllerManager.serviceAccount.annotations }}
+ annotations:
+ {{- range $key, $value := .Values.controllerManager.serviceAccount.annotations }}
+ {{ $key }}: {{ $value }}
+ {{- end }}
+ {{- end }}`, 1)
+ }
+ contentStr = strings.ReplaceAll(contentStr,
+ "name: leader-election-role",
+ fmt.Sprintf("name: %s-leader-election-role", projectName))
+ contentStr = strings.Replace(contentStr,
+ "name: leader-election-rolebinding",
+ fmt.Sprintf("name: %s-leader-election-rolebinding", projectName), 1)
+ contentStr = strings.ReplaceAll(contentStr,
+ "name: manager-role",
+ fmt.Sprintf("name: %s-manager-role", projectName))
+ contentStr = strings.Replace(contentStr,
+ "name: manager-rolebinding",
+ fmt.Sprintf("name: %s-manager-rolebinding", projectName), 1)
+
+ // The generated files do not include the namespace
+ if strings.Contains(contentStr, "leader-election-rolebinding") ||
+ strings.Contains(contentStr, "leader-election-role") {
+ namespace := `
+ namespace: {{ .Release.Namespace }}`
+ contentStr = strings.Replace(contentStr, "metadata:", "metadata:"+namespace, 1)
+ }
+ }
+
+ // Conditionally handle CRD patches and annotations for CRDs
+ if subDir == "crd" {
+ kind, group := extractKindAndGroupFromFileName(filepath.Base(srcFile))
+ hasWebhookPatch := false
+
+ // Retrieve patch content for the CRD's spec.conversion, if it exists
+ patchContent, patchExists, errPatch := getCRDPatchContent(kind, group)
+ if errPatch != nil {
+ return errPatch
+ }
+
+ // If patch content exists, inject it under spec.conversion with Helm conditional
+ if patchExists {
+ conversionSpec := extractConversionSpec(patchContent)
+ // Projects scaffolded with old Kubebuilder versions does not have the conversion
+ // webhook properly generated because before 4.4.0 this feature was not fully addressed.
+ // The patch was added by default when should not. See the related fixes:
+ //
+ // Issue fixed in release 4.3.1: (which will cause the injection of webhook conditionals for projects without
+ // conversion webhooks)
+ // (kustomize/v2, go/v4): Corrected the generation of manifests under config/crd/patches
+ // to ensure the /convert service patch is only created for webhooks configured with --conversion. (#4280)
+ //
+ // Conversion webhook fully fixed in release 4.4.0:
+ // (kustomize/v2, go/v4): Fixed CA injection for conversion webhooks. Previously, the CA injection
+ // was applied incorrectly to all CRDs instead of only conversion types. The issue dates back to release 3.5.0
+ // due to kustomize/v2-alpha changes. Now, conversion webhooks are properly generated. (#4254, #4282)
+ if len(conversionSpec) > 0 && !hasConvertionalWebhook {
+ log.Warn("\n" +
+ "============================================================\n" +
+ "| [WARNING] Webhook Patch Issue Detected |\n" +
+ "============================================================\n" +
+ "Webhook patch found, but no conversion webhook is configured for this project.\n\n" +
+ "Note: Older scaffolds have an issue where the conversion webhook patch was \n" +
+ " scaffolded by default, and conversion webhook injection was not properly limited \n" +
+ " to specific CRDs.\n\n" +
+ "Recommended Action:\n" +
+ " - Upgrade your project to the latest available version.\n" +
+ " - Consider using the 'alpha generate' command.\n\n" +
+ "The cert-manager injection and webhook conversion patch found for CRDs will\n" +
+ "be skipped and NOT added to the Helm chart.\n" +
+ "============================================================")
+
+ hasWebhookPatch = false
+ } else {
+ contentStr = injectConversionSpecWithCondition(contentStr, conversionSpec)
+ hasWebhookPatch = true
+ }
+ }
+
+ // Inject annotations after "annotations:" in a single block without extra spaces
+ contentStr = injectAnnotations(contentStr, hasWebhookPatch)
+ }
+
+ // Remove existing labels if necessary
+ contentStr = removeLabels(contentStr)
+
+ // Replace namespace with Helm template variable
+ contentStr = strings.ReplaceAll(contentStr, "namespace: system", "namespace: {{ .Release.Namespace }}")
+
+ contentStr = strings.Replace(contentStr, "metadata:", `metadata:
+ labels:
+ {{- include "chart.labels" . | nindent 4 }}`, 1)
+
+ // Append project name to webhook service name
+ contentStr = strings.ReplaceAll(contentStr, "name: webhook-service", "name: "+projectName+"-webhook-service")
+
+ var wrappedContent string
+ if isMetricRBACFile(subDir, srcFile) {
+ wrappedContent = fmt.Sprintf(
+ "{{- if and .Values.rbac.enable .Values.metrics.enable }}\n%s{{- end -}}\n", contentStr)
+ } else {
+ wrappedContent = fmt.Sprintf(
+ "{{- if .Values.%s.enable }}\n%s{{- end -}}\n", subDir, contentStr)
+ }
+
+ if err = os.MkdirAll(filepath.Dir(destFile), 0o755); err != nil {
+ return fmt.Errorf("error creating directory %q: %w", filepath.Dir(destFile), err)
+ }
+
+ err = os.WriteFile(destFile, []byte(wrappedContent), 0o644)
+ if err != nil {
+ log.Info("Error writing destination file", "destination_file", destFile)
+ return fmt.Errorf("error writing destination file %q: %w", destFile, err)
+ }
+
+ log.Info("Successfully copied file", "from", srcFile, "to", destFile)
+ return nil
+}
+
+// extractKindAndGroupFromFileName extracts the kind and group from a CRD filename
+func extractKindAndGroupFromFileName(fileName string) (kind, group string) {
+ parts := strings.Split(fileName, "_")
+ if len(parts) >= 2 {
+ group = strings.Split(parts[0], ".")[0] // Extract group up to the first dot
+ kind = strings.TrimSuffix(parts[1], ".yaml")
+ }
+ return kind, group
+}
+
+// getCRDPatchContent finds and reads the appropriate patch content for a given kind and group
+func getCRDPatchContent(kind, group string) (string, bool, error) {
+ // First, look for patches that contain both "webhook", the group, and kind in their filename
+ groupKindPattern := fmt.Sprintf("config/crd/patches/webhook_*%s*%s*.yaml", group, kind)
+ patchFiles, err := filepath.Glob(groupKindPattern)
+ if err != nil {
+ return "", false, fmt.Errorf("failed to list patches: %w", err)
+ }
+
+ // If no group-specific patch found, search for patches that contain only "webhook" and the kind
+ if len(patchFiles) == 0 {
+ kindOnlyPattern := fmt.Sprintf("config/crd/patches/webhook_*%s*.yaml", kind)
+ patchFiles, err = filepath.Glob(kindOnlyPattern)
+ if err != nil {
+ return "", false, fmt.Errorf("failed to list patches: %w", err)
+ }
+ }
+
+ // Read the first matching patch file (if any)
+ if len(patchFiles) > 0 {
+ patchContent, err := os.ReadFile(patchFiles[0])
+ if err != nil {
+ return "", false, fmt.Errorf("failed to read patch file %q: %w", patchFiles[0], err)
+ }
+ return string(patchContent), true, nil
+ }
+
+ return "", false, nil
+}
+
+// extractConversionSpec extracts only the conversion section from the patch content
+func extractConversionSpec(patchContent string) string {
+ specStart := strings.Index(patchContent, "conversion:")
+ if specStart == -1 {
+ return ""
+ }
+ return patchContent[specStart:]
+}
+
+// injectConversionSpecWithCondition inserts the conversion spec under the main spec field with Helm conditional
+func injectConversionSpecWithCondition(contentStr, conversionSpec string) string {
+ specPosition := strings.Index(contentStr, "spec:")
+ if specPosition == -1 {
+ return contentStr // No spec field found; return unchanged
+ }
+ conditionalSpec := fmt.Sprintf("\n {{- if .Values.webhook.enable }}\n %s\n {{- end }}",
+ strings.TrimRight(conversionSpec, "\n"))
+ return contentStr[:specPosition+5] + conditionalSpec + contentStr[specPosition+5:]
+}
+
+// injectAnnotations inserts the required annotations after the "annotations:" field in a single block without
+// extra spaces
+func injectAnnotations(contentStr string, hasWebhookPatch bool) string {
+ annotationsBlock := `
+ {{- if .Values.certmanager.enable }}
+ cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/serving-cert"
+ {{- end }}
+ {{- if .Values.crd.keep }}
+ "helm.sh/resource-policy": keep
+ {{- end }}`
+ if hasWebhookPatch {
+ return strings.Replace(contentStr, "annotations:", "annotations:"+annotationsBlock, 1)
+ }
+
+ // Apply only resource policy if no webhook patch
+ resourcePolicy := `
+ {{- if .Values.crd.keep }}
+ "helm.sh/resource-policy": keep
+ {{- end }}`
+ return strings.Replace(contentStr, "annotations:", "annotations:"+resourcePolicy, 1)
+}
+
+// isMetricRBACFile checks if the file is in the "rbac"
+// subdirectory and matches one of the metric-related RBAC filenames
+func isMetricRBACFile(subDir, srcFile string) bool {
+ return subDir == "rbac" && (strings.HasSuffix(srcFile, "metrics_auth_role.yaml") ||
+ strings.HasSuffix(srcFile, "metrics_auth_role_binding.yaml") ||
+ strings.HasSuffix(srcFile, "metrics_reader_role.yaml"))
+}
+
+// removeLabels removes any existing labels section from the content
+func removeLabels(content string) string {
+ labelRegex := `(?m)^ labels:\n(?: [^\n]+\n)*`
+ re := regexp.MustCompile(labelRegex)
+
+ return re.ReplaceAllString(content, "")
+}
+
+func hasWebhooksWith(c config.Config) bool {
+ // Get the list of resources
+ resources, err := c.GetResources()
+ if err != nil {
+ return false // If there's an error getting resources, assume no webhooks
+ }
+
+ for _, res := range resources {
+ if res.HasDefaultingWebhook() || res.HasValidationWebhook() || res.HasConversionWebhook() {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager/certificate.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager/certificate.go
new file mode 100644
index 00000000000..01a90f4f8cb
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager/certificate.go
@@ -0,0 +1,110 @@
+/*
+Copyright 2024 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 webhook
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Certificate{}
+
+// Certificate scaffolds the Certificate for webhooks in the Helm chart
+type Certificate struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+
+ // HasWebhooks is true when webhooks were found in the config
+ HasWebhooks bool
+}
+
+// SetTemplateDefaults sets the default template configuration
+func (f *Certificate) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "templates", "certmanager", "certificate.yaml")
+ }
+
+ f.TemplateBody = certificateTemplate
+
+ f.IfExistsAction = machinery.OverwriteFile
+
+ return nil
+}
+
+const certificateTemplate = `{{` + "`" + `{{- if .Values.certmanager.enable }}` + "`" + `}}
+# Self-signed Issuer
+apiVersion: cert-manager.io/v1
+kind: Issuer
+metadata:
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+ name: selfsigned-issuer
+ namespace: {{ "{{ .Release.Namespace }}" }}
+spec:
+ selfSigned: {}
+{{- if .HasWebhooks }}
+{{ "{{- if .Values.webhook.enable }}" }}
+---
+# Certificate for the webhook
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+ annotations:
+ {{ "{{- if .Values.crd.keep }}" }}
+ "helm.sh/resource-policy": keep
+ {{ "{{- end }}" }}
+ name: serving-cert
+ namespace: {{ "{{ .Release.Namespace }}" }}
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+spec:
+ dnsNames:
+ - {{ .ProjectName }}.{{ "{{ .Release.Namespace }}" }}.svc
+ - {{ .ProjectName }}.{{ "{{ .Release.Namespace }}" }}.svc.cluster.local
+ - {{ .ProjectName }}-webhook-service.{{ "{{ .Release.Namespace }}" }}.svc
+ issuerRef:
+ kind: Issuer
+ name: selfsigned-issuer
+ secretName: webhook-server-cert
+{{` + "`" + `{{- end }}` + "`" + `}}
+{{- end }}
+{{ "{{- if .Values.metrics.enable }}" }}
+---
+# Certificate for the metrics
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+ annotations:
+ {{ "{{- if .Values.crd.keep }}" }}
+ "helm.sh/resource-policy": keep
+ {{ "{{- end }}" }}
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+ name: metrics-certs
+ namespace: {{ "{{ .Release.Namespace }}" }}
+spec:
+ dnsNames:
+ - {{ .ProjectName }}.{{ "{{ .Release.Namespace }}" }}.svc
+ - {{ .ProjectName }}.{{ "{{ .Release.Namespace }}" }}.svc.cluster.local
+ - {{ .ProjectName }}-metrics-service.{{ "{{ .Release.Namespace }}" }}.svc
+ issuerRef:
+ kind: Issuer
+ name: selfsigned-issuer
+ secretName: metrics-server-cert
+{{` + "`" + `{{- end }}` + "`" + `}}
+{{` + "`" + `{{- end }}` + "`" + `}}
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go
new file mode 100644
index 00000000000..42d6c9f245f
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go
@@ -0,0 +1,104 @@
+/*
+Copyright 2024 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 charttemplates
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &HelmHelpers{}
+
+// HelmHelpers scaffolds the _helpers.tpl file for Helm charts
+type HelmHelpers struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults sets the default template configuration
+func (f *HelmHelpers) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "templates", "_helpers.tpl")
+ }
+
+ f.TemplateBody = helmHelpersTemplate
+
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+const helmHelpersTemplate = `{{` + "`" + `{{- define "chart.name" -}}` + "`" + `}}
+{{` + "`" + `{{- if .Chart }}` + "`" + `}}
+ {{` + "`" + `{{- if .Chart.Name }}` + "`" + `}}
+ {{` + "`" + `{{- .Chart.Name | trunc 63 | trimSuffix "-" }}` + "`" + `}}
+ {{` + "`" + `{{- else if .Values.nameOverride }}` + "`" + `}}
+ {{` + "`" + `{{ .Values.nameOverride | trunc 63 | trimSuffix "-" }}` + "`" + `}}
+ {{` + "`" + `{{- else }}` + "`" + `}}
+ {{ .ProjectName }}
+ {{` + "`" + `{{- end }}` + "`" + `}}
+{{` + "`" + `{{- else }}` + "`" + `}}
+ {{ .ProjectName }}
+{{` + "`" + `{{- end }}` + "`" + `}}
+{{` + "`" + `{{- end }}` + "`" + `}}
+
+{{/*
+Common labels for the chart.
+*/}}
+{{` + "`" + `{{- define "chart.labels" -}}` + "`" + `}}
+{{` + "`" + `{{- if .Chart.AppVersion -}}` + "`" + `}}
+app.kubernetes.io/version: {{` + "`" + `{{ .Chart.AppVersion | quote }}` + "`" + `}}
+{{` + "`" + `{{- end }}` + "`" + `}}
+{{` + "`" + `{{- if .Chart.Version }}` + "`" + `}}
+helm.sh/chart: {{` + "`" + `{{ .Chart.Version | quote }}` + "`" + `}}
+{{` + "`" + `{{- end }}` + "`" + `}}
+app.kubernetes.io/name: {{` + "`" + `{{ include "chart.name" . }}` + "`" + `}}
+app.kubernetes.io/instance: {{` + "`" + `{{ .Release.Name }}` + "`" + `}}
+app.kubernetes.io/managed-by: {{` + "`" + `{{ .Release.Service }}` + "`" + `}}
+{{` + "`" + `{{- end }}` + "`" + `}}
+
+{{/*
+Selector labels for the chart.
+*/}}
+{{` + "`" + `{{- define "chart.selectorLabels" -}}` + "`" + `}}
+app.kubernetes.io/name: {{` + "`" + `{{ include "chart.name" . }}` + "`" + `}}
+app.kubernetes.io/instance: {{` + "`" + `{{ .Release.Name }}` + "`" + `}}
+{{` + "`" + `{{- end }}` + "`" + `}}
+
+{{/*
+Helper to check if mutating webhooks exist in the services.
+*/}}
+{{` + "`" + `{{- define "chart.hasMutatingWebhooks" -}}` + "`" + `}}
+{{` + "`" + `{{- $hasMutating := false }}` + "`" + `}}
+{{` + "`" + `{{- range . }}` + "`" + `}}
+ {{` + "`" + `{{- if eq .type "mutating" }}` + "`" + `}}
+ {{` + "`" + `$hasMutating = true }}{{- end }}` + "`" + `}}
+{{` + "`" + `{{- end }}` + "`" + `}}
+{{` + "`" + `{{ $hasMutating }}}}{{- end }}` + "`" + `}}
+
+{{/*
+Helper to check if validating webhooks exist in the services.
+*/}}
+{{` + "`" + `{{- define "chart.hasValidatingWebhooks" -}}` + "`" + `}}
+{{` + "`" + `{{- $hasValidating := false }}` + "`" + `}}
+{{` + "`" + `{{- range . }}` + "`" + `}}
+ {{` + "`" + `{{- if eq .type "validating" }}` + "`" + `}}
+ {{` + "`" + `$hasValidating = true }}{{- end }}` + "`" + `}}
+{{` + "`" + `{{- end }}` + "`" + `}}
+{{` + "`" + `{{ $hasValidating }}}}{{- end }}` + "`" + `}}
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager/manager.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager/manager.go
new file mode 100644
index 00000000000..25099ff68d4
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager/manager.go
@@ -0,0 +1,162 @@
+/*
+Copyright 2024 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 manager
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Deployment{}
+
+// Deployment scaffolds the manager Deployment for the Helm chart
+type Deployment struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+
+ // DeployImages if true will scaffold the env with the images
+ DeployImages bool
+ // Force if true allow overwrite the scaffolded file
+ Force bool
+ // HasWebhooks is true when webhooks were found in the config
+ HasWebhooks bool
+}
+
+// SetTemplateDefaults sets the default template configuration
+func (f *Deployment) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "templates", "manager", "manager.yaml")
+ }
+
+ f.TemplateBody = managerDeploymentTemplate
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ } else {
+ f.IfExistsAction = machinery.SkipFile
+ }
+
+ return nil
+}
+
+//nolint:lll
+const managerDeploymentTemplate = `apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ .ProjectName }}-controller-manager
+ namespace: {{ "{{ .Release.Namespace }}" }}
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+ control-plane: controller-manager
+spec:
+ replicas: {{ "{{ .Values.controllerManager.replicas }}" }}
+ selector:
+ matchLabels:
+ {{ "{{- include \"chart.selectorLabels\" . | nindent 6 }}" }}
+ control-plane: controller-manager
+ template:
+ metadata:
+ annotations:
+ kubectl.kubernetes.io/default-container: manager
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 8 }}" }}
+ control-plane: controller-manager
+ {{ "{{- if and .Values.controllerManager.pod .Values.controllerManager.pod.labels }}" }}
+ {{ "{{- range $key, $value := .Values.controllerManager.pod.labels }}" }}
+ {{ "{{ $key }}" }}: {{ "{{ $value }}" }}
+ {{ "{{- end }}" }}
+ {{ "{{- end }}" }}
+ spec:
+ containers:
+ - name: manager
+ args:
+ {{ "{{- range .Values.controllerManager.container.args }}" }}
+ - {{ "{{ . }}" }}
+ {{ "{{- end }}" }}
+ command:
+ - /manager
+ image: {{ "{{ .Values.controllerManager.container.image.repository }}" }}:{{ "{{ .Values.controllerManager.container.image.tag }}" }}
+ {{ "{{- if .Values.controllerManager.container.imagePullPolicy }}" }}
+ imagePullPolicy: {{ "{{ .Values.controllerManager.container.imagePullPolicy }}" }}
+ {{ "{{- end }}" }}
+ {{ "{{- if .Values.controllerManager.container.env }}" }}
+ env:
+ {{ "{{- range $key, $value := .Values.controllerManager.container.env }}" }}
+ - name: {{ "{{ $key }}" }}
+ value: {{ "{{ $value }}" }}
+ {{ "{{- end }}" }}
+ {{ "{{- end }}" }}
+ livenessProbe:
+ {{ "{{- toYaml .Values.controllerManager.container.livenessProbe | nindent 12 }}" }}
+ readinessProbe:
+ {{ "{{- toYaml .Values.controllerManager.container.readinessProbe | nindent 12 }}" }}
+{{- if .HasWebhooks }}
+ {{ "{{- if .Values.webhook.enable }}" }}
+ ports:
+ - containerPort: 9443
+ name: webhook-server
+ protocol: TCP
+ {{ "{{- end }}" }}
+{{- end }}
+ resources:
+ {{ "{{- toYaml .Values.controllerManager.container.resources | nindent 12 }}" }}
+ securityContext:
+ {{ "{{- toYaml .Values.controllerManager.container.securityContext | nindent 12 }}" }}
+{{- if .HasWebhooks }}
+ {{ "{{- if and .Values.certmanager.enable (or .Values.webhook.enable .Values.metrics.enable) }}" }}
+{{- else }}
+ {{ "{{- if and .Values.certmanager.enable .Values.metrics.enable }}" }}
+{{- end }}
+ volumeMounts:
+{{- if .HasWebhooks }}
+ {{ "{{- if and .Values.webhook.enable .Values.certmanager.enable }}" }}
+ - name: webhook-cert
+ mountPath: /tmp/k8s-webhook-server/serving-certs
+ readOnly: true
+ {{ "{{- end }}" }}
+{{- end }}
+ {{ "{{- if and .Values.metrics.enable .Values.certmanager.enable }}" }}
+ - name: metrics-certs
+ mountPath: /tmp/k8s-metrics-server/metrics-certs
+ readOnly: true
+ {{ "{{- end }}" }}
+ {{ "{{- end }}" }}
+ securityContext:
+ {{ "{{- toYaml .Values.controllerManager.securityContext | nindent 8 }}" }}
+ serviceAccountName: {{ "{{ .Values.controllerManager.serviceAccountName }}" }}
+ terminationGracePeriodSeconds: {{ "{{ .Values.controllerManager.terminationGracePeriodSeconds }}" }}
+{{- if .HasWebhooks }}
+ {{ "{{- if and .Values.certmanager.enable (or .Values.webhook.enable .Values.metrics.enable) }}" }}
+{{- else }}
+ {{ "{{- if and .Values.certmanager.enable .Values.metrics.enable }}" }}
+{{- end }}
+ volumes:
+{{- if .HasWebhooks }}
+ {{ "{{- if and .Values.webhook.enable .Values.certmanager.enable }}" }}
+ - name: webhook-cert
+ secret:
+ secretName: webhook-server-cert
+ {{ "{{- end }}" }}
+{{- end }}
+ {{ "{{- if and .Values.metrics.enable .Values.certmanager.enable }}" }}
+ - name: metrics-certs
+ secret:
+ secretName: metrics-server-cert
+ {{ "{{- end }}" }}
+ {{ "{{- end }}" }}
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go
new file mode 100644
index 00000000000..06edd0d0757
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go
@@ -0,0 +1,63 @@
+/*
+Copyright 2024 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 metrics
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Service{}
+
+// Service scaffolds the Service for metrics in the Helm chart
+type Service struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults sets the default template configuration
+func (f *Service) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "templates", "metrics", "metrics-service.yaml")
+ }
+
+ f.TemplateBody = metricsServiceTemplate
+
+ f.IfExistsAction = machinery.OverwriteFile
+
+ return nil
+}
+
+const metricsServiceTemplate = `{{` + "`" + `{{- if .Values.metrics.enable }}` + "`" + `}}
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .ProjectName }}-controller-manager-metrics-service
+ namespace: {{ "{{ .Release.Namespace }}" }}
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+ control-plane: controller-manager
+spec:
+ ports:
+ - port: 8443
+ targetPort: 8443
+ protocol: TCP
+ name: https
+ selector:
+ control-plane: controller-manager
+{{` + "`" + `{{- end }}` + "`" + `}}
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go
new file mode 100644
index 00000000000..eb157cad611
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go
@@ -0,0 +1,85 @@
+/*
+Copyright 2024 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 prometheus
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Monitor{}
+
+// Monitor scaffolds the ServiceMonitor for Prometheus in the Helm chart
+type Monitor struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults sets the default template configuration
+func (f *Monitor) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "templates", "prometheus", "monitor.yaml")
+ }
+
+ f.TemplateBody = monitorTemplate
+
+ f.IfExistsAction = machinery.OverwriteFile
+
+ return nil
+}
+
+const monitorTemplate = `# To integrate with Prometheus.
+{{ "{{- if .Values.prometheus.enable }}" }}
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+ control-plane: controller-manager
+ name: {{ .ProjectName }}-controller-manager-metrics-monitor
+ namespace: {{ "{{ .Release.Namespace }}" }}
+spec:
+ endpoints:
+ - path: /metrics
+ port: https
+ scheme: https
+ bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+ tlsConfig:
+ {{ "{{- if .Values.certmanager.enable }}" }}
+ serverName: {{ .ProjectName }}-controller-manager-metrics-service.{{ "{{ .Release.Namespace }}" }}.svc
+ # Apply secure TLS configuration with cert-manager
+ insecureSkipVerify: false
+ ca:
+ secret:
+ name: metrics-server-cert
+ key: ca.crt
+ cert:
+ secret:
+ name: metrics-server-cert
+ key: tls.crt
+ keySecret:
+ name: metrics-server-cert
+ key: tls.key
+ {{ "{{- else }}" }}
+ # Development/Test mode (insecure configuration)
+ insecureSkipVerify: true
+ {{ "{{- end }}" }}
+ selector:
+ matchLabels:
+ control-plane: controller-manager
+{{ "{{- end }}" }}
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/service.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/service.go
new file mode 100644
index 00000000000..f29dac307f1
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/service.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2024 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 webhook
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Service{}
+
+// Service scaffolds the Service for webhooks in the Helm chart
+type Service struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+
+ // Force if true allows overwriting the scaffolded file
+ Force bool
+}
+
+// SetTemplateDefaults sets the default template configuration
+func (f *Service) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "templates", "webhook", "service.yaml")
+ }
+
+ f.TemplateBody = webhookServiceTemplate
+
+ f.IfExistsAction = machinery.OverwriteFile
+
+ return nil
+}
+
+const webhookServiceTemplate = `{{` + "`" + `{{- if .Values.webhook.enable }}` + "`" + `}}
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .ProjectName }}-webhook-service
+ namespace: {{ "{{ .Release.Namespace }}" }}
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+spec:
+ ports:
+ - port: 443
+ protocol: TCP
+ targetPort: 9443
+ selector:
+ control-plane: controller-manager
+{{` + "`" + `{{- end }}` + "`" + `}}
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/webhook.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/webhook.go
new file mode 100644
index 00000000000..b8b5b0d5214
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/webhook.go
@@ -0,0 +1,166 @@
+/*
+Copyright 2024 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 webhook
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &Template{}
+
+// Template scaffolds both MutatingWebhookConfiguration and ValidatingWebhookConfiguration for the Helm chart
+type Template struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+
+ MutatingWebhooks []DataWebhook
+ ValidatingWebhooks []DataWebhook
+}
+
+// SetTemplateDefaults sets default configuration for the webhook template
+func (f *Template) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "templates", "webhook", "webhooks.yaml")
+ }
+
+ f.TemplateBody = webhookTemplate
+ f.IfExistsAction = machinery.OverwriteFile
+ return nil
+}
+
+// DataWebhook helps generate manifests based on the data gathered from the kustomize files
+type DataWebhook struct {
+ ServiceName string
+ Name string
+ Path string
+ Type string
+ FailurePolicy string
+ SideEffects string
+ AdmissionReviewVersions []string
+ Rules []DataWebhookRule
+}
+
+// DataWebhookRule helps generate manifests based on the data gathered from the kustomize files
+type DataWebhookRule struct {
+ Operations []string
+ APIGroups []string
+ APIVersions []string
+ Resources []string
+}
+
+const webhookTemplate = `{{` + "`" + `{{- if .Values.webhook.enable }}` + "`" + `}}
+
+{{- if .MutatingWebhooks }}
+apiVersion: admissionregistration.k8s.io/v1
+kind: MutatingWebhookConfiguration
+metadata:
+ name: {{ .ProjectName }}-mutating-webhook-configuration
+ namespace: {{ "{{ .Release.Namespace }}" }}
+ annotations:
+ {{` + "`" + `{{- if .Values.certmanager.enable }}` + "`" + `}}
+ cert-manager.io/inject-ca-from: "{{` + "`" + `{{ $.Release.Namespace }}` + "`" + `}}/serving-cert"
+ {{` + "`" + `{{- end }}` + "`" + `}}
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+webhooks:
+ {{- range .MutatingWebhooks }}
+ - name: {{ .Name }}
+ clientConfig:
+ service:
+ name: {{ .ServiceName }}
+ namespace: {{ "{{ .Release.Namespace }}" }}
+ path: {{ .Path }}
+ failurePolicy: {{ .FailurePolicy }}
+ sideEffects: {{ .SideEffects }}
+ admissionReviewVersions:
+ {{- range .AdmissionReviewVersions }}
+ - {{ . }}
+ {{- end }}
+ rules:
+ {{- range .Rules }}
+ - operations:
+ {{- range .Operations }}
+ - {{ . }}
+ {{- end }}
+ apiGroups:
+ {{- range .APIGroups }}
+ - {{ . }}
+ {{- end }}
+ apiVersions:
+ {{- range .APIVersions }}
+ - {{ . }}
+ {{- end }}
+ resources:
+ {{- range .Resources }}
+ - {{ . }}
+ {{- end }}
+ {{- end -}}
+ {{- end }}
+{{- end }}
+{{- if and .MutatingWebhooks .ValidatingWebhooks }}
+---
+{{- end }}
+{{- if .ValidatingWebhooks }}
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+ name: {{ .ProjectName }}-validating-webhook-configuration
+ namespace: {{ "{{ .Release.Namespace }}" }}
+ annotations:
+ {{` + "`" + `{{- if .Values.certmanager.enable }}` + "`" + `}}
+ cert-manager.io/inject-ca-from: "{{` + "`" + `{{ $.Release.Namespace }}` + "`" + `}}/serving-cert"
+ {{` + "`" + `{{- end }}` + "`" + `}}
+ labels:
+ {{ "{{- include \"chart.labels\" . | nindent 4 }}" }}
+webhooks:
+ {{- range .ValidatingWebhooks }}
+ - name: {{ .Name }}
+ clientConfig:
+ service:
+ name: {{ .ServiceName }}
+ namespace: {{ "{{ .Release.Namespace }}" }}
+ path: {{ .Path }}
+ failurePolicy: {{ .FailurePolicy }}
+ sideEffects: {{ .SideEffects }}
+ admissionReviewVersions:
+ {{- range .AdmissionReviewVersions }}
+ - {{ . }}
+ {{- end }}
+ rules:
+ {{- range .Rules }}
+ - operations:
+ {{- range .Operations }}
+ - {{ . }}
+ {{- end }}
+ apiGroups:
+ {{- range .APIGroups }}
+ - {{ . }}
+ {{- end }}
+ apiVersions:
+ {{- range .APIVersions }}
+ - {{ . }}
+ {{- end }}
+ resources:
+ {{- range .Resources }}
+ - {{ . }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+{{- end }}
+{{` + "`" + `{{- end }}` + "`" + `}}
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart.go
new file mode 100644
index 00000000000..aebc7a142cd
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2024 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 templates
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &HelmChart{}
+
+// HelmChart scaffolds a file that defines the Helm chart structure
+type HelmChart struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *HelmChart) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "Chart.yaml")
+ }
+
+ f.TemplateBody = helmChartTemplate
+
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+const helmChartTemplate = `apiVersion: v2
+name: {{ .ProjectName }}
+description: A Helm chart to distribute the project {{ .ProjectName }}
+type: application
+version: 0.1.0
+appVersion: "0.1.0"
+icon: "https://example.com/icon.png"
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/github/test_chart.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/github/test_chart.go
new file mode 100644
index 00000000000..30bd74fd3d6
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/github/test_chart.go
@@ -0,0 +1,137 @@
+/*
+Copyright 2024 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 github
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &HelmChartCI{}
+
+// HelmChartCI scaffolds the GitHub Action for testing Helm charts
+type HelmChartCI struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *HelmChartCI) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join(".github", "workflows", "test-chart.yml")
+ }
+
+ f.TemplateBody = testChartTemplate
+
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+//nolint:lll
+const testChartTemplate = `name: Test Chart
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ test-e2e:
+ name: Run on Ubuntu
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone the code
+ uses: actions/checkout@v4
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: go.mod
+
+ - name: Install the latest version of kind
+ run: |
+ curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)
+ chmod +x ./kind
+ sudo mv ./kind /usr/local/bin/kind
+
+ - name: Verify kind installation
+ run: kind version
+
+ - name: Create kind cluster
+ run: kind create cluster
+
+ - name: Prepare {{ .ProjectName }}
+ run: |
+ go mod tidy
+ make docker-build IMG={{ .ProjectName }}:v0.1.0
+ kind load docker-image {{ .ProjectName }}:v0.1.0
+
+ - name: Install Helm
+ run: |
+ curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
+
+ - name: Verify Helm installation
+ run: helm version
+
+ - name: Lint Helm Chart
+ run: |
+ helm lint ./dist/chart
+
+# TODO: Uncomment if cert-manager is enabled
+# - name: Install cert-manager via Helm
+# run: |
+# helm repo add jetstack https://charts.jetstack.io
+# helm repo update
+# helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true
+#
+# - name: Wait for cert-manager to be ready
+# run: |
+# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager
+# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector
+# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook
+
+# TODO: Uncomment if Prometheus is enabled
+# - name: Install Prometheus Operator CRDs
+# run: |
+# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
+# helm repo update
+# helm install prometheus-crds prometheus-community/prometheus-operator-crds
+#
+# - name: Install Prometheus via Helm
+# run: |
+# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
+# helm repo update
+# helm install prometheus prometheus-community/prometheus --namespace monitoring --create-namespace
+#
+# - name: Wait for Prometheus to be ready
+# run: |
+# kubectl wait --namespace monitoring --for=condition=available --timeout=300s deployment/prometheus-server
+
+ - name: Install Helm chart for project
+ run: |
+ helm install my-release ./dist/chart --create-namespace --namespace {{ .ProjectName }}-system
+
+ - name: Check Helm release status
+ run: |
+ helm status my-release --namespace {{ .ProjectName }}-system
+
+# TODO: Uncomment if prometheus.enabled is set to true to confirm that the ServiceMonitor gets created
+# - name: Check Presence of ServiceMonitor
+# run: |
+# kubectl wait --namespace {{ .ProjectName }}-system --for=jsonpath='{.kind}'=ServiceMonitor servicemonitor/{{ .ProjectName }}-controller-manager-metrics-monitor
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/helmignore.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/helmignore.go
new file mode 100644
index 00000000000..5aef329f3e7
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/helmignore.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2024 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 templates
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &HelmIgnore{}
+
+// HelmIgnore scaffolds a file that defines the .helmignore for Helm packaging
+type HelmIgnore struct {
+ machinery.TemplateMixin
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *HelmIgnore) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", ".helmignore")
+ }
+
+ f.TemplateBody = helmIgnoreTemplate
+
+ f.IfExistsAction = machinery.SkipFile
+
+ return nil
+}
+
+const helmIgnoreTemplate = `# Patterns to ignore when building Helm packages.
+# Operating system files
+.DS_Store
+
+# Version control directories
+.git/
+.gitignore
+.bzr/
+.hg/
+.hgignore
+.svn/
+
+# Backup and temporary files
+*.swp
+*.tmp
+*.bak
+*.orig
+*~
+
+# IDE and editor-related files
+.idea/
+.vscode/
+
+# Helm chart artifacts
+dist/chart/*.tgz
+`
diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/values.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/values.go
new file mode 100644
index 00000000000..d24e2ec3884
--- /dev/null
+++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/values.go
@@ -0,0 +1,145 @@
+/*
+Copyright 2024 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 templates
+
+import (
+ "path/filepath"
+
+ "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
+)
+
+var _ machinery.Template = &HelmValues{}
+
+// HelmValues scaffolds a file that defines the values.yaml structure for the Helm chart
+type HelmValues struct {
+ machinery.TemplateMixin
+ machinery.ProjectNameMixin
+
+ // DeployImages stores the images used for the DeployImage plugin
+ DeployImages map[string]string
+ // Force if true allows overwriting the scaffolded file
+ Force bool
+ // HasWebhooks is true when webhooks were found in the config
+ HasWebhooks bool
+}
+
+// SetTemplateDefaults implements machinery.Template
+func (f *HelmValues) SetTemplateDefaults() error {
+ if f.Path == "" {
+ f.Path = filepath.Join("dist", "chart", "values.yaml")
+ }
+ f.TemplateBody = helmValuesTemplate
+
+ if f.Force {
+ f.IfExistsAction = machinery.OverwriteFile
+ } else {
+ f.IfExistsAction = machinery.SkipFile
+ }
+
+ return nil
+}
+
+const helmValuesTemplate = `# [MANAGER]: Manager Deployment Configurations
+controllerManager:
+ replicas: 1
+ container:
+ image:
+ repository: controller
+ tag: latest
+ imagePullPolicy: IfNotPresent
+ args:
+ - "--leader-elect"
+ - "--metrics-bind-address=:8443"
+ - "--health-probe-bind-address=:8081"
+ resources:
+ limits:
+ cpu: 500m
+ memory: 128Mi
+ requests:
+ cpu: 10m
+ memory: 64Mi
+ livenessProbe:
+ initialDelaySeconds: 15
+ periodSeconds: 20
+ httpGet:
+ path: /healthz
+ port: 8081
+ readinessProbe:
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ httpGet:
+ path: /readyz
+ port: 8081
+ {{- if .DeployImages }}
+ env:
+ {{- range $kind, $image := .DeployImages }}
+ {{ $kind }}_IMAGE: {{ $image }}
+ {{- end }}
+ {{- end }}
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - "ALL"
+ securityContext:
+ runAsNonRoot: true
+ seccompProfile:
+ type: RuntimeDefault
+ terminationGracePeriodSeconds: 10
+ serviceAccountName: {{ .ProjectName }}-controller-manager
+
+# [RBAC]: To enable RBAC (Permissions) configurations
+rbac:
+ enable: true
+
+# [CRDs]: To enable the CRDs
+crd:
+ # This option determines whether the CRDs are included
+ # in the installation process.
+ enable: true
+
+ # Enabling this option adds the "helm.sh/resource-policy": keep
+ # annotation to the CRD, ensuring it remains installed even when
+ # the Helm release is uninstalled.
+ # NOTE: Removing the CRDs will also remove all cert-manager CR(s)
+ # (Certificates, Issuers, ...) due to garbage collection.
+ keep: true
+
+# [METRICS]: Set to true to generate manifests for exporting metrics.
+# To disable metrics export set false, and ensure that the
+# ControllerManager argument "--metrics-bind-address=:8443" is removed.
+metrics:
+ enable: true
+{{ if .HasWebhooks }}
+# [WEBHOOKS]: Webhooks configuration
+# The following configuration is automatically generated from the manifests
+# generated by controller-gen. To update run 'make manifests' and
+# the edit command with the '--force' flag
+webhook:
+ enable: true
+{{ end }}
+# [PROMETHEUS]: To enable a ServiceMonitor to export metrics to Prometheus set true
+prometheus:
+ enable: false
+
+# [CERT-MANAGER]: To enable cert-manager injection to webhooks set true
+certmanager:
+ enable: {{ .HasWebhooks }}
+
+# [NETWORK POLICIES]: To enable NetworkPolicies set true
+networkPolicy:
+ enable: false
+`
diff --git a/pkg/plugins/optional/helm/v2alpha/edit.go b/pkg/plugins/optional/helm/v2alpha/edit.go
new file mode 100644
index 00000000000..0158b39f5ed
--- /dev/null
+++ b/pkg/plugins/optional/helm/v2alpha/edit.go
@@ -0,0 +1,232 @@
+/*
+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 v2alpha
+
+import (
+ "errors"
+ "fmt"
+ "log/slog"
+ "os"
+ "path/filepath"
+
+ "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"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
+ "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds"
+)
+
+const (
+ // DefaultManifestsFile is the default path for kustomize output manifests
+ DefaultManifestsFile = "dist/install.yaml"
+ // DefaultOutputDir is the default output directory for Helm charts
+ DefaultOutputDir = "dist"
+)
+
+var _ plugin.EditSubcommand = &editSubcommand{}
+
+type editSubcommand struct {
+ config config.Config
+ force bool
+ manifestsFile string
+ outputDir string
+}
+
+//nolint:lll
+func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
+ subcmdMeta.Description = `Generate a Helm chart from your project's kustomize output.
+
+This plugin dynamically generates Helm chart templates by parsing the output of 'make build-installer'
+(dist/install.yaml by default). The generated chart preserves all customizations made to your kustomize
+configuration including environment variables, labels, and annotations.
+
+The chart structure mirrors your config/ directory organization for easy maintenance.`
+
+ subcmdMeta.Examples = fmt.Sprintf(`# Generate Helm chart from default manifests (dist/install.yaml) to default output (dist/)
+ %[1]s edit --plugins=%[2]s
+
+# Generate Helm chart and overwrite existing files (useful for updates)
+ %[1]s edit --plugins=%[2]s --force
+
+# Generate Helm chart from a custom manifests file
+ %[1]s edit --plugins=%[2]s --manifests=path/to/custom-install.yaml
+
+# Generate Helm chart to a custom output directory
+ %[1]s edit --plugins=%[2]s --output-dir=charts
+
+# Generate from custom manifests to custom output directory
+ %[1]s edit --plugins=%[2]s --manifests=manifests/install.yaml --output-dir=helm-charts
+
+# Typical workflow:
+ make build-installer # Generate dist/install.yaml with latest changes
+ %[1]s edit --plugins=%[2]s # Generate/update Helm chart in dist/chart/
+
+**NOTE**: The plugin preserves customizations in values.yaml, Chart.yaml, _helpers.tpl, and .helmignore
+unless --force is used. All template files are regenerated to match your current kustomize output.
+
+The generated chart structure mirrors your config/ directory:
+