mirror of
https://github.com/paralus/paralus.git
synced 2026-02-19 19:59:52 +00:00
219 lines
5.0 KiB
Go
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
|
|
}
|