~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
121 lines · 3.3 KB Raw History
package componentcore

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"strings"
	"sync"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/component"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/exposure"
)

const ingressProxyKind = "IngressProxy"

type ingressRegistrarProvider interface {
	IngressRegistrar() exposure.IngressRegistrar
}

type ingressProviderNamer interface {
	IngressProvider() string
}

type ingressDispatcher struct {
	registry *component.Registry

	fallback exposure.IngressRegistrar

	fallbackName string

	log *slog.Logger

	mu sync.Mutex

	served map[string]string
}

func newIngressDispatcher(registry *component.Registry, fallback exposure.IngressRegistrar, fallbackName string, log *slog.Logger) *ingressDispatcher {
	if log == nil {
		log = slog.New(slog.DiscardHandler)
	}
	return &ingressDispatcher{
		registry:     registry,
		fallback:     fallback,
		fallbackName: fallbackName,
		log:          log.With("component", "exposure-ingress"),
		served:       map[string]string{},
	}
}

func (d *ingressDispatcher) Register(ctx context.Context, spec exposure.IngressSpec) (exposure.IngressRegistration, error) {
	ref := strings.TrimSpace(spec.IngressRef)
	if ref != "" {
		if entry, ok := d.resolve(spec.Workspace, ref); ok {
			service := entry.Service
			if service == nil {
				return nil, fmt.Errorf("the IngressProxy component %s/%s exposes no ingress registrar", entry.Key.Workspace, entry.Key.Name)
			}
			provider, ok := service.(ingressRegistrarProvider)
			if !ok || provider.IngressRegistrar() == nil {
				return nil, fmt.Errorf("the IngressProxy component %s/%s exposes no ingress registrar", entry.Key.Workspace, entry.Key.Name)
			}
			name := "component"
			if named, ok := service.(ingressProviderNamer); ok {
				if value := strings.TrimSpace(named.IngressProvider()); value != "" {
					name = value
				}
			}
			registration, err := provider.IngressRegistrar().Register(ctx, spec)
			if err != nil {
				return nil, err
			}
			d.logServed(spec, entry.Key.Name, name)
			return registration, nil
		}
	}
	if d.fallback == nil {
		return nil, errors.New("no ingress registrar is configured")
	}
	name := d.fallbackName
	if named, ok := d.fallback.(ingressProviderNamer); ok {
		if value := strings.TrimSpace(named.IngressProvider()); value != "" {
			name = value
		}
	}
	registration, err := d.fallback.Register(ctx, spec)
	if err != nil {
		return nil, err
	}
	d.logServed(spec, ref, name)
	return registration, nil
}

func (d *ingressDispatcher) resolve(workspace, name string) (component.Entry, bool) {
	if d.registry == nil {
		return component.Entry{}, false
	}
	if entry, ok := d.registry.Lookup(workspace, ingressProxyKind, name); ok {
		return entry, true
	}
	for _, entry := range d.registry.List("") {
		if entry.Key.Kind == ingressProxyKind && entry.Key.Name == name {
			return entry, true
		}
	}
	return component.Entry{}, false
}

func (d *ingressDispatcher) logServed(spec exposure.IngressSpec, ingress, provider string) {
	key := spec.Workspace + "/" + spec.Exposure
	d.mu.Lock()
	if _, ok := d.served[key]; ok {
		d.mu.Unlock()
		return
	}
	d.served[key] = provider
	d.mu.Unlock()
	d.log.Info("the exposure is served by an ingress provider", "workspace", spec.Workspace,
		"exposure", spec.Exposure, "ingress", ingress, "provider", provider)
}