diff --git a/.github/workflows/test-helm-samples.yml b/.github/workflows/test-helm-samples.yml new file mode 100644 index 00000000000..281e635fc6d --- /dev/null +++ b/.github/workflows/test-helm-samples.yml @@ -0,0 +1,76 @@ +name: Helm Testdata Sample + +on: + push: + paths: + - 'testdata/project-v4-with-plugins/**' + - '.github/workflows/test-helm-samples.yml' + pull_request: + paths: + - 'testdata/project-v4-with-plugins/**' + - '.github/workflows/test-helm-samples.yml' + +jobs: + helm-test-project-v4-with-plugins: + runs-on: ubuntu-latest + strategy: + fail-fast: true + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '~1.22' + + - name: Install the latest version of kind + run: | + curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 + 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: 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 installCRDs=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 + + - name: Prepare project-v4-with-plugins + run: | + cd testdata/project-v4-with-plugins/ + go mod tidy + make docker-build IMG=project-v4-with-plugins:v0.1.0 + kind load docker-image project-v4-with-plugins: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 for project-v4-with-plugins + run: | + helm lint testdata/project-v4-with-plugins/dist/chart + + - name: Install Helm chart for project-v4-with-plugins + run: | + helm install my-release testdata/project-v4-with-plugins/dist/chart --create-namespace --namespace project-v4-with-plugins-system + + - name: Check Helm release status + run: | + helm status my-release --namespace project-v4-with-plugins-system diff --git a/Makefile b/Makefile index ff51b14e5ba..ed3f4eb27d6 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes .PHONY: yamllint yamllint: - @files=$$(find testdata -name '*.yaml' ! -path 'testdata/*/dist/install.yaml'); \ + @files=$$(find testdata -name '*.yaml' ! -path 'testdata/*/dist/*'); \ docker run --rm $$(tty -s && echo "-it" || echo) -v $(PWD):/data cytopia/yamllint:latest $$files -d "{extends: relaxed, rules: {line-length: {max: 120}}}" --no-warnings GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint @@ -171,3 +171,11 @@ test-spaces: ## Run the trailing spaces check test-legacy: ## Run the tests to validate legacy path for webhooks rm -rf ./testdata/**legacy**/ ./test/testdata/legacy-webhook-path.sh + +.PHONY: install-helm +install-helm: ## Install the latest version of Helm locally + @curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + +.PHONY: helm-lint +helm-lint: install-helm ## Lint the Helm chart in testdata + helm lint testdata/project-v4-with-plugins/dist/chart diff --git a/cmd/main.go b/cmd/main.go index 64a25502cd7..f0e35e16663 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -30,6 +30,7 @@ import ( deployimagev1alpha1 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1" golangv4 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4" grafanav1alpha1 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha" + helmv1alpha1 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha" ) func init() { @@ -61,6 +62,7 @@ func main() { &kustomizecommonv2.Plugin{}, &deployimagev1alpha1.Plugin{}, &grafanav1alpha1.Plugin{}, + &helmv1alpha1.Plugin{}, ), cli.WithPlugins(externalPlugins...), cli.WithDefaultPlugins(cfgv3.Version, gov4Bundle), diff --git a/docs/book/src/cronjob-tutorial/testdata/project/README.md b/docs/book/src/cronjob-tutorial/testdata/project/README.md index b5295ca3fa8..d326a9f33b3 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/README.md +++ b/docs/book/src/cronjob-tutorial/testdata/project/README.md @@ -68,7 +68,13 @@ make undeploy ## Project Distribution -Following are the steps to build the installer and distribute this project to users. +Following the options to release and provide this solution +to the users. + +### By providing a bundle with all YAML files + +Following are the steps to build the installer and distribute +this project to users. 1. Build the installer for the image built and published in the registry: @@ -76,19 +82,40 @@ Following are the steps to build the installer and distribute this project to us make build-installer IMG=/project:tag ``` -NOTE: The makefile target mentioned above generates an 'install.yaml' +**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.: +Users can just run 'kubectl apply -f ' to install the project, i.e.: ```sh kubectl apply -f https://raw.githubusercontent.com//project//dist/install.yaml ``` +### By providing a Helm Chart + +Following are the steps to build a Helm Chart to distribute +this project to users. + +1. Build the chart using the optional helm plugin + +```sh +kubebuilder edit --plugins=helm/v1-alpha +``` + +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 diff --git a/docs/book/src/getting-started/testdata/project/README.md b/docs/book/src/getting-started/testdata/project/README.md index b5295ca3fa8..d326a9f33b3 100644 --- a/docs/book/src/getting-started/testdata/project/README.md +++ b/docs/book/src/getting-started/testdata/project/README.md @@ -68,7 +68,13 @@ make undeploy ## Project Distribution -Following are the steps to build the installer and distribute this project to users. +Following the options to release and provide this solution +to the users. + +### By providing a bundle with all YAML files + +Following are the steps to build the installer and distribute +this project to users. 1. Build the installer for the image built and published in the registry: @@ -76,19 +82,40 @@ Following are the steps to build the installer and distribute this project to us make build-installer IMG=/project:tag ``` -NOTE: The makefile target mentioned above generates an 'install.yaml' +**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.: +Users can just run 'kubectl apply -f ' to install the project, i.e.: ```sh kubectl apply -f https://raw.githubusercontent.com//project//dist/install.yaml ``` +### By providing a Helm Chart + +Following are the steps to build a Helm Chart to distribute +this project to users. + +1. Build the chart using the optional helm plugin + +```sh +kubebuilder edit --plugins=helm/v1-alpha +``` + +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 diff --git a/docs/book/src/multiversion-tutorial/testdata/project/README.md b/docs/book/src/multiversion-tutorial/testdata/project/README.md index b5295ca3fa8..d326a9f33b3 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/README.md +++ b/docs/book/src/multiversion-tutorial/testdata/project/README.md @@ -68,7 +68,13 @@ make undeploy ## Project Distribution -Following are the steps to build the installer and distribute this project to users. +Following the options to release and provide this solution +to the users. + +### By providing a bundle with all YAML files + +Following are the steps to build the installer and distribute +this project to users. 1. Build the installer for the image built and published in the registry: @@ -76,19 +82,40 @@ Following are the steps to build the installer and distribute this project to us make build-installer IMG=/project:tag ``` -NOTE: The makefile target mentioned above generates an 'install.yaml' +**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.: +Users can just run 'kubectl apply -f ' to install the project, i.e.: ```sh kubectl apply -f https://raw.githubusercontent.com//project//dist/install.yaml ``` +### By providing a Helm Chart + +Following are the steps to build a Helm Chart to distribute +this project to users. + +1. Build the chart using the optional helm plugin + +```sh +kubebuilder edit --plugins=helm/v1-alpha +``` + +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 diff --git a/docs/book/src/plugins/available/helm-v1-alpha.md b/docs/book/src/plugins/available/helm-v1-alpha.md new file mode 100644 index 00000000000..4bd25126073 --- /dev/null +++ b/docs/book/src/plugins/available/helm-v1-alpha.md @@ -0,0 +1,92 @@ +# Helm Plugin (`helm/v1-alpha`) + +The Helm plugin is an optional plugin that can be used to scaffold a Helm chart, allowing you to distribute the project using Helm. + +By default, users can generate a bundle with all the manifests by running the following command: + +```bash +make build-installer IMG=/ +``` + +This allows the project consumer to install the solution by applying the bundle with: + +```bash +kubectl apply -f https://raw.githubusercontent.com//project-v4//dist/install.yaml +``` + +However, in many scenarios, you might prefer to provide a Helm chart to package your solution. +If so, you can use this plugin to generate the Helm chart under the `dist` directory. + + + +## When to use it + +- If you want to provide a Helm chart for users to install and manage your project. +- If you need to update the Helm chart generated under `dist/chart/` with the latest project changes: + - After generating new manifests, use the `edit` option to sync the Helm chart. + - **IMPORTANT:** If you have created a webhook or an API using the [DeployImage][deployImage-plugin] plugin, + you must run the `edit` command with the `--force` flag to regenerate the Helm chart values based + on the latest manifests (_after running `make manifests`_) to ensure that the HelmChart values are + updated accordingly. In this case, if you have customized the files + under `dist/chart/values.yaml`, and the `templates/manager/manager.yaml`, you will need to manually reapply your customizations on top + of the latest changes after regenerating the Helm chart. + +## How to use it ? + +### Basic Usage + +The Helm plugin is attached to the `init` subcommand and the `edit` subcommand: + +```sh + +# Initialize a new project with helm chart +kubebuilder init --plugins=helm/v1-alpha + +# Enable or Update the helm chart via the helm plugin to an existing project +# Before run the edit command, run `make manifests` to generate the manifest under `config/` +make manifests +kubebuilder edit --plugins=helm/v1-alpha +``` + + +## Subcommands + +The Helm plugin implements the following subcommands: + +- edit (`$ kubebuilder edit [OPTIONS]`) + +- init (`$ kubebuilder init [OPTIONS]`) + +## Affected files + +The following scaffolds will be created or updated by this plugin: + +- `dist/chart/*` + +[testdata]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata/project-v4-with-plugins +[deployImage-plugin]: ./deploy-image-plugin-v1-alpha.md \ No newline at end of file diff --git a/docs/book/src/plugins/to-add-optional-features.md b/docs/book/src/plugins/to-add-optional-features.md index 6b95e7c02c2..f6e847672dd 100644 --- a/docs/book/src/plugins/to-add-optional-features.md +++ b/docs/book/src/plugins/to-add-optional-features.md @@ -2,10 +2,12 @@ The following plugins are useful to generate code and take advantage of optional features -| Plugin | Key | Description | -|---------------------------------------------------| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [grafana.kubebuilder.io/v1-alpha][grafana] | `grafana/v1-alpha` | Optional helper plugin which can be used to scaffold Grafana Manifests Dashboards for the default metrics which are exported by controller-runtime. | -| [deploy-image.go.kubebuilder.io/v1-alpha][deploy] | `deploy-image/v1-alpha` | Optional helper plugin which can be used to scaffold APIs and controller with code implementation to Deploy and Manage an Operand(image). | +| Plugin | Key | Description | +|---------------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| [grafana.kubebuilder.io/v1-alpha][grafana] | `grafana/v1-alpha` | Optional helper plugin which can be used to scaffold Grafana Manifests Dashboards for the default metrics which are exported by controller-runtime. | +| [deploy-image.go.kubebuilder.io/v1-alpha][deploy] | `deploy-image/v1-alpha` | Optional helper plugin which can be used to scaffold APIs and controller with code implementation to Deploy and Manage an Operand(image). | +| [helm.kubebuilder.io/v1-alpha][helm] | `helm/v1-alpha` | Optional helper plugin which can be used to scaffold a Helm Chart to distribute the project under the `dist` directory | [grafana]: ./available/grafana-v1-alpha.md -[deploy]: ./available/deploy-image-plugin-v1-alpha.md \ No newline at end of file +[deploy]: ./available/deploy-image-plugin-v1-alpha.md +[helm]: ./available/helm-v1-alpha.md \ No newline at end of file diff --git a/pkg/cli/alpha/internal/generate.go b/pkg/cli/alpha/internal/generate.go index 414fefa5102..9126e9befbd 100644 --- a/pkg/cli/alpha/internal/generate.go +++ b/pkg/cli/alpha/internal/generate.go @@ -44,6 +44,7 @@ const ( defaultOutputDir = "output-dir" grafanaPluginKey = "grafana.kubebuilder.io/v1-alpha" deployImagePluginKey = "deploy-image.go.kubebuilder.io/v1-alpha" + helmPluginKey = "helm.kubebuilder.io/v1-alpha" ) // Generate handles the migration and scaffolding process. @@ -77,6 +78,12 @@ func (opts *Generate) Generate() error { return err } + if hasHelmPlugin(config) { + if err := kubebuilderHelmEdit(); err != nil { + return err + } + } + if err := migrateDeployImagePlugin(config); err != nil { return err } @@ -392,3 +399,32 @@ func kubebuilderGrafanaEdit() error { } return nil } + +// Edits the project to include the Grafana plugin. +func kubebuilderHelmEdit() error { + args := []string{"edit", "--plugins", helmPluginKey} + if err := util.RunCmd("kubebuilder edit", "kubebuilder", args...); err != nil { + return fmt.Errorf("failed to run edit subcommand for Helm plugin: %w", err) + } + return nil +} + +// hasHelmPlugin checks if the Helm plugin is present by inspecting the plugin chain or configuration. +func hasHelmPlugin(cfg store.Store) bool { + var pluginConfig map[string]interface{} + + // Decode the Helm plugin configuration to check if it's present + err := cfg.Config().DecodePluginConfig(helmPluginKey, &pluginConfig) + if err != nil { + // If the Helm plugin is not found, return false + if errors.As(err, &config.PluginKeyNotFoundError{}) { + return false + } + // Log other errors if needed + log.Errorf("Error decoding Helm plugin config: %v", err) + return false + } + + // Helm plugin is present + return true +} diff --git a/pkg/plugins/golang/v4/init.go b/pkg/plugins/golang/v4/init.go index 26de76c5238..6d0a651d902 100644 --- a/pkg/plugins/golang/v4/init.go +++ b/pkg/plugins/golang/v4/init.go @@ -122,7 +122,7 @@ func (p *initSubcommand) PreScaffold(machinery.Filesystem) error { } func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { - scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner) + scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner, p.commandName) scaffolder.InjectFS(fs) err := scaffolder.Scaffold() if err != nil { diff --git a/pkg/plugins/golang/v4/scaffolds/init.go b/pkg/plugins/golang/v4/scaffolds/init.go index 9697744e2e4..3978bcac8dc 100644 --- a/pkg/plugins/golang/v4/scaffolds/init.go +++ b/pkg/plugins/golang/v4/scaffolds/init.go @@ -55,18 +55,20 @@ type initScaffolder struct { 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(config config.Config, license, owner string) plugins.Scaffolder { +func NewInitScaffolder(config config.Config, license, owner, commandName string) plugins.Scaffolder { return &initScaffolder{ config: config, boilerplatePath: hack.DefaultBoilerplatePath, license: license, owner: owner, + commandName: commandName, } } @@ -159,7 +161,7 @@ func (s *initScaffolder) Scaffold() error { }, &templates.Dockerfile{}, &templates.DockerIgnore{}, - &templates.Readme{}, + &templates.Readme{CommandName: s.commandName}, &templates.Golangci{}, &e2e.Test{}, &e2e.WebhookTestUpdater{WireWebhook: false}, diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go index ab8c88ebaff..4e2fdd95c26 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go @@ -32,6 +32,9 @@ type Readme struct { machinery.ProjectNameMixin License string + + // CommandName stores the name of the bin used + CommandName string } // SetTemplateDefaults implements file.Template @@ -55,6 +58,7 @@ func (f *Readme) SetTemplateDefaults() error { 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/v1-alpha", f.CommandName)), ) return nil @@ -117,23 +121,48 @@ You can apply the samples (examples) from the config/sample: ## Project Distribution -Following are the steps to build the installer and distribute this project to users. +Following the options to release and provide this solution +to the users. + +### By providing a bundle with all YAML files + +Following are the steps to build the installer and distribute +this project to users. 1. Build the installer for the image built and published in the registry: %s -NOTE: The makefile target mentioned above generates an 'install.yaml' +**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.: +Users can just run 'kubectl apply -f ' to install the project, i.e.: + +%s + +### By providing a Helm Chart + +Following are the steps to build a Helm Chart to distribute +this project to users. + +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 diff --git a/pkg/plugins/golang/v4/webhook.go b/pkg/plugins/golang/v4/webhook.go index b3adca06b6f..685b216db9d 100644 --- a/pkg/plugins/golang/v4/webhook.go +++ b/pkg/plugins/golang/v4/webhook.go @@ -48,6 +48,9 @@ type createWebhookSubcommand struct { // 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) { @@ -69,6 +72,8 @@ validating and/or conversion webhooks. 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, @@ -145,10 +150,13 @@ func (p *createWebhookSubcommand) PostScaffold() error { return err } - err = pluginutil.RunCmd("Running make", "make", "generate") - if err != nil { - return err + if p.runMake { + 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/optional/helm/v1alpha/commons.go b/pkg/plugins/optional/helm/v1alpha/commons.go new file mode 100644 index 00000000000..c6942322ec7 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/commons.go @@ -0,0 +1,37 @@ +/* +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" + + "sigs.k8s.io/kubebuilder/v4/pkg/config" +) + +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 + } + } + + 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..f95674030dc --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/edit.go @@ -0,0 +1,90 @@ +/* +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" + + "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/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=helm/v1-alpha + +# Update the Helm chart under the dist/ directory and overwrite all files + %[1]s edit --plugins=helm/v1-alpha --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) +} + +func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) { + fs.BoolVar(&p.force, "force", true, "if true, run re-generate 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.NewInitHelmScaffolder(p.config, p.force) + scaffolder.InjectFS(fs) + err := scaffolder.Scaffold() + if err != nil { + return err + } + + // Track the resources following a declarative approach + if err := insertPluginMetaToConfig(p.config, pluginConfig{}); err != nil { + return err + } + + return nil +} diff --git a/pkg/plugins/optional/helm/v1alpha/init.go b/pkg/plugins/optional/helm/v1alpha/init.go new file mode 100644 index 00000000000..d9d0f64eaad --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/init.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 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/helm/v1alpha/scaffolds" +) + +var _ plugin.InitSubcommand = &initSubcommand{} + +type initSubcommand struct { + config config.Config +} + +func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Initialize a helm chart to distribute the project under dist/ +` + subcmdMeta.Examples = fmt.Sprintf(`# Initialize a helm chart to distribute the project under dist/ + %[1]s init --plugins=helm/v1-alpha + +**IMORTANT** You must use %[1]s edit --plugins=helm/v1-alpha to update the chart when changes are made. + +`, cliMeta.CommandName) +} + +func (p *initSubcommand) InjectConfig(c config.Config) error { + p.config = c + return nil +} + +func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewInitHelmScaffolder(p.config, false) + scaffolder.InjectFS(fs) + err := scaffolder.Scaffold() + if err != nil { + return err + } + + // Track the resources following a declarative approach + if err := insertPluginMetaToConfig(p.config, pluginConfig{}); err != nil { + return err + } + + return nil +} diff --git a/pkg/plugins/optional/helm/v1alpha/plugin.go b/pkg/plugins/optional/helm/v1alpha/plugin.go new file mode 100644 index 00000000000..21690e4db28 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/plugin.go @@ -0,0 +1,65 @@ +/* +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} + pluginKey = plugin.KeyFor(Plugin{}) +) + +// Plugin implements the plugin.Full interface +type Plugin struct { + initSubcommand + editSubcommand +} + +var ( + _ plugin.Init = Plugin{} + _ 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 grafana 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 scaffolding grafana manifests +func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand } + +// GetEditSubcommand will return the subcommand which is responsible for adding grafana manifests +func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand } + +func (p Plugin) DeprecationWarning() string { + return "" +} diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/init.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/init.go new file mode 100644 index 00000000000..673971ef336 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/init.go @@ -0,0 +1,372 @@ +/* +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 ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "sigs.k8s.io/yaml" + + log "github.com/sirupsen/logrus" + + "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/helm" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates" + chart_templates "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" + templateswebhooks "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook" +) + +var _ plugins.Scaffolder = &initScaffolder{} + +type initScaffolder struct { + config config.Config + + fs machinery.Filesystem + + force bool +} + +// NewInitHelmScaffolder returns a new Scaffolder for HelmPlugin +func NewInitHelmScaffolder(config config.Config, force bool) plugins.Scaffolder { + return &initScaffolder{ + config: config, + force: force, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *initScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold scaffolds the Helm chart with the necessary files. +func (s *initScaffolder) Scaffold() error { + log.Println("Generating Helm Chart to distribute project") + + // Extract Images scaffolded with DeployImage to add ENVVAR to the values + imagesEnvVars := s.getDeployImagesEnvVars() + + // Extract webhooks from generated YAML files (generated by controller-gen) + webhooks, err := extractWebhooksFromGeneratedFiles() + if err != nil { + return fmt.Errorf("failed to extract webhooks: %w", err) + } + + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + + buildScaffold := []machinery.Builder{ + &templates.HelmChart{}, + &templates.HelmValues{ + HasWebhooks: len(webhooks) > 0, + Webhooks: webhooks, + DeployImages: imagesEnvVars, + Force: s.force, + }, + &templates.HelmIgnore{}, + &chart_templates.HelmHelpers{}, + &manager.ManagerDeployment{ + Force: s.force, + DeployImages: len(imagesEnvVars) > 0, + HasWebhooks: len(webhooks) > 0, + }, + &templatescertmanager.Certificate{}, + &templatesmetrics.MetricsService{}, + } + + if len(webhooks) > 0 { + buildScaffold = append(buildScaffold, &templateswebhooks.WebhookTemplate{}) + buildScaffold = append(buildScaffold, &templateswebhooks.WebhookService{}) + } + + if err := scaffold.Execute(buildScaffold...); err != nil { + return fmt.Errorf("error scaffolding helm-chart manifests: %v", 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/: %v", err) + } + + return nil +} + +// getDeployImagesEnvVars will return the values to append the envvars for projects +// which has the APIs scaffolded with DeployImage plugin +func (s *initScaffolder) 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"` + }{} + + const deployImageKey = "deploy-image.go.kubebuilder.io/v1-alpha" + err := s.config.DecodePluginConfig(deployImageKey, &pluginConfig) + if err == nil { + for _, res := range pluginConfig.Resources { + image, ok := res.Options["image"] + if ok { + deployImages[strings.ToUpper(res.Kind)] = image + } + } + } + return deployImages +} + +// Extract webhooks from manifests.yaml file +func extractWebhooksFromGeneratedFiles() ([]helm.WebhookYAML, error) { + var webhooks []helm.WebhookYAML + manifestFile := "config/webhook/manifests.yaml" + if _, err := os.Stat(manifestFile); err == nil { + content, err := os.ReadFile(manifestFile) + if err != nil { + return nil, fmt.Errorf("failed to read manifests.yaml: %w", err) + } + + // Process the content to extract webhooks + webhooks = append(webhooks, extractWebhookYAML(content)...) + } else { + // Return empty if no webhooks were found + return webhooks, nil + } + + return webhooks, nil +} + +// extractWebhookYAML parses the webhook YAML content and returns a list of WebhookYAML +func extractWebhookYAML(content []byte) []helm.WebhookYAML { + var webhooks []helm.WebhookYAML + + type 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"` + CABundle string `yaml:"caBundle"` + } `yaml:"clientConfig"` + Rules []helm.WebhookRule `yaml:"rules"` + FailurePolicy string `yaml:"failurePolicy"` + SideEffects string `yaml:"sideEffects"` + AdmissionReviewVersions []string `yaml:"admissionReviewVersions"` + } `yaml:"webhooks"` + } + + // Split the input into different documents (to handle multiple YAML docs in one file) + docs := strings.Split(string(content), "---") + + for _, doc := range docs { + var webhookConfig WebhookConfig + if err := yaml.Unmarshal([]byte(doc), &webhookConfig); err != nil { + log.Errorf("Error unmarshalling webhook YAML: %v", err) + continue + } + + // Determine the webhook type (mutating or validating) + webhookType := "unknown" + if webhookConfig.Kind == "MutatingWebhookConfiguration" { + webhookType = "mutating" + } else if webhookConfig.Kind == "ValidatingWebhookConfiguration" { + webhookType = "validating" + } + + // Parse each webhook and append it to the result + for _, webhook := range webhookConfig.Webhooks { + for i := range webhook.Rules { + // If apiGroups is empty, set it to [""] to ensure proper YAML output + if len(webhook.Rules[i].APIGroups) == 0 { + webhook.Rules[i].APIGroups = []string{""} + } + } + webhooks = append(webhooks, helm.WebhookYAML{ + Name: webhook.Name, + Type: webhookType, + Path: webhook.ClientConfig.Service.Path, + Rules: webhook.Rules, + FailurePolicy: webhook.FailurePolicy, + SideEffects: webhook.SideEffects, + AdmissionReviewVersions: webhook.AdmissionReviewVersions, + }) + } + } + return webhooks +} + +// Helper function to copy files from config/ to dist/chart/templates/ +func (s *initScaffolder) 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"}, + // Copy the conversion webhooks + {"config/crd/patches", "dist/chart/templates/webhook", "webhook"}, + {"config/prometheus", "dist/chart/templates/prometheus", "prometheus"}, + {"config/network-policy", "dist/chart/templates/network-policy", "networkPolicy"}, + } + + for _, dir := range configDirs { + files, err := filepath.Glob(filepath.Join(dir.SrcDir, "*.yaml")) + if err != nil { + return err + } + + for _, srcFile := range files { + // Only copy files prefixed with "webhook" in the webhook sub-directory + if dir.SrcDir == "config/crd/patches" && !strings.HasPrefix(filepath.Base(srcFile), "webhook") { + continue + } + + destFile := filepath.Join(dir.DestDir, filepath.Base(srcFile)) + err := copyFileWithHelmLogic(srcFile, destFile, dir.SubDir, s.config.GetProjectName()) + if err != nil { + return err + } + } + } + + return nil +} + +// copyFileWithHelmLogic reads the source file, modifies the content for Helm, and writes it to the destination +func copyFileWithHelmLogic(srcFile, destFile, subDir, projectName string) error { + if _, err := os.Stat(srcFile); os.IsNotExist(err) { + log.Printf("Source file does not exist: %s", srcFile) + return err + } + + content, err := os.ReadFile(srcFile) + if err != nil { + log.Printf("Error reading source file: %s", srcFile) + return 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" { + // Replace ServiceAccount name in RBAC files + contentStr = strings.Replace(contentStr, + "name: controller-manager", + fmt.Sprintf("name: %s-controller-manager", projectName), -1) + + // Replace metrics role name + contentStr = strings.Replace(contentStr, + "name: metrics-reader", + fmt.Sprintf("name: %s-metrics-reader", projectName), 1) + } + + // Apply Prometheus-specific replacement + if subDir == "prometheus" { + contentStr = strings.Replace(contentStr, + "name: controller-manager-metrics-monitor", + fmt.Sprintf("name: %s-controller-manager-metrics-monitor", projectName), 1) + } + + // Conditionally add "helm.sh/resource-policy: keep" annotation for CRDs + if subDir == "crd" { + contentStr = strings.Replace(contentStr, "metadata:", `metadata: + annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }}`, 1) + } + + // Handle webhook-specific modifications + if subDir == "webhook" { + // Add cert-manager CA injection annotation + contentStr = strings.Replace(contentStr, "metadata:", `metadata: + annotations: + {{- if .Values.certmanager.create }} + cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/serving-cert" + {{- end }}`, 1) + } + + // Remove existing labels if necessary + contentStr = removeLabels(contentStr) + + // Replace namespace with Helm template variable + contentStr = strings.ReplaceAll(contentStr, "namespace: system", "namespace: {{ .Release.Namespace }}") + + // Add Helm chart labels to metadata + contentStr = strings.Replace(contentStr, "metadata:", `metadata: + labels: + {{- include "chart.labels" . | nindent 4 }}`, 1) + + var wrappedContent string + if isMetricRBACFile(subDir, srcFile) { + // Use both rbac and metrics conditions for specific metric files + wrappedContent = fmt.Sprintf("{{- if and .Values.rbac.create .Values.metrics.create }}\n%s{{- end -}}\n", contentStr) + } else { + // Default condition for other files + wrappedContent = fmt.Sprintf("{{- if .Values.%s.create }}\n%s{{- end -}}\n", subDir, contentStr) + } + + if err := os.MkdirAll(filepath.Dir(destFile), os.ModePerm); err != nil { + return err + } + + err = os.WriteFile(destFile, []byte(wrappedContent), os.ModePerm) + if err != nil { + log.Printf("Error writing destination file: %s", destFile) + return err + } + + log.Printf("Successfully copied %s to %s", srcFile, destFile) + return nil +} + +// 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, "") +} 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..15c8c3177f8 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager/certificate.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 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 +} + +// 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.create }}` + "`" + `}} +# 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: {} +--- +# 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 }}` + "`" + `}} +` 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..61e312bb607 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go @@ -0,0 +1,105 @@ +/* +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 chart_templates + +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 +} + +// nolint:lll +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..89ec1a7c956 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager/manager.go @@ -0,0 +1,116 @@ +/* +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 = &ManagerDeployment{} + +// ManagerDeployment scaffolds the manager Deployment for the Helm chart +type ManagerDeployment struct { + machinery.TemplateMixin + + // Force 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 *ManagerDeployment) 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: controller-manager + namespace: {{ "{{ .Release.Namespace }}" }} + labels: + {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} + control-plane: controller-manager +spec: + selector: + matchLabels: + {{ "{{- include \"chart.selectorLabels\" . | nindent 6 }}" }} + control-plane: controller-manager + template: + metadata: + labels: + {{ "{{- include \"chart.labels\" . | nindent 8 }}" }} + control-plane: controller-manager + spec: + serviceAccountName: {{ "{{ .Values.controllerManager.serviceAccountName }}" }} + containers: + - name: manager + image: {{ "{{ .Values.controllerManager.image.repository }}" }}:{{ "{{ .Values.controllerManager.image.tag }}" }} + args: + {{ "{{- range .Values.controllerManager.args }}" }} + - {{ "{{ . }}" }} + {{ "{{- end }}" }} + {{ "{{- if .DeployImages }}" }} + env: + {{ "{{- range $key, $value := .Values.controllerManager.env }}" }} + - name: {{ "{{ $key }}" }} + value: {{ "{{ $value }}" }} + {{ "{{- end }}" }} + {{ "{{- end }}" }} + livenessProbe: + {{ "{{- toYaml .Values.controllerManager.livenessProbe | nindent 8 }}" }} + readinessProbe: + {{ "{{- toYaml .Values.controllerManager.readinessProbe | nindent 8 }}" }} + resources: + {{ "{{- toYaml .Values.controllerManager.resources | nindent 8 }}" }} + securityContext: + {{ "{{- toYaml .Values.controllerManager.securityContext | nindent 8 }}" }} + {{- if .HasWebhooks }} + {{ "{{- if and .Values.webhook.create .Values.certmanager.create }}" }} + volumeMounts: + - name: webhook-cert + mountPath: /tmp/k8s-webhook-server/serving-certs + readOnly: true + {{ "{{- end }}" }} + {{- end }} + terminationGracePeriodSeconds: {{ "{{ .Values.controllerManager.terminationGracePeriodSeconds }}" }} + {{- if .HasWebhooks }} + {{ "{{- if and .Values.webhook.create .Values.certmanager.create }}" }} + volumes: + - name: webhook-cert + secret: + secretName: webhook-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..7946d4969dd --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go @@ -0,0 +1,62 @@ +/* +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 = &MetricsService{} + +// MetricsService scaffolds the Service for metrics in the Helm chart +type MetricsService struct { + machinery.TemplateMixin + machinery.ProjectNameMixin +} + +// SetTemplateDefaults sets the default template configuration +func (f *MetricsService) 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.create }}` + "`" + `}} +apiVersion: v1 +kind: Service +metadata: + name: {{ .ProjectName }}-controller-manager-metrics-service + namespace: {{ "{{ .Release.Namespace }}" }} + labels: + {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} +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/webhook/service.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/service.go new file mode 100644 index 00000000000..38fe9b66a70 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/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 webhook + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &WebhookService{} + +// WebhookService scaffolds the Service for webhooks in the Helm chart +type WebhookService struct { + machinery.TemplateMixin + + // Force if true allows overwriting the scaffolded file + Force bool +} + +// SetTemplateDefaults sets the default template configuration +func (f *WebhookService) 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.create }}` + "`" + `}} +apiVersion: v1 +kind: Service +metadata: + name: {{ "{{ include \"chart.name\" . }}" }}-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..a6a88cfa08d --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/webhook.go @@ -0,0 +1,142 @@ +/* +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 = &WebhookTemplate{} + +// WebhookTemplate scaffolds both MutatingWebhookConfiguration and ValidatingWebhookConfiguration for the Helm chart +type WebhookTemplate struct { + machinery.TemplateMixin + machinery.ProjectNameMixin +} + +// SetTemplateDefaults sets default configuration for the webhook template +func (f *WebhookTemplate) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("dist", "chart", "templates", "webhook", "webhooks.yaml") + } + + f.TemplateBody = webhookTemplate + + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const webhookTemplate = `{{` + "`" + `{{- if .Values.webhook.create }}` + "`" + `}} + +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ .ProjectName }}-mutating-webhook-configuration + namespace: {{ "{{ .Release.Namespace }}" }} + annotations: + {{` + "`" + `{{- if .Values.certmanager.create }}` + "`" + `}} + cert-manager.io/inject-ca-from: "{{` + "`" + `{{ $.Release.Namespace }}` + "`" + `}}/serving-cert" + {{` + "`" + `{{- end }}` + "`" + `}} + labels: + {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} +webhooks: + {{` + "`" + `{{- range .Values.webhook.services }}` + "`" + `}} + {{` + "`" + `{{- if eq .type "mutating" }}` + "`" + `}} + - name: {{` + "`" + `{{ .name }}` + "`" + `}} + clientConfig: + service: + name: {{ .ProjectName }}-webhook-service + 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 }}` + "`" + `}} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: {{ .ProjectName }}-validating-webhook-configuration + namespace: {{ "{{ .Release.Namespace }}" }} + annotations: + {{` + "`" + `{{- if .Values.certmanager.create }}` + "`" + `}} + cert-manager.io/inject-ca-from: "{{` + "`" + `{{ $.Release.Namespace }}` + "`" + `}}/serving-cert" + {{` + "`" + `{{- end }}` + "`" + `}} +webhooks: + {{` + "`" + `{{- range .Values.webhook.services }}` + "`" + `}} + {{` + "`" + `{{- if eq .type "validating" }}` + "`" + `}} + - name: {{` + "`" + `{{ .name }}` + "`" + `}} + clientConfig: + service: + name: {{ .ProjectName }}-webhook-service + 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..d870b7a23cc --- /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{} + +// Chart scaffolds a file that defines the Helm chart structure +type HelmChart struct { + machinery.TemplateMixin + machinery.ProjectNameMixin +} + +// SetTemplateDefaults implements file.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/helmignore.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/helmignore.go new file mode 100644 index 00000000000..212e14db0de --- /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 file.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..c948b735f61 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/values.go @@ -0,0 +1,182 @@ +/* +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" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm" +) + +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 + // Webhooks stores the webhook configurations + Webhooks []helm.WebhookYAML + // HasWebhooks is true when webhooks were found in the config + HasWebhooks bool +} + +// SetTemplateDefaults implements file.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: + image: + repository: controller + tag: latest + replicas: 1 + 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 + securityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + serviceAccountName: {{ .ProjectName }}-controller-manager + terminationGracePeriodSeconds: 10 +{{- if .DeployImages }} + env: + {{- range $kind, $image := .DeployImages }} + {{ $kind }}_IMAGE: {{ $image }} + {{- end }} +{{- end }} + +# [CRDs]: To create RBAC (Permissions) configurations +rbac: + create: true + +# [CRDs]: To create the CRDs +crd: + # This option decides if the CRDs should be installed + # as part of the Helm installation. + create: true + + # This option makes it so that the "helm.sh/resource-policy": keep + # annotation is added to the CRD. This will prevent Helm from uninstalling + # the CRD when the Helm release is uninstalled. + # WARNING: when the CRDs are removed, all cert-manager custom resources + # (Certificates, Issuers, ...) will be removed too by the garbage collector. + 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: + create: true +{{ if .Webhooks }} +# [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: + create: true + services: + {{- range .Webhooks }} + - name: {{ .Name }} + type: {{ .Type }} + path: {{ .Path }} + failurePolicy: {{ .FailurePolicy }} + sideEffects: {{ .SideEffects }} + admissionReviewVersions: + {{- range .AdmissionReviewVersions }} + - {{ . }} + {{- end }} + rules: + {{- range .Rules }} + - operations: + {{- range .Operations }} + - {{ . }} + {{- end }} + apiGroups: + {{- if .APIGroups }} + {{- range .APIGroups }} + {{- if eq . "" }} + - "" + {{- else }} + - {{ . }} + {{- end }} + {{- end }} + {{- else }} + - "" + {{- end }} + apiVersions: + {{- range .APIVersions }} + - {{ . }} + {{- end }} + resources: + {{- range .Resources }} + - {{ . }} + {{- end }} + {{- end }} + {{- end }} +{{ end }} +# [PROMETHEUS]: To create a ServiceMonitor to export metrics to Prometheus set true +prometheus: + create: false + +# [CERT-MANAGER]: To enable cert-manager injection to webhooks set true +certmanager: + create: {{ .HasWebhooks }} + +# [NETWORK POLICIES]: To create NetworkPolices set true +networkPolicy: + create: false +` diff --git a/pkg/plugins/optional/helm/webhookYAML.go b/pkg/plugins/optional/helm/webhookYAML.go new file mode 100644 index 00000000000..3880fa79065 --- /dev/null +++ b/pkg/plugins/optional/helm/webhookYAML.go @@ -0,0 +1,35 @@ +/* +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 helm + +// WebhookRule represents the rules for operations, API groups, versions, and resources +type WebhookRule struct { + Operations []string `yaml:"operations"` + APIGroups []string `yaml:"apiGroups"` + APIVersions []string `yaml:"apiVersions"` + Resources []string `yaml:"resources"` +} + +// WebhookYAML stores the details of each webhook generated by controller-gen +type WebhookYAML struct { + Name string `yaml:"name"` + Type string `yaml:"type"` + Path string `yaml:"path"` + Rules []WebhookRule `yaml:"rules"` + FailurePolicy string `yaml:"failurePolicy"` + SideEffects string `yaml:"sideEffects"` + AdmissionReviewVersions []string `yaml:"admissionReviewVersions"` +} diff --git a/test/e2e/alphagenerate/generate_test.go b/test/e2e/alphagenerate/generate_test.go index 45353c8d77b..5b896e6f624 100644 --- a/test/e2e/alphagenerate/generate_test.go +++ b/test/e2e/alphagenerate/generate_test.go @@ -85,6 +85,12 @@ var _ = Describe("kubebuilder", func() { regenerateProject(kbc, projectOutputDir) validateDeployImagePlugin(projectFilePath) }) + + It("should regenerate project with helm plugin with success", func() { + generateProjectWithHelmPlugin(kbc) + regenerateProject(kbc, projectOutputDir) + validateHelmPlugin(projectFilePath) + }) }) }) @@ -181,6 +187,12 @@ func generateProjectWithGrafanaPlugin(kbc *utils.TestContext) { Expect(err).NotTo(HaveOccurred(), "Failed to edit project to enable Grafana Plugin") } +func generateProjectWithHelmPlugin(kbc *utils.TestContext) { + By("editing project to enable Helm plugin") + err := kbc.Edit("--plugins", "helm.kubebuilder.io/v1-alpha") + Expect(err).NotTo(HaveOccurred(), "Failed to edit project to enable Helm Plugin") +} + func generateProjectWithDeployImagePlugin(kbc *utils.TestContext) { By("creating an API with DeployImage plugin") err := kbc.CreateAPI( @@ -323,3 +335,13 @@ func validateDeployImagePlugin(projectFile string) { Expect(options.ContainerPort).To(Equal("11211"), "Expected container port to match") Expect(options.RunAsUser).To(Equal("1001"), "Expected runAsUser to match") } + +func validateHelmPlugin(projectFile string) { + projectConfig := getConfigFromProjectFile(projectFile) + + By("checking the Helm plugin in the PROJECT file") + var helmPluginConfig map[string]interface{} + err := projectConfig.DecodePluginConfig("helm.kubebuilder.io/v1-alpha", &helmPluginConfig) + Expect(err).NotTo(HaveOccurred(), "Failed to decode Helm plugin configuration") + Expect(helmPluginConfig).NotTo(BeNil(), "Expected Helm plugin configuration to be present in the PROJECT file") +} diff --git a/test/e2e/utils/test_context.go b/test/e2e/utils/test_context.go index 922e235aa29..149564088f9 100644 --- a/test/e2e/utils/test_context.go +++ b/test/e2e/utils/test_context.go @@ -331,3 +331,55 @@ func (t *TestContext) AllowProjectBeMultiGroup() error { } return nil } + +// InstallHelm installs Helm in the e2e server. +func (t *TestContext) InstallHelm() error { + helmInstallScript := "https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3" + cmd := exec.Command("bash", "-c", fmt.Sprintf("curl -fsSL %s | bash", helmInstallScript)) + _, err := t.Run(cmd) + if err != nil { + return err + } + + verifyCmd := exec.Command("helm", "version") + _, err = t.Run(verifyCmd) + if err != nil { + return err + } + + return nil +} + +// UninstallHelmRelease removes the specified Helm release from the cluster. +func (t *TestContext) UninstallHelmRelease() error { + ns := fmt.Sprintf("e2e-%s-system", t.TestSuffix) + cmd := exec.Command("helm", "uninstall", + fmt.Sprintf("release-%s", t.TestSuffix), + "--namespace", ns) + + _, err := t.Run(cmd) + if err != nil { + return err + } + + if _, err := t.Kubectl.Wait(false, "namespace", ns, "--for=delete", "--timeout=2m"); err != nil { + log.Printf("failed to wait for namespace deletion: %s", err) + } + + return nil +} + +// EditHelmPlugin is for running `kubebuilder edit --plugins=helm.kubebuilder.io/v1-alpha` +func (t *TestContext) EditHelmPlugin() error { + cmd := exec.Command(t.BinaryName, "edit", "--plugins=helm/v1-alpha") + _, err := t.Run(cmd) + return err +} + +// HelmInstallRelease is for running install the HelmChart +func (t *TestContext) HelmInstallRelease() error { + cmd := exec.Command("helm", "install", fmt.Sprintf("release-%s", t.TestSuffix), "dist/chart", + "--namespace", fmt.Sprintf("e2e-%s-system", t.TestSuffix)) + _, err := t.Run(cmd) + return err +} diff --git a/test/e2e/v4/plugin_cluster_test.go b/test/e2e/v4/plugin_cluster_test.go index 6aa21049ff6..9ed616366f7 100644 --- a/test/e2e/v4/plugin_cluster_test.go +++ b/test/e2e/v4/plugin_cluster_test.go @@ -61,7 +61,7 @@ var _ = Describe("kubebuilder", func() { _ = kbc.RemoveNamespaceLabelToWarnAboutRestricted() By("clean up API objects created during the test") - Expect(kbc.Make("undeploy")).To(Succeed()) + _ = kbc.Make("undeploy") By("removing controller image and working dir") kbc.Destroy() @@ -69,39 +69,51 @@ var _ = Describe("kubebuilder", func() { It("should generate a runnable project", func() { kbc.IsRestricted = false GenerateV4(kbc) - Run(kbc, true, false, true, false) + Run(kbc, true, false, false, true, false) }) It("should generate a runnable project with the Installer", func() { kbc.IsRestricted = false GenerateV4(kbc) - Run(kbc, true, true, true, false) + Run(kbc, true, true, false, true, false) + }) + It("should generate a runnable project using webhooks and installed with the HelmChart", func() { + kbc.IsRestricted = false + GenerateV4(kbc) + By("installing Helm") + Expect(kbc.InstallHelm()).To(Succeed()) + + Run(kbc, true, false, true, true, false) + + By("uninstalling Helm Release") + Expect(kbc.UninstallHelmRelease()).To(Succeed()) }) It("should generate a runnable project without metrics exposed", func() { kbc.IsRestricted = false GenerateV4WithoutMetrics(kbc) - Run(kbc, true, false, false, false) + Run(kbc, true, false, false, false, false) }) It("should generate a runnable project with metrics protected by network policies", func() { kbc.IsRestricted = false GenerateV4WithNetworkPoliciesWithoutWebhooks(kbc) - Run(kbc, false, false, true, true) + Run(kbc, false, false, false, true, true) }) It("should generate a runnable project with webhooks and metrics protected by network policies", func() { kbc.IsRestricted = false GenerateV4WithNetworkPolicies(kbc) - Run(kbc, true, false, true, true) + Run(kbc, true, false, false, true, true) }) It("should generate a runnable project with the manager running "+ "as restricted and without webhooks", func() { kbc.IsRestricted = true GenerateV4WithoutWebhooks(kbc) - Run(kbc, false, false, true, false) + Run(kbc, false, false, false, true, false) }) }) }) // Run runs a set of e2e tests for a scaffolded project defined by a TestContext. -func Run(kbc *utils.TestContext, hasWebhook, isToUseInstaller, hasMetrics bool, hasNetworkPolicies bool) { +func Run(kbc *utils.TestContext, hasWebhook, isToUseInstaller, isToUseHelmChart, hasMetrics bool, + hasNetworkPolicies bool) { var controllerPodName string var err error var output []byte @@ -132,14 +144,14 @@ func Run(kbc *utils.TestContext, hasWebhook, isToUseInstaller, hasMetrics bool, err = kbc.LoadImageToKindCluster() ExpectWithOffset(1, err).NotTo(HaveOccurred()) - if !isToUseInstaller { + if !isToUseInstaller && !isToUseHelmChart { By("deploying the controller-manager") cmd := exec.Command("make", "deploy", "IMG="+kbc.ImageName) output, err = kbc.Run(cmd) ExpectWithOffset(1, err).NotTo(HaveOccurred()) } - if isToUseInstaller { + if isToUseInstaller && !isToUseHelmChart { By("building the installer") err = kbc.Make("build-installer", "IMG="+kbc.ImageName) ExpectWithOffset(1, err).NotTo(HaveOccurred()) @@ -149,6 +161,30 @@ func Run(kbc *utils.TestContext, hasWebhook, isToUseInstaller, hasMetrics bool, ExpectWithOffset(1, err).NotTo(HaveOccurred()) } + if isToUseHelmChart && !isToUseInstaller { + By("building the helm-chart") + err = kbc.EditHelmPlugin() + Expect(err).NotTo(HaveOccurred(), "Failed to edit project to generate helm-chart") + + By("updating values with image name") + values := filepath.Join(kbc.Dir, "dist", "chart", "values.yaml") + err = util.ReplaceInFile(values, "repository: controller", "repository: e2e-test/controller-manager") + Expect(err).NotTo(HaveOccurred(), "Failed to edit repository in the chart/values.yaml") + err = util.ReplaceInFile(values, "tag: latest", fmt.Sprintf("tag: %s", kbc.TestSuffix)) + Expect(err).NotTo(HaveOccurred(), "Failed to edit tag in the chart/values.yaml") + + By("updating values to enable prometheus") + err = util.ReplaceInFile(values, "prometheus:\n create: false", "prometheus:\n create: true") + Expect(err).NotTo(HaveOccurred(), "Failed to enable prometheus in the chart/values.yaml") + + By("updating values to set crd.keep false") + err = util.ReplaceInFile(values, "keep: true", "keep: false") + Expect(err).NotTo(HaveOccurred(), "Failed to set keep false in the chart/values.yaml") + + By("install with Helm release") + err = kbc.HelmInstallRelease() + Expect(err).NotTo(HaveOccurred(), "Failed to install helm release") + } if kbc.IsRestricted { By("validating that manager Pod/container(s) are restricted") ExpectWithOffset(1, output).NotTo(ContainSubstring("Warning: would violate PodSecurity")) @@ -377,7 +413,7 @@ func getControllerName(kbc *utils.TestContext) string { ExpectWithOffset(1, err).NotTo(HaveOccurred()) _, _ = fmt.Fprintln(GinkgoWriter, out) }() - EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed()) + EventuallyWithOffset(1, verifyControllerUp, 5*time.Minute, time.Second).Should(Succeed()) return controllerPodName } diff --git a/test/testdata/generate.sh b/test/testdata/generate.sh index 4d96fb2abc9..9fff173cbe0 100755 --- a/test/testdata/generate.sh +++ b/test/testdata/generate.sh @@ -88,13 +88,46 @@ function scaffold_test_project { header_text 'Creating APIs with deploy-image plugin ...' $kb create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:1.6.26-alpine3.19 --image-container-command="memcached,--memory-limit=64,-o,modern,-v" --image-container-port="11211" --run-as-user="1001" --plugins="deploy-image/v1-alpha" --make=false $kb create api --group example.com --version v1alpha1 --kind Busybox --image=busybox:1.36.1 --plugins="deploy-image/v1-alpha" --make=false - $kb create webhook --group example.com --version v1alpha1 --kind Memcached --programmatic-validation + # Create only defaulting webhook for Busybox + $kb create webhook --group example.com --version v1alpha1 --kind Busybox --defaulting --make=false + # Create only validation webhook for Memcached + $kb create webhook --group example.com --version v1alpha1 --kind Memcached --programmatic-validation --make=false + # Create API to check webhook --conversion from v1 to v2 + $kb create api --group example.com --version v1 --kind Conversion --controller=true --resource=true --make=false + $kb create api --group example.com --version v2 --kind Conversion --controller=false --resource=true --make=false + $kb create webhook --group example.com --version v1 --kind Conversion --conversion --make=false + # TODO: Remove it when we have the hub and spoke scaffolded by Kubebuilder + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + if [[ $project =~ multigroup ]]; then + sed -i '' '43i \ + // +kubebuilder:storageversion\'$'\n''// +kubebuilder:conversion:hub' api/example.com/v1/conversion_types.go + fi + if [[ $project =~ with-plugins ]]; then + sed -i '' '43i \ + // +kubebuilder:storageversion\'$'\n''// +kubebuilder:conversion:hub' api/v1/conversion_types.go + fi + else + # Linux + if [[ $project =~ multigroup ]]; then + sed -i '43i // +kubebuilder:storageversion\n// +kubebuilder:conversion:hub' api/example.com/v1/conversion_types.go + fi + if [[ $project =~ with-plugins ]]; then + sed -i '43i // +kubebuilder:storageversion\n// +kubebuilder:conversion:hub' api/v1/conversion_types.go + fi + fi header_text 'Editing project with Grafana plugin ...' $kb edit --plugins=grafana.kubebuilder.io/v1-alpha fi make all make build-installer + + if [[ $project =~ with-plugins ]] ; then + header_text 'Editing project with Helm plugin ...' + $kb edit --plugins=helm.kubebuilder.io/v1-alpha + fi + # To avoid conflicts rm -f go.sum go mod tidy diff --git a/testdata/project-v4-multigroup/PROJECT b/testdata/project-v4-multigroup/PROJECT index 9a93ab257f1..f1878a2d663 100644 --- a/testdata/project-v4-multigroup/PROJECT +++ b/testdata/project-v4-multigroup/PROJECT @@ -171,4 +171,27 @@ resources: kind: Busybox path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1 version: v1alpha1 + webhooks: + defaulting: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org + group: example.com + kind: Conversion + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1 + version: v1 + webhooks: + conversion: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + domain: testproject.org + group: example.com + kind: Conversion + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v2 + version: v2 version: "3" diff --git a/testdata/project-v4-multigroup/README.md b/testdata/project-v4-multigroup/README.md index be36750a352..363319a6932 100644 --- a/testdata/project-v4-multigroup/README.md +++ b/testdata/project-v4-multigroup/README.md @@ -68,7 +68,13 @@ make undeploy ## Project Distribution -Following are the steps to build the installer and distribute this project to users. +Following the options to release and provide this solution +to the users. + +### By providing a bundle with all YAML files + +Following are the steps to build the installer and distribute +this project to users. 1. Build the installer for the image built and published in the registry: @@ -76,19 +82,40 @@ Following are the steps to build the installer and distribute this project to us make build-installer IMG=/project-v4-multigroup:tag ``` -NOTE: The makefile target mentioned above generates an 'install.yaml' +**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.: +Users can just run 'kubectl apply -f ' to install the project, i.e.: ```sh kubectl apply -f https://raw.githubusercontent.com//project-v4-multigroup//dist/install.yaml ``` +### By providing a Helm Chart + +Following are the steps to build a Helm Chart to distribute +this project to users. + +1. Build the chart using the optional helm plugin + +```sh +kubebuilder edit --plugins=helm/v1-alpha +``` + +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 diff --git a/testdata/project-v4-multigroup/api/example.com/v1/conversion_types.go b/testdata/project-v4-multigroup/api/example.com/v1/conversion_types.go new file mode 100644 index 00000000000..90937fff496 --- /dev/null +++ b/testdata/project-v4-multigroup/api/example.com/v1/conversion_types.go @@ -0,0 +1,65 @@ +/* +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 v1 + +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. + +// ConversionSpec defines the desired state of Conversion. +type ConversionSpec 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 Conversion. Edit conversion_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// ConversionStatus defines the observed state of Conversion. +type ConversionStatus 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 +// +kubebuilder:storageversion +// +kubebuilder:conversion:hub +// Conversion is the Schema for the conversions API. +type Conversion struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ConversionSpec `json:"spec,omitempty"` + Status ConversionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ConversionList contains a list of Conversion. +type ConversionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Conversion `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Conversion{}, &ConversionList{}) +} diff --git a/testdata/project-v4-multigroup/api/example.com/v1/groupversion_info.go b/testdata/project-v4-multigroup/api/example.com/v1/groupversion_info.go new file mode 100644 index 00000000000..f6ea9c38602 --- /dev/null +++ b/testdata/project-v4-multigroup/api/example.com/v1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +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 v1 contains API Schema definitions for the example.com v1 API group. +// +kubebuilder:object:generate=true +// +groupName=example.com.testproject.org +package v1 + +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: "example.com.testproject.org", Version: "v1"} + + // 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/testdata/project-v4-multigroup/api/example.com/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/example.com/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..a879a984a83 --- /dev/null +++ b/testdata/project-v4-multigroup/api/example.com/v1/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Conversion) DeepCopyInto(out *Conversion) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conversion. +func (in *Conversion) DeepCopy() *Conversion { + if in == nil { + return nil + } + out := new(Conversion) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Conversion) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionList) DeepCopyInto(out *ConversionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Conversion, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionList. +func (in *ConversionList) DeepCopy() *ConversionList { + if in == nil { + return nil + } + out := new(ConversionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConversionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionSpec) DeepCopyInto(out *ConversionSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionSpec. +func (in *ConversionSpec) DeepCopy() *ConversionSpec { + if in == nil { + return nil + } + out := new(ConversionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionStatus) DeepCopyInto(out *ConversionStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionStatus. +func (in *ConversionStatus) DeepCopy() *ConversionStatus { + if in == nil { + return nil + } + out := new(ConversionStatus) + in.DeepCopyInto(out) + return out +} diff --git a/testdata/project-v4-multigroup/api/example.com/v2/conversion_types.go b/testdata/project-v4-multigroup/api/example.com/v2/conversion_types.go new file mode 100644 index 00000000000..399a8cc9cd0 --- /dev/null +++ b/testdata/project-v4-multigroup/api/example.com/v2/conversion_types.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 v2 + +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. + +// ConversionSpec defines the desired state of Conversion. +type ConversionSpec 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 Conversion. Edit conversion_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// ConversionStatus defines the observed state of Conversion. +type ConversionStatus 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 + +// Conversion is the Schema for the conversions API. +type Conversion struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ConversionSpec `json:"spec,omitempty"` + Status ConversionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ConversionList contains a list of Conversion. +type ConversionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Conversion `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Conversion{}, &ConversionList{}) +} diff --git a/testdata/project-v4-multigroup/api/example.com/v2/groupversion_info.go b/testdata/project-v4-multigroup/api/example.com/v2/groupversion_info.go new file mode 100644 index 00000000000..fbb9302bda5 --- /dev/null +++ b/testdata/project-v4-multigroup/api/example.com/v2/groupversion_info.go @@ -0,0 +1,36 @@ +/* +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 v2 contains API Schema definitions for the example.com v2 API group. +// +kubebuilder:object:generate=true +// +groupName=example.com.testproject.org +package v2 + +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: "example.com.testproject.org", Version: "v2"} + + // 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/testdata/project-v4-multigroup/api/example.com/v2/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/example.com/v2/zz_generated.deepcopy.go new file mode 100644 index 00000000000..541e630e482 --- /dev/null +++ b/testdata/project-v4-multigroup/api/example.com/v2/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v2 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Conversion) DeepCopyInto(out *Conversion) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conversion. +func (in *Conversion) DeepCopy() *Conversion { + if in == nil { + return nil + } + out := new(Conversion) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Conversion) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionList) DeepCopyInto(out *ConversionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Conversion, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionList. +func (in *ConversionList) DeepCopy() *ConversionList { + if in == nil { + return nil + } + out := new(ConversionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConversionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionSpec) DeepCopyInto(out *ConversionSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionSpec. +func (in *ConversionSpec) DeepCopy() *ConversionSpec { + if in == nil { + return nil + } + out := new(ConversionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionStatus) DeepCopyInto(out *ConversionStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionStatus. +func (in *ConversionStatus) DeepCopy() *ConversionStatus { + if in == nil { + return nil + } + out := new(ConversionStatus) + in.DeepCopyInto(out) + return out +} diff --git a/testdata/project-v4-multigroup/cmd/main.go b/testdata/project-v4-multigroup/cmd/main.go index 0fe9c4afead..5f4b8420893 100644 --- a/testdata/project-v4-multigroup/cmd/main.go +++ b/testdata/project-v4-multigroup/cmd/main.go @@ -38,7 +38,9 @@ import ( certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1" + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1" + examplecomv2 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v2" fizv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/fiz/v1" foopolicyv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo.policy/v1" foov1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo/v1" @@ -59,6 +61,7 @@ import ( webhookcertmanagerv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/cert-manager/v1" webhookcorev1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/core/v1" webhookcrewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/crew/v1" + webhookexamplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/example.com/v1" webhookexamplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1" webhookshipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/ship/v1" webhookshipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/ship/v1beta1" @@ -85,6 +88,8 @@ func init() { utilruntime.Must(fizv1.AddToScheme(scheme)) utilruntime.Must(certmanagerv1.AddToScheme(scheme)) utilruntime.Must(examplecomv1alpha1.AddToScheme(scheme)) + utilruntime.Must(examplecomv1.AddToScheme(scheme)) + utilruntime.Must(examplecomv2.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -314,12 +319,33 @@ func main() { os.Exit(1) } // nolint:goconst + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = webhookexamplecomv1alpha1.SetupBusyboxWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Busybox") + os.Exit(1) + } + } + // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { if err = webhookexamplecomv1alpha1.SetupMemcachedWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Memcached") os.Exit(1) } } + if err = (&examplecomcontroller.ConversionReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Conversion") + os.Exit(1) + } + // nolint:goconst + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = webhookexamplecomv1.SetupConversionWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Conversion") + os.Exit(1) + } + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_conversions.yaml b/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_conversions.yaml new file mode 100644 index 00000000000..2381510dc4c --- /dev/null +++ b/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_conversions.yaml @@ -0,0 +1,92 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: conversions.example.com.testproject.org +spec: + group: example.com.testproject.org + names: + kind: Conversion + listKind: ConversionList + plural: conversions + singular: conversion + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: true + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: false + subresources: + status: {} diff --git a/testdata/project-v4-multigroup/config/crd/kustomization.yaml b/testdata/project-v4-multigroup/config/crd/kustomization.yaml index 6b362727dd7..06ce807f1e1 100644 --- a/testdata/project-v4-multigroup/config/crd/kustomization.yaml +++ b/testdata/project-v4-multigroup/config/crd/kustomization.yaml @@ -13,6 +13,7 @@ resources: - bases/fiz.testproject.org_bars.yaml - bases/example.com.testproject.org_memcacheds.yaml - bases/example.com.testproject.org_busyboxes.yaml +- bases/example.com.testproject.org_conversions.yaml # +kubebuilder:scaffold:crdkustomizeresource patches: @@ -22,7 +23,9 @@ patches: - path: patches/webhook_in_ship_frigates.yaml - path: patches/webhook_in_ship_destroyers.yaml - path: patches/webhook_in_ship_cruisers.yaml +- path: patches/webhook_in_example.com_busyboxes.yaml - path: patches/webhook_in_example.com_memcacheds.yaml +- path: patches/webhook_in_example.com_conversions.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -38,6 +41,7 @@ patches: #- path: patches/cainjection_in_fiz_bars.yaml #- path: patches/cainjection_in_example.com_memcacheds.yaml #- path: patches/cainjection_in_example.com_busyboxes.yaml +#- path: patches/cainjection_in_example.com_conversions.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # [WEBHOOK] To enable webhook, uncomment the following section diff --git a/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_busyboxes.yaml b/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_busyboxes.yaml new file mode 100644 index 00000000000..5f6b0384f48 --- /dev/null +++ b/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_busyboxes.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: busyboxes.example.com.testproject.org diff --git a/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_conversions.yaml b/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_conversions.yaml new file mode 100644 index 00000000000..e3f29728396 --- /dev/null +++ b/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_conversions.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: conversions.example.com.testproject.org diff --git a/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_busyboxes.yaml b/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_busyboxes.yaml new file mode 100644 index 00000000000..5dbd9da7176 --- /dev/null +++ b/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_busyboxes.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: busyboxes.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_conversions.yaml b/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_conversions.yaml new file mode 100644 index 00000000000..c2f37519549 --- /dev/null +++ b/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_conversions.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: conversions.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/testdata/project-v4-multigroup/config/rbac/example.com_conversion_editor_role.yaml b/testdata/project-v4-multigroup/config/rbac/example.com_conversion_editor_role.yaml new file mode 100644 index 00000000000..ca44392df7b --- /dev/null +++ b/testdata/project-v4-multigroup/config/rbac/example.com_conversion_editor_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to edit conversions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: project-v4-multigroup + app.kubernetes.io/managed-by: kustomize + name: example.com-conversion-editor-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get diff --git a/testdata/project-v4-multigroup/config/rbac/example.com_conversion_viewer_role.yaml b/testdata/project-v4-multigroup/config/rbac/example.com_conversion_viewer_role.yaml new file mode 100644 index 00000000000..7a6413fedb0 --- /dev/null +++ b/testdata/project-v4-multigroup/config/rbac/example.com_conversion_viewer_role.yaml @@ -0,0 +1,23 @@ +# permissions for end users to view conversions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: project-v4-multigroup + app.kubernetes.io/managed-by: kustomize + name: example.com-conversion-viewer-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - get + - list + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get diff --git a/testdata/project-v4-multigroup/config/rbac/kustomization.yaml b/testdata/project-v4-multigroup/config/rbac/kustomization.yaml index bbf5c747bb5..409e7314920 100644 --- a/testdata/project-v4-multigroup/config/rbac/kustomization.yaml +++ b/testdata/project-v4-multigroup/config/rbac/kustomization.yaml @@ -22,6 +22,8 @@ resources: # default, aiding admins in cluster management. Those roles are # not used by the Project itself. You can comment the following lines # if you do not want those helpers be installed with your Project. +- example.com_conversion_editor_role.yaml +- example.com_conversion_viewer_role.yaml - example.com_busybox_editor_role.yaml - example.com_busybox_viewer_role.yaml - example.com_memcached_editor_role.yaml diff --git a/testdata/project-v4-multigroup/config/rbac/role.yaml b/testdata/project-v4-multigroup/config/rbac/role.yaml index f8e24f77f46..447b190a5d8 100644 --- a/testdata/project-v4-multigroup/config/rbac/role.yaml +++ b/testdata/project-v4-multigroup/config/rbac/role.yaml @@ -101,6 +101,7 @@ rules: - example.com.testproject.org resources: - busyboxes + - conversions - memcacheds verbs: - create @@ -114,6 +115,7 @@ rules: - example.com.testproject.org resources: - busyboxes/finalizers + - conversions/finalizers - memcacheds/finalizers verbs: - update @@ -121,6 +123,7 @@ rules: - example.com.testproject.org resources: - busyboxes/status + - conversions/status - memcacheds/status verbs: - get diff --git a/testdata/project-v4-multigroup/config/samples/example.com_v1_conversion.yaml b/testdata/project-v4-multigroup/config/samples/example.com_v1_conversion.yaml new file mode 100644 index 00000000000..48223b1dfa8 --- /dev/null +++ b/testdata/project-v4-multigroup/config/samples/example.com_v1_conversion.yaml @@ -0,0 +1,9 @@ +apiVersion: example.com.testproject.org/v1 +kind: Conversion +metadata: + labels: + app.kubernetes.io/name: project-v4-multigroup + app.kubernetes.io/managed-by: kustomize + name: conversion-sample +spec: + # TODO(user): Add fields here diff --git a/testdata/project-v4-multigroup/config/samples/example.com_v2_conversion.yaml b/testdata/project-v4-multigroup/config/samples/example.com_v2_conversion.yaml new file mode 100644 index 00000000000..f948a00958b --- /dev/null +++ b/testdata/project-v4-multigroup/config/samples/example.com_v2_conversion.yaml @@ -0,0 +1,9 @@ +apiVersion: example.com.testproject.org/v2 +kind: Conversion +metadata: + labels: + app.kubernetes.io/name: project-v4-multigroup + app.kubernetes.io/managed-by: kustomize + name: conversion-sample +spec: + # TODO(user): Add fields here diff --git a/testdata/project-v4-multigroup/config/samples/kustomization.yaml b/testdata/project-v4-multigroup/config/samples/kustomization.yaml index cffe4bc820a..3e936072ac6 100644 --- a/testdata/project-v4-multigroup/config/samples/kustomization.yaml +++ b/testdata/project-v4-multigroup/config/samples/kustomization.yaml @@ -11,4 +11,6 @@ resources: - fiz_v1_bar.yaml - example.com_v1alpha1_memcached.yaml - example.com_v1alpha1_busybox.yaml +- example.com_v1_conversion.yaml +- example.com_v2_conversion.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/testdata/project-v4-multigroup/config/webhook/manifests.yaml b/testdata/project-v4-multigroup/config/webhook/manifests.yaml index 6a694a4a8e6..1ac5e62f7fa 100644 --- a/testdata/project-v4-multigroup/config/webhook/manifests.yaml +++ b/testdata/project-v4-multigroup/config/webhook/manifests.yaml @@ -44,6 +44,26 @@ webhooks: resources: - captains sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-example-com-testproject-org-v1alpha1-busybox + failurePolicy: Fail + name: mbusybox-v1alpha1.kb.io + rules: + - apiGroups: + - example.com.testproject.org + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - busyboxes + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/testdata/project-v4-multigroup/dist/install.yaml b/testdata/project-v4-multigroup/dist/install.yaml index b6f36443784..ba7acf9e71f 100644 --- a/testdata/project-v4-multigroup/dist/install.yaml +++ b/testdata/project-v4-multigroup/dist/install.yaml @@ -122,6 +122,16 @@ metadata: controller-gen.kubebuilder.io/version: v0.16.4 name: busyboxes.example.com.testproject.org spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: project-v4-multigroup-webhook-service + namespace: project-v4-multigroup-system + path: /convert + conversionReviewVersions: + - v1 group: example.com.testproject.org names: kind: Busybox @@ -297,6 +307,108 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: conversions.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: project-v4-multigroup-webhook-service + namespace: project-v4-multigroup-system + path: /convert + conversionReviewVersions: + - v1 + group: example.com.testproject.org + names: + kind: Conversion + listKind: ConversionList + plural: conversions + singular: conversion + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: true + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: false + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.16.4 @@ -932,6 +1044,56 @@ rules: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-multigroup + name: project-v4-multigroup-example.com-conversion-editor-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-multigroup + name: project-v4-multigroup-example.com-conversion-viewer-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - get + - list + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole metadata: labels: app.kubernetes.io/managed-by: kustomize @@ -1232,6 +1394,7 @@ rules: - example.com.testproject.org resources: - busyboxes + - conversions - memcacheds verbs: - create @@ -1245,6 +1408,7 @@ rules: - example.com.testproject.org resources: - busyboxes/finalizers + - conversions/finalizers - memcacheds/finalizers verbs: - update @@ -1252,6 +1416,7 @@ rules: - example.com.testproject.org resources: - busyboxes/status + - conversions/status - memcacheds/status verbs: - get @@ -1854,6 +2019,26 @@ webhooks: resources: - captains sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: project-v4-multigroup-webhook-service + namespace: project-v4-multigroup-system + path: /mutate-example-com-testproject-org-v1alpha1-busybox + failurePolicy: Fail + name: mbusybox-v1alpha1.kb.io + rules: + - apiGroups: + - example.com.testproject.org + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - busyboxes + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/testdata/project-v4-multigroup/internal/controller/example.com/conversion_controller.go b/testdata/project-v4-multigroup/internal/controller/example.com/conversion_controller.go new file mode 100644 index 00000000000..9a3fe7dd8d1 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/controller/example.com/conversion_controller.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 examplecom + +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" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1" +) + +// ConversionReconciler reconciles a Conversion object +type ConversionReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=example.com.testproject.org,resources=conversions,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=example.com.testproject.org,resources=conversions/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=example.com.testproject.org,resources=conversions/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 Conversion 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@v0.19.1/pkg/reconcile +func (r *ConversionReconciler) 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 *ConversionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&examplecomv1.Conversion{}). + Named("example.com-conversion"). + Complete(r) +} diff --git a/testdata/project-v4-multigroup/internal/controller/example.com/conversion_controller_test.go b/testdata/project-v4-multigroup/internal/controller/example.com/conversion_controller_test.go new file mode 100644 index 00000000000..ba55032dff9 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/controller/example.com/conversion_controller_test.go @@ -0,0 +1,84 @@ +/* +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 examplecom + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "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" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1" +) + +var _ = Describe("Conversion Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + conversion := &examplecomv1.Conversion{} + + BeforeEach(func() { + By("creating the custom resource for the Kind Conversion") + err := k8sClient.Get(ctx, typeNamespacedName, conversion) + if err != nil && errors.IsNotFound(err) { + resource := &examplecomv1.Conversion{ + 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 := &examplecomv1.Conversion{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance Conversion") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &ConversionReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // 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/testdata/project-v4-multigroup/internal/controller/example.com/suite_test.go b/testdata/project-v4-multigroup/internal/controller/example.com/suite_test.go index 316975ca415..4aae9289791 100644 --- a/testdata/project-v4-multigroup/internal/controller/example.com/suite_test.go +++ b/testdata/project-v4-multigroup/internal/controller/example.com/suite_test.go @@ -33,6 +33,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -80,6 +81,9 @@ var _ = BeforeSuite(func() { err = examplecomv1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = examplecomv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/testdata/project-v4-multigroup/internal/webhook/example.com/v1/conversion_webhook.go b/testdata/project-v4-multigroup/internal/webhook/example.com/v1/conversion_webhook.go new file mode 100644 index 00000000000..edd9d89bf96 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/webhook/example.com/v1/conversion_webhook.go @@ -0,0 +1,36 @@ +/* +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 v1 + +import ( + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1" +) + +// nolint:unused +// log is for logging in this package. +var conversionlog = logf.Log.WithName("conversion-resource") + +// SetupConversionWebhookWithManager registers the webhook for Conversion in the manager. +func SetupConversionWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&examplecomv1.Conversion{}). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! diff --git a/testdata/project-v4-multigroup/internal/webhook/example.com/v1/conversion_webhook_test.go b/testdata/project-v4-multigroup/internal/webhook/example.com/v1/conversion_webhook_test.go new file mode 100644 index 00000000000..88ea31e2676 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/webhook/example.com/v1/conversion_webhook_test.go @@ -0,0 +1,55 @@ +/* +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 v1 + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1" + // TODO (user): Add any additional imports if needed +) + +var _ = Describe("Conversion Webhook", func() { + var ( + obj *examplecomv1.Conversion + oldObj *examplecomv1.Conversion + ) + + BeforeEach(func() { + obj = &examplecomv1.Conversion{} + oldObj = &examplecomv1.Conversion{} + Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + 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 + }) + + Context("When creating Conversion 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() { + // convertedObj := &examplecomv1.Conversion{} + // Expect(obj.ConvertTo(convertedObj)).To(Succeed()) + // Expect(convertedObj).ToNot(BeNil()) + // }) + }) + +}) diff --git a/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/busybox_webhook.go b/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/busybox_webhook.go new file mode 100644 index 00000000000..735947d5337 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/busybox_webhook.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 v1alpha1 + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1" +) + +// nolint:unused +// log is for logging in this package. +var busyboxlog = logf.Log.WithName("busybox-resource") + +// SetupBusyboxWebhookWithManager registers the webhook for Busybox in the manager. +func SetupBusyboxWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&examplecomv1alpha1.Busybox{}). + WithDefaulter(&BusyboxCustomDefaulter{}). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// +kubebuilder:webhook:path=/mutate-example-com-testproject-org-v1alpha1-busybox,mutating=true,failurePolicy=fail,sideEffects=None,groups=example.com.testproject.org,resources=busyboxes,verbs=create;update,versions=v1alpha1,name=mbusybox-v1alpha1.kb.io,admissionReviewVersions=v1 + +// BusyboxCustomDefaulter struct is responsible for setting default values on the custom resource of the +// Kind Busybox 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 BusyboxCustomDefaulter struct { + // TODO(user): Add more fields as needed for defaulting +} + +var _ webhook.CustomDefaulter = &BusyboxCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Busybox. +func (d *BusyboxCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + busybox, ok := obj.(*examplecomv1alpha1.Busybox) + + if !ok { + return fmt.Errorf("expected an Busybox object but got %T", obj) + } + busyboxlog.Info("Defaulting for Busybox", "name", busybox.GetName()) + + // TODO(user): fill in your defaulting logic. + + return nil +} diff --git a/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/busybox_webhook_test.go b/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/busybox_webhook_test.go new file mode 100644 index 00000000000..255732a9684 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/busybox_webhook_test.go @@ -0,0 +1,61 @@ +/* +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 v1alpha1 + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1" + // TODO (user): Add any additional imports if needed +) + +var _ = Describe("Busybox Webhook", func() { + var ( + obj *examplecomv1alpha1.Busybox + oldObj *examplecomv1alpha1.Busybox + defaulter BusyboxCustomDefaulter + ) + + BeforeEach(func() { + obj = &examplecomv1alpha1.Busybox{} + oldObj = &examplecomv1alpha1.Busybox{} + defaulter = BusyboxCustomDefaulter{} + Expect(defaulter).NotTo(BeNil(), "Expected defaulter to be initialized") + Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + 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 + }) + + Context("When creating Busybox 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") + // 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")) + // }) + }) + +}) diff --git a/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/webhook_suite_test.go b/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/webhook_suite_test.go index 18d7d16a186..196525ee516 100644 --- a/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/webhook_suite_test.go @@ -118,6 +118,9 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) + err = SetupBusyboxWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + err = SetupMemcachedWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) diff --git a/testdata/project-v4-with-plugins/PROJECT b/testdata/project-v4-with-plugins/PROJECT index 0e0eccc4eb2..f22996af75d 100644 --- a/testdata/project-v4-with-plugins/PROJECT +++ b/testdata/project-v4-with-plugins/PROJECT @@ -24,6 +24,7 @@ plugins: image: busybox:1.36.1 version: v1alpha1 grafana.kubebuilder.io/v1-alpha: {} + helm.kubebuilder.io/v1-alpha: {} projectName: project-v4-with-plugins repo: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins resources: @@ -48,4 +49,27 @@ resources: kind: Busybox path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1 version: v1alpha1 + webhooks: + defaulting: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org + group: example.com + kind: Conversion + path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1 + version: v1 + webhooks: + conversion: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + domain: testproject.org + group: example.com + kind: Conversion + path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v2 + version: v2 version: "3" diff --git a/testdata/project-v4-with-plugins/README.md b/testdata/project-v4-with-plugins/README.md index 39377250d34..140a579a906 100644 --- a/testdata/project-v4-with-plugins/README.md +++ b/testdata/project-v4-with-plugins/README.md @@ -68,7 +68,13 @@ make undeploy ## Project Distribution -Following are the steps to build the installer and distribute this project to users. +Following the options to release and provide this solution +to the users. + +### By providing a bundle with all YAML files + +Following are the steps to build the installer and distribute +this project to users. 1. Build the installer for the image built and published in the registry: @@ -76,19 +82,40 @@ Following are the steps to build the installer and distribute this project to us make build-installer IMG=/project-v4-with-plugins:tag ``` -NOTE: The makefile target mentioned above generates an 'install.yaml' +**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.: +Users can just run 'kubectl apply -f ' to install the project, i.e.: ```sh kubectl apply -f https://raw.githubusercontent.com//project-v4-with-plugins//dist/install.yaml ``` +### By providing a Helm Chart + +Following are the steps to build a Helm Chart to distribute +this project to users. + +1. Build the chart using the optional helm plugin + +```sh +kubebuilder edit --plugins=helm/v1-alpha +``` + +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 diff --git a/testdata/project-v4-with-plugins/api/v1/conversion_types.go b/testdata/project-v4-with-plugins/api/v1/conversion_types.go new file mode 100644 index 00000000000..90937fff496 --- /dev/null +++ b/testdata/project-v4-with-plugins/api/v1/conversion_types.go @@ -0,0 +1,65 @@ +/* +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 v1 + +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. + +// ConversionSpec defines the desired state of Conversion. +type ConversionSpec 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 Conversion. Edit conversion_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// ConversionStatus defines the observed state of Conversion. +type ConversionStatus 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 +// +kubebuilder:storageversion +// +kubebuilder:conversion:hub +// Conversion is the Schema for the conversions API. +type Conversion struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ConversionSpec `json:"spec,omitempty"` + Status ConversionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ConversionList contains a list of Conversion. +type ConversionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Conversion `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Conversion{}, &ConversionList{}) +} diff --git a/testdata/project-v4-with-plugins/api/v1/groupversion_info.go b/testdata/project-v4-with-plugins/api/v1/groupversion_info.go new file mode 100644 index 00000000000..f6ea9c38602 --- /dev/null +++ b/testdata/project-v4-with-plugins/api/v1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +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 v1 contains API Schema definitions for the example.com v1 API group. +// +kubebuilder:object:generate=true +// +groupName=example.com.testproject.org +package v1 + +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: "example.com.testproject.org", Version: "v1"} + + // 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/testdata/project-v4-with-plugins/api/v1/zz_generated.deepcopy.go b/testdata/project-v4-with-plugins/api/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..a879a984a83 --- /dev/null +++ b/testdata/project-v4-with-plugins/api/v1/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Conversion) DeepCopyInto(out *Conversion) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conversion. +func (in *Conversion) DeepCopy() *Conversion { + if in == nil { + return nil + } + out := new(Conversion) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Conversion) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionList) DeepCopyInto(out *ConversionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Conversion, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionList. +func (in *ConversionList) DeepCopy() *ConversionList { + if in == nil { + return nil + } + out := new(ConversionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConversionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionSpec) DeepCopyInto(out *ConversionSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionSpec. +func (in *ConversionSpec) DeepCopy() *ConversionSpec { + if in == nil { + return nil + } + out := new(ConversionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionStatus) DeepCopyInto(out *ConversionStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionStatus. +func (in *ConversionStatus) DeepCopy() *ConversionStatus { + if in == nil { + return nil + } + out := new(ConversionStatus) + in.DeepCopyInto(out) + return out +} diff --git a/testdata/project-v4-with-plugins/api/v2/conversion_types.go b/testdata/project-v4-with-plugins/api/v2/conversion_types.go new file mode 100644 index 00000000000..399a8cc9cd0 --- /dev/null +++ b/testdata/project-v4-with-plugins/api/v2/conversion_types.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 v2 + +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. + +// ConversionSpec defines the desired state of Conversion. +type ConversionSpec 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 Conversion. Edit conversion_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// ConversionStatus defines the observed state of Conversion. +type ConversionStatus 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 + +// Conversion is the Schema for the conversions API. +type Conversion struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ConversionSpec `json:"spec,omitempty"` + Status ConversionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ConversionList contains a list of Conversion. +type ConversionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Conversion `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Conversion{}, &ConversionList{}) +} diff --git a/testdata/project-v4-with-plugins/api/v2/groupversion_info.go b/testdata/project-v4-with-plugins/api/v2/groupversion_info.go new file mode 100644 index 00000000000..fbb9302bda5 --- /dev/null +++ b/testdata/project-v4-with-plugins/api/v2/groupversion_info.go @@ -0,0 +1,36 @@ +/* +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 v2 contains API Schema definitions for the example.com v2 API group. +// +kubebuilder:object:generate=true +// +groupName=example.com.testproject.org +package v2 + +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: "example.com.testproject.org", Version: "v2"} + + // 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/testdata/project-v4-with-plugins/api/v2/zz_generated.deepcopy.go b/testdata/project-v4-with-plugins/api/v2/zz_generated.deepcopy.go new file mode 100644 index 00000000000..541e630e482 --- /dev/null +++ b/testdata/project-v4-with-plugins/api/v2/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v2 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Conversion) DeepCopyInto(out *Conversion) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conversion. +func (in *Conversion) DeepCopy() *Conversion { + if in == nil { + return nil + } + out := new(Conversion) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Conversion) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionList) DeepCopyInto(out *ConversionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Conversion, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionList. +func (in *ConversionList) DeepCopy() *ConversionList { + if in == nil { + return nil + } + out := new(ConversionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConversionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionSpec) DeepCopyInto(out *ConversionSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionSpec. +func (in *ConversionSpec) DeepCopy() *ConversionSpec { + if in == nil { + return nil + } + out := new(ConversionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConversionStatus) DeepCopyInto(out *ConversionStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionStatus. +func (in *ConversionStatus) DeepCopy() *ConversionStatus { + if in == nil { + return nil + } + out := new(ConversionStatus) + in.DeepCopyInto(out) + return out +} diff --git a/testdata/project-v4-with-plugins/cmd/main.go b/testdata/project-v4-with-plugins/cmd/main.go index cf093e59d6c..ce97c66b793 100644 --- a/testdata/project-v4-with-plugins/cmd/main.go +++ b/testdata/project-v4-with-plugins/cmd/main.go @@ -35,8 +35,11 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1" + examplecomv2 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v2" "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/controller" + webhookexamplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/webhook/v1" webhookexamplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/webhook/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -50,6 +53,8 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(examplecomv1alpha1.AddToScheme(scheme)) + utilruntime.Must(examplecomv1.AddToScheme(scheme)) + utilruntime.Must(examplecomv2.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -160,12 +165,33 @@ func main() { os.Exit(1) } // nolint:goconst + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = webhookexamplecomv1alpha1.SetupBusyboxWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Busybox") + os.Exit(1) + } + } + // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { if err = webhookexamplecomv1alpha1.SetupMemcachedWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Memcached") os.Exit(1) } } + if err = (&controller.ConversionReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Conversion") + os.Exit(1) + } + // nolint:goconst + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = webhookexamplecomv1.SetupConversionWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Conversion") + os.Exit(1) + } + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_conversions.yaml b/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_conversions.yaml new file mode 100644 index 00000000000..2381510dc4c --- /dev/null +++ b/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_conversions.yaml @@ -0,0 +1,92 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: conversions.example.com.testproject.org +spec: + group: example.com.testproject.org + names: + kind: Conversion + listKind: ConversionList + plural: conversions + singular: conversion + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: true + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: false + subresources: + status: {} diff --git a/testdata/project-v4-with-plugins/config/crd/kustomization.yaml b/testdata/project-v4-with-plugins/config/crd/kustomization.yaml index 7b2aba4eb3c..ec861184d78 100644 --- a/testdata/project-v4-with-plugins/config/crd/kustomization.yaml +++ b/testdata/project-v4-with-plugins/config/crd/kustomization.yaml @@ -4,18 +4,22 @@ resources: - bases/example.com.testproject.org_memcacheds.yaml - bases/example.com.testproject.org_busyboxes.yaml +- bases/example.com.testproject.org_conversions.yaml # +kubebuilder:scaffold:crdkustomizeresource patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD +- path: patches/webhook_in_busyboxes.yaml - path: patches/webhook_in_memcacheds.yaml +- path: patches/webhook_in_conversions.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- path: patches/cainjection_in_memcacheds.yaml #- path: patches/cainjection_in_busyboxes.yaml +#- path: patches/cainjection_in_conversions.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # [WEBHOOK] To enable webhook, uncomment the following section diff --git a/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_busyboxes.yaml b/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_busyboxes.yaml new file mode 100644 index 00000000000..5f6b0384f48 --- /dev/null +++ b/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_busyboxes.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: busyboxes.example.com.testproject.org diff --git a/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_conversions.yaml b/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_conversions.yaml new file mode 100644 index 00000000000..e3f29728396 --- /dev/null +++ b/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_conversions.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: conversions.example.com.testproject.org diff --git a/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_busyboxes.yaml b/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_busyboxes.yaml new file mode 100644 index 00000000000..5dbd9da7176 --- /dev/null +++ b/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_busyboxes.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: busyboxes.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_conversions.yaml b/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_conversions.yaml new file mode 100644 index 00000000000..c2f37519549 --- /dev/null +++ b/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_conversions.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: conversions.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/testdata/project-v4-with-plugins/config/rbac/conversion_editor_role.yaml b/testdata/project-v4-with-plugins/config/rbac/conversion_editor_role.yaml new file mode 100644 index 00000000000..7c7d00d4515 --- /dev/null +++ b/testdata/project-v4-with-plugins/config/rbac/conversion_editor_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to edit conversions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: project-v4-with-plugins + app.kubernetes.io/managed-by: kustomize + name: conversion-editor-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get diff --git a/testdata/project-v4-with-plugins/config/rbac/conversion_viewer_role.yaml b/testdata/project-v4-with-plugins/config/rbac/conversion_viewer_role.yaml new file mode 100644 index 00000000000..413b7e50514 --- /dev/null +++ b/testdata/project-v4-with-plugins/config/rbac/conversion_viewer_role.yaml @@ -0,0 +1,23 @@ +# permissions for end users to view conversions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: project-v4-with-plugins + app.kubernetes.io/managed-by: kustomize + name: conversion-viewer-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - get + - list + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get diff --git a/testdata/project-v4-with-plugins/config/rbac/kustomization.yaml b/testdata/project-v4-with-plugins/config/rbac/kustomization.yaml index 60653a8cf59..f6a1ad91fdd 100644 --- a/testdata/project-v4-with-plugins/config/rbac/kustomization.yaml +++ b/testdata/project-v4-with-plugins/config/rbac/kustomization.yaml @@ -22,6 +22,8 @@ resources: # default, aiding admins in cluster management. Those roles are # not used by the Project itself. You can comment the following lines # if you do not want those helpers be installed with your Project. +- conversion_editor_role.yaml +- conversion_viewer_role.yaml - busybox_editor_role.yaml - busybox_viewer_role.yaml - memcached_editor_role.yaml diff --git a/testdata/project-v4-with-plugins/config/rbac/role.yaml b/testdata/project-v4-with-plugins/config/rbac/role.yaml index a2db0d25cca..5e23e2b2dd2 100644 --- a/testdata/project-v4-with-plugins/config/rbac/role.yaml +++ b/testdata/project-v4-with-plugins/config/rbac/role.yaml @@ -35,6 +35,7 @@ rules: - example.com.testproject.org resources: - busyboxes + - conversions - memcacheds verbs: - create @@ -48,6 +49,7 @@ rules: - example.com.testproject.org resources: - busyboxes/finalizers + - conversions/finalizers - memcacheds/finalizers verbs: - update @@ -55,6 +57,7 @@ rules: - example.com.testproject.org resources: - busyboxes/status + - conversions/status - memcacheds/status verbs: - get diff --git a/testdata/project-v4-with-plugins/config/samples/example.com_v1_conversion.yaml b/testdata/project-v4-with-plugins/config/samples/example.com_v1_conversion.yaml new file mode 100644 index 00000000000..c42dd57dec1 --- /dev/null +++ b/testdata/project-v4-with-plugins/config/samples/example.com_v1_conversion.yaml @@ -0,0 +1,9 @@ +apiVersion: example.com.testproject.org/v1 +kind: Conversion +metadata: + labels: + app.kubernetes.io/name: project-v4-with-plugins + app.kubernetes.io/managed-by: kustomize + name: conversion-sample +spec: + # TODO(user): Add fields here diff --git a/testdata/project-v4-with-plugins/config/samples/example.com_v2_conversion.yaml b/testdata/project-v4-with-plugins/config/samples/example.com_v2_conversion.yaml new file mode 100644 index 00000000000..0b66b52a8b8 --- /dev/null +++ b/testdata/project-v4-with-plugins/config/samples/example.com_v2_conversion.yaml @@ -0,0 +1,9 @@ +apiVersion: example.com.testproject.org/v2 +kind: Conversion +metadata: + labels: + app.kubernetes.io/name: project-v4-with-plugins + app.kubernetes.io/managed-by: kustomize + name: conversion-sample +spec: + # TODO(user): Add fields here diff --git a/testdata/project-v4-with-plugins/config/samples/kustomization.yaml b/testdata/project-v4-with-plugins/config/samples/kustomization.yaml index 44b0f44adcb..4e8fa89cf7d 100644 --- a/testdata/project-v4-with-plugins/config/samples/kustomization.yaml +++ b/testdata/project-v4-with-plugins/config/samples/kustomization.yaml @@ -2,4 +2,6 @@ resources: - example.com_v1alpha1_memcached.yaml - example.com_v1alpha1_busybox.yaml +- example.com_v1_conversion.yaml +- example.com_v2_conversion.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/testdata/project-v4-with-plugins/config/webhook/manifests.yaml b/testdata/project-v4-with-plugins/config/webhook/manifests.yaml index 99b797e22f4..c53a3ddfe87 100644 --- a/testdata/project-v4-with-plugins/config/webhook/manifests.yaml +++ b/testdata/project-v4-with-plugins/config/webhook/manifests.yaml @@ -1,5 +1,31 @@ --- apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-example-com-testproject-org-v1alpha1-busybox + failurePolicy: Fail + name: mbusybox-v1alpha1.kb.io + rules: + - apiGroups: + - example.com.testproject.org + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - busyboxes + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration diff --git a/testdata/project-v4-with-plugins/dist/chart/.helmignore b/testdata/project-v4-with-plugins/dist/chart/.helmignore new file mode 100644 index 00000000000..7d92f7fb4f1 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/.helmignore @@ -0,0 +1,25 @@ +# 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/testdata/project-v4-with-plugins/dist/chart/Chart.yaml b/testdata/project-v4-with-plugins/dist/chart/Chart.yaml new file mode 100644 index 00000000000..0f89680c555 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +name: project-v4-with-plugins +description: A Helm chart to distribute the project project-v4-with-plugins +type: application +version: 0.1.0 +appVersion: "0.1.0" +icon: "https://example.com/icon.png" diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/_helpers.tpl b/testdata/project-v4-with-plugins/dist/chart/templates/_helpers.tpl new file mode 100644 index 00000000000..66d7523b70b --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/_helpers.tpl @@ -0,0 +1,50 @@ +{{- define "chart.name" -}} +{{- if .Chart }} + {{- if .Chart.Name }} + {{ .Chart.Name | trunc 63 | trimSuffix "-" }} + {{- else if .Values.nameOverride }} + {{ .Values.nameOverride | trunc 63 | trimSuffix "-" }} + {{- else }} + project-v4-with-plugins + {{- end }} +{{- else }} + project-v4-with-plugins +{{- end }} +{{- end }} + + +{{- 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 }} + + +{{- define "chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + + +{{- define "chart.hasMutatingWebhooks" -}} +{{- $hasMutating := false }} +{{- range . }} + {{- if eq .type "mutating" }} + $hasMutating = true }}{{- end }} +{{- end }} +{{ $hasMutating }}}}{{- end }} + + +{{- define "chart.hasValidatingWebhooks" -}} +{{- $hasValidating := false }} +{{- range . }} + {{- if eq .type "validating" }} + $hasValidating = true }}{{- end }} +{{- end }} +{{ $hasValidating }}}}{{- end }} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/certmanager/certificate.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/certmanager/certificate.yaml new file mode 100644 index 00000000000..2310829b853 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/certmanager/certificate.yaml @@ -0,0 +1,34 @@ +{{- if .Values.certmanager.create }} +# 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: {} +--- +# 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: + - project-v4-with-plugins.{{ .Release.Namespace }}.svc + - project-v4-with-plugins.{{ .Release.Namespace }}.svc.cluster.local + - project-v4-with-plugins-webhook-service.{{ .Release.Namespace }}.svc + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: webhook-server-cert +{{- end }} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml new file mode 100755 index 00000000000..e5891bda56e --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml @@ -0,0 +1,124 @@ +{{- if .Values.crd.create }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: busyboxes.example.com.testproject.org +spec: + group: example.com.testproject.org + names: + kind: Busybox + listKind: BusyboxList + plural: busyboxes + singular: busybox + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Busybox is the Schema for the busyboxes API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BusyboxSpec defines the desired state of Busybox + properties: + size: + description: |- + Size defines the number of Busybox instances + The following markers will use OpenAPI v3 schema to validate the value + More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + format: int32 + maximum: 3 + minimum: 1 + type: integer + type: object + status: + description: BusyboxStatus defines the observed state of Busybox + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_conversions.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_conversions.yaml new file mode 100755 index 00000000000..63324d78434 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_conversions.yaml @@ -0,0 +1,100 @@ +{{- if .Values.crd.create }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: conversions.example.com.testproject.org +spec: + group: example.com.testproject.org + names: + kind: Conversion + listKind: ConversionList + plural: conversions + singular: conversion + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: true + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: false + subresources: + status: {} +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml new file mode 100755 index 00000000000..110731493d3 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml @@ -0,0 +1,129 @@ +{{- if .Values.crd.create }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: memcacheds.example.com.testproject.org +spec: + group: example.com.testproject.org + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Memcached is the Schema for the memcacheds API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: MemcachedSpec defines the desired state of Memcached + properties: + containerPort: + description: Port defines the port that will be used to init the container + with the image + format: int32 + type: integer + size: + description: |- + Size defines the number of Memcached instances + The following markers will use OpenAPI v3 schema to validate the value + More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + format: int32 + maximum: 3 + minimum: 1 + type: integer + type: object + status: + description: MemcachedStatus defines the observed state of Memcached + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/manager/manager.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/manager/manager.yaml new file mode 100644 index 00000000000..b2b498866ed --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/manager/manager.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: {{ .Release.Namespace }} + labels: + {{- include "chart.labels" . | nindent 4 }} + control-plane: controller-manager +spec: + selector: + matchLabels: + {{- include "chart.selectorLabels" . | nindent 6 }} + control-plane: controller-manager + template: + metadata: + labels: + {{- include "chart.labels" . | nindent 8 }} + control-plane: controller-manager + spec: + serviceAccountName: {{ .Values.controllerManager.serviceAccountName }} + containers: + - name: manager + image: {{ .Values.controllerManager.image.repository }}:{{ .Values.controllerManager.image.tag }} + args: + {{- range .Values.controllerManager.args }} + - {{ . }} + {{- end }} + {{- if .DeployImages }} + env: + {{- range $key, $value := .Values.controllerManager.env }} + - name: {{ $key }} + value: {{ $value }} + {{- end }} + {{- end }} + livenessProbe: + {{- toYaml .Values.controllerManager.livenessProbe | nindent 8 }} + readinessProbe: + {{- toYaml .Values.controllerManager.readinessProbe | nindent 8 }} + resources: + {{- toYaml .Values.controllerManager.resources | nindent 8 }} + securityContext: + {{- toYaml .Values.controllerManager.securityContext | nindent 8 }} + {{- if and .Values.webhook.create .Values.certmanager.create }} + volumeMounts: + - name: webhook-cert + mountPath: /tmp/k8s-webhook-server/serving-certs + readOnly: true + {{- end }} + terminationGracePeriodSeconds: {{ .Values.controllerManager.terminationGracePeriodSeconds }} + {{- if and .Values.webhook.create .Values.certmanager.create }} + volumes: + - name: webhook-cert + secret: + secretName: webhook-server-cert + {{- end }} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml new file mode 100644 index 00000000000..d2784df27fc --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.metrics.create }} +apiVersion: v1 +kind: Service +metadata: + name: project-v4-with-plugins-controller-manager-metrics-service + namespace: {{ .Release.Namespace }} + labels: + {{- include "chart.labels" . | nindent 4 }} +spec: + ports: + - port: 8443 + targetPort: 8443 + protocol: TCP + name: https + selector: + control-plane: controller-manager +{{- end }} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-metrics-traffic.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-metrics-traffic.yaml new file mode 100755 index 00000000000..adb14785829 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-metrics-traffic.yaml @@ -0,0 +1,27 @@ +{{- if .Values.networkPolicy.create }} +# This NetworkPolicy allows ingress traffic +# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those +# namespaces are able to gathering data from the metrics endpoint. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: allow-metrics-traffic + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + control-plane: controller-manager + policyTypes: + - Ingress + ingress: + # This allows ingress traffic from any namespace with the label metrics: enabled + - from: + - namespaceSelector: + matchLabels: + metrics: enabled # Only from namespaces with this label + ports: + - port: 8443 + protocol: TCP +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-webhook-traffic.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-webhook-traffic.yaml new file mode 100755 index 00000000000..cbdd8412004 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-webhook-traffic.yaml @@ -0,0 +1,27 @@ +{{- if .Values.networkPolicy.create }} +# This NetworkPolicy allows ingress traffic to your webhook server running +# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks +# will only work when applied in namespaces labeled with 'webhook: enabled' +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: allow-webhook-traffic + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + control-plane: controller-manager + policyTypes: + - Ingress + ingress: + # This allows ingress traffic from any namespace with the label webhook: enabled + - from: + - namespaceSelector: + matchLabels: + webhook: enabled # Only from namespaces with this label + ports: + - port: 443 + protocol: TCP +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml new file mode 100755 index 00000000000..909399d9cac --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml @@ -0,0 +1,30 @@ +{{- if .Values.prometheus.create }} +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: project-v4-with-plugins-controller-manager-metrics-monitor + namespace: {{ .Release.Namespace }} +spec: + endpoints: + - path: /metrics + port: https # Ensure this is the name of the port that exposes HTTPS metrics + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables + # certificate verification. This poses a significant security risk by making the system vulnerable to + # man-in-the-middle attacks, where an attacker could intercept and manipulate the communication between + # Prometheus and the monitored services. This could lead to unauthorized access to sensitive metrics data, + # compromising the integrity and confidentiality of the information. + # Please use the following options for secure configurations: + # caFile: /etc/metrics-certs/ca.crt + # certFile: /etc/metrics-certs/tls.crt + # keyFile: /etc/metrics-certs/tls.key + insecureSkipVerify: true + selector: + matchLabels: + control-plane: controller-manager +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_editor_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_editor_role.yaml new file mode 100755 index 00000000000..371f3475a6f --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_editor_role.yaml @@ -0,0 +1,28 @@ +{{- if .Values.rbac.create }} +# permissions for end users to edit busyboxes. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: busybox-editor-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes/status + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_viewer_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_viewer_role.yaml new file mode 100755 index 00000000000..8553e02e781 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_viewer_role.yaml @@ -0,0 +1,24 @@ +{{- if .Values.rbac.create }} +# permissions for end users to view busyboxes. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: busybox-viewer-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes + verbs: + - get + - list + - watch +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes/status + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/conversion_editor_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/conversion_editor_role.yaml new file mode 100755 index 00000000000..bdc9c896b88 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/conversion_editor_role.yaml @@ -0,0 +1,28 @@ +{{- if .Values.rbac.create }} +# permissions for end users to edit conversions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: conversion-editor-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/conversion_viewer_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/conversion_viewer_role.yaml new file mode 100755 index 00000000000..c95aa196e6a --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/conversion_viewer_role.yaml @@ -0,0 +1,24 @@ +{{- if .Values.rbac.create }} +# permissions for end users to view conversions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: conversion-viewer-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - get + - list + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role.yaml new file mode 100755 index 00000000000..07bb4a2c407 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role.yaml @@ -0,0 +1,41 @@ +{{- if .Values.rbac.create }} +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role_binding.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role_binding.yaml new file mode 100755 index 00000000000..79420a91d06 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role_binding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: project-v4-with-plugins-controller-manager + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_editor_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_editor_role.yaml new file mode 100755 index 00000000000..8af2ebbe627 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_editor_role.yaml @@ -0,0 +1,28 @@ +{{- if .Values.rbac.create }} +# permissions for end users to edit memcacheds. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: memcached-editor-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - memcacheds + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - memcacheds/status + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_viewer_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_viewer_role.yaml new file mode 100755 index 00000000000..c9aac7582e5 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_viewer_role.yaml @@ -0,0 +1,24 @@ +{{- if .Values.rbac.create }} +# permissions for end users to view memcacheds. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: memcached-viewer-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - memcacheds + verbs: + - get + - list + - watch +- apiGroups: + - example.com.testproject.org + resources: + - memcacheds/status + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role.yaml new file mode 100755 index 00000000000..724519c5a75 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.rbac.create .Values.metrics.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: metrics-auth-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role_binding.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role_binding.yaml new file mode 100755 index 00000000000..1a1eea25c2f --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role_binding.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.rbac.create .Values.metrics.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: metrics-auth-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metrics-auth-role +subjects: +- kind: ServiceAccount + name: project-v4-with-plugins-controller-manager + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_reader_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_reader_role.yaml new file mode 100755 index 00000000000..d4c8113a4e5 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_reader_role.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.rbac.create .Values.metrics.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: project-v4-with-plugins-metrics-reader +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role.yaml new file mode 100755 index 00000000000..6f4e16920ec --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role.yaml @@ -0,0 +1,69 @@ +{{- if .Values.rbac.create }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: manager-role +rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes + - conversions + - memcacheds + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes/finalizers + - conversions/finalizers + - memcacheds/finalizers + verbs: + - update +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes/status + - conversions/status + - memcacheds/status + verbs: + - get + - patch + - update +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role_binding.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role_binding.yaml new file mode 100755 index 00000000000..d51f3c447df --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role_binding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: project-v4-with-plugins-controller-manager + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/service_account.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/service_account.yaml new file mode 100755 index 00000000000..37eee241093 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/service_account.yaml @@ -0,0 +1,9 @@ +{{- if .Values.rbac.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: project-v4-with-plugins-controller-manager + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/webhook/service.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/service.yaml new file mode 100644 index 00000000000..b23ce0aeb30 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/service.yaml @@ -0,0 +1,16 @@ +{{- if .Values.webhook.create }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "chart.name" . }}-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/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_busyboxes.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_busyboxes.yaml new file mode 100755 index 00000000000..ba44e985fda --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_busyboxes.yaml @@ -0,0 +1,24 @@ +{{- if .Values.webhook.create }} +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + {{- if .Values.certmanager.create }} + cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/serving-cert" + {{- end }} + name: busyboxes.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: {{ .Release.Namespace }} + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_conversions.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_conversions.yaml new file mode 100755 index 00000000000..b099ca21592 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_conversions.yaml @@ -0,0 +1,24 @@ +{{- if .Values.webhook.create }} +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + {{- if .Values.certmanager.create }} + cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/serving-cert" + {{- end }} + name: conversions.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: {{ .Release.Namespace }} + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_memcacheds.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_memcacheds.yaml new file mode 100755 index 00000000000..4f8b82852cd --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook_in_memcacheds.yaml @@ -0,0 +1,24 @@ +{{- if .Values.webhook.create }} +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + {{- if .Values.certmanager.create }} + cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/serving-cert" + {{- end }} + name: memcacheds.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: {{ .Release.Namespace }} + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhooks.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhooks.yaml new file mode 100644 index 00000000000..01938747b1b --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhooks.yaml @@ -0,0 +1,97 @@ +{{- if .Values.webhook.create }} + +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: project-v4-with-plugins-mutating-webhook-configuration + namespace: {{ .Release.Namespace }} + annotations: + {{- if .Values.certmanager.create }} + cert-manager.io/inject-ca-from: "{{ $.Release.Namespace }}/serving-cert" + {{- end }} + labels: + {{- include "chart.labels" . | nindent 4 }} +webhooks: + {{- range .Values.webhook.services }} + {{- if eq .type "mutating" }} + - name: {{ .name }} + clientConfig: + service: + name: project-v4-with-plugins-webhook-service + 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 }} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: project-v4-with-plugins-validating-webhook-configuration + namespace: {{ .Release.Namespace }} + annotations: + {{- if .Values.certmanager.create }} + cert-manager.io/inject-ca-from: "{{ $.Release.Namespace }}/serving-cert" + {{- end }} +webhooks: + {{- range .Values.webhook.services }} + {{- if eq .type "validating" }} + - name: {{ .name }} + clientConfig: + service: + name: project-v4-with-plugins-webhook-service + 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/testdata/project-v4-with-plugins/dist/chart/values.yaml b/testdata/project-v4-with-plugins/dist/chart/values.yaml new file mode 100644 index 00000000000..3a4468a7024 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/values.yaml @@ -0,0 +1,117 @@ +# [MANAGER]: Manager Deployment Configurations +controllerManager: + image: + repository: controller + tag: latest + replicas: 1 + 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 + securityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + serviceAccountName: project-v4-with-plugins-controller-manager + terminationGracePeriodSeconds: 10 + env: + BUSYBOX_IMAGE: busybox:1.36.1 + MEMCACHED_IMAGE: memcached:1.6.26-alpine3.19 + +# [CRDs]: To create RBAC (Permissions) configurations +rbac: + create: true + +# [CRDs]: To create the CRDs +crd: + # This option decides if the CRDs should be installed + # as part of the Helm installation. + create: true + + # This option makes it so that the "helm.sh/resource-policy": keep + # annotation is added to the CRD. This will prevent Helm from uninstalling + # the CRD when the Helm release is uninstalled. + # WARNING: when the CRDs are removed, all cert-manager custom resources + # (Certificates, Issuers, ...) will be removed too by the garbage collector. + 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: + create: true + +# [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: + create: true + services: + - name: mbusybox-v1alpha1.kb.io + type: mutating + path: /mutate-example-com-testproject-org-v1alpha1-busybox + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + apiGroups: + - example.com.testproject.org + apiVersions: + - v1alpha1 + resources: + - busyboxes + - name: vmemcached-v1alpha1.kb.io + type: validating + path: /validate-example-com-testproject-org-v1alpha1-memcached + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + apiGroups: + - example.com.testproject.org + apiVersions: + - v1alpha1 + resources: + - memcacheds + +# [PROMETHEUS]: To create a ServiceMonitor to export metrics to Prometheus set true +prometheus: + create: false + +# [CERT-MANAGER]: To enable cert-manager injection to webhooks set true +certmanager: + create: true + +# [NETWORK POLICIES]: To create NetworkPolices set true +networkPolicy: + create: false diff --git a/testdata/project-v4-with-plugins/dist/install.yaml b/testdata/project-v4-with-plugins/dist/install.yaml index 66788aef372..cbb75fe9b54 100644 --- a/testdata/project-v4-with-plugins/dist/install.yaml +++ b/testdata/project-v4-with-plugins/dist/install.yaml @@ -14,6 +14,16 @@ metadata: controller-gen.kubebuilder.io/version: v0.16.4 name: busyboxes.example.com.testproject.org spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: project-v4-with-plugins-webhook-service + namespace: project-v4-with-plugins-system + path: /convert + conversionReviewVersions: + - v1 group: example.com.testproject.org names: kind: Busybox @@ -125,6 +135,108 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: conversions.example.com.testproject.org +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: project-v4-with-plugins-webhook-service + namespace: project-v4-with-plugins-system + path: /convert + conversionReviewVersions: + - v1 + group: example.com.testproject.org + names: + kind: Conversion + listKind: ConversionList + plural: conversions + singular: conversion + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: true + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: Conversion is the Schema for the conversions API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ConversionSpec defines the desired state of Conversion. + properties: + foo: + description: Foo is an example field of Conversion. Edit conversion_types.go + to remove/update + type: string + type: object + status: + description: ConversionStatus defines the observed state of Conversion. + type: object + type: object + served: true + storage: false + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.16.4 @@ -356,6 +468,56 @@ rules: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-with-plugins + name: project-v4-with-plugins-conversion-editor-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-with-plugins + name: project-v4-with-plugins-conversion-viewer-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - conversions + verbs: + - get + - list + - watch +- apiGroups: + - example.com.testproject.org + resources: + - conversions/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole metadata: name: project-v4-with-plugins-manager-role rules: @@ -390,6 +552,7 @@ rules: - example.com.testproject.org resources: - busyboxes + - conversions - memcacheds verbs: - create @@ -403,6 +566,7 @@ rules: - example.com.testproject.org resources: - busyboxes/finalizers + - conversions/finalizers - memcacheds/finalizers verbs: - update @@ -410,6 +574,7 @@ rules: - example.com.testproject.org resources: - busyboxes/status + - conversions/status - memcacheds/status verbs: - get @@ -652,6 +817,32 @@ spec: secretName: webhook-server-cert --- apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: project-v4-with-plugins-mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: project-v4-with-plugins-webhook-service + namespace: project-v4-with-plugins-system + path: /mutate-example-com-testproject-org-v1alpha1-busybox + failurePolicy: Fail + name: mbusybox-v1alpha1.kb.io + rules: + - apiGroups: + - example.com.testproject.org + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - busyboxes + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: project-v4-with-plugins-validating-webhook-configuration diff --git a/testdata/project-v4-with-plugins/internal/controller/conversion_controller.go b/testdata/project-v4-with-plugins/internal/controller/conversion_controller.go new file mode 100644 index 00000000000..cb96b5fb292 --- /dev/null +++ b/testdata/project-v4-with-plugins/internal/controller/conversion_controller.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 controller + +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" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1" +) + +// ConversionReconciler reconciles a Conversion object +type ConversionReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=example.com.testproject.org,resources=conversions,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=example.com.testproject.org,resources=conversions/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=example.com.testproject.org,resources=conversions/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 Conversion 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@v0.19.1/pkg/reconcile +func (r *ConversionReconciler) 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 *ConversionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&examplecomv1.Conversion{}). + Named("conversion"). + Complete(r) +} diff --git a/testdata/project-v4-with-plugins/internal/controller/conversion_controller_test.go b/testdata/project-v4-with-plugins/internal/controller/conversion_controller_test.go new file mode 100644 index 00000000000..bdec9efd369 --- /dev/null +++ b/testdata/project-v4-with-plugins/internal/controller/conversion_controller_test.go @@ -0,0 +1,84 @@ +/* +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 controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "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" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1" +) + +var _ = Describe("Conversion Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + conversion := &examplecomv1.Conversion{} + + BeforeEach(func() { + By("creating the custom resource for the Kind Conversion") + err := k8sClient.Get(ctx, typeNamespacedName, conversion) + if err != nil && errors.IsNotFound(err) { + resource := &examplecomv1.Conversion{ + 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 := &examplecomv1.Conversion{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance Conversion") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &ConversionReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // 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/testdata/project-v4-with-plugins/internal/controller/suite_test.go b/testdata/project-v4-with-plugins/internal/controller/suite_test.go index 5259421161e..0114fe3b039 100644 --- a/testdata/project-v4-with-plugins/internal/controller/suite_test.go +++ b/testdata/project-v4-with-plugins/internal/controller/suite_test.go @@ -33,6 +33,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -80,6 +81,9 @@ var _ = BeforeSuite(func() { err = examplecomv1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = examplecomv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/testdata/project-v4-with-plugins/internal/webhook/v1/conversion_webhook.go b/testdata/project-v4-with-plugins/internal/webhook/v1/conversion_webhook.go new file mode 100644 index 00000000000..fc0dc4f0f85 --- /dev/null +++ b/testdata/project-v4-with-plugins/internal/webhook/v1/conversion_webhook.go @@ -0,0 +1,36 @@ +/* +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 v1 + +import ( + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1" +) + +// nolint:unused +// log is for logging in this package. +var conversionlog = logf.Log.WithName("conversion-resource") + +// SetupConversionWebhookWithManager registers the webhook for Conversion in the manager. +func SetupConversionWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&examplecomv1.Conversion{}). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! diff --git a/testdata/project-v4-with-plugins/internal/webhook/v1/conversion_webhook_test.go b/testdata/project-v4-with-plugins/internal/webhook/v1/conversion_webhook_test.go new file mode 100644 index 00000000000..ac907b6bb04 --- /dev/null +++ b/testdata/project-v4-with-plugins/internal/webhook/v1/conversion_webhook_test.go @@ -0,0 +1,55 @@ +/* +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 v1 + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + examplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1" + // TODO (user): Add any additional imports if needed +) + +var _ = Describe("Conversion Webhook", func() { + var ( + obj *examplecomv1.Conversion + oldObj *examplecomv1.Conversion + ) + + BeforeEach(func() { + obj = &examplecomv1.Conversion{} + oldObj = &examplecomv1.Conversion{} + Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + 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 + }) + + Context("When creating Conversion 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() { + // convertedObj := &examplecomv1.Conversion{} + // Expect(obj.ConvertTo(convertedObj)).To(Succeed()) + // Expect(convertedObj).ToNot(BeNil()) + // }) + }) + +}) diff --git a/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/busybox_webhook.go b/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/busybox_webhook.go new file mode 100644 index 00000000000..6067324c64f --- /dev/null +++ b/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/busybox_webhook.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 v1alpha1 + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1" +) + +// nolint:unused +// log is for logging in this package. +var busyboxlog = logf.Log.WithName("busybox-resource") + +// SetupBusyboxWebhookWithManager registers the webhook for Busybox in the manager. +func SetupBusyboxWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&examplecomv1alpha1.Busybox{}). + WithDefaulter(&BusyboxCustomDefaulter{}). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// +kubebuilder:webhook:path=/mutate-example-com-testproject-org-v1alpha1-busybox,mutating=true,failurePolicy=fail,sideEffects=None,groups=example.com.testproject.org,resources=busyboxes,verbs=create;update,versions=v1alpha1,name=mbusybox-v1alpha1.kb.io,admissionReviewVersions=v1 + +// BusyboxCustomDefaulter struct is responsible for setting default values on the custom resource of the +// Kind Busybox 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 BusyboxCustomDefaulter struct { + // TODO(user): Add more fields as needed for defaulting +} + +var _ webhook.CustomDefaulter = &BusyboxCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Busybox. +func (d *BusyboxCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + busybox, ok := obj.(*examplecomv1alpha1.Busybox) + + if !ok { + return fmt.Errorf("expected an Busybox object but got %T", obj) + } + busyboxlog.Info("Defaulting for Busybox", "name", busybox.GetName()) + + // TODO(user): fill in your defaulting logic. + + return nil +} diff --git a/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/busybox_webhook_test.go b/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/busybox_webhook_test.go new file mode 100644 index 00000000000..48113677fcb --- /dev/null +++ b/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/busybox_webhook_test.go @@ -0,0 +1,61 @@ +/* +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 v1alpha1 + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1" + // TODO (user): Add any additional imports if needed +) + +var _ = Describe("Busybox Webhook", func() { + var ( + obj *examplecomv1alpha1.Busybox + oldObj *examplecomv1alpha1.Busybox + defaulter BusyboxCustomDefaulter + ) + + BeforeEach(func() { + obj = &examplecomv1alpha1.Busybox{} + oldObj = &examplecomv1alpha1.Busybox{} + defaulter = BusyboxCustomDefaulter{} + Expect(defaulter).NotTo(BeNil(), "Expected defaulter to be initialized") + Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + 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 + }) + + Context("When creating Busybox 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") + // 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")) + // }) + }) + +}) diff --git a/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/webhook_suite_test.go b/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/webhook_suite_test.go index 7e2fed6c3ae..048cffd0f9f 100644 --- a/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/webhook_suite_test.go +++ b/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/webhook_suite_test.go @@ -118,6 +118,9 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) + err = SetupBusyboxWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + err = SetupMemcachedWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) diff --git a/testdata/project-v4-with-plugins/test/e2e/e2e_test.go b/testdata/project-v4-with-plugins/test/e2e/e2e_test.go index 08f2abd755a..dcf7b9f29f8 100644 --- a/testdata/project-v4-with-plugins/test/e2e/e2e_test.go +++ b/testdata/project-v4-with-plugins/test/e2e/e2e_test.go @@ -244,6 +244,20 @@ var _ = Describe("Manager", Ordered, func() { Eventually(verifyCertManager).Should(Succeed()) }) + 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", + "project-v4-with-plugins-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()) + }) + It("should have CA injection for validating webhooks", func() { By("checking CA injection for validating webhooks") verifyCAInjection := func(g Gomega) { diff --git a/testdata/project-v4/README.md b/testdata/project-v4/README.md index 49d9168532c..c449cd994a5 100644 --- a/testdata/project-v4/README.md +++ b/testdata/project-v4/README.md @@ -68,7 +68,13 @@ make undeploy ## Project Distribution -Following are the steps to build the installer and distribute this project to users. +Following the options to release and provide this solution +to the users. + +### By providing a bundle with all YAML files + +Following are the steps to build the installer and distribute +this project to users. 1. Build the installer for the image built and published in the registry: @@ -76,19 +82,40 @@ Following are the steps to build the installer and distribute this project to us make build-installer IMG=/project-v4:tag ``` -NOTE: The makefile target mentioned above generates an 'install.yaml' +**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.: +Users can just run 'kubectl apply -f ' to install the project, i.e.: ```sh kubectl apply -f https://raw.githubusercontent.com//project-v4//dist/install.yaml ``` +### By providing a Helm Chart + +Following are the steps to build a Helm Chart to distribute +this project to users. + +1. Build the chart using the optional helm plugin + +```sh +kubebuilder edit --plugins=helm/v1-alpha +``` + +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