ports.go

54 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
package internal

import (
	"fmt"
	"strconv"
	"strings"
)

// AllocatePort finds the next available port starting at 5002.
// Scans running Docker containers for used host ports.
func AllocatePort() (int, error) {
	containers, _ := ListContainers()
	used := make(map[int]bool)
	for _, c := range containers {
		for _, port := range parseHostPorts(c.Ports) {
			used[port] = true
		}
	}
	// Start at 5002 — port 5001 is used by the dev platform itself.
	for port := 5002; port < 5100; port++ {
		if !used[port] {
			return port, nil
		}
	}
	return 0, fmt.Errorf("no available ports")
}

// parseHostPorts extracts host ports from a Docker ports string like
// "0.0.0.0:5002->5000/tcp, 0.0.0.0:5003->8080/tcp"
func parseHostPorts(ports string) []int {
	var result []int
	for _, part := range strings.Split(ports, ",") {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}
		// Format: "0.0.0.0:5002->5000/tcp" or ":::5002->5000/tcp"
		arrow := strings.Index(part, "->")
		if arrow < 0 {
			continue
		}
		hostPart := part[:arrow]
		// Extract port from "0.0.0.0:5002" or just "5002"
		colon := strings.LastIndex(hostPart, ":")
		if colon < 0 {
			continue
		}
		portStr := hostPart[colon+1:]
		if port, err := strconv.Atoi(portStr); err == nil {
			result = append(result, port)
		}
	}
	return result
}