mirror of https://github.com/k3s-io/k3s
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
6.3 KiB
268 lines
6.3 KiB
package lifecycle
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/rancher/norman/objectclient"
|
|
"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 ObjectLifecycleCondition interface {
|
|
HasCreate() bool
|
|
HasFinalize() bool
|
|
}
|
|
|
|
type objectLifecycleAdapter struct {
|
|
name string
|
|
clusterScoped bool
|
|
lifecycle ObjectLifecycle
|
|
objectClient *objectclient.ObjectClient
|
|
}
|
|
|
|
func NewObjectLifecycleAdapter(name string, clusterScoped bool, lifecycle ObjectLifecycle, objectClient *objectclient.ObjectClient) func(key string, obj interface{}) (interface{}, error) {
|
|
o := objectLifecycleAdapter{
|
|
name: name,
|
|
clusterScoped: clusterScoped,
|
|
lifecycle: lifecycle,
|
|
objectClient: objectClient,
|
|
}
|
|
return o.sync
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) sync(key string, in interface{}) (interface{}, error) {
|
|
if in == nil || reflect.ValueOf(in).IsNil() {
|
|
return nil, nil
|
|
}
|
|
|
|
obj, ok := in.(runtime.Object)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
if newObj, cont, err := o.finalize(obj); err != nil || !cont {
|
|
return nil, err
|
|
} else if newObj != nil {
|
|
obj = newObj
|
|
}
|
|
|
|
if newObj, cont, err := o.create(obj); err != nil || !cont {
|
|
return nil, err
|
|
} else if newObj != nil {
|
|
obj = newObj
|
|
}
|
|
|
|
return o.record(obj, o.lifecycle.Updated)
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) update(name string, orig, obj runtime.Object) (runtime.Object, error) {
|
|
if obj != nil && orig != nil && !reflect.DeepEqual(orig, obj) {
|
|
newObj, err := o.objectClient.Update(name, obj)
|
|
if newObj != nil {
|
|
return newObj, err
|
|
}
|
|
return obj, err
|
|
}
|
|
if obj == nil {
|
|
return orig, nil
|
|
}
|
|
return obj, nil
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) finalize(obj runtime.Object) (runtime.Object, bool, error) {
|
|
if !o.hasFinalize() {
|
|
return obj, true, nil
|
|
}
|
|
|
|
metadata, err := meta.Accessor(obj)
|
|
if err != nil {
|
|
return obj, false, err
|
|
}
|
|
|
|
// Check finalize
|
|
if metadata.GetDeletionTimestamp() == nil {
|
|
return nil, true, nil
|
|
}
|
|
|
|
if !slice.ContainsString(metadata.GetFinalizers(), o.constructFinalizerKey()) {
|
|
return nil, false, nil
|
|
}
|
|
|
|
newObj, err := o.record(obj, o.lifecycle.Finalize)
|
|
if err != nil {
|
|
return obj, false, err
|
|
}
|
|
|
|
obj, err = o.removeFinalizer(o.constructFinalizerKey(), maybeDeepCopy(obj, newObj))
|
|
return obj, false, err
|
|
}
|
|
|
|
func maybeDeepCopy(old, newObj runtime.Object) runtime.Object {
|
|
if old == newObj {
|
|
return old.DeepCopyObject()
|
|
}
|
|
return newObj
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) removeFinalizer(name string, obj runtime.Object) (runtime.Object, error) {
|
|
for i := 0; i < 3; i++ {
|
|
metadata, err := meta.Accessor(obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var finalizers []string
|
|
for _, finalizer := range metadata.GetFinalizers() {
|
|
if finalizer == name {
|
|
continue
|
|
}
|
|
finalizers = append(finalizers, finalizer)
|
|
}
|
|
metadata.SetFinalizers(finalizers)
|
|
|
|
newObj, err := o.objectClient.Update(metadata.GetName(), obj)
|
|
if err == nil {
|
|
return newObj, nil
|
|
}
|
|
|
|
obj, err = o.objectClient.GetNamespaced(metadata.GetNamespace(), metadata.GetName(), metav1.GetOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to remove finalizer on %s", name)
|
|
}
|
|
|
|
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) hasFinalize() bool {
|
|
cond, ok := o.lifecycle.(ObjectLifecycleCondition)
|
|
return !ok || cond.HasFinalize()
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) hasCreate() bool {
|
|
cond, ok := o.lifecycle.(ObjectLifecycleCondition)
|
|
return !ok || cond.HasCreate()
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) record(obj runtime.Object, f func(runtime.Object) (runtime.Object, error)) (runtime.Object, error) {
|
|
metadata, err := meta.Accessor(obj)
|
|
if err != nil {
|
|
return obj, err
|
|
}
|
|
|
|
origObj := obj
|
|
obj = origObj.DeepCopyObject()
|
|
if newObj, err := checkNil(obj, f); err != nil {
|
|
newObj, _ = o.update(metadata.GetName(), origObj, newObj)
|
|
return newObj, err
|
|
} else if newObj != nil {
|
|
return o.update(metadata.GetName(), origObj, newObj)
|
|
}
|
|
return obj, nil
|
|
}
|
|
|
|
func checkNil(obj runtime.Object, f func(runtime.Object) (runtime.Object, error)) (runtime.Object, error) {
|
|
obj, err := f(obj)
|
|
if obj == nil || reflect.ValueOf(obj).IsNil() {
|
|
return nil, err
|
|
}
|
|
return obj, err
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) create(obj runtime.Object) (runtime.Object, bool, error) {
|
|
metadata, err := meta.Accessor(obj)
|
|
if err != nil {
|
|
return obj, false, err
|
|
}
|
|
|
|
if o.isInitialized(metadata) {
|
|
return nil, true, nil
|
|
}
|
|
|
|
if o.hasFinalize() {
|
|
obj, err = o.addFinalizer(obj)
|
|
if err != nil {
|
|
return obj, false, err
|
|
}
|
|
}
|
|
|
|
if !o.hasCreate() {
|
|
return obj, true, err
|
|
}
|
|
|
|
obj, err = o.record(obj, o.lifecycle.Create)
|
|
if err != nil {
|
|
return obj, false, err
|
|
}
|
|
|
|
obj, err = o.setInitialized(obj)
|
|
return obj, false, err
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) isInitialized(metadata metav1.Object) bool {
|
|
initialized := o.createKey()
|
|
return metadata.GetAnnotations()[initialized] == "true"
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) setInitialized(obj runtime.Object) (runtime.Object, error) {
|
|
metadata, err := meta.Accessor(obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
initialized := o.createKey()
|
|
|
|
if metadata.GetAnnotations() == nil {
|
|
metadata.SetAnnotations(map[string]string{})
|
|
}
|
|
metadata.GetAnnotations()[initialized] = "true"
|
|
|
|
return o.objectClient.Update(metadata.GetName(), obj)
|
|
}
|
|
|
|
func (o *objectLifecycleAdapter) addFinalizer(obj runtime.Object) (runtime.Object, error) {
|
|
metadata, err := meta.Accessor(obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if slice.ContainsString(metadata.GetFinalizers(), o.constructFinalizerKey()) {
|
|
return obj, nil
|
|
}
|
|
|
|
obj = obj.DeepCopyObject()
|
|
metadata, err = meta.Accessor(obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
metadata.SetFinalizers(append(metadata.GetFinalizers(), o.constructFinalizerKey()))
|
|
return o.objectClient.Update(metadata.GetName(), obj)
|
|
}
|