~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
106 lines · 2.4 KB Raw History
package kube

import (
	"context"
	"sync"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/supervise"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/tools/cache"
)

type Event[T any] struct {
	Type   watch.EventType
	Object *T
}

func Watch[T any](ctx context.Context, r Resource, opts metav1.ListOptions) (<-chan Event[T], func(), error) {
	ri, err := r.iface()
	if err != nil {
		return nil, nil, err
	}
	w, err := ri.Watch(ctx, opts)
	if err != nil {
		return nil, nil, err
	}
	out := make(chan Event[T])
	forwarder := supervise.New(ctx)
	var once sync.Once
	stop := func() {
		once.Do(func() {
			w.Stop()
			_ = forwarder.Stop()
		})
	}
	forwarder.Go("kube: watch", func(ctx context.Context) error {
		defer close(out)
		for ev := range w.ResultChan() {
			e := Event[T]{Type: ev.Type}
			if u, ok := ev.Object.(*unstructured.Unstructured); ok {
				v, err := Decode[T](u.Object)
				if err != nil {
					continue
				}
				e.Object = v
			} else if ev.Type != watch.Error {
				continue
			}
			select {
			case out <- e:
			case <-ctx.Done():
				return nil
			}
		}
		return nil
	})
	return out, stop, nil
}

type Cache[T any] struct {
	store cache.Store
	ctrl  cache.Controller
}

func NewCache[T any](r Resource, opts metav1.ListOptions, changed func()) (*Cache[T], error) {
	if _, err := r.iface(); err != nil {
		return nil, err
	}
	kick := func() {
		if changed != nil {
			changed()
		}
	}
	store, ctrl := cache.NewInformerWithOptions(cache.InformerOptions{ListerWatcher: r.ListWatch(opts), ObjectType: &unstructured.Unstructured{},
		Handler: cache.ResourceEventHandlerFuncs{
			AddFunc:    func(any) { kick() },
			UpdateFunc: func(any, any) { kick() },
			DeleteFunc: func(any) { kick() },
		}})
	return &Cache[T]{store: store, ctrl: ctrl}, nil
}

func (c *Cache[T]) Run(ctx context.Context) { c.ctrl.RunWithContext(ctx) }

func (c *Cache[T]) HasSynced() bool { return c.ctrl.HasSynced() }

func (c *Cache[T]) WaitSynced(ctx context.Context) bool {
	return c.ctrl.HasSynced() || cache.WaitForCacheSync(ctx.Done(), c.ctrl.HasSynced)
}

func (c *Cache[T]) List() []T {
	var out []T
	for _, item := range c.store.List() {
		u, ok := item.(*unstructured.Unstructured)
		if !ok {
			continue
		}
		v, err := Decode[T](u.Object)
		if err != nil {
			continue
		}
		out = append(out, *v)
	}
	return out
}