diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 6bc70fa7c67..a0b56ca8b60 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -82,7 +82,6 @@ - [Kind for Dev & CI](reference/kind.md) - [What's a webhook?](reference/webhook-overview.md) - [Admission webhook](reference/admission-webhook.md) - - [Webhooks for Core Types](reference/webhook-for-core-types.md) - [Markers for Config/Code Generation](./reference/markers.md) - [CRD Generation](./reference/markers/crd.md) diff --git a/docs/book/src/reference/project-config.md b/docs/book/src/reference/project-config.md index 2b7be3bafe8..0b9faa2a536 100644 --- a/docs/book/src/reference/project-config.md +++ b/docs/book/src/reference/project-config.md @@ -130,29 +130,30 @@ version: "3" Now let's check its layout fields definition: -| Field | Description | -|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `layout` | Defines the global plugins, e.g. a project `init` with `--plugins="go/v4,deploy-image/v1-alpha"` means that any sub-command used will always call its implementation for both plugins in a chain. | -| `domain` | Store the domain of the project. This information can be provided by the user when the project is generate with the `init` sub-command and the `domain` flag. | -| `plugins` | Defines the plugins used to do custom scaffolding, e.g. to use the optional `deploy-image/v1-alpha` plugin to do scaffolding for just a specific api via the command `kubebuider create api [options] --plugins=deploy-image/v1-alpha`. | -| `projectName` | The name of the project. This will be used to scaffold the manager data. By default it is the name of the project directory, however, it can be provided by the user in the `init` sub-command via the `--project-name` flag. | -| `repo` | The project repository which is the Golang module, e.g `github.com/example/myproject-operator`. | -| `resources` | An array of all resources which were scaffolded in the project. | -| `resources.api` | The API scaffolded in the project via the sub-command `create api`. | -| `resources.api.crdVersion` | The Kubernetes API version (`apiVersion`) used to do the scaffolding for the CRD resource. | -| `resources.api.namespaced` | The API RBAC permissions which can be namespaced or cluster scoped. | -| `resources.controller` | Indicates whether a controller was scaffolded for the API. | -| `resources.domain` | The domain of the resource which was provided by the `--domain` flag when the project was initialized or via the flag `--external-api-domain` when it was used to scaffold controllers for an [External Type][external-type]. | -| `resources.group` | The GKV group of the resource which is provided by the `--group` flag when the sub-command `create api` is used. | -| `resources.version` | The GKV version of the resource which is provided by the `--version` flag when the sub-command `create api` is used. | -| `resources.kind` | Store GKV Kind of the resource which is provided by the `--kind` flag when the sub-command `create api` is used. | +| Field | Description | +|-------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `layout` | Defines the global plugins, e.g. a project `init` with `--plugins="go/v4,deploy-image/v1-alpha"` means that any sub-command used will always call its implementation for both plugins in a chain. | +| `domain` | Store the domain of the project. This information can be provided by the user when the project is generate with the `init` sub-command and the `domain` flag. | +| `plugins` | Defines the plugins used to do custom scaffolding, e.g. to use the optional `deploy-image/v1-alpha` plugin to do scaffolding for just a specific api via the command `kubebuider create api [options] --plugins=deploy-image/v1-alpha`. | +| `projectName` | The name of the project. This will be used to scaffold the manager data. By default it is the name of the project directory, however, it can be provided by the user in the `init` sub-command via the `--project-name` flag. | +| `repo` | The project repository which is the Golang module, e.g `github.com/example/myproject-operator`. | +| `resources` | An array of all resources which were scaffolded in the project. | +| `resources.api` | The API scaffolded in the project via the sub-command `create api`. | +| `resources.api.crdVersion` | The Kubernetes API version (`apiVersion`) used to do the scaffolding for the CRD resource. | +| `resources.api.namespaced` | The API RBAC permissions which can be namespaced or cluster scoped. | +| `resources.controller` | Indicates whether a controller was scaffolded for the API. | +| `resources.domain` | The domain of the resource which was provided by the `--domain` flag when the project was initialized or via the flag `--external-api-domain` when it was used to scaffold controllers for an [External Type][external-type]. | +| `resources.group` | The GKV group of the resource which is provided by the `--group` flag when the sub-command `create api` is used. | +| `resources.version` | The GKV version of the resource which is provided by the `--version` flag when the sub-command `create api` is used. | +| `resources.kind` | Store GKV Kind of the resource which is provided by the `--kind` flag when the sub-command `create api` is used. | | `resources.path` | The import path for the API resource. It will be `/api/` unless the API added to the project is an external or core-type. For the core-types scenarios, the paths used are mapped [here][core-types]. Or either the path informed by the flag `--external-api-path` | -| `resources.external` | It is `true` when the flag `--external-api-path` was used to generated the scaffold for an [External Type][external-type]. | -| `resources.webhooks` | Store the webhooks data when the sub-command `create webhook` is used. | -| `resources.webhooks.webhookVersion` | The Kubernetes API version (`apiVersion`) used to scaffold the webhook resource. | -| `resources.webhooks.conversion` | It is `true` when the webhook was scaffold with the `--conversion` flag which means that is a conversion webhook. | -| `resources.webhooks.defaulting` | It is `true` when the webhook was scaffold with the `--defaulting` flag which means that is a defaulting webhook. | -| `resources.webhooks.validation` | It is `true` when the webhook was scaffold with the `--programmatic-validation` flag which means that is a validation webhook. | +| `resources.core` | It is `true` when the group used is from Kubernetes API and the API resource is not defined on the project. | +| `resources.external` | It is `true` when the flag `--external-api-path` was used to generated the scaffold for an [External Type][external-type]. | +| `resources.webhooks` | Store the webhooks data when the sub-command `create webhook` is used. | +| `resources.webhooks.webhookVersion` | The Kubernetes API version (`apiVersion`) used to scaffold the webhook resource. | +| `resources.webhooks.conversion` | It is `true` when the webhook was scaffold with the `--conversion` flag which means that is a conversion webhook. | +| `resources.webhooks.defaulting` | It is `true` when the webhook was scaffold with the `--defaulting` flag which means that is a defaulting webhook. | +| `resources.webhooks.validation` | It is `true` when the webhook was scaffold with the `--programmatic-validation` flag which means that is a validation webhook. | [project]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/testdata/project-v3/PROJECT [versioning]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/VERSIONING.md#Versioning diff --git a/docs/book/src/reference/using_an_external_resource.md b/docs/book/src/reference/using_an_external_resource.md index 5cecb3609cd..7359e0f887c 100644 --- a/docs/book/src/reference/using_an_external_resource.md +++ b/docs/book/src/reference/using_an_external_resource.md @@ -73,18 +73,6 @@ Also, the RBAC role: 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 Webhook to Manage an External Type - - - ## Managing Core Types Core Kubernetes API types, such as `Pods`, `Services`, and `Deployments`, are predefined by Kubernetes. @@ -169,18 +157,15 @@ Also, the RBAC for the above markers: - update ``` -``` - This scaffolds a controller for the Core type `corev1.Pod` but skips creating new resource definitions since the type is already defined in the Kubernetes API. ### Creating a Webhook to Manage a Core Type - +```go +kubebuilder create webhook --group core --version v1 --kind Pod --programmatic-validation +``` -[webhook-for-core-types]: ./webhook-for-core-types.md diff --git a/docs/book/src/reference/webhook-for-core-types.md b/docs/book/src/reference/webhook-for-core-types.md deleted file mode 100644 index f2eac3e8a86..00000000000 --- a/docs/book/src/reference/webhook-for-core-types.md +++ /dev/null @@ -1,328 +0,0 @@ -# Admission Webhook for Core Types - -It is very easy to build admission webhooks for CRDs, which has been covered in -the [CronJob tutorial][cronjob-tutorial]. Given that kubebuilder doesn't support webhook scaffolding -for core types, you have to use the library from controller-runtime to handle it. -There is an [example](https://github.com/kubernetes-sigs/controller-runtime/tree/master/examples/builtins) -in controller-runtime. - -It is suggested to use kubebuilder to initialize a project, and then you can -follow the steps below to add admission webhooks for core types. - -## Implementing Your Handler Using `Handle` - -Your handler must implement the [admission.Handler](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/webhook/admission#Handler) interface. This function is responsible for both mutating and validating the incoming resource. - -### Update your webhook: - -**Example** - -```go -package v1 - -import ( - "context" - "encoding/json" - "net/http" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - corev1 "k8s.io/api/core/v1" -) - -// **Note**: in order to have controller-gen generate the webhook configuration for you, you need to add markers. For example: - -// +kubebuilder:webhook:path=/mutate--v1-pod,mutating=true,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=mpod.kb.io - -type podAnnotator struct { - Client client.Client - decoder *admission.Decoder -} - -func (a *podAnnotator) Handle(ctx context.Context, req admission.Request) admission.Response { - pod := &corev1.Pod{} - err := a.decoder.Decode(req, pod) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - // Mutate the fields in pod - pod.Annotations["example.com/mutated"] = "true" - - marshaledPod, err := json.Marshal(pod) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - return admission.Patched(req.Object.Raw, marshaledPod) -} -``` - - -## Update main.go - -Now you need to register your handler in the webhook server. - -```go -mgr.GetWebhookServer().Register("/mutate--v1-pod", &webhook.Admission{ - Handler: &podAnnotator{Client: mgr.GetClient()}, -}) -``` - -You need to ensure the path here match the path in the marker. - -### Client/Decoder - -If you need a client and/or decoder, just pass them in at struct construction time. - -```go -mgr.GetWebhookServer().Register("/mutate--v1-pod", &webhook.Admission{ - Handler: &podAnnotator{ - Client: mgr.GetClient(), - decoder: admission.NewDecoder(mgr.GetScheme()), - }, -}) -``` - -## By using Custom interfaces instead of Handle - -### Update your webhook: - -**Example** - -```go -package v1 - -import ( - "context" - "fmt" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var podlog = logf.Log.WithName("pod-resource") - -// SetupWebhookWithManager will setup the manager to manage the webhooks -func (r *corev1.Pod) SetupWebhookWithManager(mgr ctrl.Manager) error { - runAsNonRoot := true - allowPrivilegeEscalation := false - - return ctrl.NewWebhookManagedBy(mgr). - For(r). - WithValidator(&PodCustomValidator{}). - WithDefaulter(&PodCustomDefaulter{ - DefaultSecurityContext: &corev1.SecurityContext{ - RunAsNonRoot: &runAsNonRoot, // Set to true - AllowPrivilegeEscalation: &allowPrivilegeEscalation, // Set to false - }, - }). - Complete() -} - -// +kubebuilder:webhook:path=/mutate--v1-pod,mutating=true,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=mpod.kb.io,admissionReviewVersions=v1 - -// +kubebuilder:object:generate=false -// PodCustomDefaulter struct is responsible for setting default values on the Pod resource -// when it is created or updated. -// -// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, -// as it is used only for temporary operations and does not need to be deeply copied. -type PodCustomDefaulter struct { - // Default security context to be applied to Pods - DefaultSecurityContext *corev1.SecurityContext - - // TODO: Add more fields as needed for defaulting -} - -var _ webhook.CustomDefaulter = &PodCustomDefaulter{} - -// Default implements webhook.CustomDefaulter so a webhook will be registered for the type Pod -func (d *PodCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { - pod, ok := obj.(*corev1.Pod) - if !ok { - return fmt.Errorf("expected a Pod object but got %T", obj) - } - podlog.Info("CustomDefaulter for corev1.Pod", "name", pod.GetName()) - - // Apply the default security context if it's not set - for i := range pod.Spec.Containers { - if pod.Spec.Containers[i].SecurityContext == nil { - pod.Spec.Containers[i].SecurityContext = d.DefaultSecurityContext - } - } - - // Mutate the fields in Pod (e.g., adding an annotation) - if pod.Annotations == nil { - pod.Annotations = map[string]string{} - } - pod.Annotations["example.com/mutated"] = "true" - - // TODO: Add any additional defaulting logic here. - - return nil -} - -// +kubebuilder:webhook:path=/validate--v1-pod,mutating=false,failurePolicy=fail,groups="",resources=pods,verbs=create;update;delete,versions=v1,name=vpod.kb.io,admissionReviewVersions=v1 - -// +kubebuilder:object:generate=false -// PodCustomValidator struct is responsible for validating the Pod resource -// when it is created, updated, or deleted. -// -// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, -// as this struct is used only for temporary operations and does not need to be deeply copied. -type PodCustomValidator struct { -} - -var _ webhook.CustomValidator = &PodCustomValidator{} - -// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Pod -func (v *PodCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - pod, ok := obj.(*corev1.Pod) - if !ok { - return nil, fmt.Errorf("expected a Pod object but got %T", obj) - } - podlog.Info("Validation for corev1.Pod upon creation", "name", pod.GetName()) - - // Ensure the Pod has at least one container - if len(pod.Spec.Containers) == 0 { - return nil, fmt.Errorf("pod must have at least one container") - } - - // TODO: Add any additional creation validation logic here. - - return nil, nil -} - -// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Pod -func (v *PodCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - pod, ok := newObj.(*corev1.Pod) - if !ok { - return nil, fmt.Errorf("expected a Pod object but got %T", newObj) - } - podlog.Info("Validation for corev1.Pod upon Update", "name", pod.GetName()) - - oldPod := oldObj.(*corev1.Pod) - // Prevent changing a specific annotation - if oldPod.Annotations["example.com/protected"] != pod.Annotations["example.com/protected"] { - return nil, fmt.Errorf("the annotation 'example.com/protected' cannot be changed") - } - - // Prevent changing the security context after creation - for i := range pod.Spec.Containers { - if !equalSecurityContexts(oldPod.Spec.Containers[i].SecurityContext, pod.Spec.Containers[i].SecurityContext) { - return nil, fmt.Errorf("security context of containers cannot be changed after creation") - } - } - - // TODO: Add any additional update validation logic here. - - return nil, nil -} - -// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Pod -func (v *PodCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - pod, ok := obj.(*corev1.Pod) - if !ok { - return nil, fmt.Errorf("expected a Pod object but got %T", obj) - } - podlog.Info("Deletion for corev1.Pod upon Update", "name", pod.GetName()) - - // Prevent deletion of protected Pods - if pod.Annotations["example.com/protected"] == "true" { - return nil, fmt.Errorf("protected pods cannot be deleted") - } - - // TODO: Add any additional deletion validation logic here. - - return nil, nil -} - -// equalSecurityContexts checks if two SecurityContexts are equal -func equalSecurityContexts(a, b *corev1.SecurityContext) bool { - // Implement your logic to compare SecurityContexts here - // For example, you can compare specific fields: - return a.RunAsNonRoot == b.RunAsNonRoot && - a.AllowPrivilegeEscalation == b.AllowPrivilegeEscalation -} - -``` - -### Update the main.go - -```go -if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err := (&corev1.Pod{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "corev1.Pod") - os.Exit(1) - } -} -``` - -## Deploy - -Deploying it is just like deploying a webhook server for CRD. You need to -1) provision the serving certificate -2) deploy the server - -You can follow the [tutorial](/cronjob-tutorial/running.md). - -## What are `Handle` and Custom Interfaces? - -In the context of Kubernetes admission webhooks, the `Handle` function and the custom interfaces (`CustomValidator` and `CustomDefaulter`) are two different approaches to implementing webhook logic. Each serves specific purposes, and the choice between them depends on the needs of your webhook. - -## Purpose of the `Handle` Function - -The `Handle` function is a core part of the admission webhook process. It is responsible for directly processing the incoming admission request and returning an `admission.Response`. This function is particularly useful when you need to handle both validation and mutation within the same function. - -### Mutation - -If your webhook needs to modify the resource (e.g., add or change annotations, labels, or other fields), the `Handle` function is where you would implement this logic. Mutation involves altering the resource before it is persisted in Kubernetes. - -### Response Construction - -The `Handle` function is also responsible for constructing the `admission.Response`, which determines whether the request should be allowed or denied, or if the resource should be patched (mutated). The `Handle` function gives you full control over how the response is built and what changes are applied to the resource. - -## Purpose of Custom Interfaces (`CustomValidator` and `CustomDefaulter`) - -The `CustomValidator` and `CustomDefaulter` interfaces provide a more modular approach to implementing webhook logic. They allow you to separate validation and defaulting (mutation) into distinct methods, making the code easier to maintain and reason about. - -## When to Use Each Approach - -- **Use `Handle` when**: - - You need to both mutate and validate the resource in a single function. - - You want direct control over how the admission response is constructed and returned. - - Your webhook logic is simple and doesn’t require a clear separation of concerns. - -- **Use `CustomValidator` and `CustomDefaulter` when**: - - You want to separate validation and defaulting logic for better modularity. - - Your webhook logic is complex, and separating concerns makes the code easier to manage. - - You don’t need to perform mutation and validation in the same function. - -[cronjob-tutorial]: /cronjob-tutorial/cronjob-tutorial.md \ No newline at end of file diff --git a/pkg/model/resource/resource.go b/pkg/model/resource/resource.go index c455c1f4b57..84000e958f0 100644 --- a/pkg/model/resource/resource.go +++ b/pkg/model/resource/resource.go @@ -45,6 +45,9 @@ type Resource struct { // External specifies if the resource is defined externally. External bool `json:"external,omitempty"` + + // Core specifies if the resource is from Kubernetes API. + Core bool `json:"core,omitempty"` } // Validate checks that the Resource is valid. diff --git a/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go b/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go index d6d8328eb25..934ab7f8841 100644 --- a/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go +++ b/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go @@ -73,7 +73,7 @@ func (s *webhookScaffolder) Scaffold() error { return fmt.Errorf("error updating resource: %w", err) } - if err := scaffold.Execute( + buildScaffold := []machinery.Builder{ &kdefault.WebhookCAInjectionPatch{}, &kdefault.ManagerWebhookPatch{}, &webhook.Kustomization{Force: s.force}, @@ -85,8 +85,13 @@ func (s *webhookScaffolder) Scaffold() error { &patches.EnableWebhookPatch{}, &patches.EnableCAInjectionPatch{}, &network_policy.NetworkPolicyAllowWebhooks{}, - &crd.Kustomization{}, - ); err != nil { + } + + if !s.resource.Core { + buildScaffold = append(buildScaffold, &crd.Kustomization{}) + } + + if err := scaffold.Execute(buildScaffold...); err != nil { return fmt.Errorf("error scaffolding kustomize webhook manifests: %v", err) } diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index e91b57260df..ea55db3eac4 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -131,6 +131,7 @@ func (opts Options) UpdateResource(res *resource.Resource, c config.Config) { } else { // Handle core types if domain, found := coreGroups[res.Group]; found { + res.Core = true res.Domain = domain res.Path = path.Join("k8s.io", "api", res.Group, res.Version) } diff --git a/pkg/plugins/golang/v4/webhook.go b/pkg/plugins/golang/v4/webhook.go index a78ddff850e..880fd6f86e3 100644 --- a/pkg/plugins/golang/v4/webhook.go +++ b/pkg/plugins/golang/v4/webhook.go @@ -106,9 +106,13 @@ func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { } // 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 { + resValue, err := p.config.GetResource(p.resource.GVK) + res = &resValue + if err != nil { + if !p.resource.Core { + return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + } + } else if res.Webhooks != nil && !res.Webhooks.IsEmpty() && !p.force { return fmt.Errorf("webhook resource already exists") } diff --git a/test/testdata/generate.sh b/test/testdata/generate.sh index b9888de408d..6dcca3de9ce 100755 --- a/test/testdata/generate.sh +++ b/test/testdata/generate.sh @@ -47,6 +47,8 @@ function scaffold_test_project { $kb create webhook --group crew --version v1 --kind Admiral --plural=admirales --defaulting # 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 --external-api-domain=cert-manager.io + # Webhook for Core type + $kb create webhook --group core --version v1 --kind Pod --defaulting fi if [[ $project =~ multigroup ]]; then @@ -73,6 +75,9 @@ function scaffold_test_project { $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 --external-api-domain=cert-manager.io + + # Webhook for Core type + $kb create webhook --group core --version v1 --kind Pod --programmatic-validation fi if [[ $project =~ multigroup ]] || [[ $project =~ with-plugins ]] ; then diff --git a/testdata/project-v4-multigroup/PROJECT b/testdata/project-v4-multigroup/PROJECT index 8d854bab7a5..500ac787632 100644 --- a/testdata/project-v4-multigroup/PROJECT +++ b/testdata/project-v4-multigroup/PROJECT @@ -103,6 +103,7 @@ resources: path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo.policy/v1 version: v1 - controller: true + core: true group: apps kind: Deployment path: k8s.io/api/apps/v1 @@ -132,6 +133,14 @@ resources: kind: Certificate path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 version: v1 +- core: true + group: core + kind: Pod + path: k8s.io/api/core/v1 + version: v1 + webhooks: + validation: true + webhookVersion: 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 e57587069b1..c5a77f4004b 100644 --- a/testdata/project-v4-multigroup/cmd/main.go +++ b/testdata/project-v4-multigroup/cmd/main.go @@ -56,6 +56,7 @@ import ( foopolicycontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/foo.policy" seacreaturescontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/sea-creatures" shipcontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/ship" + webhookcorev1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/core/v1" webhookcrewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/crew/v1" webhookexamplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1" webhookshipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/ship/v1" @@ -283,6 +284,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Certificate") os.Exit(1) } + // nolint:goconst + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = webhookcorev1.SetupPodWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Pod") + os.Exit(1) + } + } if err = (&examplecomcontroller.MemcachedReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_core_pods.yaml b/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_core_pods.yaml new file mode 100644 index 00000000000..b1ab830f8f6 --- /dev/null +++ b/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_core_pods.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: pods.core diff --git a/testdata/project-v4-multigroup/config/crd/patches/webhook_in_core_pods.yaml b/testdata/project-v4-multigroup/config/crd/patches/webhook_in_core_pods.yaml new file mode 100644 index 00000000000..8fa5d252208 --- /dev/null +++ b/testdata/project-v4-multigroup/config/crd/patches/webhook_in_core_pods.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: pods.core +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/testdata/project-v4-multigroup/config/webhook/manifests.yaml b/testdata/project-v4-multigroup/config/webhook/manifests.yaml index 3f6221647a1..2e9c359031d 100644 --- a/testdata/project-v4-multigroup/config/webhook/manifests.yaml +++ b/testdata/project-v4-multigroup/config/webhook/manifests.yaml @@ -50,6 +50,26 @@ kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-core-v1-pod + failurePolicy: Fail + name: vpod-v1.kb.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - pods + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/testdata/project-v4-multigroup/dist/install.yaml b/testdata/project-v4-multigroup/dist/install.yaml index d755f2547d2..3e8f7e84bc3 100644 --- a/testdata/project-v4-multigroup/dist/install.yaml +++ b/testdata/project-v4-multigroup/dist/install.yaml @@ -1860,6 +1860,26 @@ kind: ValidatingWebhookConfiguration metadata: name: project-v4-multigroup-validating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: project-v4-multigroup-webhook-service + namespace: project-v4-multigroup-system + path: /validate-core-v1-pod + failurePolicy: Fail + name: vpod-v1.kb.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - pods + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook.go b/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook.go new file mode 100644 index 00000000000..0753713f967 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook.go @@ -0,0 +1,97 @@ +/* +Copyright 2024 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// nolint:unused +// log is for logging in this package. +var podlog = logf.Log.WithName("pod-resource") + +// SetupPodWebhookWithManager registers the webhook for Pod in the manager. +func SetupPodWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&corev1.Pod{}). + WithValidator(&PodCustomValidator{}). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here. +// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. +// +kubebuilder:webhook:path=/validate-core-v1-pod,mutating=false,failurePolicy=fail,sideEffects=None,groups=core,resources=pods,verbs=create;update,versions=v1,name=vpod-v1.kb.io,admissionReviewVersions=v1 + +// PodCustomValidator struct is responsible for validating the Pod resource +// when it is created, updated, or deleted. +// +// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, +// as this struct is used only for temporary operations and does not need to be deeply copied. +type PodCustomValidator struct { + //TODO(user): Add more fields as needed for validation +} + +var _ webhook.CustomValidator = &PodCustomValidator{} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Pod. +func (v *PodCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + pod, ok := obj.(*corev1.Pod) + if !ok { + return nil, fmt.Errorf("expected a Pod object but got %T", obj) + } + podlog.Info("Validation for Pod upon creation", "name", pod.GetName()) + + // TODO(user): fill in your validation logic upon object creation. + + return nil, nil +} + +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Pod. +func (v *PodCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + pod, ok := newObj.(*corev1.Pod) + if !ok { + return nil, fmt.Errorf("expected a Pod object for the newObj but got %T", newObj) + } + podlog.Info("Validation for Pod upon update", "name", pod.GetName()) + + // TODO(user): fill in your validation logic upon object update. + + return nil, nil +} + +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Pod. +func (v *PodCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + pod, ok := obj.(*corev1.Pod) + if !ok { + return nil, fmt.Errorf("expected a Pod object but got %T", obj) + } + podlog.Info("Validation for Pod upon deletion", "name", pod.GetName()) + + // TODO(user): fill in your validation logic upon object deletion. + + return nil, nil +} diff --git a/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook_test.go b/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook_test.go new file mode 100644 index 00000000000..588a28131e9 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2024 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + // TODO (user): Add any additional imports if needed +) + +var _ = Describe("Pod Webhook", func() { + var ( + obj *corev1.Pod + oldObj *corev1.Pod + validator PodCustomValidator + ) + + BeforeEach(func() { + obj = &corev1.Pod{} + oldObj = &corev1.Pod{} + validator = PodCustomValidator{} + Expect(validator).NotTo(BeNil(), "Expected validator to be initialized") + Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") + // TODO (user): Add any setup logic common to all tests + }) + + AfterEach(func() { + // TODO (user): Add any teardown logic common to all tests + }) + + Context("When creating or updating Pod under Validating Webhook", func() { + // TODO (user): Add logic for validating webhooks + // Example: + // It("Should deny creation if a required field is missing", func() { + // By("simulating an invalid creation scenario") + // obj.SomeRequiredField = "" + // Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred()) + // }) + // + // It("Should admit creation if all required fields are present", func() { + // By("simulating an invalid creation scenario") + // obj.SomeRequiredField = "valid_value" + // Expect(validator.ValidateCreate(ctx, obj)).To(BeNil()) + // }) + // + // It("Should validate updates correctly", func() { + // By("simulating a valid update scenario") + // oldObj.SomeRequiredField = "updated_value" + // obj.SomeRequiredField = "updated_value" + // Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil()) + // }) + }) + +}) diff --git a/testdata/project-v4-multigroup/internal/webhook/core/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/internal/webhook/core/v1/webhook_suite_test.go new file mode 100644 index 00000000000..c6fcf0ebb14 --- /dev/null +++ b/testdata/project-v4-multigroup/internal/webhook/core/v1/webhook_suite_test.go @@ -0,0 +1,149 @@ +/* +Copyright 2024 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "runtime" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + + // +kubebuilder:scaffold:imports + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + cancel context.CancelFunc + cfg *rest.Config + ctx context.Context + k8sClient client.Client + testEnv *envtest.Environment +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Webhook Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "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)), + + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "..", "..", "config", "webhook")}, + }, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := apimachineryruntime.NewScheme() + err = corev1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager. + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + WebhookServer: webhook.NewServer(webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + }), + LeaderElection: false, + Metrics: metricsserver.Options{BindAddress: "0"}, + }) + Expect(err).NotTo(HaveOccurred()) + + err = SetupPodWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:webhook + + 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:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + + return conn.Close() + }).Should(Succeed()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/testdata/project-v4/PROJECT b/testdata/project-v4/PROJECT index e65c3db0df4..434965ec57e 100644 --- a/testdata/project-v4/PROJECT +++ b/testdata/project-v4/PROJECT @@ -52,4 +52,12 @@ resources: kind: Certificate path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 version: v1 +- core: true + group: core + kind: Pod + path: k8s.io/api/core/v1 + version: v1 + webhooks: + defaulting: true + webhookVersion: v1 version: "3" diff --git a/testdata/project-v4/cmd/main.go b/testdata/project-v4/cmd/main.go index b3384d0d988..6fc5a3786a2 100644 --- a/testdata/project-v4/cmd/main.go +++ b/testdata/project-v4/cmd/main.go @@ -39,6 +39,7 @@ import ( crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/controller" + webhookcorev1 "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhook/v1" webhookcrewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhook/v1" // +kubebuilder:scaffold:imports ) @@ -197,6 +198,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Certificate") os.Exit(1) } + // nolint:goconst + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = webhookcorev1.SetupPodWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Pod") + os.Exit(1) + } + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/testdata/project-v4/config/crd/patches/cainjection_in_pods.yaml b/testdata/project-v4/config/crd/patches/cainjection_in_pods.yaml new file mode 100644 index 00000000000..b1ab830f8f6 --- /dev/null +++ b/testdata/project-v4/config/crd/patches/cainjection_in_pods.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: pods.core diff --git a/testdata/project-v4/config/crd/patches/webhook_in_pods.yaml b/testdata/project-v4/config/crd/patches/webhook_in_pods.yaml new file mode 100644 index 00000000000..8fa5d252208 --- /dev/null +++ b/testdata/project-v4/config/crd/patches/webhook_in_pods.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: pods.core +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/testdata/project-v4/config/webhook/manifests.yaml b/testdata/project-v4/config/webhook/manifests.yaml index 002aef077f4..65410b884d5 100644 --- a/testdata/project-v4/config/webhook/manifests.yaml +++ b/testdata/project-v4/config/webhook/manifests.yaml @@ -44,6 +44,26 @@ webhooks: resources: - captains sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-core-v1-pod + failurePolicy: Fail + name: mpod-v1.kb.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - pods + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration diff --git a/testdata/project-v4/dist/install.yaml b/testdata/project-v4/dist/install.yaml index bc03981dc80..e7d37686402 100644 --- a/testdata/project-v4/dist/install.yaml +++ b/testdata/project-v4/dist/install.yaml @@ -688,6 +688,26 @@ webhooks: resources: - captains sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: project-v4-webhook-service + namespace: project-v4-system + path: /mutate-core-v1-pod + failurePolicy: Fail + name: mpod-v1.kb.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - pods + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration diff --git a/testdata/project-v4/internal/webhook/v1/pod_webhook.go b/testdata/project-v4/internal/webhook/v1/pod_webhook.go new file mode 100644 index 00000000000..59194faac82 --- /dev/null +++ b/testdata/project-v4/internal/webhook/v1/pod_webhook.go @@ -0,0 +1,68 @@ +/* +Copyright 2024 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// nolint:unused +// log is for logging in this package. +var podlog = logf.Log.WithName("pod-resource") + +// SetupPodWebhookWithManager registers the webhook for Pod in the manager. +func SetupPodWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&corev1.Pod{}). + WithDefaulter(&PodCustomDefaulter{}). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// +kubebuilder:webhook:path=/mutate-core-v1-pod,mutating=true,failurePolicy=fail,sideEffects=None,groups=core,resources=pods,verbs=create;update,versions=v1,name=mpod-v1.kb.io,admissionReviewVersions=v1 + +// PodCustomDefaulter struct is responsible for setting default values on the custom resource of the +// Kind Pod when those are created or updated. +// +// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, +// as it is used only for temporary operations and does not need to be deeply copied. +type PodCustomDefaulter struct { + // TODO(user): Add more fields as needed for defaulting +} + +var _ webhook.CustomDefaulter = &PodCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Pod. +func (d *PodCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + pod, ok := obj.(*corev1.Pod) + + if !ok { + return fmt.Errorf("expected an Pod object but got %T", obj) + } + podlog.Info("Defaulting for Pod", "name", pod.GetName()) + + // TODO(user): fill in your defaulting logic. + + return nil +} diff --git a/testdata/project-v4/internal/webhook/v1/pod_webhook_test.go b/testdata/project-v4/internal/webhook/v1/pod_webhook_test.go new file mode 100644 index 00000000000..1d7c3191c5a --- /dev/null +++ b/testdata/project-v4/internal/webhook/v1/pod_webhook_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 The Kubernetes authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + // TODO (user): Add any additional imports if needed +) + +var _ = Describe("Pod Webhook", func() { + var ( + obj *corev1.Pod + oldObj *corev1.Pod + defaulter PodCustomDefaulter + ) + + BeforeEach(func() { + obj = &corev1.Pod{} + oldObj = &corev1.Pod{} + defaulter = PodCustomDefaulter{} + Expect(defaulter).NotTo(BeNil(), "Expected defaulter to be initialized") + Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") + // TODO (user): Add any setup logic common to all tests + }) + + AfterEach(func() { + // TODO (user): Add any teardown logic common to all tests + }) + + Context("When creating Pod under Defaulting Webhook", func() { + // TODO (user): Add logic for defaulting webhooks + // Example: + // It("Should apply defaults when a required field is empty", func() { + // By("simulating a scenario where defaults should be applied") + // obj.SomeFieldWithDefault = "" + // By("calling the Default method to apply defaults") + // defaulter.Default(ctx, obj) + // By("checking that the default values are set") + // Expect(obj.SomeFieldWithDefault).To(Equal("default_value")) + // }) + }) + +}) diff --git a/testdata/project-v4/internal/webhook/v1/webhook_suite_test.go b/testdata/project-v4/internal/webhook/v1/webhook_suite_test.go index c49251cd192..6f3bcc160e0 100644 --- a/testdata/project-v4/internal/webhook/v1/webhook_suite_test.go +++ b/testdata/project-v4/internal/webhook/v1/webhook_suite_test.go @@ -124,6 +124,9 @@ var _ = BeforeSuite(func() { err = SetupAdmiralWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) + err = SetupPodWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:webhook go func() {