From 8fcc6438b434b861c28dadbe3dc44d5c043bd8c6 Mon Sep 17 00:00:00 2001 From: camilamacedo86 Date: Sat, 22 Oct 2022 09:31:15 +0100 Subject: [PATCH 1/6] :sparkles: Add go/v4 base and migration guide Co-authored-by: Varsha --- cmd/main.go | 6 +- docs/book/src/SUMMARY.md | 17 +- docs/book/src/migration/legacy.md | 18 ++ .../manually_migration_guide_v2_v3.md | 0 .../{ => legacy}/migration_guide_v2tov3.md | 2 +- .../book/src/migration/{ => legacy}/v1vsv2.md | 2 +- .../book/src/migration/{ => legacy}/v2vsv3.md | 12 +- .../manually_migration_guide_gov3_to_gov4.md | 86 +++++ .../migration/migration_guide_gov3_to_gov4.md | 138 ++++++++ docs/book/src/migration/multi-group.md | 12 + docs/book/src/migration/v3-plugins.md | 11 + docs/book/src/migration/v3vsv4.md | 85 +++++ docs/book/src/plugins/available-plugins.md | 8 + docs/book/src/plugins/go-v2-plugin.md | 2 +- docs/book/src/plugins/go-v4-plugin.md | 16 +- docs/book/src/plugins/to-be-extended.md | 9 +- pkg/plugins/golang/v4/api.go | 191 ++++++++++++ pkg/plugins/golang/v4/edit.go | 65 ++++ pkg/plugins/golang/v4/init.go | 209 +++++++++++++ pkg/plugins/golang/v4/plugin.go | 65 ++++ pkg/plugins/golang/v4/scaffolds/api.go | 113 +++++++ pkg/plugins/golang/v4/scaffolds/doc.go | 18 ++ pkg/plugins/golang/v4/scaffolds/edit.go | 102 ++++++ pkg/plugins/golang/v4/scaffolds/init.go | 136 ++++++++ .../scaffolds/internal/templates/api/group.go | 80 +++++ .../scaffolds/internal/templates/api/types.go | 124 ++++++++ .../internal/templates/api/webhook.go | 156 +++++++++ .../templates/api/webhook_suitetest.go | 246 +++++++++++++++ .../templates/controllers/controller.go | 119 +++++++ .../controllers/controller_suitetest.go | 190 +++++++++++ .../internal/templates/dockerfile.go | 74 +++++ .../internal/templates/dockerignore.go | 45 +++ .../scaffolds/internal/templates/gitignore.go | 67 ++++ .../v4/scaffolds/internal/templates/gomod.go | 54 ++++ .../internal/templates/hack/boilerplate.go | 125 ++++++++ .../v4/scaffolds/internal/templates/main.go | 295 ++++++++++++++++++ .../scaffolds/internal/templates/makefile.go | 218 +++++++++++++ .../v4/scaffolds/internal/templates/readme.go | 129 ++++++++ pkg/plugins/golang/v4/scaffolds/webhook.go | 108 +++++++ pkg/plugins/golang/v4/webhook.go | 133 ++++++++ .../api/v1/webhook_suite_test.go | 4 +- .../apis/crew/v1/webhook_suite_test.go | 4 +- .../apis/ship/v1/webhook_suite_test.go | 4 +- .../apis/ship/v2alpha1/webhook_suite_test.go | 4 +- .../apis/v1/webhook_suite_test.go | 4 +- .../api/v1alpha1/webhook_suite_test.go | 4 +- .../project-v4/api/v1/webhook_suite_test.go | 4 +- 47 files changed, 3475 insertions(+), 39 deletions(-) create mode 100644 docs/book/src/migration/legacy.md rename docs/book/src/migration/{ => legacy}/manually_migration_guide_v2_v3.md (100%) rename docs/book/src/migration/{ => legacy}/migration_guide_v2tov3.md (99%) rename docs/book/src/migration/{ => legacy}/v1vsv2.md (97%) rename docs/book/src/migration/{ => legacy}/v2vsv3.md (90%) create mode 100644 docs/book/src/migration/manually_migration_guide_gov3_to_gov4.md create mode 100644 docs/book/src/migration/migration_guide_gov3_to_gov4.md create mode 100644 docs/book/src/migration/v3-plugins.md create mode 100644 docs/book/src/migration/v3vsv4.md create mode 100644 pkg/plugins/golang/v4/api.go create mode 100644 pkg/plugins/golang/v4/edit.go create mode 100644 pkg/plugins/golang/v4/init.go create mode 100644 pkg/plugins/golang/v4/plugin.go create mode 100644 pkg/plugins/golang/v4/scaffolds/api.go create mode 100644 pkg/plugins/golang/v4/scaffolds/doc.go create mode 100644 pkg/plugins/golang/v4/scaffolds/edit.go create mode 100644 pkg/plugins/golang/v4/scaffolds/init.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/main.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go create mode 100644 pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go create mode 100644 pkg/plugins/golang/v4/scaffolds/webhook.go create mode 100644 pkg/plugins/golang/v4/webhook.go diff --git a/cmd/main.go b/cmd/main.go index 7dc5d59d92..98e64696b9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,6 +34,7 @@ import ( deployimagev1alpha1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/deploy-image/v1alpha1" golangv2 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2" golangv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3" + golangv4 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4" grafanav1alpha1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/optional/grafana/v1alpha" ) @@ -44,10 +45,10 @@ func main() { kustomizecommonv1.Plugin{}, golangv3.Plugin{}, ) - // Bundle plugin which built the golang projects scaffold by Kubebuilder go/v3 with kustomize alpha-v2 + // Bundle plugin which built the golang projects scaffold by Kubebuilder go/v4 with kustomize alpha-v2 gov4Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 4, Stage: stage.Alpha}, kustomizecommonv2alpha.Plugin{}, - golangv3.Plugin{}, + golangv4.Plugin{}, ) fs := machinery.Filesystem{ @@ -64,6 +65,7 @@ func main() { cli.WithPlugins( golangv2.Plugin{}, golangv3.Plugin{}, + golangv4.Plugin{}, gov3Bundle, gov4Bundle, &kustomizecommonv1.Plugin{}, diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 6fa535f013..3a90c65b21 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -60,15 +60,20 @@ - [Migrations](./migrations.md) - - [Kubebuilder v1 vs v2](./migration/v1vsv2.md) + - [Legacy (before <= v3.0.0)](./migration/legacy.md) + - [Kubebuilder v1 vs v2](migration/legacy/v1vsv2.md) - - [Migration Guide](./migration/legacy/migration_guide_v1tov2.md) + - [Migration Guide](./migration/legacy/migration_guide_v1tov2.md) - - [Kubebuilder v2 vs v3](./migration/v2vsv3.md) - - - [Migration Guide](./migration/migration_guide_v2tov3.md) - - [Migration by updating the files](./migration/manually_migration_guide_v2_v3.md) + - [Kubebuilder v2 vs v3](migration/legacy/v2vsv3.md) + - [Migration Guide](migration/legacy/migration_guide_v2tov3.md) + - [Migration by updating the files](migration/legacy/manually_migration_guide_v2_v3.md) + - [From v3.0.0 with plugins](./migration/v3-plugins.md) + - [go/v3 vs go/v4-alpha](migration/v3vsv4.md) + + - [Migration Guide](migration/migration_guide_gov3_to_gov4.md) + - [Migration by updating the files](migration/manually_migration_guide_gov3_to_gov4.md) - [Single Group to Multi-Group](./migration/multi-group.md) --- diff --git a/docs/book/src/migration/legacy.md b/docs/book/src/migration/legacy.md new file mode 100644 index 0000000000..fc35220ec8 --- /dev/null +++ b/docs/book/src/migration/legacy.md @@ -0,0 +1,18 @@ +# Migration guides from Legacy versions < 3.0.0 + +Following the migration guides from the Legacy Kubebuilder versions up its v3x version. +Note that from v3, a new ecosystem using plugins is introduced for better maintainability, reusability and user +experience . + +For more info, see the design docs of: + +- [Extensible CLI and Scaffolding Plugins: phase 1][plugins-phase1-design-doc] +- [Extensible CLI and Scaffolding Plugins: phase 1.5][plugins-phase1-design-doc-1.5] +- [Extensible CLI and Scaffolding Plugins - Phase 2][plugins-phase2-design-doc] + +Also, you can check the [Plugins section][plugins-section]. + +[plugins-phase1-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1.md +[plugins-phase1-design-doc-1.5]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md +[plugins-phase2-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md +[plugins-section]: ./../plugins/plugins.md diff --git a/docs/book/src/migration/manually_migration_guide_v2_v3.md b/docs/book/src/migration/legacy/manually_migration_guide_v2_v3.md similarity index 100% rename from docs/book/src/migration/manually_migration_guide_v2_v3.md rename to docs/book/src/migration/legacy/manually_migration_guide_v2_v3.md diff --git a/docs/book/src/migration/migration_guide_v2tov3.md b/docs/book/src/migration/legacy/migration_guide_v2tov3.md similarity index 99% rename from docs/book/src/migration/migration_guide_v2tov3.md rename to docs/book/src/migration/legacy/migration_guide_v2tov3.md index 92f7f99928..6ebd1e0420 100644 --- a/docs/book/src/migration/migration_guide_v2tov3.md +++ b/docs/book/src/migration/legacy/migration_guide_v2tov3.md @@ -173,7 +173,7 @@ Change the image name in the Makefile if needed. Finally, we can run `make` and `make docker-build` to ensure things are working fine. -[v2vsv3]: ./v2vsv3.md +[v2vsv3]: v2vsv3.md [quick-start]: /quick-start.md#installation [controller-tools]: https://github.com/kubernetes-sigs/controller-tools/releases [controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime/releases diff --git a/docs/book/src/migration/v1vsv2.md b/docs/book/src/migration/legacy/v1vsv2.md similarity index 97% rename from docs/book/src/migration/v1vsv2.md rename to docs/book/src/migration/legacy/v1vsv2.md index f426d88b1d..20ee9a3c28 100644 --- a/docs/book/src/migration/v1vsv2.md +++ b/docs/book/src/migration/legacy/v1vsv2.md @@ -1,4 +1,4 @@ -# Kubebuilder v1 vs v2 +# Kubebuilder v1 vs v2 (Legacy v1.0.0+ to v2.0.0 Kubebuilder CLI versions) This document cover all breaking changes when migrating from v1 to v2. diff --git a/docs/book/src/migration/v2vsv3.md b/docs/book/src/migration/legacy/v2vsv3.md similarity index 90% rename from docs/book/src/migration/v2vsv3.md rename to docs/book/src/migration/legacy/v2vsv3.md index a822ff7758..0073dcceeb 100644 --- a/docs/book/src/migration/v2vsv3.md +++ b/docs/book/src/migration/legacy/v2vsv3.md @@ -1,4 +1,4 @@ -# Kubebuilder v2 vs v3 +# Kubebuilder v2 vs v3 (Legacy Kubebuilder v2.0.0+ layout to 3.0.0+) This document covers all breaking changes when migrating from v2 to v3. @@ -13,7 +13,9 @@ v3 projects use Go modules and request Go 1.18+. Dep is no longer supported for ## Kubebuilder -- Preliminary support for plugins was added. For more info see the [Extensible CLI and Scaffolding Plugins: phase 1][plugins-phase1-design-doc] and [Extensible CLI and Scaffolding Plugins: phase 1.5][plugins-phase1-design-doc-1.5] +- Preliminary support for plugins was added. For more info see the [Extensible CLI and Scaffolding Plugins: phase 1][plugins-phase1-design-doc], + the [Extensible CLI and Scaffolding Plugins: phase 1.5][plugins-phase1-design-doc-1.5] and the [Extensible CLI and Scaffolding Plugins - Phase 2][plugins-phase2-design-doc] + design docs. Also, you can check the [Plugins section][plugins-section]. - The `PROJECT` file now has a new layout. It stores more information about what resources are in use, to better enable plugins to make useful decisions when scaffolding. @@ -89,8 +91,10 @@ You will check that you can still using the previous layout by using the `go/v2` [plugins-phase1-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1.md [plugins-phase1-design-doc-1.5]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md +[plugins-phase2-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md +[plugins-section]: ./../../plugins/plugins.md [manually-upgrade]: manually_migration_guide_v2_v3.md -[component-config-tutorial]: ../component-config-tutorial/tutorial.md +[component-config-tutorial]: ../../component-config-tutorial/tutorial.md [issue-1893]: https://github.com/kubernetes-sigs/kubebuilder/issues/1839 [migration-guide-v2-to-v3]: migration_guide_v2tov3.md [healthz-ping]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/healthz#CheckHandler @@ -102,5 +106,5 @@ You will check that you can still using the previous layout by using the `go/v2` [cert-manager-docs]: https://cert-manager.io/docs/installation/upgrading/ [kb-releases]: https://github.com/kubernetes-sigs/kubebuilder/releases [kube-rbac-proxy]: https://github.com/brancz/kube-rbac-proxy/releases -[basic-project-doc]: ../cronjob-tutorial/basic-project.md +[basic-project-doc]: ../../cronjob-tutorial/basic-project.md [kustomize]: https://github.com/kubernetes-sigs/kustomize \ No newline at end of file diff --git a/docs/book/src/migration/manually_migration_guide_gov3_to_gov4.md b/docs/book/src/migration/manually_migration_guide_gov3_to_gov4.md new file mode 100644 index 0000000000..ce7c48f103 --- /dev/null +++ b/docs/book/src/migration/manually_migration_guide_gov3_to_gov4.md @@ -0,0 +1,86 @@ +# Migration from go/v3 to go/v4-alpha by updating the files manually + +Make sure you understand the [differences between Kubebuilder go/v3 and go/v4-alpha][v3vsv4] +before continuing. + +Please ensure you have followed the [installation guide][quick-start] +to install the required components. + +The following guide describes the manual steps required to upgrade your PROJECT config file to began to use go/v4-alpha. + +This way is more complex, susceptible to errors, and success cannot be assured. Also, by following these steps you will not get the improvements and bug fixes in the default generated project files. + +Usually it is suggested to do it manually if you have customized your project and deviated too much from the proposed scaffold. Before continuing, ensure that you understand the note about [project customizations][project-customizations]. Note that you might need to spend more effort to do this process manually than to organize your project customizations. The proposed layout will keep your project maintainable and upgradable with less effort in the future. + +The recommended upgrade approach is to follow the [Migration Guide go/v3 to go/v4-alpha][migration-guide-gov3-to-gov4] instead. + +## Migration from project config version "go/v3" to "go/v4" + +Update `PROJECT` file layout which stores the information about what resources are in use, to better enable plugins to make useful decisions when scaffolding. + +Furthermore, the `PROJECT` file itself is now versioned. The `version` field corresponds to the version of the `PROJECT` file itself, while the `layout` field indicates the scaffolding and the primary plugin version in use. + +Update: + +```yaml +layout: +- go.kubebuilder.io/v3 +``` + +With: + +```yaml +layout: +- go.kubebuilder.io/v4-alpha + +``` + +### Steps to migrate + +- Update the `main.go` with the changes which can be found in the samples under testdata for the release tag used. (see for example `testdata/project-v4/main.go`). +- Update the Makefile with the changes which can be found in the samples under testdata for the release tag used. (see for example `testdata/project-v4/Makefile`) +- Update the `go.mod` with the changes which can be found in the samples under `testdata` for the release tag used. (see for example `testdata/project-v4/go.mod`). Then, run +`go mod tidy` to ensure that you get the latest dependencies and your Golang code has no breaking changes. +- Update the manifest under `config/` directory with all changes performed in the default scaffold done with `go/v4-alpha` plugin. (see for example `testdata/project-v4/config/`) to get all changes in the +default scaffolds to be applied on your project +- Replace the import `admissionv1beta1 "k8s.io/api/admission/v1beta1"` with `admissionv1 "k8s.io/api/admission/v1"` in the webhook test files + + + +### Verification + +In the steps above, you updated your project manually with the goal of ensuring that it follows +the changes in the layout introduced with the `go/v4-alpha` plugin that update the scaffolds. + +There is no option to verify that you properly updated the `PROJECT` file of your project. +The best way to ensure that everything is updated correctly, would be to initialize a project using the `go/v4-alpha` plugin, +(ie) using `kubebuilder init --domain tutorial.kubebuilder.io plugins=go/v4-alpha` and generating the same API(s), +controller(s), and webhook(s) in order to compare the generated configuration with the manually changed configuration. + +Also, after all updates you would run the following commands: + +- `make manifests` (to re-generate the files using the latest version of the contrller-gen after you update the Makefile) +- `make all` (to ensure that you are able to build and perform all operations) + +[v3vsv4]: v3vsv4.md +[quick-start]: ./../quick-start.md#installation +[migration-guide-gov3-to-gov4]: migration_guide_gov3_to_gov4.md +[controller-tools]: https://github.com/kubernetes-sigs/controller-tools/releases +[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime/releases +[multi-group]: multi-group.md + diff --git a/docs/book/src/migration/migration_guide_gov3_to_gov4.md b/docs/book/src/migration/migration_guide_gov3_to_gov4.md new file mode 100644 index 0000000000..c7741e8b39 --- /dev/null +++ b/docs/book/src/migration/migration_guide_gov3_to_gov4.md @@ -0,0 +1,138 @@ +# Migration from go/v3 to go/v4-alpha + +Make sure you understand the [differences between Kubebuilder go/v3 and go/v4-alpha][v3vsv4] +before continuing. + +Please ensure you have followed the [installation guide][quick-start] +to install the required components. + +The recommended way to migrate a go/v3 project is to create a new go/v4-alpha project and +copy over the API and the reconciliation code. The conversion will end up with a +project that looks like a native go/v4-alpha project layout (latest version). + +However, in some cases, it's possible to do an in-place upgrade (i.e. reuse the go/v3 project layout, upgrading +the PROJECT file, and scaffolds manually). For further information see [Migration from go/v3 to go/v4-alpha by updating the files manually][manually-upgrade] + +## Initialize a go/v4-alpha Project + + + +Create a new directory with the name of your project. Note that +this name is used in the scaffolds to create the name of your manager Pod and of the Namespace where the Manager is deployed by default. + +```bash +$ mkdir migration-project-name +$ cd migration-project-name +``` + +Now, we need to initialize a go/v4-alpha project. Before we do that, we'll need +to initialize a new go module if we're not on the `GOPATH`. While technically this is +not needed inside `GOPATH`, it is still recommended. + +```bash +go mod init tutorial.kubebuilder.io/migration-project +``` + + + +Now, we can finish initializing the project with kubebuilder. + +```bash +kubebuilder init --domain tutorial.kubebuilder.io plugins=go/v4-alpha +``` + + + +## Migrate APIs and Controllers + +Next, we'll re-scaffold out the API types and controllers. + + + +```bash +kubebuilder create api --group batch --version v1 --kind CronJob +``` + +### Migrate the APIs + + + +Now, let's copy the API definition from `api/v1/_types.go` in our old project to the new one. + +These files have not been modified by the new plugin, so you should be able to replace your freshly scaffolded files by your old one. There may be some cosmetic changes. So you can choose to only copy the types themselves. + +### Migrate the Controllers + +Now, let's migrate the controller code from `controllers/cronjob_controller.go` in our old project to the new one. + +## Migrate the Webhooks + + + +Now let's scaffold the webhooks for our CRD (CronJob). We'll need to run the +following command with the `--defaulting` and `--programmatic-validation` flags +(since our test project uses defaulting and validating webhooks): + +```bash +kubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --programmatic-validation +``` + +Now, let's copy the webhook definition from `api/v1/_webhook.go` from our old project to the new one. + +## Others + +If there are any manual updates in `main.go` in v3, we need to port the changes to the new `main.go`. We’ll also need to ensure all of needed controller-runtime `schemes` have been registered. + +If there are additional manifests added under config directory, port them as well. Please, be aware that +the new version go/v4-alpha uses Kustomize v4x and no longer Kustomize v3. Therefore, if added customized +implementations in the config you need to ensure that them can work with Kustomize v4 and/if not +update/upgrade any breaking change that you might face. + +Change the image name in the Makefile if needed. + +## Verification + +Finally, we can run `make` and `make docker-build` to ensure things are working +fine. + +[v3vsv4]: v3vsv4.md +[quick-start]: ./../quick-start.md#installation +[controller-tools]: https://github.com/kubernetes-sigs/controller-tools/releases +[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime/releases +[multi-group]: multi-group.md +[manually-upgrade]: manually_migration_guide_gov3_to_gov4.md diff --git a/docs/book/src/migration/multi-group.md b/docs/book/src/migration/multi-group.md index 605d532712..40b52a4906 100644 --- a/docs/book/src/migration/multi-group.md +++ b/docs/book/src/migration/multi-group.md @@ -20,6 +20,16 @@ to modify the default project structure to support it. Let's migrate the [CronJob example][cronjob-tutorial]. + + Generally, we use the prefix for the API group as the directory name. We can check `api/v1/groupversion_info.go` to find that out: @@ -46,6 +56,8 @@ mkdir controllers/batch mv controllers/* controllers/batch/ ``` + + Next, we'll need to update all the references to the old package name. For CronJob, that'll be `main.go` and `controllers/batch/cronjob_controller.go`. diff --git a/docs/book/src/migration/v3-plugins.md b/docs/book/src/migration/v3-plugins.md new file mode 100644 index 0000000000..cefc5dabd3 --- /dev/null +++ b/docs/book/src/migration/v3-plugins.md @@ -0,0 +1,11 @@ +# V3 - Plugins Layout Migration Guides + +Following the migration guides from the plugins versions. Note that the plugins ecosystem +was introduced with Kubebuilder v3.0.0 release where the go/v3 version is the default layout +since `28 Apr 2021`. + +Therefore, you can check here how to migrate the projects built from Kubebuilder 3.x with +the plugin go/v3 to the latest. + + + diff --git a/docs/book/src/migration/v3vsv4.md b/docs/book/src/migration/v3vsv4.md new file mode 100644 index 0000000000..7d4e6f6f38 --- /dev/null +++ b/docs/book/src/migration/v3vsv4.md @@ -0,0 +1,85 @@ +# go/v3 vs go/v4-alpha + +This document covers all breaking changes when migrating from projects built using the plugin go/v3 (default for any scaffold done since `28 Apr 2021`) to the next alpha version of the Golang plugin `go/v4-alpha`. + +The details of all changes (breaking or otherwise) can be found in: +- [controller-runtime][controller-runtime] +- [controller-tools][controller-tools] +- [kustomize][kustomize-release] +- [kb-releases][kb-releases] release notes. + +## Common changes + +- `go/v4-alpha` projects use Kustomize v4x (instead of v3x) +- note that some manifests under `config/` directory have been changed in order to no longer use the deprecated Kustomize features +such as env vars. +- adds support for Apple Silicon M1 (darwin/arm64) +- remove support to CRD/WebHooks Kubernetes API v1beta1 version which are no longer supported since k8s 1.22 +- no longer scaffold webhook test files with `"k8s.io/api/admission/v1beta1"` the k8s API which is no longer served since k8s `1.25`. By default +webhooks test files are scaffolding using `"k8s.io/api/admission/v1"` which is support from k8s `1.20` +- no longer provide backwards compatible support with k8s versions < `1.16` + + + +## TL;DR of the New `go/v4-alpha` Plugin + +***More details on this can be found at [here][kb-releases], but for the highlights, check below*** + + + + + +## Migrating to Kubebuilder go/v4-alpha + +If you want to upgrade your scaffolding to use the latest and greatest features then, follow the guide +which will cover the steps in the most straightforward way to allow you to upgrade your project to get all +latest changes and improvements. + + + +- [Migration Guide go/v3 to go/v4][migration-guide-gov3-to-gov4] **(Recommended)** + +### By updating the files manually + +If you want to use the latest version of Kubebuilder CLI without changing your scaffolding then, check the following guide which will describe the steps to be performed manually to upgrade only your PROJECT version and start using the plugins versions. + +This way is more complex, susceptible to errors, and success cannot be assured. Also, by following these steps you will not get the improvements and bug fixes in the default generated project files. + +- [Migrating to go/v4-alpha by updating the files manually][manually-upgrade] + +[plugins-phase1-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1.md +[plugins-phase1-design-doc-1.5]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md +[plugins-phase2-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md +[plugins-section]: ./../plugins/plugins.md +[kustomize]: https://github.com/kubernetes-sigs/kustomize/releases/tag/kustomize%2Fv4.0.0 +[go/v4-doc]: ./../plugins/go-v4-plugin.md +[migration-guide-gov3-to-gov4]: migration_guide_gov3_to_gov4.md +[manually-upgrade]: manually_migration_guide_gov3_to_gov4.md diff --git a/docs/book/src/plugins/available-plugins.md b/docs/book/src/plugins/available-plugins.md index 62d39e0d03..3f723d0527 100644 --- a/docs/book/src/plugins/available-plugins.md +++ b/docs/book/src/plugins/available-plugins.md @@ -5,3 +5,11 @@ This section describes the plugins supported and shipped in with the Kubebuilder {{#include to-scaffold-project.md }} {{#include to-add-optional-features.md }} {{#include to-be-extended.md }} + + \ No newline at end of file diff --git a/docs/book/src/plugins/go-v2-plugin.md b/docs/book/src/plugins/go-v2-plugin.md index 63b8c27841..3c2d3f18a2 100644 --- a/docs/book/src/plugins/go-v2-plugin.md +++ b/docs/book/src/plugins/go-v2-plugin.md @@ -13,7 +13,7 @@ versions < `3.0.0`. The recommended way to migrate a `v2` project is to create a new `v3` project and copy over the API and the reconciliation code. The conversion will end up with a project that looks like a native `v3` project. -For further information check the [Migration guide](./../migration/manually_migration_guide_v2_v3.md) +For further information check the [Migration guide](../migration/legacy/manually_migration_guide_v2_v3.md) diff --git a/docs/book/src/plugins/go-v4-plugin.md b/docs/book/src/plugins/go-v4-plugin.md index 235b5b13c2..471c2447c0 100644 --- a/docs/book/src/plugins/go-v4-plugin.md +++ b/docs/book/src/plugins/go-v4-plugin.md @@ -1,7 +1,7 @@ # go/v4-alpha (go.kubebuilder.io/v4-alpha) -Kubebuilder will scaffold using the go/v4-alpha plugin only if specified when initializing the project. -This plugin is a composition of the plugins ` kustomize.common.kubebuilder.io/v2-alpha` and `base.go.kubebuilder.io/v3`. +Kubebuilder will scaffold using the `go/v4-alpha` plugin only if specified when initializing the project. +This plugin is a composition of the plugins ` kustomize.common.kubebuilder.io/v2-alpha` and `base.go.kubebuilder.io/v4`. It scaffolds a project template that helps in constructing sets of [controllers][controller-runtime]. It scaffolds boilerplate code to create and design controllers. @@ -21,6 +21,18 @@ under the [testdata][testdata] directory on the root directory of the Kubebuilde - If you are looking to experiment with the future default scaffold that will be provided by Kubebuilder CLI - If your local environment is Apple Silicon (`darwin/arm64`) - If you are looking to use [kubernetes-sigs/kustomize][kustomize] v4 +- If you are looking to have your project update with the latest version available +- if you are not targeting k8s versions < `1.16` and `1.20` if you are using webhooks +- If you are looking for to work on with scaffolds which are compatible with k8s `1.25+` + + ## How to use it ? diff --git a/docs/book/src/plugins/to-be-extended.md b/docs/book/src/plugins/to-be-extended.md index 473db60e84..754c43613b 100644 --- a/docs/book/src/plugins/to-be-extended.md +++ b/docs/book/src/plugins/to-be-extended.md @@ -18,14 +18,7 @@ helpers on top, such as [Operator-SDK][sdk] does to add their features to integr | [kustomize.common.kubebuilder.io/v1](kustomize-v1.md) | `kustomize/v1` | Responsible for scaffold all manifests to configure the projects with [kustomize(v3)][kustomize]. (create and update the `config/` directory). This plugin is used in the composition to create the plugin (`go/v3`). | | [kustomize.common.kubebuilder.io/v2-alpha](kustomize-v2-alpha.md) | `kustomize/v2-alpha` | It has the same purpose of `kustomize/v1`. However, it works with [kustomize][kustomize] version `v4` and addresses the required changes for future kustomize configurations. It will probably be used with the future `go/v4-alpha` plugin. | | `base.go.kubebuilder.io/v3` | `base/v3` | Responsible for scaffold all files which specific requires Golang. This plugin is used in the composition to create the plugin (`go/v3`) | - - +| `base.go.kubebuilder.io/v4-alpha` | `base/v3-alpha` | Responsible for scaffolding all files which specifically requires Golang. This plugin is used in the composition to create the plugin (`go/v4-alpha`) | [create-plugins]: creating-plugins.md [kubebuilder-declarative-pattern]: https://github.com/kubernetes-sigs/kubebuilder-declarative-pattern diff --git a/pkg/plugins/golang/v4/api.go b/pkg/plugins/golang/v4/api.go new file mode 100644 index 0000000000..86720b20ea --- /dev/null +++ b/pkg/plugins/golang/v4/api.go @@ -0,0 +1,191 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v4 + +import ( + "bufio" + "errors" + "fmt" + "os" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" +) + +const ( + // defaultCRDVersion is the default CRD API version to scaffold. + defaultCRDVersion = "v1" +) + +// DefaultMainPath is default file path of main.go +const DefaultMainPath = "main.go" + +var _ plugin.CreateAPISubcommand = &createAPISubcommand{} + +type createAPISubcommand struct { + config config.Config + + options *goPlugin.Options + + resource *resource.Resource + + // Check if we have to scaffold resource and/or controller + resourceFlag *pflag.Flag + controllerFlag *pflag.Flag + + // force indicates that the resource should be created even if it already exists + force bool + + // runMake indicates whether to run make or not after scaffolding APIs + runMake bool +} + +func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Scaffold a Kubernetes API by writing a Resource definition and/or a Controller. + +If information about whether the resource and controller should be scaffolded +was not explicitly provided, it will prompt the user if they should be. + +After the scaffold is written, the dependencies will be updated and +make generate will be run. +` + subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate + %[1]s create api --group ship --version v1beta1 --kind Frigate + + # Edit the API Scheme + nano api/v1beta1/frigate_types.go + + # Edit the Controller + nano controllers/frigate/frigate_controller.go + + # Edit the Controller Test + nano controllers/frigate/frigate_controller_test.go + + # Generate the manifests + make manifests + + # Install CRDs into the Kubernetes cluster using kubectl apply + make install + + # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config + make run +`, cliMeta.CommandName) +} + +func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { + fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files") + + fs.BoolVar(&p.force, "force", false, + "attempt to create resource even if it already exists") + + p.options = &goPlugin.Options{} + + fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") + + fs.BoolVar(&p.options.DoAPI, "resource", true, + "if set, generate the resource without prompting the user") + p.resourceFlag = fs.Lookup("resource") + fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced") + + fs.BoolVar(&p.options.DoController, "controller", true, + "if set, generate the controller without prompting the user") + p.controllerFlag = fs.Lookup("controller") +} + +func (p *createAPISubcommand) InjectConfig(c config.Config) error { + p.config = c + // go/v4 no longer supports v1beta1 option + p.options.CRDVersion = defaultCRDVersion + return nil +} + +func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + // TODO: re-evaluate whether y/n input still makes sense. We should probably always + // scaffold the resource and controller. + // Ask for API and Controller if not specified + reader := bufio.NewReader(os.Stdin) + if !p.resourceFlag.Changed { + fmt.Println("Create Resource [y/n]") + p.options.DoAPI = util.YesNo(reader) + } + if !p.controllerFlag.Changed { + fmt.Println("Create Controller [y/n]") + p.options.DoController = util.YesNo(reader) + } + + p.options.UpdateResource(p.resource, p.config) + + if err := p.resource.Validate(); err != nil { + return err + } + + // In case we want to scaffold a resource API we need to do some checks + if p.options.DoAPI { + // Check that resource doesn't have the API scaffolded or flag force was set + if r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force { + return errors.New("API resource already exists") + } + + // Check that the provided group can be added to the project + if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) { + return fmt.Errorf("multiple groups are not allowed by default, " + + "to enable multi-group visit https://kubebuilder.io/migration/multi-group.html") + } + } + + return nil +} + +func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error { + // check if main.go is present in the root directory + if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) { + return fmt.Errorf("%s file should present in the root directory", DefaultMainPath) + } + + return nil +} + +func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() +} + +func (p *createAPISubcommand) PostScaffold() error { + err := util.RunCmd("Update dependencies", "go", "mod", "tidy") + if err != nil { + return err + } + if p.runMake && p.resource.HasAPI() { + err = util.RunCmd("Running make", "make", "generate") + if err != nil { + return err + } + fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n") + } + + return nil +} diff --git a/pkg/plugins/golang/v4/edit.go b/pkg/plugins/golang/v4/edit.go new file mode 100644 index 0000000000..438c016815 --- /dev/null +++ b/pkg/plugins/golang/v4/edit.go @@ -0,0 +1,65 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v4 + +import ( + "fmt" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" +) + +var _ plugin.EditSubcommand = &editSubcommand{} + +type editSubcommand struct { + config config.Config + + multigroup bool +} + +func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `This command will edit the project configuration. +Features supported: + - Toggle between single or multi group projects. +` + subcmdMeta.Examples = fmt.Sprintf(` # Enable the multigroup layout + %[1]s edit --multigroup + + # Disable the multigroup layout + %[1]s edit --multigroup=false +`, cliMeta.CommandName) +} + +func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) { + fs.BoolVar(&p.multigroup, "multigroup", false, "enable or disable multigroup layout") +} + +func (p *editSubcommand) InjectConfig(c config.Config) error { + p.config = c + + return nil +} + +func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() +} diff --git a/pkg/plugins/golang/v4/init.go b/pkg/plugins/golang/v4/init.go new file mode 100644 index 0000000000..21927ec0fe --- /dev/null +++ b/pkg/plugins/golang/v4/init.go @@ -0,0 +1,209 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v4 + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "unicode" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" +) + +// Variables and function to check Go version requirements. +var ( + goVerMin = golang.MustParse("go1.19.0") + goVerMax = golang.MustParse("go2.0alpha1") +) + +var _ plugin.InitSubcommand = &initSubcommand{} + +type initSubcommand struct { + config config.Config + // For help text. + commandName string + + // boilerplate options + license string + owner string + + // go config options + repo string + + // flags + fetchDeps bool + skipGoVersionCheck bool +} + +func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + p.commandName = cliMeta.CommandName + + subcmdMeta.Description = `Initialize a new project including the following files: + - a "go.mod" with project dependencies + - a "PROJECT" file that stores project configuration + - a "Makefile" with several useful make targets for the project + - several YAML files for project deployment under the "config" directory + - a "main.go" file that creates the manager that will run the project controllers +` + subcmdMeta.Examples = fmt.Sprintf(` # Initialize a new project with your domain and name in copyright + %[1]s init --plugins go/v3 --domain example.org --owner "Your name" + + # Initialize a new project defining a specific project version + %[1]s init --plugins go/v3 --project-version 3 +`, cliMeta.CommandName) +} + +func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { + fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check", + false, "if specified, skip checking the Go version") + + // dependency args + fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "ensure dependencies are downloaded") + + // boilerplate args + fs.StringVar(&p.license, "license", "apache2", + "license to use to boilerplate, may be one of 'apache2', 'none'") + fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright") + + // project args + fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+ + "defaults to the go package of the current working directory.") +} + +func (p *initSubcommand) InjectConfig(c config.Config) error { + p.config = c + + // Try to guess repository if flag is not set. + if p.repo == "" { + repoPath, err := golang.FindCurrentRepo() + if err != nil { + return fmt.Errorf("error finding current repository: %v", err) + } + p.repo = repoPath + } + + return p.config.SetRepository(p.repo) +} + +func (p *initSubcommand) PreScaffold(machinery.Filesystem) error { + // Ensure Go version is in the allowed range if check not turned off. + if !p.skipGoVersionCheck { + if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil { + return err + } + } + + // Check if the current directory has not files or directories which does not allow to init the project + return checkDir() +} + +func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner) + scaffolder.InjectFS(fs) + err := scaffolder.Scaffold() + if err != nil { + return err + } + + if !p.fetchDeps { + fmt.Println("Skipping fetching dependencies.") + return nil + } + + // Ensure that we are pinning controller-runtime version + // xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997 + err = util.RunCmd("Get controller runtime", "go", "get", + "sigs.k8s.io/controller-runtime@"+scaffolds.ControllerRuntimeVersion) + if err != nil { + return err + } + + return nil +} + +func (p *initSubcommand) PostScaffold() error { + err := util.RunCmd("Update dependencies", "go", "mod", "tidy") + if err != nil { + return err + } + + fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName) + return nil +} + +// checkDir will return error if the current directory has files which are not allowed. +// Note that, it is expected that the directory to scaffold the project is cleaned. +// Otherwise, it might face issues to do the scaffold. +func checkDir() error { + err := filepath.Walk(".", + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // Allow directory trees starting with '.' + if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." { + return filepath.SkipDir + } + // Allow files starting with '.' + if strings.HasPrefix(info.Name(), ".") { + return nil + } + // Allow files ending with '.md' extension + if strings.HasSuffix(info.Name(), ".md") && !info.IsDir() { + return nil + } + // Allow capitalized files except PROJECT + isCapitalized := true + for _, l := range info.Name() { + if !unicode.IsUpper(l) { + isCapitalized = false + break + } + } + if isCapitalized && info.Name() != "PROJECT" { + return nil + } + // Allow files in the following list + allowedFiles := []string{ + "go.mod", // user might run `go mod init` instead of providing the `--flag` at init + "go.sum", // auto-generated file related to go.mod + } + for _, allowedFile := range allowedFiles { + if info.Name() == allowedFile { + return nil + } + } + // Do not allow any other file + return fmt.Errorf( + "target directory is not empty (only %s, files and directories with the prefix \".\", "+ + "files with the suffix \".md\" or capitalized files name are allowed); "+ + "found existing file %q", strings.Join(allowedFiles, ", "), path) + }) + if err != nil { + return err + } + return nil +} diff --git a/pkg/plugins/golang/v4/plugin.go b/pkg/plugins/golang/v4/plugin.go new file mode 100644 index 0000000000..03a604143b --- /dev/null +++ b/pkg/plugins/golang/v4/plugin.go @@ -0,0 +1,65 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v4 + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" +) + +const pluginName = "base." + golang.DefaultNameQualifier + +var ( + pluginVersion = plugin.Version{Number: 4, Stage: stage.Alpha} + supportedProjectVersions = []config.Version{cfgv3.Version} +) + +var _ plugin.Full = Plugin{} + +// Plugin implements the plugin.Full interface +type Plugin struct { + initSubcommand + createAPISubcommand + createWebhookSubcommand + editSubcommand +} + +// Name returns the name of the plugin +func (Plugin) Name() string { return pluginName } + +// Version returns the version of the plugin +func (Plugin) Version() plugin.Version { return pluginVersion } + +// SupportedProjectVersions returns an array with all project versions supported by the plugin +func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions } + +// GetInitSubcommand will return the subcommand which is responsible for initializing and common scaffolding +func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand } + +// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis +func (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand } + +// GetCreateWebhookSubcommand will return the subcommand which is responsible for scaffolding webhooks +func (p Plugin) GetCreateWebhookSubcommand() plugin.CreateWebhookSubcommand { + return &p.createWebhookSubcommand +} + +// GetEditSubcommand will return the subcommand which is responsible for editing the scaffold of the project +func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand } diff --git a/pkg/plugins/golang/v4/scaffolds/api.go b/pkg/plugins/golang/v4/scaffolds/api.go new file mode 100644 index 0000000000..69a5a02748 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/api.go @@ -0,0 +1,113 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scaffolds + +import ( + "fmt" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/api" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/hack" +) + +var _ plugins.Scaffolder = &apiScaffolder{} + +// apiScaffolder contains configuration for generating scaffolding for Go type +// representing the API and controller that implements the behavior for the API. +type apiScaffolder struct { + config config.Config + resource resource.Resource + + // fs is the filesystem that will be used by the scaffolder + fs machinery.Filesystem + + // force indicates whether to scaffold controller files even if it exists or not + force bool +} + +// NewAPIScaffolder returns a new Scaffolder for API/controller creation operations +func NewAPIScaffolder(config config.Config, res resource.Resource, force bool) plugins.Scaffolder { + return &apiScaffolder{ + config: config, + resource: res, + force: force, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder +func (s *apiScaffolder) Scaffold() error { + fmt.Println("Writing scaffold for you to edit...") + + // Load the boilerplate + boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) + if err != nil { + return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err) + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), + ) + + // Keep track of these values before the update + doAPI := s.resource.HasAPI() + doController := s.resource.HasController() + + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } + + if doAPI { + if err := scaffold.Execute( + &api.Types{Force: s.force}, + &api.Group{}, + ); err != nil { + return fmt.Errorf("error scaffolding APIs: %v", err) + } + } + + if doController { + if err := scaffold.Execute( + &controllers.SuiteTest{Force: s.force}, + &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, + ); err != nil { + return fmt.Errorf("error scaffolding controller: %v", err) + } + } + + if err := scaffold.Execute( + &templates.MainUpdater{WireResource: doAPI, WireController: doController}, + ); err != nil { + return fmt.Errorf("error updating main.go: %v", err) + } + + return nil +} diff --git a/pkg/plugins/golang/v4/scaffolds/doc.go b/pkg/plugins/golang/v4/scaffolds/doc.go new file mode 100644 index 0000000000..81757e9692 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package scaffolds contains libraries for scaffolding code to use with controller-runtime +package scaffolds diff --git a/pkg/plugins/golang/v4/scaffolds/edit.go b/pkg/plugins/golang/v4/scaffolds/edit.go new file mode 100644 index 0000000000..1fe5e6505e --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/edit.go @@ -0,0 +1,102 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scaffolds + +import ( + "fmt" + "strings" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" +) + +var _ plugins.Scaffolder = &editScaffolder{} + +type editScaffolder struct { + config config.Config + multigroup bool + + // fs is the filesystem that will be used by the scaffolder + fs machinery.Filesystem +} + +// NewEditScaffolder returns a new Scaffolder for configuration edit operations +func NewEditScaffolder(config config.Config, multigroup bool) plugins.Scaffolder { + return &editScaffolder{ + config: config, + multigroup: multigroup, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *editScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder +func (s *editScaffolder) Scaffold() error { + filename := "Dockerfile" + bs, err := afero.ReadFile(s.fs.FS, filename) + if err != nil { + return err + } + str := string(bs) + + // update dockerfile + if s.multigroup { + str, err = ensureExistAndReplace( + str, + "COPY api/ api/", + `COPY apis/ apis/`) + + } else { + str, err = ensureExistAndReplace( + str, + "COPY apis/ apis/", + `COPY api/ api/`) + } + + // Ignore the error encountered, if the file is already in desired format. + if err != nil && s.multigroup != s.config.IsMultiGroup() { + return err + } + + if s.multigroup { + _ = s.config.SetMultiGroup() + } else { + _ = s.config.ClearMultiGroup() + } + + // Check if the str is not empty, because when the file is already in desired format it will return empty string + // because there is nothing to replace. + if str != "" { + // TODO: instead of writing it directly, we should use the scaffolding machinery for consistency + return afero.WriteFile(s.fs.FS, filename, []byte(str), 0644) + } + + return nil +} + +func ensureExistAndReplace(input, match, replace string) (string, error) { + if !strings.Contains(input, match) { + return "", fmt.Errorf("can't find %q", match) + } + return strings.Replace(input, match, replace, -1), nil +} diff --git a/pkg/plugins/golang/v4/scaffolds/init.go b/pkg/plugins/golang/v4/scaffolds/init.go new file mode 100644 index 0000000000..37a220e899 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/init.go @@ -0,0 +1,136 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scaffolds + +import ( + "fmt" + + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" + kustomizecommonv1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v1" + kustomizecommonv2alpha "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v2-alpha" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/hack" +) + +const ( + // ControllerRuntimeVersion is the kubernetes-sigs/controller-runtime version to be used in the project + ControllerRuntimeVersion = "v0.13.0" + // ControllerToolsVersion is the kubernetes-sigs/controller-tools version to be used in the project + ControllerToolsVersion = "v0.10.0" + + imageName = "controller:latest" +) + +var _ plugins.Scaffolder = &initScaffolder{} + +var kustomizeVersion string + +type initScaffolder struct { + config config.Config + boilerplatePath string + license string + owner string + + // fs is the filesystem that will be used by the scaffolder + fs machinery.Filesystem +} + +// NewInitScaffolder returns a new Scaffolder for project initialization operations +func NewInitScaffolder(config config.Config, license, owner string) plugins.Scaffolder { + return &initScaffolder{ + config: config, + boilerplatePath: hack.DefaultBoilerplatePath, + license: license, + owner: owner, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *initScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder +func (s *initScaffolder) Scaffold() error { + fmt.Println("Writing scaffold for you to edit...") + + // Initialize the machinery.Scaffold that will write the boilerplate file to disk + // The boilerplate file needs to be scaffolded as a separate step as it is going to + // be used by the rest of the files, even those scaffolded in this command call. + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + + bpFile := &hack.Boilerplate{ + License: s.license, + Owner: s.owner, + } + bpFile.Path = s.boilerplatePath + if err := scaffold.Execute(bpFile); err != nil { + return err + } + + boilerplate, err := afero.ReadFile(s.fs.FS, s.boilerplatePath) + if err != nil { + return err + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + ) + + // If the KustomizeV2 was used to do the scaffold then + // we need to ensure that we use its supported Kustomize Version + // in order to support it + kustomizeVersion = kustomizecommonv1.KustomizeVersion + kustomizev2 := kustomizecommonv2alpha.Plugin{} + gov4alpha := "go.kubebuilder.io/v4-alpha" + pluginKeyForKustomizeV2 := plugin.KeyFor(kustomizev2) + + for _, pluginKey := range s.config.GetPluginChain() { + if pluginKey == pluginKeyForKustomizeV2 || pluginKey == gov4alpha { + kustomizeVersion = kustomizecommonv2alpha.KustomizeVersion + break + } + } + + return scaffold.Execute( + &templates.Main{}, + &templates.GoMod{ + ControllerRuntimeVersion: ControllerRuntimeVersion, + }, + &templates.GitIgnore{}, + &templates.Makefile{ + Image: imageName, + BoilerplatePath: s.boilerplatePath, + ControllerToolsVersion: ControllerToolsVersion, + KustomizeVersion: kustomizeVersion, + ControllerRuntimeVersion: ControllerRuntimeVersion, + }, + &templates.Dockerfile{}, + &templates.DockerIgnore{}, + &templates.Readme{}, + ) +} diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go new file mode 100644 index 0000000000..c1acc9cfc6 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go @@ -0,0 +1,80 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Group{} + +// Group scaffolds the file that defines the registration methods for a certain group and version +type Group struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin +} + +// SetTemplateDefaults implements file.Template +func (f *Group) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("apis", "%[group]", "%[version]", "groupversion_info.go") + } else { + f.Path = filepath.Join("apis", "%[version]", "groupversion_info.go") + } + } else { + f.Path = filepath.Join("api", "%[version]", "groupversion_info.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + f.TemplateBody = groupTemplate + + return nil +} + +//nolint:lll +const groupTemplate = `{{ .Boilerplate }} + +// Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group +//+kubebuilder:object:generate=true +//+groupName={{ .Resource.QualifiedGroup }} +package {{ .Resource.Version }} + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "{{ .Resource.QualifiedGroup }}", Version: "{{ .Resource.Version }}"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go new file mode 100644 index 0000000000..567c165fa5 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go @@ -0,0 +1,124 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Types{} + +// Types scaffolds the file that defines the schema for a CRD +// nolint:maligned +type Types struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *Types) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("apis", "%[group]", "%[version]", "%[kind]_types.go") + } else { + f.Path = filepath.Join("apis", "%[version]", "%[kind]_types.go") + } + } else { + f.Path = filepath.Join("api", "%[version]", "%[kind]_types.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = typesTemplate + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } else { + f.IfExistsAction = machinery.Error + } + + return nil +} + +const typesTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Version }} + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }} +type {{ .Resource.Kind }}Spec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of {{ .Resource.Kind }}. Edit {{ lower .Resource.Kind }}_types.go to remove/update + Foo string ` + "`" + `json:"foo,omitempty"` + "`" + ` +} + +// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }} +type {{ .Resource.Kind }}Status struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +{{- if and (not .Resource.API.Namespaced) (not .Resource.IsRegularPlural) }} +//+kubebuilder:resource:path={{ .Resource.Plural }},scope=Cluster +{{- else if not .Resource.API.Namespaced }} +//+kubebuilder:resource:scope=Cluster +{{- else if not .Resource.IsRegularPlural }} +//+kubebuilder:resource:path={{ .Resource.Plural }} +{{- end }} + +// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API +type {{ .Resource.Kind }} struct { + metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` + metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` + + Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec,omitempty"` + "`" + ` + Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty"` + "`" + ` +} + +//+kubebuilder:object:root=true + +// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }} +type {{ .Resource.Kind }}List struct { + metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` + metav1.ListMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` + Items []{{ .Resource.Kind }} ` + "`" + `json:"items"` + "`" + ` +} + +func init() { + SchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{}) +} +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go new file mode 100644 index 0000000000..76561a5a79 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go @@ -0,0 +1,156 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "fmt" + "path/filepath" + "strings" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Webhook{} + +// Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource +type Webhook struct { // nolint:maligned + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + // Is the Group domain for the Resource replacing '.' with '-' + QualifiedGroupWithDash string + + // Define value for AdmissionReviewVersions marker + AdmissionReviewVersions string + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *Webhook) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("apis", "%[group]", "%[version]", "%[kind]_webhook.go") + } else { + f.Path = filepath.Join("apis", "%[version]", "%[kind]_webhook.go") + } + } else { + f.Path = filepath.Join("api", "%[version]", "%[kind]_webhook.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + webhookTemplate := webhookTemplate + if f.Resource.HasDefaultingWebhook() { + webhookTemplate = webhookTemplate + defaultingWebhookTemplate + } + if f.Resource.HasValidationWebhook() { + webhookTemplate = webhookTemplate + validatingWebhookTemplate + } + f.TemplateBody = webhookTemplate + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } else { + f.IfExistsAction = machinery.Error + } + + f.AdmissionReviewVersions = "v1" + f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1) + + return nil +} + +const ( + webhookTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Version }} + +import ( + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + {{- if .Resource.HasValidationWebhook }} + "k8s.io/apimachinery/pkg/runtime" + {{- end }} + {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }} + "sigs.k8s.io/controller-runtime/pkg/webhook" + {{- end }} +) + +// log is for logging in this package. +var {{ lower .Resource.Kind }}log = logf.Log.WithName("{{ lower .Resource.Kind }}-resource") + +func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +` + + //nolint:lll + defaultingWebhookTemplate = ` +//+kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/mutate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }} + +var _ webhook.Defaulter = &{{ .Resource.Kind }}{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *{{ .Resource.Kind }}) Default() { + {{ lower .Resource.Kind }}log.Info("default", "name", r.Name) + + // TODO(user): fill in your defaulting logic. +} +` + + //nolint:lll + validatingWebhookTemplate = ` +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//+kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/validate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }} + +var _ webhook.Validator = &{{ .Resource.Kind }}{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *{{ .Resource.Kind }}) ValidateCreate() error { + {{ lower .Resource.Kind }}log.Info("validate create", "name", r.Name) + + // TODO(user): fill in your validation logic upon object creation. + return nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *{{ .Resource.Kind }}) ValidateUpdate(old runtime.Object) error { + {{ lower .Resource.Kind }}log.Info("validate update", "name", r.Name) + + // TODO(user): fill in your validation logic upon object update. + return nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *{{ .Resource.Kind }}) ValidateDelete() error { + {{ lower .Resource.Kind }}log.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil +} +` +) diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go new file mode 100644 index 0000000000..2ebc091b9e --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go @@ -0,0 +1,246 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package api + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &WebhookSuite{} +var _ machinery.Inserter = &WebhookSuite{} + +// WebhookSuite scaffolds the file that sets up the webhook tests +type WebhookSuite struct { //nolint:maligned + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + // todo: currently is not possible to know if an API was or not scaffolded. We can fix it when #1826 be addressed + WireResource bool + + // BaseDirectoryRelativePath define the Path for the base directory when it is multigroup + BaseDirectoryRelativePath string +} + +// SetTemplateDefaults implements file.Template +func (f *WebhookSuite) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("apis", "%[group]", "%[version]", "webhook_suite_test.go") + } else { + f.Path = filepath.Join("apis", "%[version]", "webhook_suite_test.go") + } + } else { + f.Path = filepath.Join("api", "%[version]", "webhook_suite_test.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplate, + machinery.NewMarkerFor(f.Path, importMarker), + admissionImportAlias, + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, addWebhookManagerMarker), + "%s", + "%d", + ) + + // If is multigroup the path needs to be ../../.. since it has the group dir. + f.BaseDirectoryRelativePath = `"..", ".."` + if f.MultiGroup && f.Resource.Group != "" { + f.BaseDirectoryRelativePath = `"..", "..",".."` + } + + return nil +} + +const ( + admissionImportAlias = "admissionv1" + admissionPath = "k8s.io/api/admission/v1" + importMarker = "imports" + addWebhookManagerMarker = "webhook" + addSchemeMarker = "scheme" +) + +// GetMarkers implements file.Inserter +func (f *WebhookSuite) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, addWebhookManagerMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + + addWebhookManagerCodeFragment = `err = (&%s{}).SetupWebhookWithManager(mgr) +Expect(err).NotTo(HaveOccurred()) + +` +) + +// GetCodeFragments implements file.Inserter +func (f *WebhookSuite) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) + + // Generate import code fragments + imports := make([]string, 0) + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, admissionImportAlias, admissionPath)) + + // Generate add scheme code fragments + addScheme := make([]string, 0) + + // Generate add webhookManager code fragments + addWebhookManager := make([]string, 0) + addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragment, f.Resource.Kind)) + + // Only store code fragments in the map if the slices are non-empty + if len(addWebhookManager) != 0 { + fragments[machinery.NewMarkerFor(f.Path, addWebhookManagerMarker)] = addWebhookManager + } + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports + } + if len(addScheme) != 0 { + fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + } + + return fragments +} + +const webhookTestSuiteTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Version }} + +import ( + "context" + "path/filepath" + "testing" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + %s + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Webhook Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "crd", "bases")}, + ErrorIfCRDPathMissing: {{ .WireResource }}, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "webhook")}, + }, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = %s.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + %s + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + + %s + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%s", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go new file mode 100644 index 0000000000..5b5a30aa62 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go @@ -0,0 +1,119 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Controller{} + +// Controller scaffolds the file that defines the controller for a CRD or a builtin resource +// nolint:maligned +type Controller struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + ControllerRuntimeVersion string + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *Controller) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller.go") + } else { + f.Path = filepath.Join("controllers", "%[kind]_controller.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = controllerTemplate + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } else { + f.IfExistsAction = machinery.Error + } + + return nil +} + +//nolint:lll +const controllerTemplate = `{{ .Boilerplate }} + +package {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}controllers{{ end }} + +import ( + "context" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + {{ if not (isEmptyStr .Resource.Path) -}} + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" + {{- end }} +) + +// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object +type {{ .Resource.Kind }}Reconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch +//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the {{ .Resource.Kind }} object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/reconcile +func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + {{ if not (isEmptyStr .Resource.Path) -}} + For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). + {{- else -}} + // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument + // For(). + {{- end }} + Complete(r) +} +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go new file mode 100644 index 0000000000..20a574dd8a --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -0,0 +1,190 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &SuiteTest{} +var _ machinery.Inserter = &SuiteTest{} + +// SuiteTest scaffolds the file that sets up the controller tests +// nolint:maligned +type SuiteTest struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + // CRDDirectoryRelativePath define the Path for the CRD + CRDDirectoryRelativePath string + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *SuiteTest) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("controllers", "%[group]", "suite_test.go") + } else { + f.Path = filepath.Join("controllers", "suite_test.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate, + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + ) + + // If is multigroup the path needs to be ../../ since it has + // the group dir. + f.CRDDirectoryRelativePath = `".."` + if f.MultiGroup && f.Resource.Group != "" { + f.CRDDirectoryRelativePath = `"..", ".."` + } + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } + + return nil +} + +const ( + importMarker = "imports" + addSchemeMarker = "scheme" +) + +// GetMarkers implements file.Inserter +func (f *SuiteTest) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + addschemeCodeFragment = `err = %s.AddToScheme(scheme.Scheme) +Expect(err).NotTo(HaveOccurred()) + +` +) + +// GetCodeFragments implements file.Inserter +func (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 2) + + // Generate import code fragments + imports := make([]string, 0) + if f.Resource.Path != "" { + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) + } + + // Generate add scheme code fragments + addScheme := make([]string, 0) + if f.Resource.Path != "" { + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) + } + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports + } + if len(addScheme) != 0 { + fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + } + + return fragments +} + +const controllerSuiteTestTemplate = `{{ .Boilerplate }} + +{{if and .MultiGroup .Resource.Group }} +package {{ .Resource.PackageName }} +{{else}} +package controllers +{{end}} + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + %s +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")}, + ErrorIfCRDPathMissing: {{ .Resource.HasAPI }}, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + %s + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go new file mode 100644 index 0000000000..d8032525c8 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go @@ -0,0 +1,74 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Dockerfile{} + +// Dockerfile scaffolds a file that defines the containerized build process +type Dockerfile struct { + machinery.TemplateMixin +} + +// SetTemplateDefaults implements file.Template +func (f *Dockerfile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "Dockerfile" + } + + f.TemplateBody = dockerfileTemplate + + return nil +} + +const dockerfileTemplate = `# Build the manager binary +FROM golang:1.19 as builder +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY main.go main.go +COPY api/ api/ +COPY controllers/ controllers/ + +# Build +# the GOARCH has not a default value to allow the binary be built according to the host where the command +# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO +# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, +# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go new file mode 100644 index 0000000000..85f2d3f0f3 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go @@ -0,0 +1,45 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &DockerIgnore{} + +// DockerIgnore scaffolds a file that defines which files should be ignored by the containerized build process +type DockerIgnore struct { + machinery.TemplateMixin +} + +// SetTemplateDefaults implements file.Template +func (f *DockerIgnore) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = ".dockerignore" + } + + f.TemplateBody = dockerignorefileTemplate + + return nil +} + +const dockerignorefileTemplate = `# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ +testbin/ +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go new file mode 100644 index 0000000000..89931a4d11 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go @@ -0,0 +1,67 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &GitIgnore{} + +// GitIgnore scaffolds a file that defines which files should be ignored by git +type GitIgnore struct { + machinery.TemplateMixin +} + +// SetTemplateDefaults implements file.Template +func (f *GitIgnore) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = ".gitignore" + } + + f.TemplateBody = gitignoreTemplate + + return nil +} + +const gitignoreTemplate = ` +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin +testbin/* +Dockerfile.cross + +# Test binary, build with ` + "`go test -c`" + ` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Kubernetes Generated files - skip generated files, except for vendored files + +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go new file mode 100644 index 0000000000..c0fc6ec90e --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go @@ -0,0 +1,54 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &GoMod{} + +// GoMod scaffolds a file that defines the project dependencies +type GoMod struct { + machinery.TemplateMixin + machinery.RepositoryMixin + + ControllerRuntimeVersion string +} + +// SetTemplateDefaults implements file.Template +func (f *GoMod) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "go.mod" + } + + f.TemplateBody = goModTemplate + + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const goModTemplate = ` +module {{ .Repo }} + +go 1.19 + +require ( + sigs.k8s.io/controller-runtime {{ .ControllerRuntimeVersion }} +) +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go new file mode 100644 index 0000000000..9907d615d6 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go @@ -0,0 +1,125 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package hack + +import ( + "fmt" + "path/filepath" + "time" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +// DefaultBoilerplatePath is the default path to the boilerplate file +var DefaultBoilerplatePath = filepath.Join("hack", "boilerplate.go.txt") + +var _ machinery.Template = &Boilerplate{} + +// Boilerplate scaffolds a file that defines the common header for the rest of the files +type Boilerplate struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + + // License is the License type to write + License string + + // Licenses maps License types to their actual string + Licenses map[string]string + + // Owner is the copyright owner - e.g. "The Kubernetes Authors" + Owner string + + // Year is the copyright year + Year string +} + +// Validate implements file.RequiresValidation +func (f Boilerplate) Validate() error { + if f.License == "" { + // A default license will be set later + } else if _, found := knownLicenses[f.License]; found { + // One of the know licenses + } else if _, found := f.Licenses[f.License]; found { + // A map containing the requested license was also provided + } else { + return fmt.Errorf("unknown specified license %s", f.License) + } + + return nil +} + +// SetTemplateDefaults implements file.Template +func (f *Boilerplate) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = DefaultBoilerplatePath + } + + if f.License == "" { + f.License = "apache2" + } + + if f.Licenses == nil { + f.Licenses = make(map[string]string, len(knownLicenses)) + } + + for key, value := range knownLicenses { + if _, hasLicense := f.Licenses[key]; !hasLicense { + f.Licenses[key] = value + } + } + + if f.Year == "" { + f.Year = fmt.Sprintf("%v", time.Now().Year()) + } + + // Boilerplate given + if len(f.Boilerplate) > 0 { + f.TemplateBody = f.Boilerplate + return nil + } + + f.TemplateBody = boilerplateTemplate + + return nil +} + +const boilerplateTemplate = `/* +{{ if .Owner -}} +Copyright {{ .Year }} {{ .Owner }}. +{{- else -}} +Copyright {{ .Year }}. +{{- end }} +{{ index .Licenses .License }}*/` + +var knownLicenses = map[string]string{ + "apache2": apache2, + "none": "", +} + +const apache2 = ` +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go new file mode 100644 index 0000000000..f1122f5696 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go @@ -0,0 +1,295 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +const defaultMainPath = "main.go" + +var _ machinery.Template = &Main{} + +// Main scaffolds a file that defines the controller manager entry point +type Main struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.DomainMixin + machinery.RepositoryMixin + machinery.ComponentConfigMixin +} + +// SetTemplateDefaults implements file.Template +func (f *Main) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(defaultMainPath) + } + + f.TemplateBody = fmt.Sprintf(mainTemplate, + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, setupMarker), + ) + + return nil +} + +var _ machinery.Inserter = &MainUpdater{} + +// MainUpdater updates main.go to run Controllers +type MainUpdater struct { //nolint:maligned + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin + + // Flags to indicate which parts need to be included when updating the file + WireResource, WireController, WireWebhook bool +} + +// GetPath implements file.Builder +func (*MainUpdater) GetPath() string { + return defaultMainPath +} + +// GetIfExistsAction implements file.Builder +func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile +} + +const ( + importMarker = "imports" + addSchemeMarker = "scheme" + setupMarker = "builder" +) + +// GetMarkers implements file.Inserter +func (f *MainUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(defaultMainPath, importMarker), + machinery.NewMarkerFor(defaultMainPath, addSchemeMarker), + machinery.NewMarkerFor(defaultMainPath, setupMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + controllerImportCodeFragment = `"%s/controllers" +` + multiGroupControllerImportCodeFragment = `%scontrollers "%s/controllers/%s" +` + addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme)) +` + reconcilerSetupCodeFragment = `if err = (&controllers.%sReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "%s") + os.Exit(1) + } +` + multiGroupReconcilerSetupCodeFragment = `if err = (&%scontrollers.%sReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "%s") + os.Exit(1) + } +` + webhookSetupCodeFragment = `if err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "%s") + os.Exit(1) + } +` +) + +// GetCodeFragments implements file.Inserter +func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) + + // If resource is not being provided we are creating the file, not updating it + if f.Resource == nil { + return fragments + } + + // Generate import code fragments + imports := make([]string, 0) + if f.WireResource { + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) + } + + if f.WireController { + if !f.MultiGroup || f.Resource.Group == "" { + imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo)) + } else { + imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment, + f.Resource.PackageName(), f.Repo, f.Resource.Group)) + } + } + + // Generate add scheme code fragments + addScheme := make([]string, 0) + if f.WireResource { + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) + } + + // Generate setup code fragments + setup := make([]string, 0) + if f.WireController { + if !f.MultiGroup || f.Resource.Group == "" { + setup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment, + f.Resource.Kind, f.Resource.Kind)) + } else { + setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment, + f.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind)) + } + } + if f.WireWebhook { + setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, + f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) + } + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports + } + if len(addScheme) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme + } + if len(setup) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup + } + + return fragments +} + +var mainTemplate = `{{ .Boilerplate }} + +package main + +import ( + "flag" + "os" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/healthz" + %s +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + %s +} + +func main() { +{{- if not .ComponentConfig }} + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. " + + "Enabling this will ensure there is only one active controller manager.") +{{- else }} + var configFile string + flag.StringVar(&configFile, "config", "", + "The controller will load its initial configuration from this file. " + + "Omit this flag to use the default configuration values. " + + "Command-line flags override configuration from this file.") +{{- end }} + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + +{{ if not .ComponentConfig }} + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: metricsAddr, + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "{{ hashFNV .Repo }}.{{ .Domain }}", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) +{{- else }} + var err error + options := ctrl.Options{Scheme: scheme} + if configFile != "" { + options, err = options.AndFrom(ctrl.ConfigFile().AtPath(configFile)) + if err != nil { + setupLog.Error(err, "unable to load the config file") + os.Exit(1) + } + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options) +{{- end }} + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + %s + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go new file mode 100644 index 0000000000..101411755c --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go @@ -0,0 +1,218 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Makefile{} + +// Makefile scaffolds a file that defines project management CLI commands +type Makefile struct { + machinery.TemplateMixin + machinery.ComponentConfigMixin + machinery.ProjectNameMixin + + // Image is controller manager image name + Image string + // BoilerplatePath is the path to the boilerplate file + BoilerplatePath string + // Controller tools version to use in the project + ControllerToolsVersion string + // Kustomize version to use in the project + KustomizeVersion string + // ControllerRuntimeVersion version to be used to download the envtest setup script + ControllerRuntimeVersion string +} + +// SetTemplateDefaults implements file.Template +func (f *Makefile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "Makefile" + } + + f.TemplateBody = makefileTemplate + + f.IfExistsAction = machinery.Error + + if f.Image == "" { + f.Image = "controller:latest" + } + + return nil +} + +//nolint:lll +const makefileTemplate = ` +# Image URL to use all building/pushing image targets +IMG ?= {{ .Image }} +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.25.0 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out + +##@ Build + +.PHONY: build +build: manifests generate fmt vet ## Build manager binary. + go build -o bin/manager main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./main.go + +# If you wish built the manager image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it. +# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +.PHONY: docker-build +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ +# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> then the export will fail) +# To properly provided solutions that supports more than one platform you should use this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: test ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - docker buildx create --name project-v3-builder + docker buildx use project-v3-builder + - docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - docker buildx rm project-v3-builder + rm Dockerfile.cross + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | kubectl apply -f - + +.PHONY: undeploy +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Build Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUSTOMIZE ?= $(LOCALBIN)/kustomize +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +ENVTEST ?= $(LOCALBIN)/setup-envtest + +## Tool Versions +KUSTOMIZE_VERSION ?= {{ .KustomizeVersion }} +CONTROLLER_TOOLS_VERSION ?= {{ .ControllerToolsVersion }} + +KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. +$(KUSTOMIZE): $(LOCALBIN) + @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ + echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ + rm -rf $(LOCALBIN)/kustomize; \ + fi + test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. +$(CONTROLLER_GEN): $(LOCALBIN) + test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ + GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) + +.PHONY: envtest +envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. +$(ENVTEST): $(LOCALBIN) + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go new file mode 100644 index 0000000000..64d541bf17 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go @@ -0,0 +1,129 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "fmt" + "strings" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Readme{} + +// Readme scaffolds a README.md file +type Readme struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.ProjectNameMixin + + License string +} + +// SetTemplateDefaults implements file.Template +func (f *Readme) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "README.md" + } + + f.License = strings.Replace( + strings.Replace(f.Boilerplate, "/*", "", 1), + "*/", "", 1) + + f.TemplateBody = fmt.Sprintf(readmeFileTemplate, + codeFence("kubectl apply -f config/samples/"), + codeFence("make docker-build docker-push IMG=/{{ .ProjectName }}:tag"), + codeFence("make deploy IMG=/{{ .ProjectName }}:tag"), + codeFence("make uninstall"), + codeFence("make undeploy"), + codeFence("make install"), + codeFence("make run"), + codeFence("make manifests")) + + return nil +} + +//nolint:lll +const readmeFileTemplate = `# {{ .ProjectName }} +// TODO(user): Add simple overview of use/purpose + +## Description +// TODO(user): An in-depth paragraph about your project and overview of use + +## Getting Started +You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster. +**Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster ` + "`kubectl cluster-info`" + ` shows). + +### Running on the cluster +1. Install Instances of Custom Resources: + +%s + +2. Build and push your image to the location specified by ` + "`IMG`" + `: + +%s + +3. Deploy the controller to the cluster with the image specified by ` + "`IMG`" + `: + +%s + +### Uninstall CRDs +To delete the CRDs from the cluster: + +%s + +### Undeploy controller +UnDeploy the controller to the cluster: + +%s + +## Contributing +// TODO(user): Add detailed information on how you would like others to contribute to this project + +### How it works +This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) + +It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/) +which provides a reconcile function responsible for synchronizing resources untile the desired state is reached on the cluster + +### Test It Out +1. Install the CRDs into the cluster: + +%s + +2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running): + +%s + +**NOTE:** You can also run this in one step by running: ` + "`make install run`" + ` + +### Modifying the API definitions +If you are editing the API definitions, generate the manifests such as CRs or CRDs using: + +%s + +**NOTE:** Run ` + "`make --help`" + ` for more information on all potential ` + "`make`" + ` targets + +More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) + +## License +{{ .License }} +` + +func codeFence(code string) string { + return "```sh" + "\n" + code + "\n" + "```" +} diff --git a/pkg/plugins/golang/v4/scaffolds/webhook.go b/pkg/plugins/golang/v4/scaffolds/webhook.go new file mode 100644 index 0000000000..029f75ee5e --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/webhook.go @@ -0,0 +1,108 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scaffolds + +import ( + "fmt" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/api" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/hack" +) + +var _ plugins.Scaffolder = &webhookScaffolder{} + +type webhookScaffolder struct { + config config.Config + resource resource.Resource + + // fs is the filesystem that will be used by the scaffolder + fs machinery.Filesystem + + // force indicates whether to scaffold controller files even if it exists or not + force bool +} + +// NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations +func NewWebhookScaffolder(config config.Config, resource resource.Resource, force bool) plugins.Scaffolder { + return &webhookScaffolder{ + config: config, + resource: resource, + force: force, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder +func (s *webhookScaffolder) Scaffold() error { + fmt.Println("Writing scaffold for you to edit...") + + // Load the boilerplate + boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) + if err != nil { + return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err) + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), + ) + + // Keep track of these values before the update + doDefaulting := s.resource.HasDefaultingWebhook() + doValidation := s.resource.HasValidationWebhook() + doConversion := s.resource.HasConversionWebhook() + + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } + + if err := scaffold.Execute( + &api.Webhook{Force: s.force}, + &templates.MainUpdater{WireWebhook: true}, + ); err != nil { + return err + } + + if doConversion { + fmt.Println(`Webhook server has been set up for you. +You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`) + } + + // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest. + if doDefaulting || doValidation { + if err := scaffold.Execute( + &api.WebhookSuite{}, + ); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/plugins/golang/v4/webhook.go b/pkg/plugins/golang/v4/webhook.go new file mode 100644 index 0000000000..6e017ab1fd --- /dev/null +++ b/pkg/plugins/golang/v4/webhook.go @@ -0,0 +1,133 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v4 + +import ( + "fmt" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + pluginutil "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" +) + +// defaultWebhookVersion is the default mutating/validating webhook config API version to scaffold. +const defaultWebhookVersion = "v1" + +var _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{} + +type createWebhookSubcommand struct { + config config.Config + // For help text. + commandName string + + options *goPlugin.Options + + resource *resource.Resource + + // force indicates that the resource should be created even if it already exists + force bool +} + +func (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + p.commandName = cliMeta.CommandName + + subcmdMeta.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting, +validating and/or conversion webhooks. +` + subcmdMeta.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for Group: ship, Version: v1beta1 + # and Kind: Frigate + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation + + # Create conversion webhook for Group: ship, Version: v1beta1 + # and Kind: Frigate + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion +`, cliMeta.CommandName) +} + +func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { + p.options = &goPlugin.Options{} + + fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") + + fs.BoolVar(&p.options.DoDefaulting, "defaulting", false, + "if set, scaffold the defaulting webhook") + fs.BoolVar(&p.options.DoValidation, "programmatic-validation", false, + "if set, scaffold the validating webhook") + fs.BoolVar(&p.options.DoConversion, "conversion", false, + "if set, scaffold the conversion webhook") + + fs.BoolVar(&p.force, "force", false, + "attempt to create resource even if it already exists") +} + +func (p *createWebhookSubcommand) InjectConfig(c config.Config) error { + p.config = c + // go/v4 no longer supports v1beta1 option + p.options.WebhookVersion = defaultWebhookVersion + return nil +} + +func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + p.options.UpdateResource(p.resource, p.config) + + if err := p.resource.Validate(); err != nil { + return err + } + + if !p.resource.HasDefaultingWebhook() && !p.resource.HasValidationWebhook() && !p.resource.HasConversionWebhook() { + return fmt.Errorf("%s create webhook requires at least one of --defaulting,"+ + " --programmatic-validation and --conversion to be true", p.commandName) + } + + // check if resource exist to create webhook + if r, err := p.config.GetResource(p.resource.GVK); err != nil { + return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() && !p.force { + return fmt.Errorf("webhook resource already exists") + } + + return nil +} + +func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() +} + +func (p *createWebhookSubcommand) PostScaffold() error { + err := pluginutil.RunCmd("Update dependencies", "go", "mod", "tidy") + if err != nil { + return err + } + + err = pluginutil.RunCmd("Running make", "make", "generate") + if err != nil { + return err + } + fmt.Print("Next: implement your new Webhook and generate the manifests with:\n$ make manifests\n") + + return nil +} diff --git a/testdata/project-v4-config/api/v1/webhook_suite_test.go b/testdata/project-v4-config/api/v1/webhook_suite_test.go index 06549f198e..91b9bf273c 100644 --- a/testdata/project-v4-config/api/v1/webhook_suite_test.go +++ b/testdata/project-v4-config/api/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-multigroup/apis/crew/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/apis/crew/v1/webhook_suite_test.go index 41c8c57de0..95d604fbf9 100644 --- a/testdata/project-v4-multigroup/apis/crew/v1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/apis/crew/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-multigroup/apis/ship/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/apis/ship/v1/webhook_suite_test.go index af22a1df03..b4228bbd46 100644 --- a/testdata/project-v4-multigroup/apis/ship/v1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/apis/ship/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-multigroup/apis/ship/v2alpha1/webhook_suite_test.go b/testdata/project-v4-multigroup/apis/ship/v2alpha1/webhook_suite_test.go index 23748cbe17..b31858834b 100644 --- a/testdata/project-v4-multigroup/apis/ship/v2alpha1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/apis/ship/v2alpha1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-multigroup/apis/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/apis/v1/webhook_suite_test.go index 0b451a7426..1af25ebb4d 100644 --- a/testdata/project-v4-multigroup/apis/v1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/apis/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-with-deploy-image/api/v1alpha1/webhook_suite_test.go b/testdata/project-v4-with-deploy-image/api/v1alpha1/webhook_suite_test.go index 91bbf3d07d..9e3757a42f 100644 --- a/testdata/project-v4-with-deploy-image/api/v1alpha1/webhook_suite_test.go +++ b/testdata/project-v4-with-deploy-image/api/v1alpha1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4/api/v1/webhook_suite_test.go b/testdata/project-v4/api/v1/webhook_suite_test.go index 06549f198e..91b9bf273c 100644 --- a/testdata/project-v4/api/v1/webhook_suite_test.go +++ b/testdata/project-v4/api/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme From 920b923e9f8c7476691919696c18ec94e0d5e836 Mon Sep 17 00:00:00 2001 From: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com> Date: Fri, 18 Nov 2022 06:59:25 +0000 Subject: [PATCH 2/6] Update docs/book/src/plugins/go-v4-plugin.md Co-authored-by: Varsha --- docs/book/src/plugins/go-v4-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/src/plugins/go-v4-plugin.md b/docs/book/src/plugins/go-v4-plugin.md index 471c2447c0..4b2fb1fa6d 100644 --- a/docs/book/src/plugins/go-v4-plugin.md +++ b/docs/book/src/plugins/go-v4-plugin.md @@ -23,7 +23,7 @@ under the [testdata][testdata] directory on the root directory of the Kubebuilde - If you are looking to use [kubernetes-sigs/kustomize][kustomize] v4 - If you are looking to have your project update with the latest version available - if you are not targeting k8s versions < `1.16` and `1.20` if you are using webhooks -- If you are looking for to work on with scaffolds which are compatible with k8s `1.25+` +- If you are looking to work on with scaffolds which are compatible with k8s `1.25+`