Files
kubesphere/pkg/models/notification/notification.go

743 lines
24 KiB
Go

// Copyright 2022 The KubeSphere 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 notification
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"k8s.io/apimachinery/pkg/types"
"github.com/emicklei/go-restful/v3"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
"kubesphere.io/api/notification/v2beta1"
"kubesphere.io/api/notification/v2beta2"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
"kubesphere.io/kubesphere/pkg/simple/client/notification"
)
const (
Secret = "secrets"
ConfigMap = "configmaps"
VerificationAPIPath = "/api/v2/verify"
V2beta1 = "v2beta1"
V2beta2 = "v2beta2"
)
type Operator interface {
ListV2beta1(user, resource, subresource string, query *query.Query) (*api.ListResult, error)
GetV2beta1(user, resource, name, subresource string) (runtime.Object, error)
CreateV2beta1(user, resource string, obj runtime.Object) (runtime.Object, error)
DeleteV2beta1(user, resource, name string) error
UpdateV2beta1(user, resource, name string, obj runtime.Object) (runtime.Object, error)
List(user, resource, subresource string, query *query.Query) (*api.ListResult, error)
Get(user, resource, name, subresource string) (runtime.Object, error)
Create(user, resource string, obj runtime.Object) (runtime.Object, error)
Delete(user, resource, name string) error
Update(user, resource, name string, obj runtime.Object) (runtime.Object, error)
Patch(user, resource, name string, data []byte) (runtime.Object, error)
Verify(request *restful.Request, response *restful.Response)
GetObject(resource, version string) runtime.Object
IsKnownResource(resource, version, subresource string) bool
}
type operator struct {
k8sClient kubernetes.Interface
ksClient kubesphere.Interface
informers informers.InformerFactory
resourceGetter *resource.ResourceGetter
options *notification.Options
}
type Data struct {
Config v2beta2.Config `json:"config"`
Receiver v2beta2.Receiver `json:"receiver"`
}
type Result struct {
Code int `json:"Status"`
Message string `json:"Message"`
}
func NewOperator(
informers informers.InformerFactory,
k8sClient kubernetes.Interface,
ksClient kubesphere.Interface,
options *notification.Options) Operator {
return &operator{
informers: informers,
k8sClient: k8sClient,
ksClient: ksClient,
resourceGetter: resource.NewResourceGetter(informers, nil),
options: options,
}
}
// ListV2beta1 list objects of version v2beta1. Only global objects will be returned if the user is nil.
// If the user is not nil, only tenant objects whose tenant label matches the user will be returned.
func (o *operator) ListV2beta1(user, resource, subresource string, q *query.Query) (*api.ListResult, error) {
return o.list(user, resource, V2beta1, subresource, q)
}
// List objects. Only global objects will be returned if the user is nil.
// If the user is not nil, only tenant objects whose tenant label matches the user will be returned.
func (o *operator) List(user, resource, subresource string, q *query.Query) (*api.ListResult, error) {
return o.list(user, resource, V2beta2, subresource, q)
}
func (o *operator) list(user, resource, version, subresource string, q *query.Query) (*api.ListResult, error) {
if user != "" {
if resource == v2beta2.ResourcesPluralRouter ||
resource == v2beta2.ResourcesPluralNotificationManager {
return nil, errors.NewForbidden(v2beta2.Resource(resource), "",
fmt.Errorf("tenant can not list %s", resource))
}
}
q.LabelSelector = o.generateLabelSelector(q, user, resource, version)
ns := ""
if resource == Secret || resource == ConfigMap {
ns = constants.NotificationSecretNamespace
}
res, err := o.resourceGetter.List(resource, ns, q)
if err != nil {
klog.Error(err)
return nil, err
}
if subresource == "" ||
(resource != v2beta2.ResourcesPluralConfig && resource != v2beta2.ResourcesPluralReceiver) {
return res, nil
}
results := &api.ListResult{}
for _, item := range res.Items {
obj := clean(item, resource, subresource)
if obj != nil {
if version == V2beta1 {
obj = convert(obj)
}
results.Items = append(results.Items, obj)
}
}
results.TotalItems = len(results.Items)
return results, nil
}
func (o *operator) generateLabelSelector(q *query.Query, user, resource, version string) string {
if resource == v2beta2.ResourcesPluralNotificationManager {
return q.LabelSelector
}
labelSelector := q.LabelSelector
if len(labelSelector) > 0 {
labelSelector = q.LabelSelector + ","
}
filter := ""
// If user is nil, it will list all global object.
if user == "" {
if isConfig(o.GetObject(resource, version)) {
filter = "type=default"
} else {
filter = "type=global"
}
} else {
// If the user is not nil, only return the object belong to this user.
filter = "type=tenant,user=" + user
}
labelSelector = labelSelector + filter
return labelSelector
}
// GetV2beta1 get the specified object of version v2beta1, if you want to get a global object, the user must be nil.
// If you want to get a tenant object, the user must equal to the tenant specified in labels of the object.
func (o *operator) GetV2beta1(user, resource, name, subresource string) (runtime.Object, error) {
return o.get(user, resource, V2beta1, name, subresource)
}
// Get the specified object, if you want to get a global object, the user must be nil.
// If you want to get a tenant object, the user must equal to the tenant specified in labels of the object.
func (o *operator) Get(user, resource, name, subresource string) (runtime.Object, error) {
return o.get(user, resource, V2beta2, name, subresource)
}
func (o *operator) get(user, resource, version, name, subresource string) (runtime.Object, error) {
if user != "" {
if resource == v2beta2.ResourcesPluralRouter ||
resource == v2beta2.ResourcesPluralNotificationManager {
return nil, errors.NewForbidden(v2beta2.Resource(resource), "",
fmt.Errorf("tenant can not get %s", resource))
}
}
ns := ""
if resource == Secret || resource == ConfigMap {
ns = constants.NotificationSecretNamespace
}
obj, err := o.resourceGetter.Get(resource, ns, name)
if err != nil {
klog.Error(err)
return nil, err
}
if err := authorizer(user, obj); err != nil {
klog.Error(err)
return nil, err
}
if subresource == "" ||
(resource != v2beta2.ResourcesPluralConfig && resource != v2beta2.ResourcesPluralReceiver) {
return obj, nil
}
res := clean(obj, resource, subresource)
if res == nil {
return nil, errors.NewNotFound(v2beta1.Resource(obj.GetObjectKind().GroupVersionKind().GroupKind().Kind), name)
}
if version == V2beta1 {
res = convert(res)
}
return res, nil
}
// CreateV2beta1 an object of version v2beta1. A global object will be created if the user is nil.
// A tenant object will be created if the user is not nil.
func (o *operator) CreateV2beta1(user, resource string, obj runtime.Object) (runtime.Object, error) {
return o.create(user, resource, V2beta1, obj)
}
// Create an object. A global object will be created if the user is nil.
// A tenant object will be created if the user is not nil.
func (o *operator) Create(user, resource string, obj runtime.Object) (runtime.Object, error) {
return o.create(user, resource, V2beta2, obj)
}
func (o *operator) create(user, resource, version string, obj runtime.Object) (runtime.Object, error) {
if user != "" {
if resource == v2beta2.ResourcesPluralRouter ||
resource == v2beta2.ResourcesPluralNotificationManager {
return nil, errors.NewForbidden(v2beta2.Resource(resource), "",
fmt.Errorf("tenant can not create %s", resource))
}
}
if err := appendLabel(user, resource, obj); err != nil {
return nil, err
}
switch resource {
case v2beta2.ResourcesPluralNotificationManager:
return o.ksClient.NotificationV2beta2().NotificationManagers().Create(context.Background(), obj.(*v2beta2.NotificationManager), v1.CreateOptions{})
case v2beta2.ResourcesPluralConfig:
if version == V2beta1 {
return o.ksClient.NotificationV2beta1().Configs().Create(context.Background(), obj.(*v2beta1.Config), v1.CreateOptions{})
} else {
return o.ksClient.NotificationV2beta2().Configs().Create(context.Background(), obj.(*v2beta2.Config), v1.CreateOptions{})
}
case v2beta2.ResourcesPluralReceiver:
if version == V2beta1 {
return o.ksClient.NotificationV2beta1().Receivers().Create(context.Background(), obj.(*v2beta1.Receiver), v1.CreateOptions{})
} else {
return o.ksClient.NotificationV2beta2().Receivers().Create(context.Background(), obj.(*v2beta2.Receiver), v1.CreateOptions{})
}
case v2beta2.ResourcesPluralRouter:
return o.ksClient.NotificationV2beta2().Routers().Create(context.Background(), obj.(*v2beta2.Router), v1.CreateOptions{})
case v2beta2.ResourcesPluralSilence:
return o.ksClient.NotificationV2beta2().Silences().Create(context.Background(), obj.(*v2beta2.Silence), v1.CreateOptions{})
case Secret:
return o.k8sClient.CoreV1().Secrets(constants.NotificationSecretNamespace).Create(context.Background(), obj.(*corev1.Secret), v1.CreateOptions{})
case ConfigMap:
return o.k8sClient.CoreV1().ConfigMaps(constants.NotificationSecretNamespace).Create(context.Background(), obj.(*corev1.ConfigMap), v1.CreateOptions{})
default:
return nil, errors.NewInternalError(nil)
}
}
// DeleteV2beta1 an object of version v2beta1. A global object will be deleted if the user is nil.
// If the user is not nil, a tenant object whose tenant label matches the user will be deleted.
func (o *operator) DeleteV2beta1(user, resource, name string) error {
return o.delete(user, resource, name)
}
// Delete an object. A global object will be deleted if the user is nil.
// If the user is not nil, a tenant object whose tenant label matches the user will be deleted.
func (o *operator) Delete(user, resource, name string) error {
return o.delete(user, resource, name)
}
func (o *operator) delete(user, resource, name string) error {
if user != "" {
if resource == v2beta2.ResourcesPluralRouter ||
resource == v2beta2.ResourcesPluralNotificationManager {
return errors.NewForbidden(v2beta2.Resource(resource), "",
fmt.Errorf("tenant can not delete %s", resource))
}
}
if _, err := o.Get(user, resource, name, ""); err != nil {
klog.Error(err)
return err
}
switch resource {
case v2beta2.ResourcesPluralNotificationManager:
return o.ksClient.NotificationV2beta2().NotificationManagers().Delete(context.Background(), name, v1.DeleteOptions{})
case v2beta2.ResourcesPluralConfig:
return o.ksClient.NotificationV2beta2().Configs().Delete(context.Background(), name, v1.DeleteOptions{})
case v2beta2.ResourcesPluralReceiver:
return o.ksClient.NotificationV2beta2().Receivers().Delete(context.Background(), name, v1.DeleteOptions{})
case v2beta2.ResourcesPluralRouter:
return o.ksClient.NotificationV2beta2().Routers().Delete(context.Background(), name, v1.DeleteOptions{})
case v2beta2.ResourcesPluralSilence:
return o.ksClient.NotificationV2beta2().Silences().Delete(context.Background(), name, v1.DeleteOptions{})
case Secret:
return o.k8sClient.CoreV1().Secrets(constants.NotificationSecretNamespace).Delete(context.Background(), name, v1.DeleteOptions{})
case ConfigMap:
return o.k8sClient.CoreV1().ConfigMaps(constants.NotificationSecretNamespace).Delete(context.Background(), name, v1.DeleteOptions{})
default:
return errors.NewInternalError(nil)
}
}
// UpdateV2beta1 an object of version v2beta1, only a global object will be updated if the user is nil.
// If the user is not nil, a tenant object whose tenant label matches the user will be updated.
func (o *operator) UpdateV2beta1(user, resource, name string, obj runtime.Object) (runtime.Object, error) {
return o.update(user, resource, V2beta1, name, obj)
}
// Update an object, only a global object will be updated if the user is nil.
// If the user is not nil, a tenant object whose tenant label matches the user will be updated.
func (o *operator) Update(user, resource, name string, obj runtime.Object) (runtime.Object, error) {
return o.update(user, resource, V2beta2, name, obj)
}
func (o *operator) update(user, resource, version, name string, obj runtime.Object) (runtime.Object, error) {
if user != "" {
if resource == v2beta2.ResourcesPluralRouter ||
resource == v2beta2.ResourcesPluralNotificationManager {
return nil, errors.NewForbidden(v2beta2.Resource(resource), "",
fmt.Errorf("tenant can not update %s", resource))
}
}
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
if accessor.GetName() != name {
return nil, fmt.Errorf("incorrcet parameter, resource name is not equal to the name in body")
}
_, err = o.Get(user, resource, name, "")
if err != nil {
klog.Error(err)
return nil, err
}
if err := appendLabel(user, resource, obj); err != nil {
return nil, err
}
switch resource {
case v2beta2.ResourcesPluralNotificationManager:
return o.ksClient.NotificationV2beta2().NotificationManagers().Update(context.Background(), obj.(*v2beta2.NotificationManager), v1.UpdateOptions{})
case v2beta2.ResourcesPluralConfig:
if version == V2beta1 {
return o.ksClient.NotificationV2beta1().Configs().Update(context.Background(), obj.(*v2beta1.Config), v1.UpdateOptions{})
} else {
return o.ksClient.NotificationV2beta2().Configs().Update(context.Background(), obj.(*v2beta2.Config), v1.UpdateOptions{})
}
case v2beta2.ResourcesPluralReceiver:
if version == V2beta1 {
return o.ksClient.NotificationV2beta1().Receivers().Update(context.Background(), obj.(*v2beta1.Receiver), v1.UpdateOptions{})
} else {
return o.ksClient.NotificationV2beta2().Receivers().Update(context.Background(), obj.(*v2beta2.Receiver), v1.UpdateOptions{})
}
case v2beta2.ResourcesPluralRouter:
return o.ksClient.NotificationV2beta2().Routers().Update(context.Background(), obj.(*v2beta2.Router), v1.UpdateOptions{})
case v2beta2.ResourcesPluralSilence:
return o.ksClient.NotificationV2beta2().Silences().Update(context.Background(), obj.(*v2beta2.Silence), v1.UpdateOptions{})
case Secret:
return o.k8sClient.CoreV1().Secrets(constants.NotificationSecretNamespace).Update(context.Background(), obj.(*corev1.Secret), v1.UpdateOptions{})
case ConfigMap:
return o.k8sClient.CoreV1().ConfigMaps(constants.NotificationSecretNamespace).Update(context.Background(), obj.(*corev1.ConfigMap), v1.UpdateOptions{})
default:
return nil, errors.NewInternalError(nil)
}
}
// Patch an object, only a global object will be patched if the user is nil.
// If the user is not nil, a tenant object whose tenant label matches the user will be patched.
func (o *operator) Patch(user, resource, name string, data []byte) (runtime.Object, error) {
if user != "" {
if resource == v2beta2.ResourcesPluralRouter ||
resource == v2beta2.ResourcesPluralNotificationManager {
return nil, errors.NewForbidden(v2beta2.Resource(resource), "",
fmt.Errorf("tenant can not update %s", resource))
}
}
_, err := o.Get(user, resource, name, "")
if err != nil {
klog.Error(err)
return nil, err
}
switch resource {
case v2beta2.ResourcesPluralNotificationManager:
return o.ksClient.NotificationV2beta2().NotificationManagers().Patch(context.Background(), name, types.MergePatchType, data, v1.PatchOptions{})
case v2beta2.ResourcesPluralConfig:
return o.ksClient.NotificationV2beta2().Configs().Patch(context.Background(), name, types.MergePatchType, data, v1.PatchOptions{})
case v2beta2.ResourcesPluralReceiver:
return o.ksClient.NotificationV2beta2().Receivers().Patch(context.Background(), name, types.MergePatchType, data, v1.PatchOptions{})
case v2beta2.ResourcesPluralRouter:
return o.ksClient.NotificationV2beta2().Routers().Patch(context.Background(), name, types.MergePatchType, data, v1.PatchOptions{})
case v2beta2.ResourcesPluralSilence:
return o.ksClient.NotificationV2beta2().Silences().Patch(context.Background(), name, types.MergePatchType, data, v1.PatchOptions{})
case Secret:
return o.k8sClient.CoreV1().Secrets(constants.NotificationSecretNamespace).Patch(context.Background(), name, types.MergePatchType, data, v1.PatchOptions{})
case ConfigMap:
return o.k8sClient.CoreV1().ConfigMaps(constants.NotificationSecretNamespace).Patch(context.Background(), name, types.MergePatchType, data, v1.PatchOptions{})
default:
return nil, errors.NewInternalError(nil)
}
}
func (o *operator) GetObject(resource, version string) runtime.Object {
switch resource {
case v2beta2.ResourcesPluralNotificationManager:
return &v2beta2.NotificationManager{}
case v2beta2.ResourcesPluralConfig:
if version == V2beta1 {
return &v2beta1.Config{}
} else {
return &v2beta2.Config{}
}
case v2beta2.ResourcesPluralReceiver:
if version == V2beta1 {
return &v2beta1.Receiver{}
} else {
return &v2beta2.Receiver{}
}
case v2beta2.ResourcesPluralRouter:
if version == V2beta1 {
return nil
}
return &v2beta2.Router{}
case v2beta2.ResourcesPluralSilence:
if version == V2beta1 {
return nil
}
return &v2beta2.Silence{}
case Secret:
return &corev1.Secret{}
case ConfigMap:
return &corev1.ConfigMap{}
default:
return nil
}
}
func (o *operator) IsKnownResource(resource, version, subresource string) bool {
if obj := o.GetObject(resource, version); obj == nil {
return false
}
res := false
// "" means get all types of the config or receiver.
if subresource == "dingtalk" ||
subresource == "email" ||
subresource == "slack" ||
subresource == "webhook" ||
subresource == "wechat" ||
subresource == "" {
res = true
}
if version == V2beta2 && subresource == "feishu" {
res = true
}
return res
}
func (o *operator) Verify(request *restful.Request, response *restful.Response) {
if o.options == nil || len(o.options.Endpoint) == 0 {
_ = response.WriteAsJson(Result{
http.StatusInternalServerError,
"Cannot find Notification Manager endpoint",
})
return
}
reqBody, err := io.ReadAll(request.Request.Body)
if err != nil {
klog.Error(err)
_ = response.WriteHeaderAndEntity(http.StatusBadRequest, err)
return
}
data := Data{}
err = json.Unmarshal(reqBody, &data)
if err != nil {
_ = response.WriteHeaderAndEntity(http.StatusBadRequest, err)
return
}
receiver := data.Receiver
user := request.PathParameter("user")
if err := authorizer(user, &receiver); err != nil {
klog.Error(err)
_ = response.WriteHeaderAndEntity(http.StatusBadRequest, err)
return
}
req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", o.options.Endpoint, VerificationAPIPath), bytes.NewReader(reqBody))
if err != nil {
klog.Error(err)
_ = response.WriteHeaderAndEntity(http.StatusInternalServerError, err)
return
}
req.Header = request.Request.Header
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
klog.Error(err)
_ = response.WriteHeaderAndEntity(http.StatusInternalServerError, err)
return
}
defer func() {
_ = resp.Body.Close()
}()
body, err := io.ReadAll(resp.Body)
if err != nil {
klog.Error(err)
// return 500
_ = response.WriteHeaderAndEntity(http.StatusInternalServerError, err)
return
}
response.AddHeader(restful.HEADER_ContentType, restful.MIME_JSON)
response.WriteHeader(http.StatusOK)
_, _ = response.Write(body)
}
// Does the user has permission to access this object.
func authorizer(user string, obj runtime.Object) error {
// If the user is not nil, it must equal to the tenant specified in labels of the object.
if user != "" && !isOwner(user, obj) {
return errors.NewForbidden(v2beta2.Resource(obj.GetObjectKind().GroupVersionKind().GroupKind().Kind), "",
fmt.Errorf("user '%s' is not the owner of object", user))
}
// If the user is nil, the object must be a global object.
if user == "" && !isGlobal(obj) {
return errors.NewForbidden(v2beta2.Resource(obj.GetObjectKind().GroupVersionKind().GroupKind().Kind), "",
fmt.Errorf("object is not a global object"))
}
return nil
}
// Is the user equal to the tenant specified in the object labels.
func isOwner(user string, obj interface{}) bool {
accessor, err := meta.Accessor(obj)
if err != nil {
klog.Errorln(err)
return false
}
return accessor.GetLabels()["user"] == user && accessor.GetLabels()["type"] == "tenant"
}
func isConfig(obj runtime.Object) bool {
switch obj.(type) {
case *v2beta1.Config, *v2beta2.Config:
return true
default:
return false
}
}
// Is the object is a global object.
func isGlobal(obj runtime.Object) bool {
if _, ok := obj.(*v2beta2.NotificationManager); ok {
return true
}
accessor, err := meta.Accessor(obj)
if err != nil {
klog.Errorln(err)
return false
}
if isConfig(obj) {
return accessor.GetLabels()["type"] == "default"
} else {
return accessor.GetLabels()["type"] == "global"
}
}
func appendLabel(user, resource string, obj runtime.Object) error {
accessor, err := meta.Accessor(obj)
if err != nil {
klog.Errorln(err)
return err
}
labels := accessor.GetLabels()
if labels == nil {
labels = make(map[string]string)
}
if resource == Secret || resource == ConfigMap {
labels[constants.NotificationManagedLabel] = "true"
}
if user == "" {
if isConfig(obj) {
labels["type"] = "default"
} else {
labels["type"] = "global"
}
} else {
labels["type"] = "tenant"
labels["user"] = user
}
accessor.SetLabels(labels)
return nil
}
func clean(obj interface{}, resource, subresource string) runtime.Object {
if resource == v2beta2.ResourcesPluralConfig {
config := obj.(*v2beta2.Config)
newConfig := config.DeepCopy()
newConfig.Spec = v2beta2.ConfigSpec{}
switch subresource {
case "dingtalk":
newConfig.Spec.DingTalk = config.Spec.DingTalk
case "email":
newConfig.Spec.Email = config.Spec.Email
case "feishu":
newConfig.Spec.Feishu = config.Spec.Feishu
case "slack":
newConfig.Spec.Slack = config.Spec.Slack
case "webhook":
newConfig.Spec.Webhook = config.Spec.Webhook
case "wechat":
newConfig.Spec.Wechat = config.Spec.Wechat
default:
return nil
}
if reflect.ValueOf(newConfig.Spec).IsZero() {
return nil
}
return newConfig
} else if resource == v2beta2.ResourcesPluralReceiver {
receiver := obj.(*v2beta2.Receiver)
newReceiver := receiver.DeepCopy()
newReceiver.Spec = v2beta2.ReceiverSpec{}
switch subresource {
case "dingtalk":
newReceiver.Spec.DingTalk = receiver.Spec.DingTalk
case "email":
newReceiver.Spec.Email = receiver.Spec.Email
case "feishu":
newReceiver.Spec.Feishu = receiver.Spec.Feishu
case "slack":
newReceiver.Spec.Slack = receiver.Spec.Slack
case "webhook":
newReceiver.Spec.Webhook = receiver.Spec.Webhook
case "wechat":
newReceiver.Spec.Wechat = receiver.Spec.Wechat
default:
return nil
}
if reflect.ValueOf(newReceiver.Spec).IsZero() {
return nil
}
return newReceiver
} else {
return obj.(runtime.Object)
}
}
func convert(obj runtime.Object) runtime.Object {
switch obj := obj.(type) {
case *v2beta2.Config:
dst := &v2beta1.Config{}
_ = obj.ConvertTo(dst)
return dst
case *v2beta2.Receiver:
dst := &v2beta1.Receiver{}
_ = obj.ConvertTo(dst)
return dst
default:
return obj
}
}