1
0
mirror of https://github.com/rancher/norman.git synced 2025-08-25 10:28:37 +00:00
norman/lifecycle/object.go
Craig Jellick 905797b50f Add suport for cluster scoped controllers
Some worload controllers need to watch resoruces in the mangement plane
and react to them. But, they should only react to resources that
correspond to their cluster. This adds framework support for that.
2018-01-16 09:04:15 -07:00

187 lines
4.5 KiB
Go

package lifecycle
import (
"reflect"
"github.com/rancher/norman/clientbase"
"github.com/rancher/norman/types/slice"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
var (
created = "lifecycle.cattle.io/create"
finalizerKey = "controller.cattle.io/"
ScopedFinalizerKey = "clusterscoped.controller.cattle.io/"
)
type ObjectLifecycle interface {
Create(obj runtime.Object) (runtime.Object, error)
Finalize(obj runtime.Object) (runtime.Object, error)
Updated(obj runtime.Object) (runtime.Object, error)
}
type objectLifecycleAdapter struct {
name string
clusterScoped bool
lifecycle ObjectLifecycle
objectClient *clientbase.ObjectClient
}
func NewObjectLifecycleAdapter(name string, clusterScoped bool, lifecycle ObjectLifecycle, objectClient *clientbase.ObjectClient) func(key string, obj runtime.Object) error {
o := objectLifecycleAdapter{
name: name,
clusterScoped: clusterScoped,
lifecycle: lifecycle,
objectClient: objectClient,
}
return o.sync
}
func (o *objectLifecycleAdapter) sync(key string, obj runtime.Object) error {
if obj == nil {
return nil
}
metadata, err := meta.Accessor(obj)
if err != nil {
return err
}
if cont, err := o.finalize(metadata, obj); err != nil || !cont {
return err
}
if cont, err := o.create(metadata, obj); err != nil || !cont {
return err
}
obj = obj.DeepCopyObject()
newObj, err := o.lifecycle.Updated(obj)
o.update(metadata.GetName(), obj, newObj)
return err
}
func (o *objectLifecycleAdapter) update(name string, orig, obj runtime.Object) (runtime.Object, error) {
if obj != nil && !reflect.DeepEqual(orig, obj) {
return o.objectClient.Update(name, obj)
}
return obj, nil
}
func (o *objectLifecycleAdapter) finalize(metadata metav1.Object, obj runtime.Object) (bool, error) {
// Check finalize
if metadata.GetDeletionTimestamp() == nil {
return true, nil
}
if !slice.ContainsString(metadata.GetFinalizers(), o.constructFinalizerKey()) {
return false, nil
}
obj = obj.DeepCopyObject()
if newObj, err := o.lifecycle.Finalize(obj); err != nil {
o.update(metadata.GetName(), obj, newObj)
return false, err
} else if newObj != nil {
obj = newObj
}
if err := removeFinalizer(o.constructFinalizerKey(), obj); err != nil {
return false, err
}
_, err := o.objectClient.Update(metadata.GetName(), obj)
return false, err
}
func removeFinalizer(name string, obj runtime.Object) error {
metadata, err := meta.Accessor(obj)
if err != nil {
return err
}
var finalizers []string
for _, finalizer := range metadata.GetFinalizers() {
if finalizer == name {
continue
}
finalizers = append(finalizers, finalizer)
}
metadata.SetFinalizers(finalizers)
return nil
}
func (o *objectLifecycleAdapter) createKey() string {
return created + "." + o.name
}
func (o *objectLifecycleAdapter) constructFinalizerKey() string {
if o.clusterScoped {
return ScopedFinalizerKey + o.name
}
return finalizerKey + o.name
}
func (o *objectLifecycleAdapter) create(metadata metav1.Object, obj runtime.Object) (bool, error) {
if o.isInitialized(metadata) {
return true, nil
}
// addFinalizer will always return a DeepCopy
obj, err := o.addFinalizer(obj)
if err != nil {
return false, err
}
orig := obj.DeepCopyObject()
if newObj, err := o.lifecycle.Create(obj); err != nil {
o.update(metadata.GetName(), orig, newObj)
return false, err
} else if newObj != nil {
obj = newObj
}
return false, o.setInitialized(obj)
}
func (o *objectLifecycleAdapter) isInitialized(metadata metav1.Object) bool {
initialized := o.createKey()
return metadata.GetAnnotations()[initialized] == "true"
}
func (o *objectLifecycleAdapter) setInitialized(obj runtime.Object) error {
metadata, err := meta.Accessor(obj)
if err != nil {
return err
}
initialized := o.createKey()
if metadata.GetAnnotations() == nil {
metadata.SetAnnotations(map[string]string{})
}
metadata.GetAnnotations()[initialized] = "true"
_, err = o.objectClient.Update(metadata.GetName(), obj)
return err
}
func (o *objectLifecycleAdapter) addFinalizer(obj runtime.Object) (runtime.Object, error) {
obj = obj.DeepCopyObject()
metadata, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
if slice.ContainsString(metadata.GetFinalizers(), o.constructFinalizerKey()) {
return obj, nil
}
metadata.SetFinalizers(append(metadata.GetFinalizers(), o.constructFinalizerKey()))
return o.objectClient.Update(metadata.GetName(), obj)
}