feat: respect individual ports instead of replacing the entire slice of ports
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing

The following patch adapts the logic of the merge strategy existing and last
win for service ports.

In the past, the complete object has been replaced based on the merge strategy.
This behavior has been adapted, that each port should now considered
individually per strategy.

Both strategies now focus on the src port of the host system. With a last-win,
the dest port of the container is overwritten with an existing src port.

```diff
  service:
    my-app:
      ports:
- - 0.0.0.0:8080:80
+ - 0.0.0.0:8080:8080
      - 0.0.0.0:8443:8443
```

The situation is different with the existing win strategy. There, the destination
port can no longer be changed once there is a connection with a sourc port.
This commit is contained in:
2025-02-21 13:40:40 +01:00
parent f8b4fe9af6
commit 003db26fe5
4 changed files with 1038 additions and 135 deletions

View File

@ -232,7 +232,7 @@ func TestService_Equal(t *testing.T) {
Image: "",
Labels: []string{},
Networks: map[string]*dockerCompose.ServiceNetwork{},
Ports: []string{},
Ports: []dockerCompose.Port{},
Secrets: []string{},
ULimits: nil,
Volumes: []string{},
@ -247,7 +247,7 @@ func TestService_Equal(t *testing.T) {
Image: "",
Labels: []string{},
Networks: map[string]*dockerCompose.ServiceNetwork{},
Ports: []string{},
Ports: []dockerCompose.Port{},
Secrets: []string{},
ULimits: nil,
Volumes: []string{},
@ -418,19 +418,19 @@ func TestService_Equal(t *testing.T) {
},
{
equalableA: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
equalableB: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
expectedResult: true,
},
{
equalableA: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
equalableB: &dockerCompose.Service{
Ports: []string{"80:80/udp"},
Ports: []dockerCompose.Port{"80:80/udp"},
},
expectedResult: false,
},
@ -1108,13 +1108,13 @@ func TestService_MergeExistingWin(t *testing.T) {
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: nil,
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
@ -1122,79 +1122,79 @@ func TestService_MergeExistingWin(t *testing.T) {
Ports: nil,
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"10080:80"},
Ports: []dockerCompose.Port{"80:8080"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"10080:80/udp"},
Ports: []dockerCompose.Port{"80:80/udp"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"0.0.0.0:6300:6300/tcp"},
Ports: []dockerCompose.Port{"0.0.0.0:6300:6300/tcp"},
},
expectedService: &dockerCompose.Service{
Ports: []string{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
"0.0.0.0:6300:6300/tcp",
@ -1203,18 +1203,18 @@ func TestService_MergeExistingWin(t *testing.T) {
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{
Ports: []dockerCompose.Port{
"15005:15005",
},
},
expectedService: &dockerCompose.Service{
Ports: []string{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
@ -2053,13 +2053,13 @@ func TestService_MergeLastWin(t *testing.T) {
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: nil,
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
@ -2067,76 +2067,113 @@ func TestService_MergeLastWin(t *testing.T) {
Ports: nil,
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
expectedService: &dockerCompose.Service{
Ports: []string{},
Ports: []dockerCompose.Port{},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"10080:80"},
Ports: []dockerCompose.Port{"80:10080"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"10080:80"},
Ports: []dockerCompose.Port{"80:10080"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80/tcp"},
Ports: []dockerCompose.Port{"80:80/tcp"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{"10080:80/udp"},
Ports: []dockerCompose.Port{"80:80/udp"},
},
expectedService: &dockerCompose.Service{
Ports: []string{"10080:80/udp"},
Ports: []dockerCompose.Port{"80:80/udp"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []string{""},
Ports: []dockerCompose.Port{""},
},
expectedService: &dockerCompose.Service{
Ports: []string{"80:80"},
Ports: []dockerCompose.Port{"80:80"},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:6300:6300/tcp"},
},
expectedService: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
"0.0.0.0:6300:6300/tcp",
},
},
},
{
serviceDeploymentA: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:5005/tcp",
"0.0.0.0:18080:8080/tcp",
},
},
serviceDeploymentB: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:15005",
},
},
expectedService: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:15005:15005",
"0.0.0.0:18080:8080/tcp",
},
},
},
@ -2409,6 +2446,203 @@ func TestService_MergeLastWin(t *testing.T) {
}
}
func TestService_RemovePortByDst(t *testing.T) {
require := require.New(t)
testCases := []struct {
s *dockerCompose.Service
removePortsByDst []string
expectedPorts []dockerCompose.Port
}{
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
removePortsByDst: []string{
"53",
},
expectedPorts: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
removePortsByDst: []string{
"172.25.18.20:443",
},
expectedPorts: []dockerCompose.Port{
"80:80/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:443:443/tcp",
},
},
removePortsByDst: []string{
"443",
},
expectedPorts: []dockerCompose.Port{},
},
}
for i, testCase := range testCases {
for _, removePortByDst := range testCase.removePortsByDst {
testCase.s.RemovePortByDst(removePortByDst)
}
require.Equal(testCase.expectedPorts, testCase.s.Ports, "TestCase %v", i)
}
}
func TestService_RemovePortBySrc(t *testing.T) {
require := require.New(t)
testCases := []struct {
s *dockerCompose.Service
removePortsBySrc []string
expectedPorts []dockerCompose.Port
}{
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
removePortsBySrc: []string{
"10.11.12.13:53",
},
expectedPorts: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"80:80/tcp",
"0.0.0.0:443:172.25.18.20:443/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
removePortsBySrc: []string{
"0.0.0.0:443",
},
expectedPorts: []dockerCompose.Port{
"80:80/tcp",
"10.11.12.13:53:53/tcp",
"10.11.12.13:53:53/udp",
},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{
"0.0.0.0:443:443/tcp",
},
},
removePortsBySrc: []string{
"0.0.0.0:443",
},
expectedPorts: []dockerCompose.Port{},
},
}
for i, testCase := range testCases {
for _, removePortBySrc := range testCase.removePortsBySrc {
testCase.s.RemovePortBySrc(removePortBySrc)
}
require.Equal(testCase.expectedPorts, testCase.s.Ports, "TestCase %v", i)
}
}
func TestService_SetPort(t *testing.T) {
require := require.New(t)
testCases := []struct {
s *dockerCompose.Service
setPorts []string
expectedPorts []dockerCompose.Port
}{
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"8080:8080"},
},
setPorts: []string{},
expectedPorts: []dockerCompose.Port{"8080:8080"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"8080:8080"},
},
setPorts: []string{"8080:8080"},
expectedPorts: []dockerCompose.Port{"8080:8080"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"8080:8080"},
},
setPorts: []string{"8080:80"},
expectedPorts: []dockerCompose.Port{"8080:80"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
setPorts: []string{},
expectedPorts: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
setPorts: []string{"0.0.0.0:8080:8080"},
expectedPorts: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:8080:8080"},
},
setPorts: []string{"0.0.0.0:8080:80"},
expectedPorts: []dockerCompose.Port{"0.0.0.0:8080:80"},
},
{
s: &dockerCompose.Service{
Ports: []dockerCompose.Port{"0.0.0.0:8080:8080", "0.0.0.0:8443:8443"},
},
setPorts: []string{"0.0.0.0:8080:80"},
expectedPorts: []dockerCompose.Port{"0.0.0.0:8080:80", "0.0.0.0:8443:8443"},
},
}
for i, testCase := range testCases {
for _, setPort := range testCase.setPorts {
testCase.s.SetPort(setPort)
}
require.ElementsMatch(testCase.expectedPorts, testCase.s.Ports, "TestCase %v", i)
}
}
func TestSecretDeploy_Equal(t *testing.T) {
require := require.New(t)