@@ -319,22 +319,23 @@ func runDockerInspect(ctx context.Context, execer agentexec.Execer, ids ...strin
319
319
// To avoid a direct dependency on the Docker API, we use the docker CLI
320
320
// to fetch information about containers.
321
321
type dockerInspect struct {
322
- ID string `json:"Id"`
323
- Created time.Time `json:"Created"`
324
- Config dockerInspectConfig `json:"Config"`
325
- HostConfig dockerInspectHostConfig `json:"HostConfig "`
326
- Name string `json:"Name "`
327
- Mounts [] dockerInspectMount `json:"Mounts "`
328
- State dockerInspectState `json:"State "`
322
+ ID string `json:"Id"`
323
+ Created time.Time `json:"Created"`
324
+ Config dockerInspectConfig `json:"Config"`
325
+ Name string `json:"Name "`
326
+ Mounts [] dockerInspectMount `json:"Mounts "`
327
+ State dockerInspectState `json:"State "`
328
+ NetworkSettings dockerInspectNetworkSettings `json:"NetworkSettings "`
329
329
}
330
330
331
331
type dockerInspectConfig struct {
332
332
Image string `json:"Image"`
333
333
Labels map [string ]string `json:"Labels"`
334
334
}
335
335
336
- type dockerInspectHostConfig struct {
337
- PortBindings map [string ]any `json:"PortBindings"`
336
+ type dockerInspectPort struct {
337
+ HostIP string `json:"HostIp"`
338
+ HostPort string `json:"HostPort"`
338
339
}
339
340
340
341
type dockerInspectMount struct {
@@ -349,6 +350,10 @@ type dockerInspectState struct {
349
350
Error string `json:"Error"`
350
351
}
351
352
353
+ type dockerInspectNetworkSettings struct {
354
+ Ports map [string ][]dockerInspectPort `json:"Ports"`
355
+ }
356
+
352
357
func (dis dockerInspectState ) String () string {
353
358
if dis .Running {
354
359
return "running"
@@ -388,20 +393,41 @@ func convertDockerInspect(raw string) ([]codersdk.WorkspaceAgentDevcontainer, []
388
393
Volumes : make (map [string ]string , len (in .Mounts )),
389
394
}
390
395
391
- if in .HostConfig . PortBindings == nil {
392
- in .HostConfig . PortBindings = make (map [string ]any )
396
+ if in .NetworkSettings . Ports == nil {
397
+ in .NetworkSettings . Ports = make (map [string ][] dockerInspectPort )
393
398
}
394
- portKeys := maps .Keys (in .HostConfig . PortBindings )
399
+ portKeys := maps .Keys (in .NetworkSettings . Ports )
395
400
// Sort the ports for deterministic output.
396
401
sort .Strings (portKeys )
397
- for _ , p := range portKeys {
398
- if port , network , err := convertDockerPort (p ); err != nil {
399
- warns = append (warns , err .Error ())
400
- } else {
402
+ // Each port binding may have multiple entries mapped to the same interface.
403
+ // Keep track of the ports we've already seen.
404
+ seen := make (map [int ]struct {}, len (in .NetworkSettings .Ports ))
405
+ for _ , pk := range portKeys {
406
+ for _ , p := range in .NetworkSettings .Ports [pk ] {
407
+ _ , network , err := convertDockerPort (pk )
408
+ if err != nil {
409
+ warns = append (warns , fmt .Sprintf ("convert docker port: %s" , err .Error ()))
410
+ // Default network to "tcp" if we can't parse it.
411
+ network = "tcp"
412
+ }
413
+ hp , err := strconv .Atoi (p .HostPort )
414
+ if err != nil {
415
+ warns = append (warns , fmt .Sprintf ("convert docker port: %s" , err .Error ()))
416
+ continue
417
+ }
418
+ if hp > 65535 || hp < 0 { // invalid port
419
+ warns = append (warns , fmt .Sprintf ("convert docker port: invalid host port %d" , hp ))
420
+ continue
421
+ }
422
+ if _ , ok := seen [hp ]; ok {
423
+ // We've already seen this port, so skip it.
424
+ continue
425
+ }
401
426
out .Ports = append (out .Ports , codersdk.WorkspaceAgentListeningPort {
402
427
Network : network ,
403
- Port : port ,
428
+ Port : uint16 ( hp ) ,
404
429
})
430
+ seen [hp ] = struct {}{}
405
431
}
406
432
}
407
433
0 commit comments