Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix(agent/agentcontainers): improve testing of convertDockerInspect, return correct host port#16887

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

Merged
johnstcn merged 12 commits intomainfromcj/agentcontainers-port-fix-2
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
12 commits
Select commitHold shift + click to select a range
80ac9a3
chore(agent/agentcontainers): refactor runDockerInspect and convertDo…
johnstcnMar 11, 2025
0ecceb0
chore(agent/agentcontainers): add dedicated test for convertDockerIns…
johnstcnMar 11, 2025
55998d0
fix(agent/agentcontainers): fix incorrectly parsed port
johnstcnMar 11, 2025
fb78d33
nolint paralleltest
johnstcnMar 12, 2025
a7d1ea4
chore: adjust testdata structure
johnstcnMar 12, 2025
393f6e9
use a []byte instead of a string
johnstcnMar 12, 2025
95b156e
fix(agent/agentcontainers): create new WorkspaceAgentDevcontainerPort…
johnstcnMar 13, 2025
f8f3000
fix(site): correct container port link
johnstcnMar 13, 2025
999469f
chore(site): add stories for AgentDevcontainerCard
johnstcnMar 13, 2025
8338af3
make fmt lint
johnstcnMar 13, 2025
2f0180e
rm extraneous null check
johnstcnMar 18, 2025
1ae6015
address PR comment
johnstcnMar 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
NextNext commit
fix(agent/agentcontainers): fix incorrectly parsed port
  • Loading branch information
@johnstcn
johnstcn committedMar 13, 2025
commit55998d07564f1d2c2d1b832fe4964483812d0868
60 changes: 43 additions & 17 deletionsagent/agentcontainers/containers_dockercli.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -319,22 +319,23 @@ 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"`
Namestring`json:"Name"`
Mounts[]dockerInspectMount `json:"Mounts"`
State dockerInspectState`json:"State"`
IDstring `json:"Id"`
Createdtime.Time `json:"Created"`
ConfigdockerInspectConfig `json:"Config"`
Name string`json:"Name"`
Mounts[]dockerInspectMount `json:"Mounts"`
State dockerInspectState`json:"State"`
NetworkSettings dockerInspectNetworkSettings`json:"NetworkSettings"`
}

type dockerInspectConfig struct {
Image string `json:"Image"`
Labels map[string]string `json:"Labels"`
}

