package controller import ( "context" "errors" "fmt" "reflect" "strings" "sync" "sync/atomic" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" ) var ( ErrIgnore = errors.New("ignore handler error") ) type handlerEntry struct { id int64 name string handler SharedControllerHandler } type SharedHandler struct { // keep first because arm32 needs atomic.AddInt64 target to be mem aligned idCounter int64 lock sync.RWMutex handlers []handlerEntry } func (h *SharedHandler) Register(ctx context.Context, name string, handler SharedControllerHandler) { h.lock.Lock() defer h.lock.Unlock() id := atomic.AddInt64(&h.idCounter, 1) h.handlers = append(h.handlers, handlerEntry{ id: id, name: name, handler: handler, }) go func() { <-ctx.Done() h.lock.Lock() defer h.lock.Unlock() for i := range h.handlers { if h.handlers[i].id == id { h.handlers = append(h.handlers[:i], h.handlers[i+1:]...) break } } }() } func (h *SharedHandler) OnChange(key string, obj runtime.Object) error { var ( errs errorList ) h.lock.RLock() handlers := h.handlers h.lock.RUnlock() for _, handler := range handlers { newObj, err := handler.handler.OnChange(key, obj) if err != nil && !errors.Is(err, ErrIgnore) { errs = append(errs, &handlerError{ HandlerName: handler.name, Err: err, }) } if newObj != nil && !reflect.ValueOf(newObj).IsNil() { meta, err := meta.Accessor(newObj) if err == nil && meta.GetUID() != "" { // avoid using an empty object obj = newObj } else if err != nil { // assign if we can't determine metadata obj = newObj } } } return errs.ToErr() } type errorList []error func (e errorList) Error() string { buf := strings.Builder{} for _, err := range e { if buf.Len() > 0 { buf.WriteString(", ") } buf.WriteString(err.Error()) } return buf.String() } func (e errorList) ToErr() error { switch len(e) { case 0: return nil case 1: return e[0] default: return e } } func (e errorList) Cause() error { if len(e) > 0 { return e[0] } return nil } type handlerError struct { HandlerName string Err error } func (h handlerError) Error() string { return fmt.Sprintf("handler %s: %v", h.HandlerName, h.Err) } func (h handlerError) Cause() error { return h.Err }