main bac21c6

builder_test.go RawHistoryFolder

1package traffic
2
3import (
4	"testing"
5	"time"
6
7	"github.com/stretchr/testify/require"
8
9	"github.com/bnema/gordon/internal/domain"
10)
11
12func TestBuildSmartTCPRoutesDefaultToEdge(t *testing.T) {
13	graph, err := Build(Input{
14		EntryPoints: map[string]EntryPointConfig{"edge": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}},
15		Routes:      []domain.Route{{Domain: "app.example.com"}},
16	})
17	require.NoError(t, err)
18	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:app.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "app.example.com"}, Service: "route:app.example.com"})
19}
20
21func TestBuildManagedRoutesUseOnlyCustomSmartTCPEntrypoint(t *testing.T) {
22	graph, err := Build(Input{
23		EntryPoints: map[string]EntryPointConfig{"public": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}},
24		Routes:      []domain.Route{{Domain: "app.example.com", HTTPS: true}},
25	})
26	require.NoError(t, err)
27	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:app.example.com", EntryPoint: "public", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "app.example.com"}, Service: "route:app.example.com"})
28}
29
30func TestBuildRouteEntrypointPrefersEdge(t *testing.T) {
31	graph, err := Build(Input{
32		EntryPoints: map[string]EntryPointConfig{
33			"edge":   {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP},
34			"public": {Address: ":8443", Protocol: domain.EntryPointProtocolSmartTCP},
35		},
36		Routes: []domain.Route{{Domain: "app.example.com", HTTPS: true}},
37	})
38	require.NoError(t, err)
39	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:app.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "app.example.com"}, Service: "route:app.example.com"})
40}
41
42func TestBuildRoutesRejectAmbiguousRouteEntrypoints(t *testing.T) {
43	_, err := Build(Input{
44		EntryPoints: map[string]EntryPointConfig{
45			"public-a": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP},
46			"public-b": {Address: ":8443", Protocol: domain.EntryPointProtocolTLSMux},
47		},
48		Routes: []domain.Route{{Domain: "app.example.com", HTTPS: true}},
49	})
50	require.ErrorContains(t, err, "multiple route-capable entrypoints")
51}
52
53func TestBuildExternalRoutesUseResolvedRouteEntryPoint(t *testing.T) {
54	graph, err := Build(Input{
55		EntryPoints:    map[string]EntryPointConfig{"public": {Address: ":443", Protocol: domain.EntryPointProtocolTLSMux}},
56		ExternalRoutes: map[string]string{"ext.example.com": "backend.local:8080"},
57	})
58	require.NoError(t, err)
59	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "external_route:ext.example.com", EntryPoint: "public", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "ext.example.com"}, Service: "external_route:ext.example.com"})
60}
61
62func TestBuildRawFallbackConfigMapsToEntryPoint(t *testing.T) {
63	graph, err := Build(Input{
64		EntryPoints: map[string]EntryPointConfig{"edge": {
65			Address:                 ":443",
66			Protocol:                domain.EntryPointProtocolSmartTCP,
67			RawFallback:             "ssh-fallback",
68			RawFallbackTrustedCIDRs: []string{"100.64.0.0/10"},
69			AllowPublicRawFallback:  true,
70		}},
71		Traffic:         Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "ssh-fallback", EntryPoint: "edge", Service: "network_service:ssh:ssh"}}}},
72		NetworkServices: []NetworkServiceConfig{{Name: "ssh", Ports: []PortConfig{{Name: "ssh", Container: 22, Protocol: domain.NetworkProtocolTCP}}}},
73	})
74	require.NoError(t, err)
75	require.Equal(t, []domain.EntryPoint{{
76		Name:                    "edge",
77		Address:                 ":443",
78		Protocol:                domain.EntryPointProtocolSmartTCP,
79		RawFallback:             "ssh-fallback",
80		RawFallbackTrustedCIDRs: []string{"100.64.0.0/10"},
81		AllowPublicRawFallback:  true,
82	}}, graph.EntryPoints)
83}
84
85func TestBuildSmartTCPRoutesRequireCompatibleEdge(t *testing.T) {
86	_, err := Build(Input{Routes: []domain.Route{{Domain: "app.example.com"}}})
87	require.ErrorContains(t, err, "entrypoint")
88}
89
90func TestBuildDefaultsAndHTTPRoutes(t *testing.T) {
91	graph, err := Build(Input{
92		EntryPoints:    map[string]EntryPointConfig{"edge": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}},
93		Routes:         []domain.Route{{Domain: "app.example.com"}, {Domain: "secure.example.com", HTTPS: true}},
94		ExternalRoutes: map[string]string{"ext.example.com": "backend.local:8080"},
95	})
96	require.NoError(t, err)
97
98	require.Equal(t, []domain.EntryPoint{{Name: "edge", Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}}, graph.EntryPoints)
99	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:app.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "app.example.com"}, Service: "route:app.example.com"})
100	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:secure.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "secure.example.com"}, Service: "route:secure.example.com"})
101	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "external_route:ext.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "ext.example.com"}, Service: "external_route:ext.example.com"})
102	require.Contains(t, graph.Services, domain.TrafficService{Name: "external_route:ext.example.com", Backends: []domain.TrafficBackend{{Name: "ext.example.com", Host: "backend.local", Port: 8080, Protocol: domain.NetworkProtocolTCP}}})
103}
104
105func TestBuildCustomEntryPointTrustedCIDRs(t *testing.T) {
106	graph, err := Build(Input{EntryPoints: map[string]EntryPointConfig{"tcp-public": {Address: ":5432", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"10.0.0.0/8"}}}})
107	require.NoError(t, err)
108	require.Equal(t, []domain.EntryPoint{{Name: "tcp-public", Address: ":5432", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"10.0.0.0/8"}}}, graph.EntryPoints)
109}
110
111func TestBuildExplicitTCPUDPTLSRouterResolution(t *testing.T) {
112	graph, err := Build(Input{
113		EntryPoints: map[string]EntryPointConfig{
114			"tcp-db":  {Address: ":5432", Protocol: domain.EntryPointProtocolTCP},
115			"udp-dns": {Address: ":5353", Protocol: domain.EntryPointProtocolUDP},
116			"tls-db":  {Address: ":15432", Protocol: domain.EntryPointProtocolTLSMux},
117		},
118		Traffic: Config{
119			TCP: TCPConfig{Routers: []RouterConfig{{Name: "db", EntryPoint: "tcp-db", Service: "network_service:postgres:sql"}}},
120			UDP: UDPConfig{Routers: []RouterConfig{{Name: "dns", EntryPoint: "udp-dns", Service: "network_service:coredns:dns"}}},
121			TLS: TLSConfig{Routers: []RouterConfig{{Name: "tlsdb", EntryPoint: "tls-db", SNI: "db.example.com", Service: "network_service:postgres:sql"}}},
122		},
123		NetworkServices: []NetworkServiceConfig{
124			{Name: "postgres", Ports: []PortConfig{{Name: "sql", Container: 5432, Protocol: domain.NetworkProtocolTCP}}},
125			{Name: "coredns", Ports: []PortConfig{{Name: "dns", Container: 53, Protocol: domain.NetworkProtocolUDP}}},
126		},
127	})
128	require.NoError(t, err)
129	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "db", EntryPoint: "tcp-db", Protocol: domain.RouterProtocolTCP, Service: "network_service:postgres:sql"})
130	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "dns", EntryPoint: "udp-dns", Protocol: domain.RouterProtocolUDP, Service: "network_service:coredns:dns"})
131	require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "tlsdb", EntryPoint: "tls-db", Protocol: domain.RouterProtocolTLSPassthrough, Rule: domain.TrafficRule{SNI: "db.example.com"}, Service: "network_service:postgres:sql"})
132	require.Contains(t, graph.Services, domain.TrafficService{Name: "network_service:postgres:sql", Backends: []domain.TrafficBackend{{Name: "postgres:sql", Host: "postgres", Port: 5432, Protocol: domain.NetworkProtocolTCP}}})
133}
134
135func TestBuildRejectsStaticServiceRef(t *testing.T) {
136	_, err := Build(Input{
137		EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
138		Traffic:     Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "static:legacy"}}}},
139	})
140	require.ErrorContains(t, err, "static")
141}
142
143func TestBuildRejectsTLSHTTPRouteConflict(t *testing.T) {
144	_, err := Build(Input{
145		EntryPoints:     map[string]EntryPointConfig{"edge": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}},
146		Routes:          []domain.Route{{Domain: "app.example.com", HTTPS: true}},
147		Traffic:         Config{TLS: TLSConfig{Routers: []RouterConfig{{Name: "passthrough", EntryPoint: "edge", SNI: "app.example.com", Service: "network_service:app:https"}}}},
148		NetworkServices: []NetworkServiceConfig{{Name: "app", Ports: []PortConfig{{Name: "https", Container: 443, Protocol: domain.NetworkProtocolTCP}}}},
149	})
150	require.ErrorContains(t, err, "http host conflicts")
151}
152
153func TestBuildOptionsParsing(t *testing.T) {
154	graph, err := Build(Input{Traffic: Config{TCP: TCPConfig{DialTimeout: "1s", IdleTimeout: "2m", DrainTimeout: "3s", MaxConnections: 12}, UDP: UDPConfig{IdleTimeout: "4s", DrainTimeout: "5s", MaxSessions: 34}}})
155	require.NoError(t, err)
156	require.Equal(t, time.Second, graph.Options.TCP.DialTimeout)
157	require.Equal(t, 2*time.Minute, graph.Options.TCP.IdleTimeout)
158	require.Equal(t, 3*time.Second, graph.Options.TCP.DrainTimeout)
159	require.Equal(t, 12, graph.Options.TCP.MaxConnections)
160	require.Equal(t, 4*time.Second, graph.Options.UDP.IdleTimeout)
161	require.Equal(t, 5*time.Second, graph.Options.UDP.DrainTimeout)
162	require.Equal(t, 34, graph.Options.UDP.MaxSessions)
163
164	defaults, err := Build(Input{})
165	require.NoError(t, err)
166	require.Equal(t, 10*time.Second, defaults.Options.TCP.DialTimeout)
167	require.Equal(t, 5*time.Minute, defaults.Options.TCP.IdleTimeout)
168	require.Equal(t, 30*time.Second, defaults.Options.TCP.DrainTimeout)
169	require.Equal(t, 30*time.Second, defaults.Options.UDP.IdleTimeout)
170	require.Equal(t, 30*time.Second, defaults.Options.UDP.DrainTimeout)
171
172	_, err = Build(Input{Traffic: Config{TCP: TCPConfig{DialTimeout: "bogus"}}})
173	require.ErrorContains(t, err, "traffic.tcp.dial_timeout")
174}
175
176func TestBuildHTTPOptions(t *testing.T) {
177	defaults, err := Build(Input{})
178	require.NoError(t, err)
179	require.Equal(t, domain.DefaultHTTPOptions(), defaults.Options.HTTP)
180
181	graph, err := Build(Input{Traffic: Config{HTTP: HTTPConfig{ReadHeaderTimeout: "1s", IdleTimeout: "2s", TransferIdleTimeout: "3m"}}})
182	require.NoError(t, err)
183	require.Equal(t, domain.HTTPOptions{ReadHeaderTimeout: time.Second, IdleTimeout: 2 * time.Second, TransferIdleTimeout: 3 * time.Minute}, graph.Options.HTTP)
184
185	for _, value := range []string{"bogus", "-1s", "0s"} {
186		_, err = Build(Input{Traffic: Config{HTTP: HTTPConfig{TransferIdleTimeout: value}}})
187		require.ErrorContains(t, err, "traffic.http.transfer_idle_timeout", value)
188	}
189}
190
191func TestBuildRejectsAmbiguousNetworkServices(t *testing.T) {
192	t.Run("duplicate service name", func(t *testing.T) {
193		_, err := Build(Input{NetworkServices: []NetworkServiceConfig{
194			{Name: "db", Ports: []PortConfig{{Name: "sql", Container: 5432, Protocol: domain.NetworkProtocolTCP}}},
195			{Name: "db", Ports: []PortConfig{{Name: "sql", Container: 5433, Protocol: domain.NetworkProtocolTCP}}},
196		}})
197		require.ErrorContains(t, err, "duplicate network service")
198	})
199
200	t.Run("duplicate port name", func(t *testing.T) {
201		_, err := Build(Input{NetworkServices: []NetworkServiceConfig{
202			{Name: "db", Ports: []PortConfig{
203				{Name: "sql", Container: 5432, Protocol: domain.NetworkProtocolTCP},
204				{Name: "sql", Container: 5433, Protocol: domain.NetworkProtocolTCP},
205			}},
206		}})
207		require.ErrorContains(t, err, "duplicate port")
208	})
209
210	t.Run("unused invalid port rejected", func(t *testing.T) {
211		_, err := Build(Input{NetworkServices: []NetworkServiceConfig{
212			{Name: "db", Ports: []PortConfig{{Name: "unused", Container: 0, Protocol: domain.NetworkProtocolTCP}}},
213		}})
214		require.ErrorContains(t, err, "invalid container port")
215	})
216
217	t.Run("unused invalid protocol rejected", func(t *testing.T) {
218		_, err := Build(Input{NetworkServices: []NetworkServiceConfig{
219			{Name: "db", Ports: []PortConfig{{Name: "unused", Container: 5432, Protocol: domain.NetworkProtocol("sctp")}}},
220		}})
221		require.ErrorContains(t, err, "invalid protocol")
222	})
223}
224
225func TestBuildResolvesStandaloneServiceBackend(t *testing.T) {
226	graph, err := Build(Input{
227		EntryPoints: map[string]EntryPointConfig{"udp": {Address: ":28015", Protocol: domain.EntryPointProtocolUDP}},
228		Traffic:     Config{UDP: UDPConfig{Routers: []RouterConfig{{Name: "rust-game", EntryPoint: "udp", Service: "service:rust:game"}}}},
229		Services: []domain.StandaloneService{{
230			Name:    "rust",
231			Image:   "localhost/rust:latest",
232			Enabled: true,
233			Ports:   []domain.StandaloneServicePort{{Name: "game", Container: 28015, Protocol: domain.NetworkProtocolUDP, Publish: "127.0.0.1:38015"}},
234		}},
235	})
236	require.NoError(t, err)
237	require.Contains(t, graph.Services, domain.TrafficService{Name: "service:rust:game", Backends: []domain.TrafficBackend{{Name: "rust:game", Host: "127.0.0.1", Port: 38015, Protocol: domain.NetworkProtocolUDP}}})
238}
239
240func TestBuildRejectsInvalidStandaloneServiceRefs(t *testing.T) {
241	base := Input{
242		EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
243		Services: []domain.StandaloneService{{
244			Name:    "rust",
245			Image:   "localhost/rust:latest",
246			Enabled: true,
247			Ports:   []domain.StandaloneServicePort{{Name: "game", Container: 28015, Protocol: domain.NetworkProtocolUDP, Publish: "127.0.0.1:38015"}, {Name: "rcon", Container: 28016, Protocol: domain.NetworkProtocolTCP, Publish: "127.0.0.1:38016", Private: true, TrustedCIDRs: []string{"100.64.0.0/10"}}},
248		}},
249	}
250
251	t.Run("unknown service", func(t *testing.T) {
252		input := base
253		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:missing:rcon"}}}}
254		_, err := Build(input)
255		require.ErrorContains(t, err, "router \"bad\": unknown service \"missing\"")
256	})
257
258	t.Run("unknown port", func(t *testing.T) {
259		input := base
260		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:missing"}}}}
261		_, err := Build(input)
262		require.ErrorContains(t, err, "unknown port \"missing\" on service \"rust\"")
263	})
264
265	t.Run("disabled service", func(t *testing.T) {
266		input := base
267		input.Services = append([]domain.StandaloneService(nil), base.Services...)
268		input.Services[0].Enabled = false
269		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
270		_, err := Build(input)
271		require.ErrorContains(t, err, "unknown service \"rust\"")
272	})
273
274	t.Run("protocol mismatch", func(t *testing.T) {
275		input := base
276		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:game"}}}}
277		_, err := Build(input)
278		require.ErrorContains(t, err, "service \"rust\" port \"game\" protocol udp does not match router protocol tcp")
279	})
280
281	t.Run("missing publish", func(t *testing.T) {
282		input := base
283		input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}}
284		input.Services = append([]domain.StandaloneService(nil), base.Services...)
285		input.Services[0].Ports = append([]domain.StandaloneServicePort(nil), base.Services[0].Ports...)
286		input.Services[0].Ports[1].Publish = ""
287		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
288		_, err := Build(input)
289		require.ErrorContains(t, err, "service \"rust\" port \"rcon\" publish address")
290	})
291
292	t.Run("invalid publish", func(t *testing.T) {
293		input := base
294		input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}}
295		input.Services = append([]domain.StandaloneService(nil), base.Services...)
296		input.Services[0].Ports = append([]domain.StandaloneServicePort(nil), base.Services[0].Ports...)
297		input.Services[0].Ports[1].Publish = "127.0.0.1:notaport"
298		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
299		_, err := Build(input)
300		require.ErrorContains(t, err, "publish address")
301	})
302}
303
304func TestBuildWrapsStandaloneServiceValidationErrorsWithServiceName(t *testing.T) {
305	_, err := Build(Input{
306		EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
307		Services: []domain.StandaloneService{{
308			Name:    "rust",
309			Image:   "localhost/rust:latest",
310			Enabled: true,
311			Ports:   []domain.StandaloneServicePort{{Name: " admin ", Container: 28016, Protocol: domain.NetworkProtocolTCP}},
312		}},
313	})
314
315	require.ErrorContains(t, err, "standalone service \"rust\"")
316	require.ErrorContains(t, err, "leading or trailing whitespace")
317}
318
319func TestBuildEnforcesPrivateStandaloneServicePortRouting(t *testing.T) {
320	base := func() Input {
321		return Input{
322			EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}},
323			Traffic:     Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "rust-rcon", EntryPoint: "tcp", Service: "service:rust:rcon"}}}},
324			Services: []domain.StandaloneService{{
325				Name:    "rust",
326				Image:   "localhost/rust:latest",
327				Enabled: true,
328				Ports:   []domain.StandaloneServicePort{{Name: "rcon", Container: 28016, Protocol: domain.NetworkProtocolTCP, Publish: "127.0.0.1:38016", TrustedCIDRs: []string{"100.64.0.0/10"}}},
329			}},
330		}
331	}
332
333	t.Run("rcon defaults private and allows exact trusted cidrs", func(t *testing.T) {
334		_, err := Build(base())
335		require.NoError(t, err)
336	})
337
338	t.Run("private port rejects service port without trusted cidrs", func(t *testing.T) {
339		input := base()
340		input.Services[0].Ports[0].TrustedCIDRs = nil
341		_, err := Build(input)
342		require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
343	})
344
345	t.Run("rcon rejects both service and entrypoint without trusted cidrs", func(t *testing.T) {
346		input := base()
347		input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
348		input.Services[0].Ports[0].TrustedCIDRs = nil
349		_, err := Build(input)
350		require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
351	})
352
353	t.Run("explicit private non-rcon rejects service port without trusted cidrs", func(t *testing.T) {
354		input := base()
355		input.Services[0].Ports[0].Name = "admin"
356		input.Services[0].Ports[0].Private = true
357		input.Services[0].Ports[0].TrustedCIDRs = nil
358		input.Traffic.TCP.Routers[0].Service = "service:rust:admin"
359		_, err := Build(input)
360		require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
361	})
362
363	t.Run("private port rejects entrypoint without trusted cidrs", func(t *testing.T) {
364		input := base()
365		input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
366		_, err := Build(input)
367		require.ErrorContains(t, err, "entrypoint \"tcp\" requires non-empty trusted_cidrs")
368	})
369
370	t.Run("private port rejects non-matching trusted cidrs", func(t *testing.T) {
371		input := base()
372		input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"10.0.0.0/8"}}}
373		_, err := Build(input)
374		require.ErrorContains(t, err, "must exactly match")
375	})
376
377	t.Run("private port compares canonical trusted cidrs", func(t *testing.T) {
378		input := base()
379		input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.1/10"}}}
380		_, err := Build(input)
381		require.NoError(t, err)
382	})
383
384	t.Run("explicit public rcon is allowed without trusted cidrs", func(t *testing.T) {
385		input := base()
386		input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
387		input.Services[0].Ports[0].Public = true
388		input.Services[0].Ports[0].TrustedCIDRs = nil
389		_, err := Build(input)
390		require.NoError(t, err)
391	})
392
393	t.Run("explicit public non-rcon is allowed without trusted cidrs", func(t *testing.T) {
394		input := base()
395		input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
396		input.Services[0].Ports[0].Name = "admin"
397		input.Services[0].Ports[0].Private = false
398		input.Services[0].Ports[0].Public = true
399		input.Services[0].Ports[0].TrustedCIDRs = nil
400		input.Traffic.TCP.Routers[0].Service = "service:rust:admin"
401		_, err := Build(input)
402		require.NoError(t, err)
403	})
404}
405
406func TestBuildRejectsInvalidServiceRefs(t *testing.T) {
407	base := Input{
408		EntryPoints:     map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
409		NetworkServices: []NetworkServiceConfig{{Name: "svc", Ports: []PortConfig{{Name: "udp", Container: 53, Protocol: domain.NetworkProtocolUDP}}}},
410	}
411
412	t.Run("unknown service", func(t *testing.T) {
413		input := base
414		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:missing:tcp"}}}}
415		_, err := Build(input)
416		require.ErrorContains(t, err, "unknown network service")
417	})
418
419	t.Run("unknown port", func(t *testing.T) {
420		input := base
421		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:svc:missing"}}}}
422		_, err := Build(input)
423		require.ErrorContains(t, err, "unknown port")
424	})
425
426	t.Run("protocol mismatch", func(t *testing.T) {
427		input := base
428		input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:svc:udp"}}}}
429		_, err := Build(input)
430		require.ErrorContains(t, err, "does not match")
431	})
432}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close