- Notifications
You must be signed in to change notification settings - Fork1k
fix(agent/agentcontainers): return host port instead of container port#16866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -320,13 +320,14 @@ func runDockerInspect(ctx context.Context, execer agentexec.Execer, ids ...strin | ||||||
// To avoid a direct dependency on the Docker API, we use the docker CLI | ||||||
// to fetch information about containers. | ||||||
type dockerInspect struct { | ||||||
ID string `json:"Id"` | ||||||
Created time.Time `json:"Created"` | ||||||
Config dockerInspectConfig `json:"Config"` | ||||||
HostConfig dockerInspectHostConfig `json:"HostConfig"` | ||||||
Name string `json:"Name"` | ||||||
Mounts []dockerInspectMount `json:"Mounts"` | ||||||
State dockerInspectState `json:"State"` | ||||||
NetworkSettings dockerNetworkSettings `json:"NetworkSettings"` | ||||||
} | ||||||
type dockerInspectConfig struct { | ||||||
@@ -335,7 +336,16 @@ type dockerInspectConfig struct { | ||||||
} | ||||||
type dockerInspectHostConfig struct { | ||||||
PortBindings map[string][]dockerPortBinding `json:"PortBindings"` | ||||||
} | ||||||
type dockerPortBinding struct { | ||||||
HostIP string `json:"HostIp"` | ||||||
HostPort string `json:"HostPort"` | ||||||
} | ||||||
type dockerNetworkSettings struct { | ||||||
Ports map[string][]dockerPortBinding `json:"Ports"` | ||||||
} | ||||||
type dockerInspectMount struct { | ||||||
@@ -382,20 +392,55 @@ func convertDockerInspect(in dockerInspect) (codersdk.WorkspaceAgentDevcontainer | ||||||
Volumes: make(map[string]string, len(in.Mounts)), | ||||||
} | ||||||
// Use NetworkSettings.Ports for externally accessible ports | ||||||
if in.NetworkSettings.Ports == nil { | ||||||
in.NetworkSettings.Ports = make(map[string][]dockerPortBinding) | ||||||
} | ||||||
// Track host ports to avoid duplicates (same port on multiple interfaces) | ||||||
seenHostPorts := make(map[string]bool) | ||||||
// Get port mappings in sorted order for deterministic output | ||||||
portKeys := maps.Keys(in.NetworkSettings.Ports) | ||||||
sort.Strings(portKeys) | ||||||
for _, containerPortSpec := range portKeys { | ||||||
bindings := in.NetworkSettings.Ports[containerPortSpec] | ||||||
// Skip if no bindings exist (don't expose the container port) | ||||||
if len(bindings) == 0 { | ||||||
continue | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Nit: This check seems like it's not needed, we're iterating on the map keys so technically this can't happen. | ||||||
// Parse the containerPortSpec (e.g., "8080/tcp") | ||||||
parts := strings.Split(containerPortSpec, "/") | ||||||
// Get the network protocol | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Suggested change
Nit: Comment why not what. What is inferable from the code. | ||||||
network := "tcp" // Default to tcp | ||||||
if len(parts) == 2 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Since this is output from Alt: why did we get rid of the | ||||||
network = parts[1] | ||||||
} | ||||||
// Process all bindings for this port (usually different interfaces) | ||||||
for _, binding := range bindings { | ||||||
// Skip if we've already seen this host port | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Suggested change
| ||||||
if seenHostPorts[binding.HostPort] { | ||||||
continue | ||||||
} | ||||||
hostPort, err := strconv.Atoi(binding.HostPort) | ||||||
if err != nil { | ||||||
warns = append(warns, fmt.Sprintf("invalid host port format: %s", binding.HostPort)) | ||||||
continue | ||||||
} | ||||||
out.Ports = append(out.Ports, codersdk.WorkspaceAgentListeningPort{ | ||||||
Network: network, | ||||||
Port:uint16(hostPort), | ||||||
}) | ||||||
// Mark this host port as seen | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Suggested change
| ||||||
seenHostPorts[binding.HostPort] = true | ||||||
} | ||||||
} | ||||||
@@ -412,28 +457,3 @@ func convertDockerInspect(in dockerInspect) (codersdk.WorkspaceAgentDevcontainer | ||||||
return out, warns | ||||||
} | ||||||
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -11,6 +11,7 @@ import ( | ||||||||
"go.uber.org/mock/gomock" | ||||||||
"github.com/google/go-cmp/cmp" | ||||||||
"github.com/google/uuid" | ||||||||
"github.com/ory/dockertest/v3" | ||||||||
"github.com/ory/dockertest/v3/docker" | ||||||||
@@ -46,7 +47,7 @@ func TestIntegrationDocker(t *testing.T) { | ||||||||
// Create a temporary directory to validate that we surface mounts correctly. | ||||||||
testTempDir := t.TempDir() | ||||||||
// Pick a random port to expose for testing port bindings. | ||||||||
testRandHostPort, testRandContainerPort := testutil.RandomPortNoListen(t), testutil.RandomPortNoListen(t) | ||||||||
ct, err := pool.RunWithOptions(&dockertest.RunOptions{ | ||||||||
Repository: "busybox", | ||||||||
Tag: "latest", | ||||||||
@@ -56,12 +57,12 @@ func TestIntegrationDocker(t *testing.T) { | ||||||||
"devcontainer.metadata": `[{"remoteEnv": {"FOO": "bar", "MULTILINE": "foo\nbar\nbaz"}}]`, | ||||||||
}, | ||||||||
Mounts: []string{testTempDir + ":" + testTempDir}, | ||||||||
ExposedPorts: []string{fmt.Sprintf("%d/tcp",testRandContainerPort)}, | ||||||||
PortBindings: map[docker.Port][]docker.PortBinding{ | ||||||||
docker.Port(fmt.Sprintf("%d/tcp",testRandContainerPort)): { | ||||||||
{ | ||||||||
HostIP: "0.0.0.0", | ||||||||
HostPort: strconv.FormatInt(int64(testRandHostPort), 10), | ||||||||
}, | ||||||||
}, | ||||||||
}, | ||||||||
@@ -99,7 +100,7 @@ func TestIntegrationDocker(t *testing.T) { | ||||||||
assert.True(t, foundContainer.Running) | ||||||||
assert.Equal(t, "running", foundContainer.Status) | ||||||||
if assert.Len(t, foundContainer.Ports, 1) { | ||||||||
assert.Equal(t,testRandHostPort, foundContainer.Ports[0].Port) | ||||||||
assert.Equal(t, "tcp", foundContainer.Ports[0].Network) | ||||||||
} | ||||||||
if assert.Len(t, foundContainer.Volumes, 1) { | ||||||||
@@ -306,68 +307,138 @@ func TestContainersHandler(t *testing.T) { | ||||||||
}) | ||||||||
} | ||||||||
funcTestDockerPortBinding(t *testing.T) { | ||||||||
t.Parallel() | ||||||||
testCases := []struct { | ||||||||
name string | ||||||||
networkPorts map[string][]dockerPortBinding | ||||||||
expectedPorts []codersdk.WorkspaceAgentListeningPort | ||||||||
expectedWarns int | ||||||||
}{ | ||||||||
{ | ||||||||
name: "nil", | ||||||||
networkPorts:nil, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{}, | ||||||||
}, | ||||||||
{ | ||||||||
name: "simple port binding", | ||||||||
networkPorts: map[string][]dockerPortBinding{ | ||||||||
"8080/tcp": { | ||||||||
{HostIP: "0.0.0.0", HostPort: "9090"}, | ||||||||
}, | ||||||||
}, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{ | ||||||||
{Network: "tcp", Port: 9090}, | ||||||||
}, | ||||||||
}, | ||||||||
{ | ||||||||
name: "multiple port bindings", | ||||||||
networkPorts: map[string][]dockerPortBinding{ | ||||||||
"8080/tcp": { | ||||||||
{HostIP: "0.0.0.0", HostPort: "9090"}, | ||||||||
}, | ||||||||
"8081/tcp": { | ||||||||
{HostIP: "0.0.0.0", HostPort: "9091"}, | ||||||||
}, | ||||||||
}, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{ | ||||||||
{Network: "tcp", Port: 9090}, | ||||||||
{Network: "tcp", Port: 9091}, | ||||||||
}, | ||||||||
}, | ||||||||
{ | ||||||||
name: "duplicate host ports on different interfaces", | ||||||||
networkPorts: map[string][]dockerPortBinding{ | ||||||||
"8080/tcp": { | ||||||||
{HostIP: "0.0.0.0", HostPort: "9090"}, | ||||||||
{HostIP: "127.0.0.1", HostPort: "9090"}, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Suggested change
For completeness, let's include IPv6 | ||||||||
}, | ||||||||
}, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{ | ||||||||
{Network: "tcp", Port: 9090}, | ||||||||
}, | ||||||||
}, | ||||||||
{ | ||||||||
name: "udp protocol", | ||||||||
networkPorts: map[string][]dockerPortBinding{ | ||||||||
"8080/udp": { | ||||||||
{HostIP: "0.0.0.0", HostPort: "9090"}, | ||||||||
}, | ||||||||
}, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{ | ||||||||
{Network: "udp", Port: 9090}, | ||||||||
}, | ||||||||
}, | ||||||||
{ | ||||||||
name: "no protocol defaults to tcp", | ||||||||
networkPorts: map[string][]dockerPortBinding{ | ||||||||
"8080": { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Nit: Mentioned earlier, but is this valid output of | ||||||||
{HostIP: "0.0.0.0", HostPort: "9090"}, | ||||||||
}, | ||||||||
}, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{ | ||||||||
{Network: "tcp", Port: 9090}, | ||||||||
}, | ||||||||
}, | ||||||||
{ | ||||||||
name: "no bindings should not create ports", | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Nit: Can this actually happen? If not, perhaps we could mention it's only here for completeness? | ||||||||
networkPorts: map[string][]dockerPortBinding{ | ||||||||
"8080/tcp": {}, | ||||||||
}, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{}, | ||||||||
}, | ||||||||
{ | ||||||||
name: "invalid host port", | ||||||||
networkPorts: map[string][]dockerPortBinding{ | ||||||||
"8080/tcp": { | ||||||||
{HostIP: "0.0.0.0", HostPort: "invalid"}, | ||||||||
}, | ||||||||
}, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{}, | ||||||||
expectedWarns: 1, | ||||||||
}, | ||||||||
{ | ||||||||
name: "mix of valid and invalid ports", | ||||||||
networkPorts: map[string][]dockerPortBinding{ | ||||||||
"8080/tcp": { | ||||||||
{HostIP: "0.0.0.0", HostPort: "9090"}, | ||||||||
}, | ||||||||
"8081/tcp": { | ||||||||
{HostIP: "0.0.0.0", HostPort: "invalid"}, | ||||||||
}, | ||||||||
}, | ||||||||
expectedPorts: []codersdk.WorkspaceAgentListeningPort{ | ||||||||
{Network: "tcp", Port: 9090}, | ||||||||
}, | ||||||||
expectedWarns: 1, | ||||||||
}, | ||||||||
} | ||||||||
for _, tc := range testCases { | ||||||||
tc := tc | ||||||||
t.Run(tc.name, func(t *testing.T) { | ||||||||
t.Parallel() | ||||||||
dockerData := dockerInspect{ | ||||||||
ID: "test-container", | ||||||||
Created: time.Now(), | ||||||||
Config: dockerInspectConfig{ | ||||||||
Image: "test-image", | ||||||||
Labels: map[string]string{"test": "value"}, | ||||||||
}, | ||||||||
Name: "test-container", | ||||||||
State: dockerInspectState{Running: true}, | ||||||||
NetworkSettings: dockerNetworkSettings{ | ||||||||
Ports: tc.networkPorts, | ||||||||
}, | ||||||||
} | ||||||||
container, warns := convertDockerInspect(dockerData) | ||||||||
if diff := cmp.Diff(tc.expectedPorts, container.Ports); diff != "" { | ||||||||
assert.Failf(t, "port mismatch", "(-want +got):\n%s", diff) | ||||||||
} | ||||||||
assert.Len(t, warns, tc.expectedWarns, "wrong number of warnings") | ||||||||
}) | ||||||||
} | ||||||||
} | ||||||||
Uh oh!
There was an error while loading.Please reload this page.