From 7ea823d80075f42ac1ca205c57905b04e3ed4e6d Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Sun, 15 Sep 2024 15:09:40 +0100 Subject: [PATCH] Add support to scaffold controllers for External Types Introduces the option to allow users scaffold controllers for external types by running: kubebuilder create api --group certmanager --version v1 --kind Certificate --controller=true --resource=false --make=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --- docs/book/src/SUMMARY.md | 2 +- docs/book/src/reference/reference.md | 2 +- docs/book/src/reference/submodule-layouts.md | 6 +- .../reference/using_an_external_resource.md | 88 ++++++ .../src/reference/using_an_external_type.md | 275 ------------------ pkg/plugins/golang/options.go | 15 +- pkg/plugins/golang/v4/api.go | 12 +- .../controllers/controller_suitetest.go | 1 + test/testdata/generate.sh | 3 + testdata/project-v4-multigroup/PROJECT | 6 + testdata/project-v4-multigroup/cmd/main.go | 8 + .../config/rbac/role.yaml | 26 ++ .../project-v4-multigroup/dist/install.yaml | 26 ++ testdata/project-v4-multigroup/go.mod | 24 +- .../certmanager/certificate_controller.go | 62 ++++ .../certificate_controller_test.go | 32 ++ .../controller/certmanager/suite_test.go | 95 ++++++ 17 files changed, 386 insertions(+), 297 deletions(-) create mode 100644 docs/book/src/reference/using_an_external_resource.md delete mode 100644 docs/book/src/reference/using_an_external_type.md create mode 100644 testdata/project-v4-multigroup/internal/controller/certmanager/certificate_controller.go create mode 100644 testdata/project-v4-multigroup/internal/controller/certmanager/certificate_controller_test.go create mode 100644 testdata/project-v4-multigroup/internal/controller/certmanager/suite_test.go diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 67590e84a28..f2b717265b1 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -102,7 +102,7 @@ - [Manager and CRDs Scope](./reference/scopes.md) - [Sub-Module Layouts](./reference/submodule-layouts.md) - - [Using an external Type / API](./reference/using_an_external_type.md) + - [Using an external Resource / API](./reference/using_an_external_resource.md) - [Configuring EnvTest](./reference/envtest.md) diff --git a/docs/book/src/reference/reference.md b/docs/book/src/reference/reference.md index 6a40cafa4bd..5d4f02a0dbe 100644 --- a/docs/book/src/reference/reference.md +++ b/docs/book/src/reference/reference.md @@ -35,7 +35,7 @@ - [Platform Support](platform.md) - [Sub-Module Layouts](submodule-layouts.md) - - [Using an external Type / API](using_an_external_type.md) + - [Using an external Resource / API](using_an_external_resource.md) - [Metrics](metrics.md) - [Reference](metrics-reference.md) diff --git a/docs/book/src/reference/submodule-layouts.md b/docs/book/src/reference/submodule-layouts.md index 2d44e9f8528..63a069311bd 100644 --- a/docs/book/src/reference/submodule-layouts.md +++ b/docs/book/src/reference/submodule-layouts.md @@ -5,9 +5,11 @@ This part describes how to modify a scaffolded project for use with multiple `go Sub-Module Layouts (in a way you could call them a special form of [Monorepo's][monorepo]) are a special use case and can help in scenarios that involve reuse of APIs without introducing indirect dependencies that should not be available in the project consuming the API externally. diff --git a/docs/book/src/reference/using_an_external_resource.md b/docs/book/src/reference/using_an_external_resource.md new file mode 100644 index 00000000000..a826cbce47d --- /dev/null +++ b/docs/book/src/reference/using_an_external_resource.md @@ -0,0 +1,88 @@ +# Using External Resources + +In some cases, your project may need to work with resources that aren't defined by your own APIs. +These external resources fall into two main categories: + +- **Core Types**: API types defined by Kubernetes itself, such as `Pods`, `Services`, and `Deployments`. +- **External Types**: API types defined in other projects, such as CRDs managed by another operator. + +## Managing External Types + +### Creating a Controller for External Types + +To create a controller for an external type without scaffolding a resource, you can use the `create api` command with +the `--resource=false` option and specify the path to the external API type using the `--external-api-path` option. +This allows you to generate a controller that operates on types defined outside of your project, such as CRDs managed +by other operators. + +The command looks like this: + +```shell +kubebuilder create api --group --version v1alpha1 --kind --controller --resource=false --external-api-path= +``` + +- `--external-api-path`: This is the import path for the external API type in your Go modules. +You should provide the Go import path where the external types are defined. + +For example, if you're managing Certificates from Cert Manager: + +```shell +kubebuilder create api --group certmanager --version v1 --kind Certificate --controller=true --resource=false --make=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 +``` + +This scaffolds a controller for the external type but skips creating new resource definitions since the type is defined in an external project. + + +### Creating a Webhooks to manage an external type + +> Webhook support for external types is not currently available. (TODO) + +## Managing Core Types + +Core Kubernetes API types, such as `Pods`, `Services`, and `Deployments`, are predefined by Kubernetes. +To create a controller for these core types without scaffolding the resource, use the appropriate group for the type. + +The following table lists the core groups and their corresponding API group paths. +Use this information when defining controllers for core Kubernetes resources. + +| Group | API Group | +|--------------------------|-------------------------| +| admission | `k8s.io` | +| admissionregistration | `k8s.io` | +| apps | *(none)* | +| auditregistration | `k8s.io` | +| apiextensions | `k8s.io` | +| authentication | `k8s.io` | +| authorization | `k8s.io` | +| autoscaling | *(none)* | +| batch | *(none)* | +| certificates | `k8s.io` | +| coordination | `k8s.io` | +| core | *(none)* | +| events | `k8s.io` | +| extensions | *(none)* | +| imagepolicy | `k8s.io` | +| networking | `k8s.io` | +| node | `k8s.io` | +| metrics | `k8s.io` | +| policy | *(none)* | +| rbac.authorization | `k8s.io` | +| scheduling | `k8s.io` | +| setting | `k8s.io` | +| storage | `k8s.io` | + +Use the group and API version specific to the type you're +managing when creating a controller for a core type. + +The command for managing a core type looks like this: + +```shell +kubebuilder create api --group core --version v1 --kind Pod --controller=true --resource=false +``` + +This scaffolds a controller for the Core type `corev1.Pod` but skips creating new resource +definitions since the type is defined in the Kubernetes API. + +### Creating a Webhooks to manage a Core Type + +> Webhook support for Core Types is not currently available. (TODO) diff --git a/docs/book/src/reference/using_an_external_type.md b/docs/book/src/reference/using_an_external_type.md deleted file mode 100644 index 22a31a7d9ab..00000000000 --- a/docs/book/src/reference/using_an_external_type.md +++ /dev/null @@ -1,275 +0,0 @@ -# Using an External Type - -There are several different external types that may be referenced when writing a controller. -* Custom Resource Definitions (CRDs) that are defined in the current project (such as via `kubebuilder create api`). -* Core Kubernetes Resources (e.g. Deployments or Pods). -* CRDs that are created and installed in another project. -* A custom API defined via the aggregation layer, served by an extension API server for which the primary API server acts as a proxy. - -Currently, kubebuilder handles the first two, CRDs and Core Resources, seamlessly. You must scaffold the latter two, External CRDs and APIs created via aggregation, manually. - -In order to use a Kubernetes Custom Resource that has been defined in another project -you will need to have several items of information. -* The Domain of the CR -* The Group under the Domain -* The Go import path of the CR Type definition -* The Custom Resource Type you want to depend on. - -The Domain and Group variables have been discussed in other parts of the documentation. The import path would be located in the project that installs the CR. -The Custom Resource Type is usually a Go Type of the same name as the CustomResourceDefinition in kubernetes, e.g. for a `Pod` there will be a type `Pod` in the `v1` group. -For Kubernetes Core Types, the domain can be omitted. -`` -This document uses `my` and `their` prefixes as a naming convention for repos, groups, and types to clearly distinguish between your own project and the external one you are referencing. - -In our example we will assume the following external API Type: - -`github.com/theiruser/theirproject` is another kubebuilder project on whose CRD we want to depend and extend on. -Thus, it contains a `go.mod` in its repository root. The import path for the go types would be `github.com/theiruser/theirproject/apis/theirgroup/v1alpha1`. - -The Domain of the CR is `theirs.com`, the Group is `theirgroup` and the kind and go type would be `ExternalType`. - -If there is an interest to have multiple Controllers running in different Groups (e.g. because one is an owned CRD and one is an external Type), please first -reconfigure the Project to use a multi-group layout as described in the [Multi-Group documentation](../migration/multi-group.md). - -### Prerequisites - -The following guide assumes that you have already created a project using `kubebuilder init` in a directory in the GOPATH. Please reference the [Getting Started Guide](../getting-started.md) for more information. - -Note that if you did not pass `--domain` to `kubebuilder init` you will need to modify it for the individual api types as the default is `my.domain`, not `theirs.com`. -Similarly, if you intend to use your own domain, please configure your own domain with `kubebuilder init` and do not use `theirs.com for the domain. - -### Add a controller for the external Type - -Run the command `create api` to scaffold only the controller to manage the external type: - -```shell -kubebuilder create api --group --version v1alpha1 --kind --controller --resource=false -``` - -Note that the `resource` argument is set to false, as we are not attempting to create our own CustomResourceDefinition, -but instead rely on an external one. - -This will result in a `PROJECT` entry with the default domain of the `PROJECT` (`my.domain` if not specified in `kubebuilder init`). -For use of other domains, such as `theirs.com`, one will have to manually adjust the `PROJECT` file with the correct domain for the entry: - - - -file: PROJECT -``` -domain: my.domain -layout: -- go.kubebuilder.io/v4 -projectName: testkube -repo: example.com -resources: -- controller: true - domain: my.domain ## <- Replace the domain with theirs.com domain - group: mygroup - kind: ExternalType - version: v1alpha1 -version: "3" -``` - -At the same time, the generated RBAC manifests need to be adjusted: - -file: internal/controller/externaltype_controller.go -```go -// ExternalTypeReconciler reconciles a ExternalType object -type ExternalTypeReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -// external types can be added like this -// +kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=theirgroup.theirs.com,resources=externaltypes/finalizers,verbs=update -// core types can be added like this -// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=core,resources=pods/status,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=update -``` - -### Register your Types - - - -Edit the following lines to the main.go file to register the external types: - -file: cmd/main.go -```go -package apis - -import ( - theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1" -) - -func init() { - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(theirgroupv1alpha1.AddToScheme(scheme)) // this contains the external API types - // +kubebuilder:scaffold:scheme -} -``` - -## Edit the Controller `SetupWithManager` function - -### Use the correct imports for your API and uncomment the controlled resource - -file: internal/controllers/externaltype_controller.go -```go -package controllers - -import ( - theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1" -) - -//... - -// SetupWithManager sets up the controller with the Manager. -func (r *ExternalTypeReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&theirgroupv1alpha1.ExternalType{}). - Complete(r) -} - -``` - -Note that core resources may simply be imported by depending on the API's from upstream Kubernetes and do not need additional `AddToScheme` registrations: - -file: internal/controllers/externaltype_controller.go -```go -package controllers -// contains core resources like Deployment -import ( - v1 "k8s.io/api/apps/v1" -) - - -// SetupWithManager sets up the controller with the Manager. -func (r *ExternalTypeReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&v1.Pod{}). - Complete(r) -} -``` - -### Update dependencies - -``` -go mod tidy -``` - -### Generate RBACs with updated Groups and Resources - -``` -make manifests -``` - -## Prepare for testing - -### Register your resource in the Scheme - -Edit the `CRDDirectoryPaths` in your test suite and add the correct `AddToScheme` entry during suite initialization: - -file: internal/controllers/suite_test.go -```go -package controller - -import ( - "fmt" - "path/filepath" - "runtime" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - // +kubebuilder:scaffold:imports - theirgroupv1alpha1 "github.com/theiruser/theirproject/apis/theirgroup/v1alpha1" -) - -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment - -func TestControllers(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Controller Suite") -} - - -var _ = BeforeSuite(func() { - //... - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{ - // if you are using vendoring and rely on a kubebuilder based project, you can simply rely on the vendored config directory - filepath.Join("..", "..", "..", "vendor", "github.com", "theiruser", "theirproject", "config", "crds"), - // otherwise you can simply download the CRD from any source and place it within the config/crd/bases directory, - filepath.Join("..", "..", "config", "crd", "bases"), - }, - ErrorIfCRDPathMissing: false, - - // The BinaryAssetsDirectory is only required if you want to run the tests directly - // without call the makefile target test. If not informed it will look for the - // default path defined in controller-runtime which is /usr/local/kubebuilder/. - // Note that you must have the required binaries setup under the bin directory to perform - // the tests directly. When we run make test it will be setup and used automatically. - BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", - fmt.Sprintf("1.28.3-%s-%s", runtime.GOOS, runtime.GOARCH)), - } - - var err error - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - // +kubebuilder:scaffold:scheme - Expect(theirgroupv1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - - -}) - -``` - -### Verifying API Availability in the Cluster - -Since we are now using external types, you will now have to rely on them being installed into the cluster. -If the APIs are not available at the time the manager starts, all informers listening to the non-available types -will fail, causing the manager to exit with an error similar to - -``` -failed to get informer from cache {"error": "Timeout: failed waiting for *v1alpha1.ExternalType Informer to sync"} -``` - -This will signal that the API Server is not yet ready to serve the external types. - -## Helpful Tips - -### Locate your domain and group variables - -The following kubectl commands may be useful - -```shell -kubectl api-resources --verbs=list -o name -kubectl api-resources --verbs=list -o name | grep my.domain -``` - diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index 865f6137949..6fcadf6ccc2 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -56,6 +56,9 @@ type Options struct { // Plural is the resource's kind plural form. Plural string + // ExternalAPIPath allows inform a path for APIs not defined in the project + ExternalAPIPath string + // Namespaced is true if the resource should be namespaced. Namespaced bool @@ -109,15 +112,19 @@ func (opts Options) UpdateResource(res *resource.Resource, c config.Config) { // - Check if we already scaffolded the resource => project resource // - Check if the resource group is a well-known core group => builtin core resource // - In any other case, default to => project resource - // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath if !opts.DoAPI { var alreadyHasAPI bool loadedRes, err := c.GetResource(res.GVK) alreadyHasAPI = err == nil && loadedRes.HasAPI() if !alreadyHasAPI { - if domain, found := coreGroups[res.Group]; found { - res.Domain = domain - res.Path = path.Join("k8s.io", "api", res.Group, res.Version) + if len(opts.ExternalAPIPath) == 0 { + // Handle core types + if domain, found := coreGroups[res.Group]; found { + res.Domain = domain + res.Path = path.Join("k8s.io", "api", res.Group, res.Version) + } + } else { + res.Path = opts.ExternalAPIPath } } } diff --git a/pkg/plugins/golang/v4/api.go b/pkg/plugins/golang/v4/api.go index 3f0f27958d5..7a4ed098afd 100644 --- a/pkg/plugins/golang/v4/api.go +++ b/pkg/plugins/golang/v4/api.go @@ -108,6 +108,9 @@ func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.options.DoController, "controller", true, "if set, generate the controller without prompting the user") p.controllerFlag = fs.Lookup("controller") + + fs.StringVar(&p.options.ExternalAPIPath, "external-api-path", "", + "Specify the Go package path for external APIs to scaffold controllers for resources not defined in this project.") } func (p *createAPISubcommand) InjectConfig(c config.Config) error { @@ -118,9 +121,6 @@ func (p *createAPISubcommand) InjectConfig(c config.Config) error { 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 { log.Println("Create Resource [y/n]") @@ -131,6 +131,12 @@ func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { p.options.DoController = util.YesNo(reader) } + if len(p.options.ExternalAPIPath) != 0 && p.options.DoAPI { + return errors.New("Cannot create an API and specify an external API path at the same time. " + + "Use '--resource=true' to create an API or '--external-api-path' to reference an external type, " + + "but not both.") + } + p.options.UpdateResource(p.resource, p.config) if err := p.resource.Validate(); err != nil { 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 index e2f8796bc5f..b28bd11a326 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -136,6 +136,7 @@ package controller {{end}} import ( + "context" "fmt" "path/filepath" "runtime" diff --git a/test/testdata/generate.sh b/test/testdata/generate.sh index bc16751b992..5ab844a71de 100755 --- a/test/testdata/generate.sh +++ b/test/testdata/generate.sh @@ -65,9 +65,12 @@ function scaffold_test_project { $kb create api --group sea-creatures --version v1beta1 --kind Kraken --controller=true --resource=true --make=false $kb create api --group sea-creatures --version v1beta2 --kind Leviathan --controller=true --resource=true --make=false $kb create api --group foo.policy --version v1 --kind HealthCheckPolicy --controller=true --resource=true --make=false + # Controller for Core types $kb create api --group apps --version v1 --kind Deployment --controller=true --resource=false --make=false $kb create api --group foo --version v1 --kind Bar --controller=true --resource=true --make=false $kb create api --group fiz --version v1 --kind Bar --controller=true --resource=true --make=false + # Controller for External types + $kb create api --group certmanager --version v1 --kind Certificate --controller=true --resource=false --make=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 fi if [[ $project =~ multigroup ]] || [[ $project =~ with-plugins ]] ; then diff --git a/testdata/project-v4-multigroup/PROJECT b/testdata/project-v4-multigroup/PROJECT index ea5536b0583..3761774c5d8 100644 --- a/testdata/project-v4-multigroup/PROJECT +++ b/testdata/project-v4-multigroup/PROJECT @@ -125,6 +125,12 @@ resources: kind: Bar path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/fiz/v1 version: v1 +- controller: true + domain: testproject.org + group: certmanager + kind: Certificate + path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 + version: v1 - api: crdVersion: v1 namespaced: true diff --git a/testdata/project-v4-multigroup/cmd/main.go b/testdata/project-v4-multigroup/cmd/main.go index e7dc7e0a55e..855d904b27d 100644 --- a/testdata/project-v4-multigroup/cmd/main.go +++ b/testdata/project-v4-multigroup/cmd/main.go @@ -46,6 +46,7 @@ import ( shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1beta1" shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1" appscontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/apps" + certmanagercontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/certmanager" crewcontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/crew" examplecomcontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/example.com" fizcontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/fiz" @@ -267,6 +268,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Bar") os.Exit(1) } + if err = (&certmanagercontroller.CertificateReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Certificate") + os.Exit(1) + } if err = (&examplecomcontroller.MemcachedReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/testdata/project-v4-multigroup/config/rbac/role.yaml b/testdata/project-v4-multigroup/config/rbac/role.yaml index 3bf7c0bed2f..0bf2d893cc2 100644 --- a/testdata/project-v4-multigroup/config/rbac/role.yaml +++ b/testdata/project-v4-multigroup/config/rbac/role.yaml @@ -30,6 +30,32 @@ rules: - get - patch - update +- apiGroups: + - certmanager.testproject.org + resources: + - certificates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - certmanager.testproject.org + resources: + - certificates/finalizers + verbs: + - update +- apiGroups: + - certmanager.testproject.org + resources: + - certificates/status + verbs: + - get + - patch + - update - apiGroups: - "" resources: diff --git a/testdata/project-v4-multigroup/dist/install.yaml b/testdata/project-v4-multigroup/dist/install.yaml index 05a31522eeb..1dd8dc4f2c2 100644 --- a/testdata/project-v4-multigroup/dist/install.yaml +++ b/testdata/project-v4-multigroup/dist/install.yaml @@ -1161,6 +1161,32 @@ rules: - get - patch - update +- apiGroups: + - certmanager.testproject.org + resources: + - certificates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - certmanager.testproject.org + resources: + - certificates/finalizers + verbs: + - update +- apiGroups: + - certmanager.testproject.org + resources: + - certificates/status + verbs: + - get + - patch + - update - apiGroups: - "" resources: diff --git a/testdata/project-v4-multigroup/go.mod b/testdata/project-v4-multigroup/go.mod index 03db01b03e8..1fdc0ee0701 100644 --- a/testdata/project-v4-multigroup/go.mod +++ b/testdata/project-v4-multigroup/go.mod @@ -3,6 +3,7 @@ module sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup go 1.22.0 require ( + github.com/cert-manager/cert-manager v1.15.3 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 k8s.io/api v0.31.0 @@ -13,13 +14,13 @@ require ( require ( github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -27,9 +28,9 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -41,7 +42,7 @@ require ( github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -56,7 +57,7 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect @@ -67,15 +68,15 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/term v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect @@ -89,9 +90,10 @@ require ( k8s.io/apiserver v0.31.0 // indirect k8s.io/component-base v0.31.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect + sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/testdata/project-v4-multigroup/internal/controller/certmanager/certificate_controller.go b/testdata/project-v4-multigroup/internal/controller/certmanager/certificate_controller.go new file mode 100644 index 00000000000..399964fed03 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/controller/certmanager/certificate_controller.go @@ -0,0 +1,62 @@ +/* +Copyright 2024 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certmanager + +import ( + "context" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "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" +) + +// CertificateReconciler reconciles a Certificate object +type CertificateReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=certmanager.testproject.org,resources=certificates,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=certmanager.testproject.org,resources=certificates/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=certmanager.testproject.org,resources=certificates/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 Certificate object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.0/pkg/reconcile +func (r *CertificateReconciler) 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 *CertificateReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&certmanagerv1.Certificate{}). + Named("certmanager-certificate"). + Complete(r) +} diff --git a/testdata/project-v4-multigroup/internal/controller/certmanager/certificate_controller_test.go b/testdata/project-v4-multigroup/internal/controller/certmanager/certificate_controller_test.go new file mode 100644 index 00000000000..3959874773b --- /dev/null +++ b/testdata/project-v4-multigroup/internal/controller/certmanager/certificate_controller_test.go @@ -0,0 +1,32 @@ +/* +Copyright 2024 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certmanager + +import ( + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("Certificate Controller", func() { + Context("When reconciling a resource", func() { + + It("should successfully reconcile the resource", func() { + + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/testdata/project-v4-multigroup/internal/controller/certmanager/suite_test.go b/testdata/project-v4-multigroup/internal/controller/certmanager/suite_test.go new file mode 100644 index 00000000000..8a55c1d28e9 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/controller/certmanager/suite_test.go @@ -0,0 +1,95 @@ +/* +Copyright 2024 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certmanager + +import ( + "context" + "fmt" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + // +kubebuilder:scaffold:imports +) + +// 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 TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", + fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = certmanagerv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +})