1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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)
}