type dockerInspectHostConfig struct {
PortBindings map[string]any `json:"PortBindings"`
type dockerInspectPort struct {
HostIP string `json:"HostIp"`
HostPort string `json:"HostPort"`
}

type dockerInspectMount struct {
Expand All@@ -349,6 +350,10 @@ type dockerInspectState struct {
Error string `json:"Error"`
}

type dockerInspectNetworkSettings struct {
Ports map[string][]dockerInspectPort `json:"Ports"`
}

func (dis dockerInspectState) String() string {
if dis.Running {
return "running"
Expand DownExpand Up@@ -388,20 +393,41 @@ func convertDockerInspect(raw string) ([]codersdk.WorkspaceAgentDevcontainer, []
Volumes: make(map[string]string, len(in.Mounts)),
}

if in.HostConfig.PortBindings == nil {
in.HostConfig.PortBindings = make(map[string]any)
if in.NetworkSettings.Ports == nil {
in.NetworkSettings.Ports = make(map[string][]dockerInspectPort)
}
portKeys := maps.Keys(in.HostConfig.PortBindings)
portKeys := maps.Keys(in.NetworkSettings.Ports)
// Sort the ports for deterministic output.
sort.Strings(portKeys)
for _, p := range portKeys {
if port, network, err := convertDockerPort(p); err != nil {
warns = append(warns, err.Error())
} else {
// Each port binding may have multiple entries mapped to the same interface.
// Keep track of the ports we've already seen.
seen := make(map[int]struct{}, len(in.NetworkSettings.Ports))
Copy link
Member

@mafredrimafredriMar 12, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I feel this seen map, as well as port info, is now a bit misplaced when we're looking at the HostPort and not the container port. This port is under container, but is a host port, so it may appear on multiple containers which would be confusing.

Since we're throwing away the information where this port maps to, it could be container X or Y depending on if it's listening on IPv4 or IPv6, for instance. Thus it would be listed on both container responses but likely only one of those will be reachable.

TL;DR, seen host ports are better placed outside the container structure, on the parent response.

❯ docker run -it --rm --name alpy1 -p 127.0.0.1:8181:8181 alpine❯ docker run -it --rm --name alpy2 -p :::8181:8182 alpine❯ docker inspect alpy1 alpy2 | jq '.[] | .Name, .NetworkSettings.Ports'"/alpy1"{  "8181/tcp": [    {      "HostIp": "127.0.0.1",      "HostPort": "8181"    }  ]}"/alpy2"{  "8182/tcp": [    {      "HostIp": "::",      "HostPort": "8181"    }  ]}

Copy link
Member

@mafredrimafredriMar 12, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Addendum: As a further improvement, I would suggest that this data parsing should also decide (or output if predetermined) which container/port the host port maps to.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Right now havingHostIp doesn't seem to be useful to the UI for the purposes of accessing applications listening inside the workspace.

Let's say you have two containers, both listening on the same port but on different interfaces:

docker run --name nginx-a -v /path/to/some/html/a:/usr/share/nginx/html:ro -d -p 127.0.0.1:8000:80 nginxdocker run --name nginx-b -v /path/to/some/html/b:/usr/share/nginx/html:ro -d -p [::1]:8000:80 nginx

When I hit the forwarded port in the Coder UI, it hits the container listening on IPv4.
Similarly, when Icurl http://localhost:8000 it also hits the container listening on IPv4. I needed to explicitly specifycurl -6 http://localhost:8000 to hit the container listening on the IPv6 interface.

If I stopped either theipv4 oripv6 container, it would fall back to whichever one was running.

Whether or not the v4 or v6 address is preferred seems to be down to the underlying OS configuration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

That makes sense, thanks for investigating. So ultimately, this confirms we can't with confidence say which container aHostPort belongs to.

I think the best option for us now is to detect that multiple containers have the same host port mapped, and if that's the case, we flag it via additional parameter. Perhaps (simplified)"conflicts": ["container2:9090/tcp"] or something like that? (Likewise container2 should have"conflicts": ["container1:8080/tcp"].)

This would allow us to surface a? in the UI, saying "this could go to either container1 port 8080 or container2 port 9090.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Alternative suggestion: a message to this effect can be dropped intowarnings as a relatively cheap and easy way to surface this situation. I don't think it's going to be that common.

mafredri reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

OK, I've added a test case for this scenario and hopefully improved clarity overall.

for _, pk := range portKeys {
for _, p := range in.NetworkSettings.Ports[pk] {
_, network, err := convertDockerPort(pk)
if err != nil {
warns = append(warns, fmt.Sprintf("convert docker port: %s", err.Error()))
// Default network to "tcp" if we can't parse it.
network = "tcp"
}
hp, err := strconv.Atoi(p.HostPort)
if err != nil {
warns = append(warns, fmt.Sprintf("convert docker port: %s", err.Error()))
continue
}
if hp > 65535 || hp < 0 { // invalid port
warns = append(warns, fmt.Sprintf("convert docker port: invalid host port %d", hp))
continue
}
if _, ok := seen[hp]; ok {
// We've already seen this port, so skip it.
continue
}
out.Ports = append(out.Ports, codersdk.WorkspaceAgentListeningPort{
Network: network,
Port:port,
Port:uint16(hp),
})
seen[hp] = struct{}{}
}
}

Expand Down
27 changes: 14 additions & 13 deletionsagent/agentcontainers/containers_internal_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -357,7 +357,7 @@ func TestConvertDockerPort(t *testing.T) {
expectError: "invalid port",
},
} {
tc := tc //not needed anymore but makes the linter happy
//nolint: paralleltest //variable recapture no longer required
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
actualPort, actualNetwork, actualErr := convertDockerPort(tc.in)
Expand DownExpand Up@@ -511,7 +511,7 @@ func TestConvertDockerInspect(t *testing.T) {
Ports: []codersdk.WorkspaceAgentListeningPort{
{
Network: "tcp",
Port:23456,
Port:12345,
},
},
Volumes: map[string]string{},
Expand DownExpand Up@@ -548,10 +548,10 @@ func TestConvertDockerInspect(t *testing.T) {
"devcontainer.config_file": "/home/coder/src/coder/coder/agent/agentcontainers/testdata/devcontainer_simple.json",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

A little bit of coder inflation in this path 😂

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

TBH there's probably a lot of "pruning" we can do in those fixtures, but I'd like to avoid changing them if possible :)

mafredri reacted with thumbs up emoji
"devcontainer.metadata": "[]",
},
Running:true,
Status:"running",
Ports:[]codersdk.WorkspaceAgentListeningPort{},
Volumes:map[string]string{},
Running: true,
Status: "running",
Ports: []codersdk.WorkspaceAgentListeningPort{},
Volumes: map[string]string{},
},
},
},
Expand All@@ -567,10 +567,10 @@ func TestConvertDockerInspect(t *testing.T) {
"devcontainer.config_file": "/home/coder/src/coder/coder/agent/agentcontainers/testdata/devcontainer_forwardport.json",
"devcontainer.metadata": "[]",
},
Running:true,
Status:"running",
Ports:[]codersdk.WorkspaceAgentListeningPort{},
Volumes:map[string]string{},
Running: true,
Status: "running",
Ports: []codersdk.WorkspaceAgentListeningPort{},
Volumes: map[string]string{},
},
},
},
Expand All@@ -586,12 +586,13 @@ func TestConvertDockerInspect(t *testing.T) {
"devcontainer.config_file": "/home/coder/src/coder/coder/agent/agentcontainers/testdata/devcontainer_appport.json",
"devcontainer.metadata": "[]",
},
Running:true,
Status:"running",
Running: true,
Status: "running",
Ports: []codersdk.WorkspaceAgentListeningPort{
{
Network: "tcp",
Port: 8080,
// Container port 8080 is mapped to 32768 on the host.
Port: 32768,
},
},
Volumes: map[string]string{},
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp