# Facilitate Webhooks

Webhook validates and mutates the Object before it get stored in api-server. Usage and example can be found in [Implementing defaulting/validating webhooks](https://book.kubebuilder.io/cronjob-tutorial/webhook-implementation.html).

## Add Webhooks for Current Resources

Edit(2021/06/07): you will have to edit the generated files since we have modifications on the skeleton, see next chapter.

```
kubebuilder create webhook --group cloud --version v1 --kind ProviderType --defaulting --programmatic-validation
kubebuilder create webhook --group cloud --version v1 --kind Provider --defaulting --programmatic-validation
kubebuilder create webhook --group cloud --version v1 --kind NetworkPeer --defaulting --programmatic-validation
kubebuilder create webhook --group cloud --version v1 --kind Import --defaulting --programmatic-validation
kubebuilder create webhook --group cloud --version v1 --kind Network --defaulting --programmatic-validation
kubebuilder create webhook --group cloud --version v1 --kind Cluster --defaulting --programmatic-validation
kubebuilder create webhook --group cloud --version v1 --kind Backup --defaulting --programmatic-validation
kubebuilder create webhook --group cloud --version v1 --kind Restore --defaulting --programmatic-validation
```

## Important: modifications on kubebuilder skeleton

Since the webhook generated by kubeduiler does not support querying the api-server when validate/mutate the request, we
have the following modifications on the skeleton:

1. Make `setupWithManager()` method private:

```diff
- func (r *NetworkPeer) SetupWebhookWithManager(mgr ctrl.Manager) error {
+ func (r *NetworkPeer) setupWebhookWithManager(mgr ctrl.Manager) error {
```

2. Call `setupWithManager()` in a unified entrypoint function (located in `api/v1/webhook_helper.go`):

```go
func RegisterWebhooks(mgr ctrl.Manager) error {
	var err error
	once.Do(func() {
		webhookClient = readThroughReader{
			cached: mgr.GetClient(),
			direct: mgr.GetAPIReader(),
		}
	})
	if err = (&ProviderType{}).setupWebhookWithManager(mgr); err != nil {
		return err
	}
	// ... other hooks
	return nil
}
```

3. `RegisterWebhooks()` call will initialize a package-scope `readThroughReader` once, which can be safely used in the webhook. `readThroughReader` will read from the local cache first and read from the api-server directly if not found. So, when you want to test whether an object is `NotExist`, use `readThroughReader.direct` instead.

## Example: Inter-objects Validation

```go
func (r *Provider) ValidateCreate() error {
	providerlog.Info("validate create", "name", r.Name)

	var allErrs field.ErrorList

	ptPath := field.NewPath("spec").Child("type")
	var pt ProviderType
	err := webhookClient.Get(context.TODO(), types.NamespacedName{Name: r.Spec.Type}, &pt)
	if err != nil {
		if apierrors.IsNotFound(err) {
			allErrs = append(allErrs, field.Invalid(ptPath, r.Spec.Type, "provider type is not declared"))
		}
		allErrs = append(allErrs, field.Invalid(ptPath, r.Spec.Type, err.Error()))
	}

	return invalidOrNil(allErrs, r)
}
```

The above function validates whether the `Provider` is referencing a valid `ProviderType`.

## Example: Immutable Field

```go
func (r *Provider) ValidateUpdate(old runtime.Object) error {
	providerlog.Info("validate update", "name", r.Name)

	var allErrs field.ErrorList

	oldR := old.(*Provider)
	if (oldR.Spec.Type != r.Spec.Type) {
		allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("type"), r.Spec.Type, "field is immutable"))
	}

	return invalidOrNil(allErrs, r)
}
```

The above function ensures the `.spec.type` field is immutable.

## Example: Test Your Webhook

see [provider_types_test.go](../../api/v1/provider_types_test.go)

Always make sure that your webhooks are unit tested!
