feat: detect conflicing service port on a container

This commit is contained in:
Pavel Sviderski
2025-02-13 11:50:34 +10:00
parent 9859be50a6
commit b17fd7531f
2 changed files with 227 additions and 1 deletions
+177
View File
@@ -3,6 +3,8 @@ package api
import (
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"net/netip"
"testing"
)
@@ -90,3 +92,178 @@ func TestContainer_Healthy(t *testing.T) {
assert.False(t, c.Healthy())
})
}
func TestContainer_ConflictingServicePorts(t *testing.T) {
tests := []struct {
name string
containerPorts string
checkPorts []PortSpec
want []PortSpec
wantErr bool
}{
{
name: "no conflicts when container has no ports",
containerPorts: "",
checkPorts: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
},
want: nil,
wantErr: false,
},
{
name: "host mode ports with same published port and protocol conflict",
containerPorts: "8080:80/tcp@host",
checkPorts: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
},
want: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
},
wantErr: false,
},
{
name: "host mode ports with same port but different protocols don't conflict",
containerPorts: "8080:80/tcp@host",
checkPorts: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolUDP},
},
want: nil,
wantErr: false,
},
{
name: "multiple protocols on same port don't conflict",
containerPorts: "8080:80/tcp@host,8080:80/udp@host",
checkPorts: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolUDP},
},
want: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolUDP},
},
wantErr: false,
},
{
name: "host mode ports with different published ports don't conflict",
containerPorts: "8080:80/tcp@host",
checkPorts: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8081, ContainerPort: 80, Protocol: ProtocolTCP},
},
want: nil,
wantErr: false,
},
{
name: "host mode ports with same published port but different host IPs don't conflict",
containerPorts: "127.0.0.1:8080:80/tcp@host",
checkPorts: []PortSpec{
{
Mode: PortModeHost,
HostIP: netip.MustParseAddr("127.0.0.2"),
PublishedPort: 8080,
ContainerPort: 80,
Protocol: ProtocolTCP,
},
},
want: nil,
wantErr: false,
},
{
name: "host mode ports with same published port, protocol, and host IP conflict",
containerPorts: "127.0.0.1:8080:80/tcp@host",
checkPorts: []PortSpec{
{
Mode: PortModeHost,
HostIP: netip.MustParseAddr("127.0.0.1"),
PublishedPort: 8080,
ContainerPort: 80,
Protocol: ProtocolTCP,
},
},
want: []PortSpec{
{
Mode: PortModeHost,
HostIP: netip.MustParseAddr("127.0.0.1"),
PublishedPort: 8080,
ContainerPort: 80,
Protocol: ProtocolTCP,
},
},
wantErr: false,
},
{
name: "host mode port with no host IP conflicts with specific host IP on same port and protocol",
containerPorts: "8080:80/tcp@host",
checkPorts: []PortSpec{
{
Mode: PortModeHost,
HostIP: netip.MustParseAddr("127.0.0.1"),
PublishedPort: 8080,
ContainerPort: 80,
Protocol: ProtocolTCP,
},
},
want: []PortSpec{
{
Mode: PortModeHost,
HostIP: netip.MustParseAddr("127.0.0.1"),
PublishedPort: 8080,
ContainerPort: 80,
Protocol: ProtocolTCP,
},
},
wantErr: false,
},
{
name: "host mode port with no host IP doesn't conflict with different protocol",
containerPorts: "8080:80/tcp@host",
checkPorts: []PortSpec{
{
Mode: PortModeHost,
HostIP: netip.MustParseAddr("127.0.0.1"),
PublishedPort: 8080,
ContainerPort: 80,
Protocol: ProtocolUDP,
},
},
want: nil,
wantErr: false,
},
{
name: "ingress mode ports don't conflict with host mode ports",
containerPorts: "8080:80/tcp",
checkPorts: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
},
want: nil,
wantErr: false,
},
{
name: "container with invalid port spec returns error",
containerPorts: "invalid:port:spec",
checkPorts: []PortSpec{
{Mode: PortModeHost, PublishedPort: 8080, ContainerPort: 80, Protocol: ProtocolTCP},
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
container := &Container{
Container: types.Container{
Labels: map[string]string{
LabelServicePorts: tt.containerPorts,
},
},
}
got, err := container.ConflictingServicePorts(tt.checkPorts)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}