From ad4afdb7c88a8d50883af248435eb1526f6f2de9 Mon Sep 17 00:00:00 2001 From: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com> Date: Sun, 10 Nov 2024 06:48:23 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20helm=20plugin=20to=20distribu?= =?UTF-8?q?te=20projects=20(#4227)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add helm plugin to distribute projects This PR introduces an optional Helm plugin, enabling users to scaffold Helm charts for Kubebuilder projects. - **Helm Chart Scaffolding**: Allows generation of a Helm chart under the `dist/chart` directory, providing an alternative for distributing and managing projects via Helm. - **Usage**: - **To add when init the project**: `kubebuilder init --plugins=helm/v1-alpha` for new projects. - **To Init/Update in a project previously scaffolded**: `kubebuilder edit --plugins=helm/v1-alpha` to add or update Helm charts for existing projects. - **Sync Helm Chart**: Syncs the Helm chart with project manifests using the `edit` command, with `--force` required for updates after webhook or `DeployImage` plugin changes in order to overwritten the values.yaml and manager.yaml files. --- .github/workflows/test-helm-samples.yml | 76 +++ Makefile | 17 +- cmd/main.go | 2 + .../testdata/project/README.md | 31 +- .../network-policy/allow-metrics-traffic.yaml | 2 +- .../testdata/project/README.md | 31 +- .../network-policy/allow-metrics-traffic.yaml | 2 +- .../testdata/project/README.md | 31 +- .../network-policy/allow-metrics-traffic.yaml | 2 +- .../src/plugins/available/helm-v1-alpha.md | 92 ++++ .../src/plugins/to-add-optional-features.md | 12 +- pkg/cli/alpha/internal/generate.go | 36 ++ .../network-policy/allow-metrics-traffic.go | 2 +- pkg/plugins/golang/v4/init.go | 2 +- pkg/plugins/golang/v4/scaffolds/init.go | 6 +- .../v4/scaffolds/internal/templates/readme.go | 33 +- pkg/plugins/optional/helm/v1alpha/commons.go | 37 ++ pkg/plugins/optional/helm/v1alpha/edit.go | 90 ++++ pkg/plugins/optional/helm/v1alpha/init.go | 63 +++ pkg/plugins/optional/helm/v1alpha/plugin.go | 65 +++ .../optional/helm/v1alpha/scaffolds/init.go | 448 ++++++++++++++++++ .../cert-manager/certificate.go | 105 ++++ .../templates/chart-templates/helpers_tpl.go | 105 ++++ .../chart-templates/manager/manager.go | 144 ++++++ .../metrics/metrics_service.go | 62 +++ .../chart-templates/prometheus/monitor.go | 83 ++++ .../chart-templates/webhook/service.go | 63 +++ .../chart-templates/webhook/webhook.go | 142 ++++++ .../scaffolds/internal/templates/chart.go | 53 +++ .../internal/templates/helmignore.go | 70 +++ .../scaffolds/internal/templates/values.go | 175 +++++++ pkg/plugins/optional/helm/webhookYAML.go | 35 ++ test/e2e/alphagenerate/generate_test.go | 22 + test/e2e/utils/test_context.go | 52 ++ test/e2e/v4/plugin_cluster_test.go | 62 ++- test/testdata/generate.sh | 6 + testdata/project-v4-multigroup/README.md | 31 +- .../network-policy/allow-metrics-traffic.yaml | 2 +- testdata/project-v4-with-plugins/PROJECT | 1 + testdata/project-v4-with-plugins/README.md | 31 +- .../network-policy/allow-metrics-traffic.yaml | 2 +- .../dist/chart/.helmignore | 25 + .../dist/chart/Chart.yaml | 7 + .../dist/chart/templates/_helpers.tpl | 50 ++ .../templates/certmanager/certificate.yaml | 60 +++ ...example.com.testproject.org_busyboxes.yaml | 123 +++++ ...xample.com.testproject.org_memcacheds.yaml | 128 +++++ ...ample.com.testproject.org_wordpresses.yaml | 114 +++++ .../dist/chart/templates/manager/manager.yaml | 79 +++ .../templates/metrics/metrics-service.yaml | 17 + .../network-policy/allow-metrics-traffic.yaml | 27 ++ .../network-policy/allow-webhook-traffic.yaml | 27 ++ .../chart/templates/prometheus/monitor.yaml | 38 ++ .../templates/rbac/busybox_admin_role.yaml | 28 ++ .../templates/rbac/busybox_editor_role.yaml | 34 ++ .../templates/rbac/busybox_viewer_role.yaml | 30 ++ .../templates/rbac/leader_election_role.yaml | 41 ++ .../rbac/leader_election_role_binding.yaml | 16 + .../templates/rbac/memcached_admin_role.yaml | 28 ++ .../templates/rbac/memcached_editor_role.yaml | 34 ++ .../templates/rbac/memcached_viewer_role.yaml | 30 ++ .../templates/rbac/metrics_auth_role.yaml | 21 + .../rbac/metrics_auth_role_binding.yaml | 16 + .../templates/rbac/metrics_reader_role.yaml | 13 + .../dist/chart/templates/rbac/role.yaml | 69 +++ .../chart/templates/rbac/role_binding.yaml | 16 + .../chart/templates/rbac/service_account.yaml | 9 + .../templates/rbac/wordpress_admin_role.yaml | 28 ++ .../templates/rbac/wordpress_editor_role.yaml | 34 ++ .../templates/rbac/wordpress_viewer_role.yaml | 30 ++ .../dist/chart/templates/webhook/service.yaml | 16 + .../chart/templates/webhook/webhooks.yaml | 97 ++++ .../dist/chart/values.yaml | 93 ++++ testdata/project-v4/README.md | 31 +- .../network-policy/allow-metrics-traffic.yaml | 2 +- 75 files changed, 3670 insertions(+), 67 deletions(-) create mode 100644 .github/workflows/test-helm-samples.yml create mode 100644 docs/book/src/plugins/available/helm-v1-alpha.md create mode 100644 pkg/plugins/optional/helm/v1alpha/commons.go create mode 100644 pkg/plugins/optional/helm/v1alpha/edit.go create mode 100644 pkg/plugins/optional/helm/v1alpha/init.go create mode 100644 pkg/plugins/optional/helm/v1alpha/plugin.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/init.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager/certificate.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager/manager.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/service.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/webhook.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/helmignore.go create mode 100644 pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/values.go create mode 100644 pkg/plugins/optional/helm/webhookYAML.go create mode 100644 testdata/project-v4-with-plugins/dist/chart/.helmignore create mode 100644 testdata/project-v4-with-plugins/dist/chart/Chart.yaml create mode 100644 testdata/project-v4-with-plugins/dist/chart/templates/_helpers.tpl create mode 100644 testdata/project-v4-with-plugins/dist/chart/templates/certmanager/certificate.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_wordpresses.yaml create mode 100644 testdata/project-v4-with-plugins/dist/chart/templates/manager/manager.yaml create mode 100644 testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-metrics-traffic.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-webhook-traffic.yaml create mode 100644 testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_admin_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_editor_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_viewer_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role_binding.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_admin_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_editor_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_viewer_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role_binding.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_reader_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/role_binding.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/service_account.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_admin_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_editor_role.yaml create mode 100755 testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_viewer_role.yaml create mode 100644 testdata/project-v4-with-plugins/dist/chart/templates/webhook/service.yaml create mode 100644 testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhooks.yaml create mode 100644 testdata/project-v4-with-plugins/dist/chart/values.yaml diff --git a/.github/workflows/test-helm-samples.yml b/.github/workflows/test-helm-samples.yml new file mode 100644 index 00000000000..1fc31a21393 --- /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: 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 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: 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..be4fa376ff6 100644 --- a/Makefile +++ b/Makefile @@ -80,9 +80,14 @@ generate-testdata: ## Update/generate the testdata in $GOPATH/src/sigs.k8s.io/ku ./test/testdata/generate.sh .PHONY: generate-docs -generate-docs: ## Update/generate the docs in $GOPATH/src/sigs.k8s.io/kubebuilder +generate-docs: ## Update/generate the docs ./hack/docs/generate.sh +.PHONY: generate-charts +generate-charts: ## Re-generate the helm chart testdata only + rm -rf testdata/project-v4-with-plugins/dist/chart + (cd testdata/project-v4-with-plugins && kubebuilder edit --plugins=helm/v1-alpha) + .PHONY: check-docs check-docs: ## Run the script to ensure that the docs are updated ./hack/docs/check.sh @@ -97,7 +102,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 +176,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..2ae27e2f60a 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/README.md +++ b/docs/book/src/cronjob-tutorial/testdata/project/README.md @@ -68,7 +68,9 @@ 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 1. Build the installer for the image built and published in the registry: @@ -76,19 +78,38 @@ 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. +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 + +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/cronjob-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml b/docs/book/src/cronjob-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml index de6ec5f8097..57aee327188 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml +++ b/docs/book/src/cronjob-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml @@ -1,6 +1,6 @@ # 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. +# namespaces are able to gather data from the metrics endpoint. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: diff --git a/docs/book/src/getting-started/testdata/project/README.md b/docs/book/src/getting-started/testdata/project/README.md index b5295ca3fa8..2ae27e2f60a 100644 --- a/docs/book/src/getting-started/testdata/project/README.md +++ b/docs/book/src/getting-started/testdata/project/README.md @@ -68,7 +68,9 @@ 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 1. Build the installer for the image built and published in the registry: @@ -76,19 +78,38 @@ 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. +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 + +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/config/network-policy/allow-metrics-traffic.yaml b/docs/book/src/getting-started/testdata/project/config/network-policy/allow-metrics-traffic.yaml index de6ec5f8097..57aee327188 100644 --- a/docs/book/src/getting-started/testdata/project/config/network-policy/allow-metrics-traffic.yaml +++ b/docs/book/src/getting-started/testdata/project/config/network-policy/allow-metrics-traffic.yaml @@ -1,6 +1,6 @@ # 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. +# namespaces are able to gather data from the metrics endpoint. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: diff --git a/docs/book/src/multiversion-tutorial/testdata/project/README.md b/docs/book/src/multiversion-tutorial/testdata/project/README.md index b5295ca3fa8..2ae27e2f60a 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/README.md +++ b/docs/book/src/multiversion-tutorial/testdata/project/README.md @@ -68,7 +68,9 @@ 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 1. Build the installer for the image built and published in the registry: @@ -76,19 +78,38 @@ 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. +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 + +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/config/network-policy/allow-metrics-traffic.yaml b/docs/book/src/multiversion-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml index de6ec5f8097..57aee327188 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml +++ b/docs/book/src/multiversion-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml @@ -1,6 +1,6 @@ # 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. +# namespaces are able to gather data from the metrics endpoint. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: 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..2d5cc1c025c 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 Helm 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/common/kustomize/v2/scaffolds/internal/templates/config/network-policy/allow-metrics-traffic.go b/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy/allow-metrics-traffic.go index e9aa320f21c..5253ca15cf2 100644 --- a/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy/allow-metrics-traffic.go +++ b/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy/allow-metrics-traffic.go @@ -44,7 +44,7 @@ func (f *NetworkPolicyAllowMetrics) SetTemplateDefaults() error { const metricsNetworkPolicyTemplate = `# 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. +# namespaces are able to gather data from the metrics endpoint. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: 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..37f376284f1 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,42 @@ 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 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. +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 + +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/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..75ff2591032 --- /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, regenerates all the files") +} + +func (p *editSubcommand) InjectConfig(c config.Config) error { + p.config = c + return nil +} + +func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.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..b34c317b655 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/init.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 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 + +**IMPORTANT** 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..dc3d2fe2951 --- /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 Helm plugin +func (Plugin) Version() plugin.Version { return pluginVersion } + +// SupportedProjectVersions returns an array with all project versions supported by the plugin +func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions } + +// GetInitSubcommand will return the subcommand which is responsible for initializing and scaffolding helm manifests +func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand } + +// GetEditSubcommand will return the subcommand which is responsible for adding and/or edit a helm chart +func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand } + +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..bede1a92d5e --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/init.go @@ -0,0 +1,448 @@ +/* +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/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus" + + "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{}, + &prometheus.Monitor{}, + } + + 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"}, + {"config/network-policy", "dist/chart/templates/network-policy", "networkPolicy"}, + } + + for _, dir := range configDirs { + // Ensure destination directory exists + if err := os.MkdirAll(dir.DestDir, os.ModePerm); err != nil { + return fmt.Errorf("failed to create directory %s: %v", dir.DestDir, err) + } + + files, err := filepath.Glob(filepath.Join(dir.SrcDir, "*.yaml")) + if err != nil { + return err + } + + for _, srcFile := range files { + 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, applies patches +// to spec.conversion if applicable, 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" { + contentStr = strings.Replace(contentStr, + "name: controller-manager", + fmt.Sprintf("name: %s-controller-manager", projectName), -1) + contentStr = strings.Replace(contentStr, + "name: metrics-reader", + fmt.Sprintf("name: %s-metrics-reader", projectName), 1) + } + + // Conditionally handle CRD patches and annotations for CRDs + if subDir == "crd" { + kind, group := extractKindAndGroupFromFileName(filepath.Base(srcFile)) + hasWebhookPatch := false + + // Retrieve patch content for the CRD's spec.conversion, if it exists + patchContent, patchExists, err := getCRDPatchContent(kind, group) + if err != nil { + return err + } + + // If patch content exists, inject it under spec.conversion with Helm conditional + if patchExists { + conversionSpec := extractConversionSpec(patchContent) + contentStr = injectConversionSpecWithCondition(contentStr, conversionSpec) + hasWebhookPatch = true + } + + // Inject annotations after "annotations:" in a single block without extra spaces + contentStr = injectAnnotations(contentStr, hasWebhookPatch) + } + + // Remove existing labels if necessary + contentStr = removeLabels(contentStr) + + // Replace namespace with Helm template variable + contentStr = strings.ReplaceAll(contentStr, "namespace: system", "namespace: {{ .Release.Namespace }}") + + contentStr = strings.Replace(contentStr, "metadata:", `metadata: + labels: + {{- include "chart.labels" . | nindent 4 }}`, 1) + + var wrappedContent string + if isMetricRBACFile(subDir, srcFile) { + wrappedContent = fmt.Sprintf( + "{{- if and .Values.rbac.enable .Values.metrics.enable }}\n%s{{- end -}}\n", contentStr) + } else { + wrappedContent = fmt.Sprintf( + "{{- if .Values.%s.enable }}\n%s{{- end -}}\n", subDir, contentStr) + } + + if err := os.MkdirAll(filepath.Dir(destFile), 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 +} + +// extractKindAndGroupFromFileName extracts the kind and group from a CRD filename +func extractKindAndGroupFromFileName(fileName string) (kind, group string) { + parts := strings.Split(fileName, "_") + if len(parts) >= 2 { + group = strings.Split(parts[0], ".")[0] // Extract group up to the first dot + kind = strings.TrimSuffix(parts[1], ".yaml") + } + return kind, group +} + +// getCRDPatchContent finds and reads the appropriate patch content for a given kind and group +func getCRDPatchContent(kind, group string) (string, bool, error) { + // First, look for patches that contain both "webhook", the group, and kind in their filename + groupKindPattern := fmt.Sprintf("config/crd/patches/webhook_*%s*%s*.yaml", group, kind) + patchFiles, err := filepath.Glob(groupKindPattern) + if err != nil { + return "", false, fmt.Errorf("failed to list patches: %v", err) + } + + // If no group-specific patch found, search for patches that contain only "webhook" and the kind + if len(patchFiles) == 0 { + kindOnlyPattern := fmt.Sprintf("config/crd/patches/webhook_*%s*.yaml", kind) + patchFiles, err = filepath.Glob(kindOnlyPattern) + if err != nil { + return "", false, fmt.Errorf("failed to list patches: %v", err) + } + } + + // Read the first matching patch file (if any) + if len(patchFiles) > 0 { + patchContent, err := os.ReadFile(patchFiles[0]) + if err != nil { + return "", false, fmt.Errorf("failed to read patch file %s: %v", patchFiles[0], err) + } + return string(patchContent), true, nil + } + + return "", false, nil +} + +// extractConversionSpec extracts only the conversion section from the patch content +func extractConversionSpec(patchContent string) string { + specStart := strings.Index(patchContent, "conversion:") + if specStart == -1 { + return "" + } + return patchContent[specStart:] +} + +// injectConversionSpecWithCondition inserts the conversion spec under the main spec field with Helm conditional +func injectConversionSpecWithCondition(contentStr, conversionSpec string) string { + specPosition := strings.Index(contentStr, "spec:") + if specPosition == -1 { + return contentStr // No spec field found; return unchanged + } + conditionalSpec := fmt.Sprintf("\n {{- if .Values.webhook.enable }}\n %s\n {{- end }}", + strings.TrimRight(conversionSpec, "\n")) + return contentStr[:specPosition+5] + conditionalSpec + contentStr[specPosition+5:] +} + +// injectAnnotations inserts the required annotations after the "annotations:" field in a single block without +// extra spaces +func injectAnnotations(contentStr string, hasWebhookPatch bool) string { + annotationsBlock := ` + {{- if .Values.certmanager.enable }} + cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/serving-cert" + {{- end }} + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }}` + if hasWebhookPatch { + return strings.Replace(contentStr, "annotations:", "annotations:"+annotationsBlock, 1) + } + + // Apply only resource policy if no webhook patch + resourcePolicy := ` + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }}` + return strings.Replace(contentStr, "annotations:", "annotations:"+resourcePolicy, 1) +} + +// isMetricRBACFile checks if the file is in the "rbac" +// subdirectory and matches one of the metric-related RBAC filenames +func isMetricRBACFile(subDir, srcFile string) bool { + return subDir == "rbac" && (strings.HasSuffix(srcFile, "metrics_auth_role.yaml") || + strings.HasSuffix(srcFile, "metrics_auth_role_binding.yaml") || + strings.HasSuffix(srcFile, "metrics_reader_role.yaml")) +} + +// removeLabels removes any existing labels section from the content +func removeLabels(content string) string { + labelRegex := `(?m)^ labels:\n(?: [^\n]+\n)*` + re := regexp.MustCompile(labelRegex) + + return re.ReplaceAllString(content, "") +} 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..4d82986316d --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager/certificate.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 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.enable }}` + "`" + `}} +# Self-signed Issuer +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} + name: selfsigned-issuer + namespace: {{ "{{ .Release.Namespace }}" }} +spec: + selfSigned: {} +{{ "{{- if .Values.webhook.enable }}" }} +--- +# Certificate for the webhook +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + {{ "{{- if .Values.crd.keep }}" }} + "helm.sh/resource-policy": keep + {{ "{{- end }}" }} + name: serving-cert + namespace: {{ "{{ .Release.Namespace }}" }} + labels: + {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} +spec: + dnsNames: + - {{ .ProjectName }}.{{ "{{ .Release.Namespace }}" }}.svc + - {{ .ProjectName }}.{{ "{{ .Release.Namespace }}" }}.svc.cluster.local + - {{ .ProjectName }}-webhook-service.{{ "{{ .Release.Namespace }}" }}.svc + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: webhook-server-cert +{{` + "`" + `{{- end }}` + "`" + `}} +{{ "{{- if and .Values.metrics.enable .Values.certmanager.enable }}" }} +--- +# Certificate for the metrics +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + {{ "{{- if .Values.crd.keep }}" }} + "helm.sh/resource-policy": keep + {{ "{{- end }}" }} + labels: + {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} + name: metrics-certs + namespace: {{ "{{ .Release.Namespace }}" }} +spec: + dnsNames: + - {{ .ProjectName }}.{{ "{{ .Release.Namespace }}" }}.svc + - {{ .ProjectName }}.{{ "{{ .Release.Namespace }}" }}.svc.cluster.local + - {{ .ProjectName }}-metrics-service.{{ "{{ .Release.Namespace }}" }}.svc + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: metrics-server-cert +{{` + "`" + `{{- end }}` + "`" + `}} +{{` + "`" + `{{- end }}` + "`" + `}} +` diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go new file mode 100644 index 00000000000..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..603ff8d39f6 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager/manager.go @@ -0,0 +1,144 @@ +/* +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 + + // DeployImages if true will scaffold the env with the images + DeployImages bool + // Force if true allow overwrite the scaffolded file + Force bool + // HasWebhooks is true when webhooks were found in the config + HasWebhooks bool +} + +// SetTemplateDefaults sets the default template configuration +func (f *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: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + {{ "{{- include \"chart.labels\" . | nindent 8 }}" }} + control-plane: controller-manager + spec: + containers: + - name: manager + args: + {{ "{{- range .Values.controllerManager.container.args }}" }} + - {{ "{{ . }}" }} + {{ "{{- end }}" }} + command: + - /manager + image: {{ "{{ .Values.controllerManager.container.image.repository }}" }}:{{ "{{ .Values.controllerManager.container.image.tag }}" }} +{{- if .DeployImages }} + env: + {{ "{{- range $key, $value := .Values.controllerManager.container.env }}" }} + - name: {{ "{{ $key }}" }} + value: {{ "{{ $value }}" }} + {{ "{{- end }}" }} +{{- end }} + livenessProbe: + {{ "{{- toYaml .Values.controllerManager.container.livenessProbe | nindent 12 }}" }} + readinessProbe: + {{ "{{- toYaml .Values.controllerManager.container.readinessProbe | nindent 12 }}" }} +{{- if .HasWebhooks }} + {{ "{{- if .Values.webhook.enable }}" }} + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + {{ "{{- end }}" }} +{{- end }} + resources: + {{ "{{- toYaml .Values.controllerManager.container.resources | nindent 12 }}" }} + securityContext: + {{ "{{- toYaml .Values.controllerManager.container.securityContext | nindent 12 }}" }} + {{ "{{- if and .Values.certmanager.enable (or .Values.webhook.enable .Values.metrics.enable) }}" }} + volumeMounts: +{{- if .HasWebhooks }} + {{ "{{- if and .Values.webhook.enable .Values.certmanager.enable }}" }} + - name: webhook-cert + mountPath: /tmp/k8s-webhook-server/serving-certs + readOnly: true + {{ "{{- end }}" }} +{{- end }} + {{ "{{- if and .Values.metrics.enable .Values.certmanager.enable }}" }} + - name: metrics-certs + mountPath: /tmp/k8s-metrics-server/metrics-certs + readOnly: true + {{ "{{- end }}" }} + {{ "{{- end }}" }} + securityContext: + {{ "{{- toYaml .Values.controllerManager.securityContext | nindent 8 }}" }} + serviceAccountName: {{ "{{ .Values.controllerManager.serviceAccountName }}" }} + terminationGracePeriodSeconds: {{ "{{ .Values.controllerManager.terminationGracePeriodSeconds }}" }} + {{ "{{- if and .Values.certmanager.enable (or .Values.webhook.enable .Values.metrics.enable) }}" }} + volumes: +{{- if .HasWebhooks }} + {{ "{{- if and .Values.webhook.enable .Values.certmanager.enable }}" }} + - name: webhook-cert + secret: + secretName: webhook-server-cert + {{ "{{- end }}" }} +{{- end }} + {{ "{{- if and .Values.metrics.enable .Values.certmanager.enable }}" }} + - name: metrics-certs + secret: + secretName: metrics-server-cert + {{ "{{- end }}" }} + {{ "{{- end }}" }} +` diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go new file mode 100644 index 00000000000..d735fc2bf78 --- /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.enable }}` + "`" + `}} +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/prometheus/monitor.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go new file mode 100644 index 00000000000..e40aed48c3d --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go @@ -0,0 +1,83 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package prometheus + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &Monitor{} + +// Monitor scaffolds the ServiceMonitor for Prometheus in the Helm chart +type Monitor struct { + machinery.TemplateMixin + machinery.ProjectNameMixin +} + +// SetTemplateDefaults sets the default template configuration +func (f *Monitor) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("dist", "chart", "templates", "prometheus", "monitor.yaml") + } + + f.TemplateBody = monitorTemplate + + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const monitorTemplate = `# To integrate with Prometheus. +{{ "{{- if .Values.prometheus.enable }}" }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} + name: {{ .ProjectName }}-controller-manager-metrics-monitor + namespace: {{ "{{ .Release.Namespace }}" }} +spec: + endpoints: + - path: /metrics + port: https + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + {{ "{{- if .Values.certmanager.enable }}" }} + # Apply secure TLS configuration with cert-manager + insecureSkipVerify: false + ca: + secret: + name: metrics-server-cert + key: ca.crt + cert: + secret: + name: metrics-server-cert + key: tls.crt + keySecret: + name: metrics-server-cert + key: tls.key + {{ "{{- else }}" }} + # Development/Test mode (insecure configuration) + insecureSkipVerify: true + {{ "{{- end }}" }} + selector: + matchLabels: + control-plane: controller-manager +{{ "{{- end }}" }} +` diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/service.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/service.go new file mode 100644 index 00000000000..90097fe63db --- /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.enable }}` + "`" + `}} +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..f22f71129a4 --- /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.enable }}` + "`" + `}} + +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ .ProjectName }}-mutating-webhook-configuration + namespace: {{ "{{ .Release.Namespace }}" }} + annotations: + {{` + "`" + `{{- if .Values.certmanager.enable }}` + "`" + `}} + cert-manager.io/inject-ca-from: "{{` + "`" + `{{ $.Release.Namespace }}` + "`" + `}}/serving-cert" + {{` + "`" + `{{- end }}` + "`" + `}} + labels: + {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} +webhooks: + {{` + "`" + `{{- range .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.enable }}` + "`" + `}} + 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..3615a407818 --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &HelmChart{} + +// HelmChart scaffolds a file that defines the Helm chart structure +type HelmChart struct { + machinery.TemplateMixin + machinery.ProjectNameMixin +} + +// SetTemplateDefaults implements 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..202a4a5a12e --- /dev/null +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/values.go @@ -0,0 +1,175 @@ +/* +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: + container: + 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 + {{- if .DeployImages }} + env: + {{- range $kind, $image := .DeployImages }} + {{ $kind }}_IMAGE: {{ $image }} + {{- end }} + {{- end }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + serviceAccountName: {{ .ProjectName }}-controller-manager + +# [RBAC]: To enable RBAC (Permissions) configurations +rbac: + enable: true + +# [CRDs]: To enable the CRDs +crd: + enable: true + keep: true + +# [METRICS]: Set to true to generate manifests for exporting metrics +metrics: + enable: true + +{{ if .Webhooks }} +# [WEBHOOKS]: Webhooks configuration +webhook: + enable: 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 enable a ServiceMonitor to export metrics to Prometheus set true +prometheus: + enable: false + +# [CERT-MANAGER]: To enable cert-manager injection to webhooks set true +certmanager: + enable: {{ .HasWebhooks }} + +# [NETWORK POLICIES]: To enable NetworkPolicies set true +networkPolicy: + enable: false +` diff --git a/pkg/plugins/optional/helm/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..5ce58f18a3d 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 `helm install` +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 45b5ba39bd1..009ea15e0ba 100644 --- a/test/e2e/v4/plugin_cluster_test.go +++ b/test/e2e/v4/plugin_cluster_test.go @@ -61,44 +61,54 @@ 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() }) It("should generate a runnable project", func() { 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() { 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() { + 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() { 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() { 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() { 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() { 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 By("creating manager namespace") err = kbc.CreateManagerNamespace() @@ -124,14 +134,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) + _, 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()) @@ -141,8 +151,30 @@ func Run(kbc *utils.TestContext, hasWebhook, isToUseInstaller, hasMetrics bool, ExpectWithOffset(1, err).NotTo(HaveOccurred()) } - By("validating that manager Pod/container(s) are restricted") - ExpectWithOffset(1, output).NotTo(ContainSubstring("Warning: would violate PodSecurity")) + 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 enable: false", "prometheus:\n enable: 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") + } By("Checking controllerManager and getting the name of the Pod") controllerPodName = getControllerName(kbc) @@ -423,7 +455,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 f97cc2b378a..5c169bb4017 100755 --- a/test/testdata/generate.sh +++ b/test/testdata/generate.sh @@ -121,6 +121,12 @@ function scaffold_test_project { 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/README.md b/testdata/project-v4-multigroup/README.md index be36750a352..781364a7172 100644 --- a/testdata/project-v4-multigroup/README.md +++ b/testdata/project-v4-multigroup/README.md @@ -68,7 +68,9 @@ 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 1. Build the installer for the image built and published in the registry: @@ -76,19 +78,38 @@ 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. +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 + +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/config/network-policy/allow-metrics-traffic.yaml b/testdata/project-v4-multigroup/config/network-policy/allow-metrics-traffic.yaml index 3f144fb140e..c48d5a4be7a 100644 --- a/testdata/project-v4-multigroup/config/network-policy/allow-metrics-traffic.yaml +++ b/testdata/project-v4-multigroup/config/network-policy/allow-metrics-traffic.yaml @@ -1,6 +1,6 @@ # 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. +# namespaces are able to gather data from the metrics endpoint. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: diff --git a/testdata/project-v4-with-plugins/PROJECT b/testdata/project-v4-with-plugins/PROJECT index d71a9425376..0a20eedaa16 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: diff --git a/testdata/project-v4-with-plugins/README.md b/testdata/project-v4-with-plugins/README.md index 39377250d34..9c58ab5d5ee 100644 --- a/testdata/project-v4-with-plugins/README.md +++ b/testdata/project-v4-with-plugins/README.md @@ -68,7 +68,9 @@ 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 1. Build the installer for the image built and published in the registry: @@ -76,19 +78,38 @@ 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. +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 + +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/config/network-policy/allow-metrics-traffic.yaml b/testdata/project-v4-with-plugins/config/network-policy/allow-metrics-traffic.yaml index c7a0bf9dd58..8619c9591a1 100644 --- a/testdata/project-v4-with-plugins/config/network-policy/allow-metrics-traffic.yaml +++ b/testdata/project-v4-with-plugins/config/network-policy/allow-metrics-traffic.yaml @@ -1,6 +1,6 @@ # 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. +# namespaces are able to gather data from the metrics endpoint. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: 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..a4b67d26b06 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/certmanager/certificate.yaml @@ -0,0 +1,60 @@ +{{- if .Values.certmanager.enable }} +# Self-signed Issuer +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: selfsigned-issuer + namespace: {{ .Release.Namespace }} +spec: + selfSigned: {} +{{- if .Values.webhook.enable }} +--- +# Certificate for the webhook +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} + name: serving-cert + namespace: {{ .Release.Namespace }} + labels: + {{- include "chart.labels" . | nindent 4 }} +spec: + dnsNames: + - 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 }} +{{- if and .Values.metrics.enable .Values.certmanager.enable }} +--- +# Certificate for the metrics +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} + labels: + {{- include "chart.labels" . | nindent 4 }} + name: metrics-certs + namespace: {{ .Release.Namespace }} +spec: + dnsNames: + - project-v4-with-plugins.{{ .Release.Namespace }}.svc + - project-v4-with-plugins.{{ .Release.Namespace }}.svc.cluster.local + - project-v4-with-plugins-metrics-service.{{ .Release.Namespace }}.svc + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: metrics-server-cert +{{- end }} +{{- 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..8c09ad940f4 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml @@ -0,0 +1,123 @@ +{{- if .Values.crd.enable }} +--- +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 }} + 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_memcacheds.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml new file mode 100755 index 00000000000..ceb4df9617d --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml @@ -0,0 +1,128 @@ +{{- if .Values.crd.enable }} +--- +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 }} + 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/crd/example.com.testproject.org_wordpresses.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_wordpresses.yaml new file mode 100755 index 00000000000..1381f2bf871 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_wordpresses.yaml @@ -0,0 +1,114 @@ +{{- if .Values.crd.enable }} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + {{- if .Values.certmanager.enable }} + cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/serving-cert" + {{- end }} + {{- if .Values.crd.keep }} + "helm.sh/resource-policy": keep + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.4 + name: wordpresses.example.com.testproject.org +spec: + {{- if .Values.webhook.enable }} + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: {{ .Release.Namespace }} + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 + {{- end }} + group: example.com.testproject.org + names: + kind: Wordpress + listKind: WordpressList + plural: wordpresses + singular: wordpress + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Wordpress is the Schema for the wordpresses 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: WordpressSpec defines the desired state of Wordpress. + properties: + foo: + description: Foo is an example field of Wordpress. Edit wordpress_types.go + to remove/update + type: string + type: object + status: + description: WordpressStatus defines the observed state of Wordpress. + type: object + type: object + served: true + storage: true + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: Wordpress is the Schema for the wordpresses 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: WordpressSpec defines the desired state of Wordpress. + properties: + foo: + description: Foo is an example field of Wordpress. Edit wordpress_types.go + to remove/update + type: string + type: object + status: + description: WordpressStatus defines the observed state of Wordpress. + type: object + type: object + served: true + storage: false + 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..7c72f4d8e58 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/manager/manager.yaml @@ -0,0 +1,79 @@ +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: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + {{- include "chart.labels" . | nindent 8 }} + control-plane: controller-manager + spec: + containers: + - name: manager + args: + {{- range .Values.controllerManager.container.args }} + - {{ . }} + {{- end }} + command: + - /manager + image: {{ .Values.controllerManager.container.image.repository }}:{{ .Values.controllerManager.container.image.tag }} + env: + {{- range $key, $value := .Values.controllerManager.container.env }} + - name: {{ $key }} + value: {{ $value }} + {{- end }} + livenessProbe: + {{- toYaml .Values.controllerManager.container.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.controllerManager.container.readinessProbe | nindent 12 }} + {{- if .Values.webhook.enable }} + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + {{- end }} + resources: + {{- toYaml .Values.controllerManager.container.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.controllerManager.container.securityContext | nindent 12 }} + {{- if and .Values.certmanager.enable (or .Values.webhook.enable .Values.metrics.enable) }} + volumeMounts: + {{- if and .Values.webhook.enable .Values.certmanager.enable }} + - name: webhook-cert + mountPath: /tmp/k8s-webhook-server/serving-certs + readOnly: true + {{- end }} + {{- if and .Values.metrics.enable .Values.certmanager.enable }} + - name: metrics-certs + mountPath: /tmp/k8s-metrics-server/metrics-certs + readOnly: true + {{- end }} + {{- end }} + securityContext: + {{- toYaml .Values.controllerManager.securityContext | nindent 8 }} + serviceAccountName: {{ .Values.controllerManager.serviceAccountName }} + terminationGracePeriodSeconds: {{ .Values.controllerManager.terminationGracePeriodSeconds }} + {{- if and .Values.certmanager.enable (or .Values.webhook.enable .Values.metrics.enable) }} + volumes: + {{- if and .Values.webhook.enable .Values.certmanager.enable }} + - name: webhook-cert + secret: + secretName: webhook-server-cert + {{- end }} + {{- if and .Values.metrics.enable .Values.certmanager.enable }} + - name: metrics-certs + secret: + secretName: metrics-server-cert + {{- end }} + {{- 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..b25499bf53a --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.metrics.enable }} +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..55364c4e2f8 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-metrics-traffic.yaml @@ -0,0 +1,27 @@ +{{- if .Values.networkPolicy.enable }} +# This NetworkPolicy allows ingress traffic +# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those +# namespaces are able to gather 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..5bb1aa7e8d1 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-webhook-traffic.yaml @@ -0,0 +1,27 @@ +{{- if .Values.networkPolicy.enable }} +# 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 100644 index 00000000000..4619ee55ffd --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml @@ -0,0 +1,38 @@ +# To integrate with Prometheus. +{{- if .Values.prometheus.enable }} +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 + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + {{- if .Values.certmanager.enable }} + # Apply secure TLS configuration with cert-manager + insecureSkipVerify: false + ca: + secret: + name: metrics-server-cert + key: ca.crt + cert: + secret: + name: metrics-server-cert + key: tls.crt + keySecret: + name: metrics-server-cert + key: tls.key + {{- else }} + # Development/Test mode (insecure configuration) + insecureSkipVerify: true + {{- end }} + selector: + matchLabels: + control-plane: controller-manager +{{- end }} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_admin_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_admin_role.yaml new file mode 100755 index 00000000000..c149f20460b --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_admin_role.yaml @@ -0,0 +1,28 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over example.com.testproject.org. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: busybox-admin-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes + verbs: + - '*' +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes/status + verbs: + - get +{{- 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..f0c7bd2dfbe --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_editor_role.yaml @@ -0,0 +1,34 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants permissions to create, update, and delete resources within the example.com.testproject.org. +# This role is intended for users who need to manage these resources +# but should not control RBAC or manage permissions for others. + +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..3866e7bf22a --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_viewer_role.yaml @@ -0,0 +1,30 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants read-only access to example.com.testproject.org resources. +# This role is intended for users who need visibility into these resources +# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. + +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/leader_election_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role.yaml new file mode 100755 index 00000000000..bae1f30d0a8 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role.yaml @@ -0,0 +1,41 @@ +{{- if .Values.rbac.enable }} +# 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..919673d8977 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role_binding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.enable }} +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_admin_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_admin_role.yaml new file mode 100755 index 00000000000..562928a55c2 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_admin_role.yaml @@ -0,0 +1,28 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over example.com.testproject.org. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: memcached-admin-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - memcacheds + verbs: + - '*' +- apiGroups: + - example.com.testproject.org + resources: + - memcacheds/status + verbs: + - get +{{- 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..995149a58d0 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_editor_role.yaml @@ -0,0 +1,34 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants permissions to create, update, and delete resources within the example.com.testproject.org. +# This role is intended for users who need to manage these resources +# but should not control RBAC or manage permissions for others. + +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..7741c22acb2 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_viewer_role.yaml @@ -0,0 +1,30 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants read-only access to example.com.testproject.org resources. +# This role is intended for users who need visibility into these resources +# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. + +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..196ca1041f6 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.rbac.enable .Values.metrics.enable }} +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..b5f6aa9b101 --- /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.enable .Values.metrics.enable }} +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..b45a2bd5d17 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_reader_role.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.rbac.enable .Values.metrics.enable }} +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..c139ee74b3b --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role.yaml @@ -0,0 +1,69 @@ +{{- if .Values.rbac.enable }} +--- +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 + - memcacheds + - wordpresses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes/finalizers + - memcacheds/finalizers + - wordpresses/finalizers + verbs: + - update +- apiGroups: + - example.com.testproject.org + resources: + - busyboxes/status + - memcacheds/status + - wordpresses/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..67e38e87109 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role_binding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.enable }} +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..147bfef8cae --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/service_account.yaml @@ -0,0 +1,9 @@ +{{- if .Values.rbac.enable }} +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/rbac/wordpress_admin_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_admin_role.yaml new file mode 100755 index 00000000000..5eb6793da7a --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_admin_role.yaml @@ -0,0 +1,28 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over example.com.testproject.org. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: wordpress-admin-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - wordpresses + verbs: + - '*' +- apiGroups: + - example.com.testproject.org + resources: + - wordpresses/status + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_editor_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_editor_role.yaml new file mode 100755 index 00000000000..a07e4d65591 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_editor_role.yaml @@ -0,0 +1,34 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants permissions to create, update, and delete resources within the example.com.testproject.org. +# This role is intended for users who need to manage these resources +# but should not control RBAC or manage permissions for others. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: wordpress-editor-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - wordpresses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - example.com.testproject.org + resources: + - wordpresses/status + verbs: + - get +{{- end -}} diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_viewer_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_viewer_role.yaml new file mode 100755 index 00000000000..51cde64879e --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_viewer_role.yaml @@ -0,0 +1,30 @@ +{{- if .Values.rbac.enable }} +# This rule is not used by the project project-v4-with-plugins itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants read-only access to example.com.testproject.org resources. +# This role is intended for users who need visibility into these resources +# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "chart.labels" . | nindent 4 }} + name: wordpress-viewer-role +rules: +- apiGroups: + - example.com.testproject.org + resources: + - wordpresses + verbs: + - get + - list + - watch +- apiGroups: + - example.com.testproject.org + resources: + - wordpresses/status + verbs: + - get +{{- 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..36487954e20 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/service.yaml @@ -0,0 +1,16 @@ +{{- if .Values.webhook.enable }} +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/webhooks.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhooks.yaml new file mode 100644 index 00000000000..429fd8d6cb0 --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhooks.yaml @@ -0,0 +1,97 @@ +{{- if .Values.webhook.enable }} + +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: project-v4-with-plugins-mutating-webhook-configuration + namespace: {{ .Release.Namespace }} + annotations: + {{- if .Values.certmanager.enable }} + cert-manager.io/inject-ca-from: "{{ $.Release.Namespace }}/serving-cert" + {{- end }} + labels: + {{- include "chart.labels" . | nindent 4 }} +webhooks: + {{- range .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.enable }} + 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..1cb9c14d0df --- /dev/null +++ b/testdata/project-v4-with-plugins/dist/chart/values.yaml @@ -0,0 +1,93 @@ +# [MANAGER]: Manager Deployment Configurations +controllerManager: + container: + 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 + env: + BUSYBOX_IMAGE: busybox:1.36.1 + MEMCACHED_IMAGE: memcached:1.6.26-alpine3.19 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + terminationGracePeriodSeconds: 10 + serviceAccountName: project-v4-with-plugins-controller-manager + +# [RBAC]: To enable RBAC (Permissions) configurations +rbac: + enable: true + +# [CRDs]: To enable the CRDs +crd: + enable: true + keep: true + +# [METRICS]: Set to true to generate manifests for exporting metrics +metrics: + enable: true + + +# [WEBHOOKS]: Webhooks configuration +webhook: + enable: true + services: + - 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 enable a ServiceMonitor to export metrics to Prometheus set true +prometheus: + enable: false + +# [CERT-MANAGER]: To enable cert-manager injection to webhooks set true +certmanager: + enable: true + +# [NETWORK POLICIES]: To enable NetworkPolicies set true +networkPolicy: + enable: false diff --git a/testdata/project-v4/README.md b/testdata/project-v4/README.md index 49d9168532c..28b9088287b 100644 --- a/testdata/project-v4/README.md +++ b/testdata/project-v4/README.md @@ -68,7 +68,9 @@ 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 1. Build the installer for the image built and published in the registry: @@ -76,19 +78,38 @@ 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. +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 + +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/config/network-policy/allow-metrics-traffic.yaml b/testdata/project-v4/config/network-policy/allow-metrics-traffic.yaml index d3058fee06d..8ae8843eca6 100644 --- a/testdata/project-v4/config/network-policy/allow-metrics-traffic.yaml +++ b/testdata/project-v4/config/network-policy/allow-metrics-traffic.yaml @@ -1,6 +1,6 @@ # 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. +# namespaces are able to gather data from the metrics endpoint. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: