feat: k8s informer for ServiceAdapter CRDs, bump CI to go1.26
- Add dynamic-client informer to watch ServiceAdapter CRs in-cluster - Remove typed scheme registration (dynamic client needs no DeepCopy) - Bump Dockerfile + CI images from golang:1.25 to golang:1.26 (k8s.io/[email protected] requires go>=1.26) - Wire informer into main.go startup with graceful fallback
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
package serviceadapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// InformerManager watches ServiceAdapter CRs and populates the registry.
|
||||
type InformerManager struct {
|
||||
registry *Registry
|
||||
informer cache.SharedIndexInformer
|
||||
stopChan chan struct{}
|
||||
factory dynamicinformer.DynamicSharedInformerFactory
|
||||
}
|
||||
|
||||
// NewInformerManager creates a new informer that watches ServiceAdapters in the given namespace.
|
||||
func NewInformerManager(dynamicClient dynamic.Interface, registry *Registry, namespace string) (*InformerManager, error) {
|
||||
if registry == nil {
|
||||
return nil, fmt.Errorf("registry cannot be nil")
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = "api"
|
||||
}
|
||||
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: "gateway.riotpiao.com",
|
||||
Version: "v1",
|
||||
Resource: "serviceadapters",
|
||||
}
|
||||
|
||||
factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
|
||||
dynamicClient, 30*time.Second, namespace, nil,
|
||||
)
|
||||
|
||||
informer := factory.ForResource(gvr).Informer()
|
||||
|
||||
_, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
u := obj.(*unstructured.Unstructured)
|
||||
if a, err := parseServiceAdapter(u); err == nil {
|
||||
if registry.Add(a) == nil {
|
||||
log.Printf("serviceadapter added: %s", a.ServiceName)
|
||||
}
|
||||
} else {
|
||||
log.Printf("serviceadapter parse error on add: %v", err)
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(_, newObj interface{}) {
|
||||
u := newObj.(*unstructured.Unstructured)
|
||||
if a, err := parseServiceAdapter(u); err == nil {
|
||||
registry.Update(a)
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
u, ok = tombstone.Obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
if name, ok := u.Object["metadata"].(map[string]interface{})["name"]; ok {
|
||||
registry.Delete(name.(string))
|
||||
log.Printf("serviceadapter deleted: %s", name)
|
||||
}
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adding event handler: %w", err)
|
||||
}
|
||||
|
||||
return &InformerManager{
|
||||
registry: registry,
|
||||
informer: informer,
|
||||
stopChan: make(chan struct{}),
|
||||
factory: factory,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start begins watching ServiceAdapter resources. Blocks until cache syncs or timeout.
|
||||
func (im *InformerManager) Start(ctx context.Context) error {
|
||||
im.factory.Start(im.stopChan)
|
||||
|
||||
done := make(chan bool, 1)
|
||||
go func() { done <- cache.WaitForCacheSync(im.stopChan, im.informer.HasSynced) }()
|
||||
|
||||
select {
|
||||
case synced := <-done:
|
||||
if !synced {
|
||||
return fmt.Errorf("cache sync failed")
|
||||
}
|
||||
case <-time.After(30 * time.Second):
|
||||
return fmt.Errorf("cache sync timed out")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the informer.
|
||||
func (im *InformerManager) Stop() {
|
||||
close(im.stopChan)
|
||||
}
|
||||
|
||||
// parseServiceAdapter converts an unstructured object to ServiceAdapter.
|
||||
func parseServiceAdapter(u *unstructured.Unstructured) (*ServiceAdapter, error) {
|
||||
metadata, ok := u.Object["metadata"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid metadata")
|
||||
}
|
||||
name, _ := metadata["name"].(string)
|
||||
namespace, _ := metadata["namespace"].(string)
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
spec, ok := u.Object["spec"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing spec")
|
||||
}
|
||||
|
||||
serviceName, ok := spec["serviceName"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing spec.serviceName")
|
||||
}
|
||||
|
||||
adapter := &ServiceAdapter{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
ServiceName: serviceName,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if upstreamMap, ok := spec["upstream"].(map[string]interface{}); ok {
|
||||
if url, ok := upstreamMap["url"].(string); ok {
|
||||
adapter.Spec.Upstream.URL = url
|
||||
}
|
||||
if timeout, ok := upstreamMap["timeoutSeconds"].(float64); ok {
|
||||
adapter.Spec.Upstream.TimeoutSeconds = int32(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
if authMap, ok := spec["auth"].(map[string]interface{}); ok {
|
||||
if required, ok := authMap["required"].(bool); ok {
|
||||
adapter.Spec.Auth.Required = required
|
||||
}
|
||||
if capability, ok := authMap["capability"].(string); ok {
|
||||
adapter.Spec.Auth.Capability = capability
|
||||
}
|
||||
}
|
||||
|
||||
return adapter, nil
|
||||
}
|
||||
Reference in New Issue
Block a user