@@ -18,6 +18,7 @@ import (
18
18
19
19
"cdr.dev/slog"
20
20
"github.com/coder/coder/agent"
21
+ "github.com/coder/coder/cli/cliflag"
21
22
"github.com/coder/coder/cli/cliui"
22
23
"github.com/coder/coder/codersdk"
23
24
)
@@ -45,6 +46,10 @@ func portForward() *cobra.Command {
45
46
Description : "Port forward multiple TCP ports and a UDP port" ,
46
47
Command : "coder port-forward <workspace> --tcp 8080:8080 --tcp 9000:3000 --udp 5353:53" ,
47
48
},
49
+ example {
50
+ Description : "Port forward multiple ports (TCP or UDP) in condensed syntax" ,
51
+ Command : "coder port-forward <workspace> --tcp 8080,9000:3000,9090-9092,10000-10002:10010-10012" ,
52
+ },
48
53
),
49
54
RunE : func (cmd * cobra.Command , args []string ) error {
50
55
ctx , cancel := context .WithCancel (cmd .Context ())
@@ -164,8 +169,8 @@ func portForward() *cobra.Command {
164
169
},
165
170
}
166
171
167
- cmd .Flags (). StringArrayVarP ( & tcpForwards , "tcp" , "p" , [] string {}, "Forward a TCP port from the workspace to the local machine" )
168
- cmd .Flags (). StringArrayVar ( & udpForwards , "udp" , [] string {} , "Forward a UDP port from the workspace to the local machine. The UDP connection has TCP-like semantics to support stateful UDP protocols" )
172
+ cliflag . StringArrayVarP ( cmd .Flags (), & tcpForwards , "tcp" , "p" , "CODER_PORT_FORWARD_TCP" , nil , "Forward TCP port(s) from the workspace to the local machine" )
173
+ cliflag . StringArrayVarP ( cmd .Flags (), & udpForwards , "udp" , "" , "CODER_PORT_FORWARD_UDP" , nil , "Forward UDP port(s) from the workspace to the local machine. The UDP connection has TCP-like semantics to support stateful UDP protocols" )
169
174
return cmd
170
175
}
171
176
@@ -242,32 +247,40 @@ type portForwardSpec struct {
242
247
func parsePortForwards (tcpSpecs , udpSpecs []string ) ([]portForwardSpec , error ) {
243
248
specs := []portForwardSpec {}
244
249
245
- for _ , spec := range tcpSpecs {
246
- local , remote , err := parsePortPort (spec )
247
- if err != nil {
248
- return nil , xerrors .Errorf ("failed to parse TCP port-forward specification %q: %w" , spec , err )
249
- }
250
+ for _ , specEntry := range tcpSpecs {
251
+ for _ , spec := range strings .Split (specEntry , "," ) {
252
+ ports , err := parseSrcDestPorts (spec )
253
+ if err != nil {
254
+ return nil , xerrors .Errorf ("failed to parse TCP port-forward specification %q: %w" , spec , err )
255
+ }
250
256
251
- specs = append (specs , portForwardSpec {
252
- listenNetwork : "tcp" ,
253
- listenAddress : fmt .Sprintf ("127.0.0.1:%v" , local ),
254
- dialNetwork : "tcp" ,
255
- dialAddress : fmt .Sprintf ("127.0.0.1:%v" , remote ),
256
- })
257
+ for _ , port := range ports {
258
+ specs = append (specs , portForwardSpec {
259
+ listenNetwork : "tcp" ,
260
+ listenAddress : fmt .Sprintf ("127.0.0.1:%v" , port .local ),
261
+ dialNetwork : "tcp" ,
262
+ dialAddress : fmt .Sprintf ("127.0.0.1:%v" , port .remote ),
263
+ })
264
+ }
265
+ }
257
266
}
258
267
259
- for _ , spec := range udpSpecs {
260
- local , remote , err := parsePortPort (spec )
261
- if err != nil {
262
- return nil , xerrors .Errorf ("failed to parse UDP port-forward specification %q: %w" , spec , err )
263
- }
268
+ for _ , specEntry := range udpSpecs {
269
+ for _ , spec := range strings .Split (specEntry , "," ) {
270
+ ports , err := parseSrcDestPorts (spec )
271
+ if err != nil {
272
+ return nil , xerrors .Errorf ("failed to parse UDP port-forward specification %q: %w" , spec , err )
273
+ }
264
274
265
- specs = append (specs , portForwardSpec {
266
- listenNetwork : "udp" ,
267
- listenAddress : fmt .Sprintf ("127.0.0.1:%v" , local ),
268
- dialNetwork : "udp" ,
269
- dialAddress : fmt .Sprintf ("127.0.0.1:%v" , remote ),
270
- })
275
+ for _ , port := range ports {
276
+ specs = append (specs , portForwardSpec {
277
+ listenNetwork : "udp" ,
278
+ listenAddress : fmt .Sprintf ("127.0.0.1:%v" , port .local ),
279
+ dialNetwork : "udp" ,
280
+ dialAddress : fmt .Sprintf ("127.0.0.1:%v" , port .remote ),
281
+ })
282
+ }
283
+ }
271
284
}
272
285
273
286
// Check for duplicate entries.
@@ -295,24 +308,72 @@ func parsePort(in string) (uint16, error) {
295
308
return uint16 (port ), nil
296
309
}
297
310
298
- func parsePortPort (in string ) (local uint16 , remote uint16 , err error ) {
311
+ type parsedSrcDestPort struct {
312
+ local , remote uint16
313
+ }
314
+
315
+ func parseSrcDestPorts (in string ) ([]parsedSrcDestPort , error ) {
299
316
parts := strings .Split (in , ":" )
300
317
if len (parts ) > 2 {
301
- return 0 , 0 , xerrors .Errorf ("invalid port specification %q" , in )
318
+ return nil , xerrors .Errorf ("invalid port specification %q" , in )
302
319
}
303
320
if len (parts ) == 1 {
304
321
// Duplicate the single part
305
322
parts = append (parts , parts [0 ])
306
323
}
324
+ if ! strings .Contains (parts [0 ], "-" ) {
325
+ local , err := parsePort (parts [0 ])
326
+ if err != nil {
327
+ return nil , xerrors .Errorf ("parse local port from %q: %w" , in , err )
328
+ }
329
+ remote , err := parsePort (parts [1 ])
330
+ if err != nil {
331
+ return nil , xerrors .Errorf ("parse remote port from %q: %w" , in , err )
332
+ }
307
333
308
- local , err = parsePort (parts [0 ])
334
+ return []parsedSrcDestPort {{local : local , remote : remote }}, nil
335
+ }
336
+
337
+ local , err := parsePortRange (parts [0 ])
309
338
if err != nil {
310
- return 0 , 0 , xerrors .Errorf ("parse local port from %q: %w" , in , err )
339
+ return nil , xerrors .Errorf ("parse local port range from %q: %w" , in , err )
311
340
}
312
- remote , err = parsePort (parts [1 ])
341
+ remote , err := parsePortRange (parts [1 ])
313
342
if err != nil {
314
- return 0 , 0 , xerrors .Errorf ("parse remote port from %q: %w" , in , err )
343
+ return nil , xerrors .Errorf ("parse remote port range from %q: %w" , in , err )
344
+ }
345
+ if len (local ) != len (remote ) {
346
+ return nil , xerrors .Errorf ("port ranges must be the same length, got %d ports forwarded to %d ports" , len (local ), len (remote ))
347
+ }
348
+ var out []parsedSrcDestPort
349
+ for i := range local {
350
+ out = append (out , parsedSrcDestPort {
351
+ local : local [i ],
352
+ remote : remote [i ],
353
+ })
315
354
}
355
+ return out , nil
356
+ }
316
357
317
- return local , remote , nil
358
+ func parsePortRange (in string ) ([]uint16 , error ) {
359
+ parts := strings .Split (in , "-" )
360
+ if len (parts ) != 2 {
361
+ return nil , xerrors .Errorf ("invalid port range specification %q" , in )
362
+ }
363
+ start , err := parsePort (parts [0 ])
364
+ if err != nil {
365
+ return nil , xerrors .Errorf ("parse range start port from %q: %w" , in , err )
366
+ }
367
+ end , err := parsePort (parts [1 ])
368
+ if err != nil {
369
+ return nil , xerrors .Errorf ("parse range end port from %q: %w" , in , err )
370
+ }
371
+ if end < start {
372
+ return nil , xerrors .Errorf ("range end port %v is less than start port %v" , end , start )
373
+ }
374
+ var ports []uint16
375
+ for i := start ; i <= end ; i ++ {
376
+ ports = append (ports , i )
377
+ }
378
+ return ports , nil
318
379
}
0 commit comments