Files
paralus/pkg/controller/runtime/object.go
2022-06-08 17:21:22 +05:30

219 lines
5.0 KiB
Go

package runtime
import (
"bytes"
"errors"
"github.com/paralus/paralus/pkg/controller/scheme"
apiv2 "github.com/paralus/paralus/proto/types/controller"
rbacv1 "k8s.io/api/rbac/v1"
apixv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/paralus/paralus/pkg/controller/util"
)
var (
// ErrInvalidObject is returned for invalid object
ErrInvalidObject = errors.New("object interface not implemented")
)
// FromObject creates step object from runtime object
func FromObject(ro runtime.Object) (*apiv2.StepObject, error) {
var so apiv2.StepObject
var err error
bb := new(bytes.Buffer)
err = scheme.Serializer.Encode(ro, bb)
if err != nil {
return nil, err
}
// use step object accessor to get object gvk
// so.SetGroupVersionKind(ro.GetObjectKind().GroupVersionKind())
// if mo, ok := ro.(metav1.Object); ok {
// so.Name = mo.GetName()
// }
so.Raw = bb.Bytes()
// so.Raw, err = util.CleanPatch(so.Raw)
// if err != nil {
// return nil, err
// }
return &so, nil
}
// SetNamespace sets namespace for runtime object
func SetNamespace(ro runtime.Object, namespace string) error {
switch ro.(type) {
case *apixv1beta1.CustomResourceDefinition:
case *rbacv1.ClusterRole:
case *rbacv1.ClusterRoleBinding:
crb := ro.(*rbacv1.ClusterRoleBinding)
for i := range crb.Subjects {
if crb.Subjects[i].Kind == rbacv1.ServiceAccountKind &&
crb.Subjects[i].Namespace == "" {
crb.Subjects[i].Namespace = namespace
}
}
case *rbacv1.RoleBinding:
rb := ro.(*rbacv1.RoleBinding)
if rb.Namespace == "" {
rb.Namespace = namespace
}
for i := range rb.Subjects {
if rb.Subjects[i].Kind == rbacv1.ServiceAccountKind &&
rb.Subjects[i].Namespace == "" {
rb.Subjects[i].Namespace = namespace
}
}
case *rbacv1.Role:
rb := ro.(*rbacv1.Role)
if rb.Namespace == "" {
rb.Namespace = namespace
}
default:
if mo, ok := ro.(metav1.Object); ok {
mo.SetNamespace(namespace)
return nil
}
return ErrInvalidObject
}
return nil
}
// ToObject converts step object to runtime object
func ToObject(so *apiv2.StepObject) (o runtime.Object, gvk *schema.GroupVersionKind, err error) {
accessor, err := so.Accessor()
if err != nil {
return
}
eGVK, err := accessor.GroupVersionKind()
if err != nil {
return
}
if scheme.Scheme.Recognizes(eGVK) {
o, gvk, err = scheme.Serializer.Decode(so.Raw, nil, nil)
} else {
o, gvk, err = scheme.Serializer.Decode(so.Raw, nil, &unstructured.Unstructured{})
}
return o, gvk, err
}
// ToStructuredObject converts unstructured object to structured object
func ToStructuredObject(obj runtime.Object) runtime.Object {
if _, ok := obj.(*unstructured.Unstructured); ok {
bb := new(bytes.Buffer)
err := scheme.Serializer.Encode(obj, bb)
if err != nil {
return obj
}
o, _, err := scheme.Serializer.Decode(bb.Bytes(), nil, nil)
if err != nil {
return obj
}
return o
}
return obj
}
// ToUnstructuredObject converts step object to unstructured object,
// this is useful for preserving original json serialized input in step object.
// Note: while patching k8s resources, we should preserve the user input, as
// patching can remove fields; which are represented as null values in the patch
func ToUnstructuredObject(so *apiv2.StepObject) (*unstructured.Unstructured, *schema.GroupVersionKind, error) {
var o runtime.Object
var err error
var gvk *schema.GroupVersionKind
o, gvk, err = scheme.Serializer.Decode(so.Raw, nil, &unstructured.Unstructured{})
return o.(*unstructured.Unstructured), gvk, err
}
// PatchMeta is the metadata to be added while patching
type PatchMeta struct {
Annotations map[string]string
}
// PatchOption is the functional patch option
type PatchOption func(*PatchMeta)
// AddAnnotations adds annotations to object
func AddAnnotations(annotations map[string]string) PatchOption {
return func(m *PatchMeta) {
m.Annotations = annotations
}
}
// Patch patches the step object with give step object
func Patch(input *apiv2.StepObject, with *apiv2.StepObject, opts ...PatchOption) error {
pm := &PatchMeta{}
for _, opt := range opts {
opt(pm)
}
accessor, err := input.Accessor()
if err != nil {
return err
}
gvk, err := accessor.GroupVersionKind()
if err != nil {
return err
}
if util.IsStrategicMergePatch(gvk) {
pb, err := util.CreateStrategicMergePatch(gvk, nil, input.Raw, with.Raw)
if err != nil {
return err
}
fb, err := util.ApplyStrategicMergePatch(gvk, input.Raw, pb)
if err != nil {
return err
}
input.Raw = fb
} else {
pb, err := util.CreateJSONMergePatch(nil, input.Raw, with.Raw)
if err != nil {
return err
}
fb, err := util.ApplyJSONMergePatch(input.Raw, pb)
if err != nil {
return err
}
input.Raw = fb
}
if pm.Annotations != nil {
accessor, err := input.Accessor()
if err != nil {
return err
}
accessor.SetAnnotations(pm.Annotations)
input.Raw = accessor.Bytes()
}
return nil
}