v3.3.1 31f1eec

runtime_backend_binds_test.go RawHistoryFolder

1package docker
2
3import (
4	"context"
5	"net/http"
6	"net/http/httptest"
7	"strings"
8	"testing"
9
10	"github.com/moby/moby/client"
11	"github.com/stretchr/testify/assert"
12	"github.com/stretchr/testify/require"
13
14	"github.com/bnema/gordon/internal/domain"
15)
16
17// backendBindsRuntime builds a docker runtime whose container inspection
18// reports the given port mappings.
19func backendBindsRuntime(t *testing.T, portsJSON string) *Runtime {
20	t.Helper()
21	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22		if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/containers/abc123/json") {
23			w.Header().Set("Content-Type", "application/json")
24			_, _ = w.Write([]byte(`{"Id":"abc123","NetworkSettings":{"Ports":` + portsJSON + `}}`))
25			return
26		}
27		t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
28	}))
29	t.Cleanup(server.Close)
30
31	host := strings.TrimPrefix(server.URL, "http://")
32	cli, err := client.New(client.WithHost("tcp://"+host), client.WithAPIVersion("1.41"), client.WithHTTPClient(server.Client()))
33	require.NoError(t, err)
34	return NewRuntimeWithClient(cli)
35}
36
37// TestRuntime_GetContainerBackendBindsRejectsMalformedMappings proves only
38// an exact single loopback binding is accepted: a wildcard, non-loopback,
39// empty, or duplicated mapping fails closed.
40func TestRuntime_GetContainerBackendBindsRejectsMalformedMappings(t *testing.T) {
41	cases := []struct {
42		name  string
43		ports string
44	}{
45		{"wildcard", `{"9000/tcp":[{"HostIp":"0.0.0.0","HostPort":"32771"}]}`},
46		{"non-loopback", `{"9000/tcp":[{"HostIp":"192.168.1.5","HostPort":"32771"}]}`},
47		{"empty host ip", `{"9000/tcp":[{"HostIp":"","HostPort":"32771"}]}`},
48		{"multiple bindings", `{"9000/tcp":[{"HostIp":"127.0.0.1","HostPort":"32771"},{"HostIp":"127.0.0.1","HostPort":"32772"}]}`},
49		{"invalid host port", `{"9000/tcp":[{"HostIp":"127.0.0.1","HostPort":"0"}]}`},
50	}
51	for _, tc := range cases {
52		t.Run(tc.name, func(t *testing.T) {
53			runtime := backendBindsRuntime(t, tc.ports)
54			_, err := runtime.GetContainerBackendBinds(context.Background(), "abc123", []domain.ContainerBackendPort{
55				{ContainerPort: 9000, Protocol: domain.NetworkProtocolTCP},
56			})
57			require.Error(t, err)
58		})
59	}
60}
61
62// TestRuntime_GetContainerBackendBindsResolvesTCPAndUDPTogether proves one
63// inspection resolves both a TCP and a UDP bind, including the same
64// container port number on both protocols (distinct sockets).
65func TestRuntime_GetContainerBackendBindsResolvesTCPAndUDPTogether(t *testing.T) {
66	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
67		if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/containers/abc123/json") {
68			w.Header().Set("Content-Type", "application/json")
69			_, _ = w.Write([]byte(`{
70				"Id":"abc123",
71				"NetworkSettings":{"Ports":{
72					"5432/tcp":[{"HostIp":"127.0.0.1","HostPort":"32770"}],
73					"9000/tcp":[{"HostIp":"127.0.0.1","HostPort":"32771"}],
74					"9000/udp":[{"HostIp":"127.0.0.1","HostPort":"32772"}]
75				}}
76			}`))
77			return
78		}
79		t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
80	}))
81	defer server.Close()
82
83	host := strings.TrimPrefix(server.URL, "http://")
84	cli, err := client.New(client.WithHost("tcp://"+host), client.WithAPIVersion("1.41"), client.WithHTTPClient(server.Client()))
85	require.NoError(t, err)
86
87	runtime := NewRuntimeWithClient(cli)
88	binds, err := runtime.GetContainerBackendBinds(context.Background(), "abc123", []domain.ContainerBackendPort{
89		{ContainerPort: 5432, Protocol: domain.NetworkProtocolTCP},
90		{ContainerPort: 9000, Protocol: domain.NetworkProtocolTCP},
91		{ContainerPort: 9000, Protocol: domain.NetworkProtocolUDP},
92	})
93	require.NoError(t, err)
94	require.Len(t, binds, 3)
95	assert.Equal(t, domain.ContainerBackendBind{ContainerPort: 5432, HostPort: 32770, Protocol: domain.NetworkProtocolTCP}, binds[0])
96	assert.Equal(t, domain.ContainerBackendBind{ContainerPort: 9000, HostPort: 32771, Protocol: domain.NetworkProtocolTCP}, binds[1])
97	assert.Equal(t, domain.ContainerBackendBind{ContainerPort: 9000, HostPort: 32772, Protocol: domain.NetworkProtocolUDP}, binds[2])
98}
99
100// TestRuntime_GetContainerBackendBindsFailsWhenProtocolMissing proves a
101// missing protocol-specific mapping fails closed instead of falling
102// back to the other protocol's bind.
103func TestRuntime_GetContainerBackendBindsFailsWhenProtocolMissing(t *testing.T) {
104	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
105		if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/containers/abc123/json") {
106			w.Header().Set("Content-Type", "application/json")
107			_, _ = w.Write([]byte(`{
108				"Id":"abc123",
109				"NetworkSettings":{"Ports":{
110					"9000/tcp":[{"HostIp":"127.0.0.1","HostPort":"32771"}]
111				}}
112			}`))
113			return
114		}
115		t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
116	}))
117	defer server.Close()
118
119	host := strings.TrimPrefix(server.URL, "http://")
120	cli, err := client.New(client.WithHost("tcp://"+host), client.WithAPIVersion("1.41"), client.WithHTTPClient(server.Client()))
121	require.NoError(t, err)
122
123	runtime := NewRuntimeWithClient(cli)
124	_, err = runtime.GetContainerBackendBinds(context.Background(), "abc123", []domain.ContainerBackendPort{
125		{ContainerPort: 9000, Protocol: domain.NetworkProtocolUDP},
126	})
127	require.ErrorContains(t, err, "9000/udp")
128}

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