| 394 | } |
| 395 | |
| 396 | func convertDockerInspect(raw []byte) ([]codersdk.WorkspaceAgentContainer, []string, error) { |
| 397 | var warns []string |
| 398 | var ins []dockerInspect |
| 399 | if err := json.NewDecoder(bytes.NewReader(raw)).Decode(&ins); err != nil { |
| 400 | return nil, nil, xerrors.Errorf("decode docker inspect output: %w", err) |
| 401 | } |
| 402 | outs := make([]codersdk.WorkspaceAgentContainer, 0, len(ins)) |
| 403 | |
| 404 | // Say you have two containers: |
| 405 | // - Container A with Host IP 127.0.0.1:8000 mapped to container port 8001 |
| 406 | // - Container B with Host IP [::1]:8000 mapped to container port 8001 |
| 407 | // A request to localhost:8000 may be routed to either container. |
| 408 | // We don't know which one for sure, so we need to surface this to the user. |
| 409 | // Keep track of all host ports we see. If we see the same host port |
| 410 | // mapped to multiple containers on different host IPs, we need to |
| 411 | // warn the user about this. |
| 412 | // Note that we only do this for loopback or unspecified IPs. |
| 413 | // We'll assume that the user knows what they're doing if they bind to |
| 414 | // a specific IP address. |
| 415 | hostPortContainers := make(map[int][]string) |
| 416 | |
| 417 | for _, in := range ins { |
| 418 | out := codersdk.WorkspaceAgentContainer{ |
| 419 | CreatedAt: in.Created, |
| 420 | // Remove the leading slash from the container name |
| 421 | FriendlyName: strings.TrimPrefix(in.Name, "/"), |
| 422 | ID: in.ID, |
| 423 | Image: in.Config.Image, |
| 424 | Labels: in.Config.Labels, |
| 425 | Ports: make([]codersdk.WorkspaceAgentContainerPort, 0), |
| 426 | Running: in.State.Running, |
| 427 | Status: in.State.String(), |
| 428 | Volumes: make(map[string]string, len(in.Mounts)), |
| 429 | } |
| 430 | |
| 431 | if in.NetworkSettings.Ports == nil { |
| 432 | in.NetworkSettings.Ports = make(map[string][]dockerInspectPort) |
| 433 | } |
| 434 | portKeys := maps.Keys(in.NetworkSettings.Ports) |
| 435 | // Sort the ports for deterministic output. |
| 436 | slices.Sort(portKeys) |
| 437 | // If we see the same port bound to both ipv4 and ipv6 loopback or unspecified |
| 438 | // interfaces to the same container port, there is no point in adding it multiple times. |
| 439 | loopbackHostPortContainerPorts := make(map[int]uint16, 0) |
| 440 | for _, pk := range portKeys { |
| 441 | for _, p := range in.NetworkSettings.Ports[pk] { |
| 442 | cp, network, err := convertDockerPort(pk) |
| 443 | if err != nil { |
| 444 | warns = append(warns, fmt.Sprintf("convert docker port: %s", err.Error())) |
| 445 | // Default network to "tcp" if we can't parse it. |
| 446 | network = "tcp" |
| 447 | } |
| 448 | hp, err := strconv.Atoi(p.HostPort) |
| 449 | if err != nil { |
| 450 | warns = append(warns, fmt.Sprintf("convert docker host port: %s", err.Error())) |
| 451 | continue |
| 452 | } |
| 453 | if hp > 65535 || hp < 1 { // invalid port |