Skip to content

Commit 53f4009

Browse files
bdchathamclaude
andauthored
feat: dual hostname generation for platform.sei.io (#74)
* feat: dual hostname generation for platform.sei.io Add SEI_GATEWAY_PUBLIC_DOMAIN (required) to platform config. Each HTTPRoute now emits two hostnames: - {name}-{protocol}.{domain} (existing internal domain) - {name}-{protocol}.{namespace}.{publicDomain} (public-facing) Both hostnames land in the same HTTPRoute spec.hostnames[] array. The Gateway selects the correct listener/cert via SNI matching. Hostname format changed from {name}.{protocol}.{domain} to {name}-{protocol}.{domain} for both domains — this is a breaking change for existing routes, requiring redeployment of affected SeiNodeDeployments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: make SEI_GATEWAY_PUBLIC_DOMAIN optional Dev environments don't have a platform.sei.io zone or certs, so the public domain must be optional. When unset, HTTPRoutes emit only the internal hostname. When set, the public hostname is appended. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0284501 commit 53f4009

5 files changed

Lines changed: 84 additions & 54 deletions

File tree

cmd/main.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ func main() {
135135
GatewayName: os.Getenv("SEI_GATEWAY_NAME"),
136136
GatewayNamespace: os.Getenv("SEI_GATEWAY_NAMESPACE"),
137137
GatewayDomain: os.Getenv("SEI_GATEWAY_DOMAIN"),
138+
GatewayPublicDomain: os.Getenv("SEI_GATEWAY_PUBLIC_DOMAIN"),
138139
}
139140

140141
if err := platformCfg.Validate(); err != nil {
@@ -180,13 +181,14 @@ func main() {
180181
//nolint:staticcheck // migrating to events.EventRecorder API is a separate effort
181182
recorder := mgr.GetEventRecorderFor("seinodedeployment-controller")
182183
if err := (&nodedeploymentcontroller.SeiNodeDeploymentReconciler{
183-
Client: kc,
184-
Scheme: mgr.GetScheme(),
185-
Recorder: recorder,
186-
ControllerSA: controllerSA,
187-
GatewayName: platformCfg.GatewayName,
188-
GatewayNamespace: platformCfg.GatewayNamespace,
189-
GatewayDomain: platformCfg.GatewayDomain,
184+
Client: kc,
185+
Scheme: mgr.GetScheme(),
186+
Recorder: recorder,
187+
ControllerSA: controllerSA,
188+
GatewayName: platformCfg.GatewayName,
189+
GatewayNamespace: platformCfg.GatewayNamespace,
190+
GatewayDomain: platformCfg.GatewayDomain,
191+
GatewayPublicDomain: platformCfg.GatewayPublicDomain,
190192
PlanExecutor: &planner.Executor[*seiv1alpha1.SeiNodeDeployment]{
191193
Client: kc,
192194
ConfigFor: func(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) task.ExecutionConfig {

internal/controller/nodedeployment/controller.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ type SeiNodeDeploymentReconciler struct {
4343
// GatewayName, GatewayNamespace, and GatewayDomain identify the platform
4444
// Gateway for HTTPRoute parentRefs and hostname derivation.
4545
// Read from SEI_GATEWAY_NAME / SEI_GATEWAY_NAMESPACE / SEI_GATEWAY_DOMAIN.
46-
GatewayName string
47-
GatewayNamespace string
48-
GatewayDomain string
46+
GatewayName string
47+
GatewayNamespace string
48+
GatewayDomain string
49+
GatewayPublicDomain string
4950

5051
// PlanExecutor drives group-level task plans (e.g. genesis assembly).
5152
PlanExecutor planner.PlanExecutor[*seiv1alpha1.SeiNodeDeployment]

internal/controller/nodedeployment/networking.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func portsForMode(mode seiconfig.NodeMode) []corev1.ServicePort {
208208
}
209209

210210
func (r *SeiNodeDeploymentReconciler) reconcileRoute(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) error {
211-
routes := resolveEffectiveRoutes(group, r.GatewayDomain)
211+
routes := resolveEffectiveRoutes(group, r.GatewayDomain, r.GatewayPublicDomain)
212212
if len(routes) == 0 {
213213
removeCondition(group, seiv1alpha1.ConditionRouteReady)
214214
return r.deleteHTTPRoutesByLabel(ctx, group)
@@ -234,7 +234,7 @@ func (r *SeiNodeDeploymentReconciler) deleteHTTPRoutesByLabel(ctx context.Contex
234234
return nil
235235
}
236236

237-
func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment, domain string) []effectiveRoute {
237+
func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment, domain, publicDomain string) []effectiveRoute {
238238
modePorts := seiconfig.NodePortsForMode(groupMode(group))
239239

240240
activePorts := make(map[string]bool, len(modePorts))
@@ -247,9 +247,18 @@ func resolveEffectiveRoutes(group *seiv1alpha1.SeiNodeDeployment, domain string)
247247
if !isProtocolActiveForMode(proto.Prefix, activePorts) {
248248
continue
249249
}
250+
subdomain := fmt.Sprintf("%s-%s", group.Name, proto.Prefix)
251+
hostnames := []string{
252+
fmt.Sprintf("%s.%s", subdomain, domain),
253+
}
254+
if publicDomain != "" {
255+
hostnames = append(hostnames,
256+
fmt.Sprintf("%s.%s.%s", subdomain, group.Namespace, publicDomain),
257+
)
258+
}
250259
er := effectiveRoute{
251-
Name: fmt.Sprintf("%s-%s", group.Name, proto.Prefix),
252-
Hostnames: []string{fmt.Sprintf("%s.%s.%s", group.Name, proto.Prefix, domain)},
260+
Name: subdomain,
261+
Hostnames: hostnames,
253262
Port: proto.Port,
254263
}
255264
if proto.Prefix == "evm" && activePorts["evm-ws"] {

internal/controller/nodedeployment/networking_test.go

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -135,29 +135,29 @@ func TestGenerateExternalService_FullModeIncludesAllPorts(t *testing.T) {
135135

136136
func TestResolveEffectiveRoutes_FullMode_FourRoutes(t *testing.T) {
137137
g := NewWithT(t)
138-
group := newTestGroup("pacific-1-rpc", "sei")
138+
group := newTestGroup("pacific-1-wave", "pacific-1")
139139
group.Spec.Networking = &seiv1alpha1.NetworkingConfig{
140140
Service: &seiv1alpha1.ExternalServiceConfig{},
141141
Gateway: &seiv1alpha1.GatewayRouteConfig{},
142142
}
143143

144-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
144+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
145145
g.Expect(routes).To(HaveLen(4))
146146

147147
type routeExpectation struct {
148-
Name string
149-
Hostname string
150-
Port int32
148+
Name string
149+
Hostnames []string
150+
Port int32
151151
}
152152
expected := []routeExpectation{
153-
{"pacific-1-rpc-evm", "pacific-1-rpc.evm.prod.platform.sei.io", 8545},
154-
{"pacific-1-rpc-rpc", "pacific-1-rpc.rpc.prod.platform.sei.io", 26657},
155-
{"pacific-1-rpc-rest", "pacific-1-rpc.rest.prod.platform.sei.io", 1317},
156-
{"pacific-1-rpc-grpc", "pacific-1-rpc.grpc.prod.platform.sei.io", 9090},
153+
{"pacific-1-wave-evm", []string{"pacific-1-wave-evm.prod.platform.sei.io", "pacific-1-wave-evm.pacific-1.platform.sei.io"}, 8545},
154+
{"pacific-1-wave-rpc", []string{"pacific-1-wave-rpc.prod.platform.sei.io", "pacific-1-wave-rpc.pacific-1.platform.sei.io"}, 26657},
155+
{"pacific-1-wave-rest", []string{"pacific-1-wave-rest.prod.platform.sei.io", "pacific-1-wave-rest.pacific-1.platform.sei.io"}, 1317},
156+
{"pacific-1-wave-grpc", []string{"pacific-1-wave-grpc.prod.platform.sei.io", "pacific-1-wave-grpc.pacific-1.platform.sei.io"}, 9090},
157157
}
158158
for i, exp := range expected {
159159
g.Expect(routes[i].Name).To(Equal(exp.Name))
160-
g.Expect(routes[i].Hostnames).To(Equal([]string{exp.Hostname}))
160+
g.Expect(routes[i].Hostnames).To(Equal(exp.Hostnames))
161161
g.Expect(routes[i].Port).To(Equal(exp.Port))
162162
}
163163
}
@@ -172,7 +172,7 @@ func TestResolveEffectiveRoutes_ArchiveMode_FourRoutes(t *testing.T) {
172172
Gateway: &seiv1alpha1.GatewayRouteConfig{},
173173
}
174174

175-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
175+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
176176
g.Expect(routes).To(HaveLen(4))
177177
}
178178

@@ -186,44 +186,45 @@ func TestResolveEffectiveRoutes_ValidatorMode_NoRoutes(t *testing.T) {
186186
Gateway: &seiv1alpha1.GatewayRouteConfig{},
187187
}
188188

189-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
189+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
190190
g.Expect(routes).To(BeEmpty())
191191
}
192192

193193
func TestGenerateHTTPRoute_HostnamePattern(t *testing.T) {
194194
g := NewWithT(t)
195-
group := newTestGroup("pacific-1-rpc", "sei")
195+
group := newTestGroup("pacific-1-wave", "pacific-1")
196196
group.Spec.Networking = &seiv1alpha1.NetworkingConfig{
197197
Service: &seiv1alpha1.ExternalServiceConfig{},
198198
Gateway: &seiv1alpha1.GatewayRouteConfig{},
199199
}
200200

201-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
201+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
202202
g.Expect(routes).NotTo(BeEmpty())
203203

204204
for _, er := range routes {
205205
route := generateHTTPRoute(group, er, "sei-gateway", "istio-system")
206206
spec := route.Object["spec"].(map[string]any)
207207
hostnames := spec["hostnames"].([]any)
208-
g.Expect(hostnames).To(HaveLen(1))
209-
g.Expect(hostnames[0]).To(MatchRegexp(`^pacific-1-rpc\.\w+\.prod\.platform\.sei\.io$`))
208+
g.Expect(hostnames).To(HaveLen(2))
209+
g.Expect(hostnames[0]).To(MatchRegexp(`^pacific-1-wave-\w+\.prod\.platform\.sei\.io$`))
210+
g.Expect(hostnames[1]).To(MatchRegexp(`^pacific-1-wave-\w+\.pacific-1\.platform\.sei\.io$`))
210211
}
211212
}
212213

213214
func TestGenerateHTTPRoute_EVMMerged(t *testing.T) {
214215
g := NewWithT(t)
215-
group := newTestGroup("pacific-1-rpc", "sei")
216+
group := newTestGroup("pacific-1-wave", "pacific-1")
216217
group.Spec.Networking = &seiv1alpha1.NetworkingConfig{
217218
Service: &seiv1alpha1.ExternalServiceConfig{},
218219
Gateway: &seiv1alpha1.GatewayRouteConfig{},
219220
}
220221

221-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
222+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
222223

223224
var evmRoute effectiveRoute
224225
evmCount := 0
225226
for _, r := range routes {
226-
if r.Name == "pacific-1-rpc-evm" {
227+
if r.Name == "pacific-1-wave-evm" {
227228
evmCount++
228229
evmRoute = r
229230
}
@@ -240,15 +241,15 @@ func TestGenerateHTTPRoute_EVMMerged(t *testing.T) {
240241

241242
func TestGenerateHTTPRoute_EVMWebSocketRule(t *testing.T) {
242243
g := NewWithT(t)
243-
group := newTestGroup("pacific-1-rpc", "sei")
244+
group := newTestGroup("pacific-1-wave", "pacific-1")
244245
group.Spec.Networking = &seiv1alpha1.NetworkingConfig{
245246
Service: &seiv1alpha1.ExternalServiceConfig{},
246247
}
247248

248-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
249+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
249250
var evmRoute effectiveRoute
250251
for _, r := range routes {
251-
if r.Name == "pacific-1-rpc-evm" {
252+
if r.Name == "pacific-1-wave-evm" {
252253
evmRoute = r
253254
break
254255
}
@@ -284,7 +285,7 @@ func TestGenerateHTTPRoute_BasicFields(t *testing.T) {
284285
Gateway: &seiv1alpha1.GatewayRouteConfig{},
285286
}
286287

287-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
288+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
288289
g.Expect(routes).NotTo(BeEmpty())
289290
route := generateHTTPRoute(group, routes[0], "sei-gateway", "istio-system")
290291

@@ -307,7 +308,7 @@ func TestGenerateHTTPRoute_ManagedByAnnotation(t *testing.T) {
307308
Gateway: &seiv1alpha1.GatewayRouteConfig{},
308309
}
309310

310-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
311+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
311312
route := generateHTTPRoute(group, routes[0], "sei-gateway", "istio-system")
312313
g.Expect(route.GetAnnotations()).To(HaveKeyWithValue("sei.io/managed-by", "seinodedeployment"))
313314
}
@@ -320,7 +321,7 @@ func TestGenerateHTTPRoute_BackendRef(t *testing.T) {
320321
Gateway: &seiv1alpha1.GatewayRouteConfig{},
321322
}
322323

323-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
324+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
324325
var rpcRoute effectiveRoute
325326
for _, r := range routes {
326327
if r.Name == "archive-rpc-rpc" {
@@ -344,17 +345,17 @@ func TestGenerateHTTPRoute_BackendRef(t *testing.T) {
344345

345346
func TestGenerateHTTPRoute_GRPCRoutePort(t *testing.T) {
346347
g := NewWithT(t)
347-
group := newTestGroup("pacific-1-rpc", "sei")
348+
group := newTestGroup("pacific-1-wave", "pacific-1")
348349
group.Spec.Networking = &seiv1alpha1.NetworkingConfig{
349350
Service: &seiv1alpha1.ExternalServiceConfig{},
350351
Gateway: &seiv1alpha1.GatewayRouteConfig{},
351352
}
352353

353-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
354+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
354355

355356
var grpcRoute effectiveRoute
356357
for _, r := range routes {
357-
if r.Name == "pacific-1-rpc-grpc" {
358+
if r.Name == "pacific-1-wave-grpc" {
358359
grpcRoute = r
359360
break
360361
}
@@ -364,12 +365,12 @@ func TestGenerateHTTPRoute_GRPCRoutePort(t *testing.T) {
364365
httpRoute := generateHTTPRoute(group, grpcRoute, "sei-gateway", "istio-system")
365366
spec := httpRoute.Object["spec"].(map[string]any)
366367
hostnames := spec["hostnames"].([]any)
367-
g.Expect(hostnames).To(ConsistOf("pacific-1-rpc.grpc.prod.platform.sei.io"))
368+
g.Expect(hostnames).To(ConsistOf("pacific-1-wave-grpc.prod.platform.sei.io", "pacific-1-wave-grpc.pacific-1.platform.sei.io"))
368369

369370
rules := spec["rules"].([]any)
370371
backend := rules[0].(map[string]any)["backendRefs"].([]any)[0].(map[string]any)
371372
g.Expect(backend["port"]).To(Equal(int64(9090)))
372-
g.Expect(backend["name"]).To(Equal("pacific-1-rpc-external"))
373+
g.Expect(backend["name"]).To(Equal("pacific-1-wave-external"))
373374
}
374375

375376
// --- isProtocolActiveForMode ---
@@ -387,14 +388,30 @@ func TestIsProtocolActiveForMode_EVMMapping(t *testing.T) {
387388

388389
func TestResolveEffectiveRoutes_EmptyDomain_MalformedHostnames(t *testing.T) {
389390
g := NewWithT(t)
390-
group := newTestGroup("pacific-1-rpc", "sei")
391+
group := newTestGroup("pacific-1-wave", "pacific-1")
391392
group.Spec.Networking = &seiv1alpha1.NetworkingConfig{
392393
Service: &seiv1alpha1.ExternalServiceConfig{},
393394
}
394395

395-
routes := resolveEffectiveRoutes(group, "")
396+
routes := resolveEffectiveRoutes(group, "", "")
396397
g.Expect(routes).To(HaveLen(4), "routes are still generated even with empty domain")
397-
g.Expect(routes[0].Hostnames[0]).To(Equal("pacific-1-rpc.evm."), "empty domain produces trailing dot")
398+
g.Expect(routes[0].Hostnames).To(HaveLen(1), "no public hostname when public domain is empty")
399+
g.Expect(routes[0].Hostnames[0]).To(Equal("pacific-1-wave-evm."), "empty domain produces trailing dot")
400+
}
401+
402+
func TestResolveEffectiveRoutes_NoPublicDomain_SingleHostname(t *testing.T) {
403+
g := NewWithT(t)
404+
group := newTestGroup("pacific-1-wave", "pacific-1")
405+
group.Spec.Networking = &seiv1alpha1.NetworkingConfig{
406+
Service: &seiv1alpha1.ExternalServiceConfig{},
407+
}
408+
409+
routes := resolveEffectiveRoutes(group, "dev.platform.sei.io", "")
410+
g.Expect(routes).To(HaveLen(4))
411+
for _, r := range routes {
412+
g.Expect(r.Hostnames).To(HaveLen(1), "only internal hostname when public domain is empty")
413+
}
414+
g.Expect(routes[0].Hostnames[0]).To(Equal("pacific-1-wave-evm.dev.platform.sei.io"))
398415
}
399416

400417
func TestReconcileRoute_NoRoutesForValidatorMode(t *testing.T) {
@@ -405,20 +422,20 @@ func TestReconcileRoute_NoRoutesForValidatorMode(t *testing.T) {
405422
Service: &seiv1alpha1.ExternalServiceConfig{},
406423
}
407424

408-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
425+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
409426
g.Expect(routes).To(BeEmpty(), "validator mode should produce zero routes")
410427
}
411428

412429
func TestGenerateHTTPRoute_NonEVMRoute_SingleRule(t *testing.T) {
413430
g := NewWithT(t)
414-
group := newTestGroup("pacific-1-rpc", "sei")
431+
group := newTestGroup("pacific-1-wave", "pacific-1")
415432
group.Spec.Networking = &seiv1alpha1.NetworkingConfig{
416433
Service: &seiv1alpha1.ExternalServiceConfig{},
417434
}
418435

419-
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io")
436+
routes := resolveEffectiveRoutes(group, "prod.platform.sei.io", "platform.sei.io")
420437
for _, r := range routes {
421-
if r.Name == "pacific-1-rpc-rpc" {
438+
if r.Name == "pacific-1-wave-rpc" {
422439
httpRoute := generateHTTPRoute(group, r, "sei-gateway", "gateway")
423440
spec := httpRoute.Object["spec"].(map[string]any)
424441
rules := spec["rules"].([]any)

internal/platform/platform.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ type Config struct {
3737
GenesisBucket string
3838
GenesisRegion string
3939

40-
GatewayName string
41-
GatewayNamespace string
42-
GatewayDomain string
40+
GatewayName string
41+
GatewayNamespace string
42+
GatewayDomain string
43+
GatewayPublicDomain string
4344
}
4445

4546
// Validate returns an error if required fields are missing.

0 commit comments

Comments
 (0)