Skip to content

Commit 562bb25

Browse files
committed
fix: attach extra_networks to service containers
ServiceContainerSpec was silently dropping extra_networks from orchestrator_opts. Services were only attached to bridge and the database overlay, even when extra networks were specified. Mirror the existing Postgres spec.go pattern to append ExtraNetworks with target and aliases.
1 parent 63c54b8 commit 562bb25

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed

server/internal/orchestrator/swarm/service_spec.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,18 @@ func ServiceContainerSpec(opts *ServiceContainerSpecOptions) (swarm.ServiceSpec,
6767
"pgedge.host.id": opts.HostID,
6868
}
6969

70-
// Merge user-provided extra labels (matches Postgres ExtraLabels behavior)
71-
if opts.ServiceSpec.OrchestratorOpts != nil && opts.ServiceSpec.OrchestratorOpts.Swarm != nil {
72-
for k, v := range opts.ServiceSpec.OrchestratorOpts.Swarm.ExtraLabels {
70+
// Extract swarm orchestrator options (matches Postgres pattern in spec.go).
71+
// NOTE: ExtraVolumes is not supported for service containers — services get
72+
// their bind mounts configured per service type below. Add support here if a
73+
// use case arises.
74+
var swarmOpts *database.SwarmOpts
75+
if opts.ServiceSpec.OrchestratorOpts != nil {
76+
swarmOpts = opts.ServiceSpec.OrchestratorOpts.Swarm
77+
}
78+
79+
// Merge user-provided extra labels
80+
if swarmOpts != nil {
81+
for k, v := range swarmOpts.ExtraLabels {
7382
labels[k] = v
7483
}
7584
}
@@ -90,6 +99,18 @@ func ServiceContainerSpec(opts *ServiceContainerSpecOptions) (swarm.ServiceSpec,
9099
},
91100
}
92101

102+
// Append user-requested extra networks (e.g. Traefik, reverse proxy).
103+
// NOTE: DriverOpts on ExtraNetworkSpec is accepted by the API but not
104+
// passed through here or in Postgres spec.go — add if needed.
105+
if swarmOpts != nil {
106+
for _, net := range swarmOpts.ExtraNetworks {
107+
networks = append(networks, swarm.NetworkAttachmentConfig{
108+
Target: net.ID,
109+
Aliases: net.Aliases,
110+
})
111+
}
112+
}
113+
93114
// Get container image (already resolved in ServiceImage)
94115
image := opts.ServiceImage.Tag
95116

server/internal/orchestrator/swarm/service_spec_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,86 @@ func intPtr(i int) *int {
309309
return &i
310310
}
311311

312+
func TestServiceContainerSpec_ExtraNetworks(t *testing.T) {
313+
opts := &ServiceContainerSpecOptions{
314+
ServiceSpec: &database.ServiceSpec{
315+
ServiceID: "mcp-server",
316+
ServiceType: "mcp",
317+
OrchestratorOpts: &database.OrchestratorOpts{
318+
Swarm: &database.SwarmOpts{
319+
ExtraNetworks: []database.ExtraNetworkSpec{
320+
{ID: "traefik_us-west-2a", Aliases: []string{"mcp"}},
321+
{ID: "monitoring"},
322+
},
323+
},
324+
},
325+
},
326+
ServiceInstanceID: "db1-mcp-host1",
327+
DatabaseID: "db1",
328+
DatabaseName: "testdb",
329+
HostID: "host1",
330+
ServiceName: "db1-mcp-host1",
331+
Hostname: "mcp-host1",
332+
CohortMemberID: "node-123",
333+
ServiceImage: &ServiceImage{Tag: "ghcr.io/pgedge/postgres-mcp:latest"},
334+
Credentials: &database.ServiceUser{Username: "svc_mcp", Password: "pw"},
335+
DatabaseNetworkID: "db1-database",
336+
Port: intPtr(8080),
337+
DataPath: "/var/lib/pgedge/services/db1-mcp-host1",
338+
}
339+
340+
spec, err := ServiceContainerSpec(opts)
341+
if err != nil {
342+
t.Fatalf("ServiceContainerSpec() error = %v", err)
343+
}
344+
345+
networks := spec.TaskTemplate.Networks
346+
// [0]=bridge, [1]=database overlay, [2..]=extra networks
347+
if len(networks) != 4 {
348+
t.Fatalf("got %d networks, want 4", len(networks))
349+
}
350+
if networks[2].Target != "traefik_us-west-2a" {
351+
t.Errorf("networks[2].Target = %q, want %q", networks[2].Target, "traefik_us-west-2a")
352+
}
353+
if len(networks[2].Aliases) != 1 || networks[2].Aliases[0] != "mcp" {
354+
t.Errorf("networks[2].Aliases = %v, want [mcp]", networks[2].Aliases)
355+
}
356+
if networks[3].Target != "monitoring" {
357+
t.Errorf("networks[3].Target = %q, want %q", networks[3].Target, "monitoring")
358+
}
359+
}
360+
361+
func TestServiceContainerSpec_NoExtraNetworks(t *testing.T) {
362+
opts := &ServiceContainerSpecOptions{
363+
ServiceSpec: &database.ServiceSpec{
364+
ServiceID: "mcp-server",
365+
ServiceType: "mcp",
366+
},
367+
ServiceInstanceID: "db1-mcp-host1",
368+
DatabaseID: "db1",
369+
DatabaseName: "testdb",
370+
HostID: "host1",
371+
ServiceName: "db1-mcp-host1",
372+
Hostname: "mcp-host1",
373+
CohortMemberID: "node-123",
374+
ServiceImage: &ServiceImage{Tag: "ghcr.io/pgedge/postgres-mcp:latest"},
375+
Credentials: &database.ServiceUser{Username: "svc_mcp", Password: "pw"},
376+
DatabaseNetworkID: "db1-database",
377+
Port: intPtr(8080),
378+
DataPath: "/var/lib/pgedge/services/db1-mcp-host1",
379+
}
380+
381+
spec, err := ServiceContainerSpec(opts)
382+
if err != nil {
383+
t.Fatalf("ServiceContainerSpec() error = %v", err)
384+
}
385+
386+
networks := spec.TaskTemplate.Networks
387+
if len(networks) != 2 {
388+
t.Fatalf("got %d networks, want 2", len(networks))
389+
}
390+
}
391+
312392
// --- PostgREST container spec tests ---
313393

314394
func makePostgRESTSpecOpts() *ServiceContainerSpecOptions {

server/internal/orchestrator/swarm/spec.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ func DatabaseServiceSpec(
7171
mounts = append(mounts, docker.BuildMount(vol.HostPath, vol.DestinationPath, false))
7272
}
7373

74+
// NOTE: DriverOpts on ExtraNetworkSpec is accepted by the API but not
75+
// passed through here — add if needed.
7476
for _, net := range instance.OrchestratorOpts.Swarm.ExtraNetworks {
7577
networks = append(networks, swarm.NetworkAttachmentConfig{
7678
Target: net.ID,

0 commit comments

Comments
 (0)