1 package opts
2
3 import (
4 "bytes"
5 "os"
6 "testing"
7
8 "github.com/docker/docker/api/types/swarm"
9 "github.com/docker/go-connections/nat"
10 "github.com/sirupsen/logrus"
11 "gotest.tools/v3/assert"
12 is "gotest.tools/v3/assert/cmp"
13 )
14
15 func TestPortOptValidSimpleSyntax(t *testing.T) {
16 testCases := []struct {
17 value string
18 expected []swarm.PortConfig
19 }{
20 {
21 value: "80",
22 expected: []swarm.PortConfig{
23 {
24 Protocol: "tcp",
25 TargetPort: 80,
26 PublishMode: swarm.PortConfigPublishModeIngress,
27 },
28 },
29 },
30 {
31 value: "80:8080",
32 expected: []swarm.PortConfig{
33 {
34 Protocol: "tcp",
35 TargetPort: 8080,
36 PublishedPort: 80,
37 PublishMode: swarm.PortConfigPublishModeIngress,
38 },
39 },
40 },
41 {
42 value: "8080:80/tcp",
43 expected: []swarm.PortConfig{
44 {
45 Protocol: "tcp",
46 TargetPort: 80,
47 PublishedPort: 8080,
48 PublishMode: swarm.PortConfigPublishModeIngress,
49 },
50 },
51 },
52 {
53 value: "80:8080/udp",
54 expected: []swarm.PortConfig{
55 {
56 Protocol: "udp",
57 TargetPort: 8080,
58 PublishedPort: 80,
59 PublishMode: swarm.PortConfigPublishModeIngress,
60 },
61 },
62 },
63 {
64 value: "80-81:8080-8081/tcp",
65 expected: []swarm.PortConfig{
66 {
67 Protocol: "tcp",
68 TargetPort: 8080,
69 PublishedPort: 80,
70 PublishMode: swarm.PortConfigPublishModeIngress,
71 },
72 {
73 Protocol: "tcp",
74 TargetPort: 8081,
75 PublishedPort: 81,
76 PublishMode: swarm.PortConfigPublishModeIngress,
77 },
78 },
79 },
80 {
81 value: "80-82:8080-8082/udp",
82 expected: []swarm.PortConfig{
83 {
84 Protocol: "udp",
85 TargetPort: 8080,
86 PublishedPort: 80,
87 PublishMode: swarm.PortConfigPublishModeIngress,
88 },
89 {
90 Protocol: "udp",
91 TargetPort: 8081,
92 PublishedPort: 81,
93 PublishMode: swarm.PortConfigPublishModeIngress,
94 },
95 {
96 Protocol: "udp",
97 TargetPort: 8082,
98 PublishedPort: 82,
99 PublishMode: swarm.PortConfigPublishModeIngress,
100 },
101 },
102 },
103 {
104 value: "80-82:8080/udp",
105 expected: []swarm.PortConfig{
106 {
107 Protocol: "udp",
108 TargetPort: 8080,
109 PublishedPort: 80,
110 PublishMode: swarm.PortConfigPublishModeIngress,
111 },
112 {
113 Protocol: "udp",
114 TargetPort: 8080,
115 PublishedPort: 81,
116 PublishMode: swarm.PortConfigPublishModeIngress,
117 },
118 {
119 Protocol: "udp",
120 TargetPort: 8080,
121 PublishedPort: 82,
122 PublishMode: swarm.PortConfigPublishModeIngress,
123 },
124 },
125 },
126 {
127 value: "80-80:8080/tcp",
128 expected: []swarm.PortConfig{
129 {
130 Protocol: "tcp",
131 TargetPort: 8080,
132 PublishedPort: 80,
133 PublishMode: swarm.PortConfigPublishModeIngress,
134 },
135 },
136 },
137 }
138 for _, tc := range testCases {
139 var port PortOpt
140 assert.NilError(t, port.Set(tc.value))
141 assert.Check(t, is.Len(port.Value(), len(tc.expected)))
142 for _, expectedPortConfig := range tc.expected {
143 assertContains(t, port.Value(), expectedPortConfig)
144 }
145 }
146 }
147
148 func TestPortOptValidComplexSyntax(t *testing.T) {
149 testCases := []struct {
150 value string
151 expected []swarm.PortConfig
152 }{
153 {
154 value: "target=80",
155 expected: []swarm.PortConfig{
156 {
157 TargetPort: 80,
158 Protocol: "tcp",
159 PublishMode: swarm.PortConfigPublishModeIngress,
160 },
161 },
162 },
163 {
164 value: "target=80,protocol=tcp",
165 expected: []swarm.PortConfig{
166 {
167 Protocol: "tcp",
168 TargetPort: 80,
169 PublishMode: swarm.PortConfigPublishModeIngress,
170 },
171 },
172 },
173 {
174 value: "target=80,published=8080,protocol=tcp",
175 expected: []swarm.PortConfig{
176 {
177 Protocol: "tcp",
178 TargetPort: 80,
179 PublishedPort: 8080,
180 PublishMode: swarm.PortConfigPublishModeIngress,
181 },
182 },
183 },
184 {
185 value: "published=80,target=8080,protocol=tcp",
186 expected: []swarm.PortConfig{
187 {
188 Protocol: "tcp",
189 TargetPort: 8080,
190 PublishedPort: 80,
191 PublishMode: swarm.PortConfigPublishModeIngress,
192 },
193 },
194 },
195 {
196 value: "target=80,published=8080,protocol=tcp,mode=host",
197 expected: []swarm.PortConfig{
198 {
199 Protocol: "tcp",
200 TargetPort: 80,
201 PublishedPort: 8080,
202 PublishMode: "host",
203 },
204 },
205 },
206 {
207 value: "target=80,published=8080,mode=host",
208 expected: []swarm.PortConfig{
209 {
210 TargetPort: 80,
211 PublishedPort: 8080,
212 PublishMode: "host",
213 Protocol: "tcp",
214 },
215 },
216 },
217 {
218 value: "target=80,published=8080,mode=ingress",
219 expected: []swarm.PortConfig{
220 {
221 TargetPort: 80,
222 PublishedPort: 8080,
223 PublishMode: "ingress",
224 Protocol: "tcp",
225 },
226 },
227 },
228 }
229 for _, tc := range testCases {
230 var port PortOpt
231 assert.NilError(t, port.Set(tc.value))
232 assert.Check(t, is.Len(port.Value(), len(tc.expected)))
233 for _, expectedPortConfig := range tc.expected {
234 assertContains(t, port.Value(), expectedPortConfig)
235 }
236 }
237 }
238
239 func TestPortOptInvalidComplexSyntax(t *testing.T) {
240 testCases := []struct {
241 value string
242 expectedError string
243 }{
244 {
245 value: "invalid,target=80",
246 expectedError: "invalid field",
247 },
248 {
249 value: "invalid=field",
250 expectedError: "invalid field",
251 },
252 {
253 value: "protocol=invalid",
254 expectedError: "invalid protocol value",
255 },
256 {
257 value: "target=invalid",
258 expectedError: "invalid syntax",
259 },
260 {
261 value: "published=invalid",
262 expectedError: "invalid syntax",
263 },
264 {
265 value: "mode=invalid",
266 expectedError: "invalid publish mode value",
267 },
268 {
269 value: "published=8080,protocol=tcp,mode=ingress",
270 expectedError: "missing mandatory field",
271 },
272 {
273 value: `target=80,protocol="tcp,mode=ingress"`,
274 expectedError: "non-quoted-field",
275 },
276 {
277 value: `target=80,"protocol=tcp,mode=ingress"`,
278 expectedError: "invalid protocol value",
279 },
280 }
281 for _, tc := range testCases {
282 var port PortOpt
283 assert.ErrorContains(t, port.Set(tc.value), tc.expectedError)
284 }
285 }
286
287 func TestPortOptInvalidSimpleSyntax(t *testing.T) {
288 testCases := []struct {
289 value string
290 expectedError string
291 }{
292 {
293 value: "9999999",
294 expectedError: "invalid containerPort: 9999999",
295 },
296 {
297 value: "80/xyz",
298 expectedError: "invalid proto: xyz",
299 },
300 {
301 value: "tcp",
302 expectedError: "invalid containerPort: tcp",
303 },
304 {
305 value: "udp",
306 expectedError: "invalid containerPort: udp",
307 },
308 {
309 value: "",
310 expectedError: "no port specified: <empty>",
311 },
312 {
313 value: "1.1.1.1:80:80",
314 expectedError: "hostip is not supported",
315 },
316 }
317 for _, tc := range testCases {
318 var port PortOpt
319 assert.Error(t, port.Set(tc.value), tc.expectedError)
320 }
321 }
322
323 func TestConvertPortToPortConfigWithIP(t *testing.T) {
324 testCases := []struct {
325 value string
326 expectedWarning string
327 }{
328 {
329 value: "0.0.0.0",
330 },
331 {
332 value: "::",
333 },
334 {
335 value: "192.168.1.5",
336 expectedWarning: `ignoring IP-address (192.168.1.5:2345:80/tcp) service will listen on '0.0.0.0'`,
337 },
338 {
339 value: "::2",
340 expectedWarning: `ignoring IP-address ([::2]:2345:80/tcp) service will listen on '0.0.0.0'`,
341 },
342 }
343
344 var b bytes.Buffer
345 logrus.SetOutput(&b)
346 for _, tc := range testCases {
347 tc := tc
348 t.Run(tc.value, func(t *testing.T) {
349 _, err := ConvertPortToPortConfig("80/tcp", map[nat.Port][]nat.PortBinding{
350 "80/tcp": {{HostIP: tc.value, HostPort: "2345"}},
351 })
352 assert.NilError(t, err)
353 if tc.expectedWarning == "" {
354 assert.Equal(t, b.String(), "")
355 } else {
356 assert.Assert(t, is.Contains(b.String(), tc.expectedWarning))
357 }
358 })
359 }
360 logrus.SetOutput(os.Stderr)
361 }
362
363 func assertContains(t *testing.T, portConfigs []swarm.PortConfig, expected swarm.PortConfig) {
364 t.Helper()
365 contains := false
366 for _, portConfig := range portConfigs {
367 if portConfig == expected {
368 contains = true
369 break
370 }
371 }
372 if !contains {
373 t.Errorf("expected %v to contain %v, did not", portConfigs, expected)
374 }
375 }
376
View as plain text