# Use Provider Type Label To Route Resource Efficiently

## Problem

By design, infra resources are belongs to different `ProviderTypes`. However, the `ProviderType` of resource is implicit, thus
clients cannot filter resources by `ProviderType` efficient.

## Proposal

Automatically add a `ProvderType` label to resources, the form is

```
cloud.pingcap.com/provider-type: <ProviderType>
```

e.g.

```
cloud.pingcap.com/provider-type: aws
```

The automation is implemented in the webhook, including `Defaults` and `Validate`:

### Default

`Default` should try to set the `ProviderType` label if not present

```go
func (r *Obj) Default() {
	if r.Labels == nil {
		r.Labels = map[string]string{}
	}
	if _, ok := r.Labels[ProviderTypeLabel]; !ok {
		pt, err := findType()
		if err == nil {
			r.Labels[ProviderTypeLabel] = pt
		}
	}
}
```

The provider type label might be wrong in the following cases:

- Client set a wrong provider type label
- `findType()` encounters error

We address such problems in `Validation`

### Validation

`ValidateCreate()/ValidateUpdate()` should valid whether the provider type label is correct:

```go
func (r *Obj) ValidateCreate() error {
	var allErrs field.ErrorList

	if r.Labels == nil {
		r.Labels = map[string]string{}
	}
	if invalid(r.Labels[ProviderTypeLabel]) {
		allErrs = append(allErrs, badProviderTypeLabel())
	}
	return invalidOrNil(allErrs, r)
}
```

So resources with wrong/empty provider type labels will never get stored in infra api-server.

### Providers

The above webhooks ensures that all resources will have a correct `ProviderType` label. So different providers can watch
the resources they are interested in by the `ProviderType` label.

```go
func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
	pred, err := predicate.LabelSelectorPredicate(metav1.LabelSelector{
		MatchLabels: map[string]string{
			cloudv1.ProviderTypeLabel: r.Provider,
		},
	})
	return ctrl.NewControllerManagedBy(mgr).
		For(&cloudv1.Cluster{}, builder.WithPredicates(pred)).
		Complete(r)
}
```

Currently, `aws` and `gcp` provider shares the same controller code and are bundled in a same process, so we register
a same reconciler with different `ProviderType` parameter twice:

```go
if err = (&cluster.ClusterReconciler{
    Client: mgr.GetClient(),
    Log:    ctrl.Log.WithName("controllers").WithName("Cluster"),
    Scheme: mgr.GetScheme(),
    Provider: "aws",
}).SetupWithManager(mgr); err != nil {
    setupLog.Error(err, "unable to create controller", "controller", "Cluster")
    os.Exit(1)
}
if err = (&cluster.ClusterReconciler{
    Client: mgr.GetClient(),
    Log:    ctrl.Log.WithName("controllers").WithName("Cluster"),
    Scheme: mgr.GetScheme(),
    Provider: "gcp",
}).SetupWithManager(mgr); err != nil {
    setupLog.Error(err, "unable to create controller", "controller", "Cluster")
    os.Exit(1)
}
```
