diff --git a/go.mod b/go.mod index f4bbe5b2f..8a15ba21a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.6.1 + github.com/compose-spec/compose-go/v2 v2.6.2-0.20250423090706-30ff01d36f76 github.com/containerd/containerd/v2 v2.0.5 github.com/containerd/platforms v1.0.0-rc.1 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 512a169bf..b4f626f4e 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.6.1 h1:276YiQKRcGGtgkxiymzWHJ2CTv5joQA+7DTNrUA+rys= -github.com/compose-spec/compose-go/v2 v2.6.1/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA= +github.com/compose-spec/compose-go/v2 v2.6.2-0.20250423090706-30ff01d36f76 h1:KZvD41eTRr9/n43zccAcGPBRgzHXdbLZY4IXSeJxqIw= +github.com/compose-spec/compose-go/v2 v2.6.2-0.20250423090706-30ff01d36f76/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 7e531bf7d..825fe4709 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -154,6 +154,7 @@ func (s *composeService) ensureProjectVolumes(ctx context.Context, project *type return ids, nil } +//nolint:gocyclo func (s *composeService) getCreateConfigs(ctx context.Context, p *types.Project, service types.ServiceConfig, @@ -246,7 +247,10 @@ func (s *composeService) getCreateConfigs(ctx context.Context, if err != nil { return createConfigs{}, err } - networkMode, networkingConfig := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases, apiVersion) + networkMode, networkingConfig, err := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases, apiVersion) + if err != nil { + return createConfigs{}, err + } portBindings := buildContainerPortBindingOptions(service) // MISC @@ -356,7 +360,7 @@ func (s *composeService) prepareContainerMACAddress(ctx context.Context, service } if len(withMacAddress) > 1 { - return "", fmt.Errorf("a MAC address is specified for multiple networks (%s), but this feature requires Docker Engine 1.44 or later (currently: %s)", strings.Join(withMacAddress, ", "), version) + return "", fmt.Errorf("a MAC address is specified for multiple networks (%s), but this feature requires Docker Engine v25 or later", strings.Join(withMacAddress, ", ")) } if mainNw != nil && mainNw.MacAddress != "" { @@ -379,6 +383,8 @@ func getAliases(project *types.Project, service types.ServiceConfig, serviceInde } func createEndpointSettings(p *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, links []string, useNetworkAliases bool) *network.EndpointSettings { + const ifname = "com.docker.network.endpoint.ifname" + config := service.Networks[networkKey] var ipam *network.EndpointIPAMConfig var ( @@ -398,6 +404,15 @@ func createEndpointSettings(p *types.Project, service types.ServiceConfig, servi } macAddress = config.MacAddress driverOpts = config.DriverOpts + if config.InterfaceName != "" { + if driverOpts == nil { + driverOpts = map[string]string{} + } + if name, ok := driverOpts[ifname]; ok && name != config.InterfaceName { + logrus.Warnf("ignoring services.%s.networks.%s.interface_name as %s driver_opts is already declared", service.Name, networkKey, ifname) + } + driverOpts[ifname] = config.InterfaceName + } gwPriority = config.GatewayPriority } return &network.EndpointSettings{ @@ -471,20 +486,17 @@ func (s *composeService) prepareLabels(labels types.Labels, service types.Servic } // defaultNetworkSettings determines the container.NetworkMode and corresponding network.NetworkingConfig (nil if not applicable). -func defaultNetworkSettings( - project *types.Project, - service types.ServiceConfig, - serviceIndex int, - links []string, - useNetworkAliases bool, +func defaultNetworkSettings(project *types.Project, + service types.ServiceConfig, serviceIndex int, + links []string, useNetworkAliases bool, version string, -) (container.NetworkMode, *network.NetworkingConfig) { +) (container.NetworkMode, *network.NetworkingConfig, error) { if service.NetworkMode != "" { - return container.NetworkMode(service.NetworkMode), nil + return container.NetworkMode(service.NetworkMode), nil, nil } if len(project.Networks) == 0 { - return "none", nil + return "none", nil, nil } var primaryNetworkKey string @@ -515,6 +527,14 @@ func defaultNetworkSettings( } } + if versions.LessThan(version, "1.49") { + for _, config := range service.Networks { + if config != nil && config.InterfaceName != "" { + return "", nil, fmt.Errorf("interface_name requires Docker Engine v28.1 or later") + } + } + } + endpointsConfig[primaryNetworkMobyNetworkName] = primaryNetworkEndpoint networkConfig := &network.NetworkingConfig{ EndpointsConfig: endpointsConfig, @@ -523,7 +543,7 @@ func defaultNetworkSettings( // From the Engine API docs: // > Supported standard values are: bridge, host, none, and container:. // > Any other value is taken as a custom network's name to which this container should connect to. - return container.NetworkMode(primaryNetworkMobyNetworkName), networkConfig + return container.NetworkMode(primaryNetworkMobyNetworkName), networkConfig, nil } func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy { diff --git a/pkg/compose/create_test.go b/pkg/compose/create_test.go index bec42beef..3394bf3c9 100644 --- a/pkg/compose/create_test.go +++ b/pkg/compose/create_test.go @@ -219,7 +219,8 @@ func TestDefaultNetworkSettings(t *testing.T) { }), } - networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + assert.NilError(t, err) assert.Equal(t, string(networkMode), "myProject_myNetwork2") assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1)) assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_myNetwork2")) @@ -247,7 +248,8 @@ func TestDefaultNetworkSettings(t *testing.T) { }), } - networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + assert.NilError(t, err) assert.Equal(t, string(networkMode), "myProject_default") assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1)) assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_default")) @@ -264,7 +266,8 @@ func TestDefaultNetworkSettings(t *testing.T) { }, } - networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + assert.NilError(t, err) assert.Equal(t, string(networkMode), "none") assert.Check(t, cmp.Nil(networkConfig)) }) @@ -284,7 +287,8 @@ func TestDefaultNetworkSettings(t *testing.T) { }), } - networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + networkMode, networkConfig, err := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") + assert.NilError(t, err) assert.Equal(t, string(networkMode), "host") assert.Check(t, cmp.Nil(networkConfig)) }) diff --git a/pkg/e2e/fixtures/network-interface-name/compose.yaml b/pkg/e2e/fixtures/network-interface-name/compose.yaml new file mode 100644 index 000000000..701830a48 --- /dev/null +++ b/pkg/e2e/fixtures/network-interface-name/compose.yaml @@ -0,0 +1,7 @@ +services: + test: + image: alpine + command: ip link show + networks: + default: + interface_name: foobar diff --git a/pkg/e2e/networks_test.go b/pkg/e2e/networks_test.go index c74687adc..cbd4e0e71 100644 --- a/pkg/e2e/networks_test.go +++ b/pkg/e2e/networks_test.go @@ -181,3 +181,21 @@ func TestMacAddress(t *testing.T) { res := c.RunDockerCmd(t, "inspect", fmt.Sprintf("%s-test-1", projectName), "-f", "{{ (index .NetworkSettings.Networks \"network_mac_address_default\" ).MacAddress }}") res.Assert(t, icmd.Expected{Out: "00:e0:84:35:d0:e8"}) } + +func TestInterfaceName(t *testing.T) { + c := NewCLI(t) + + version := c.RunDockerCmd(t, "version", "-f", "{{.Server.Version}}") + major, _, found := strings.Cut(version.Combined(), ".") + assert.Assert(t, found) + if major == "26" || major == "27" { + t.Skip("Skipping test due to docker version < 28") + } + + const projectName = "network_interface_name" + res := c.RunDockerComposeCmd(t, "-f", "./fixtures/network-interface-name/compose.yaml", "--project-name", projectName, "run", "test") + t.Cleanup(func() { + c.cleanupWithDown(t, projectName) + }) + res.Assert(t, icmd.Expected{Out: "foobar@"}) +}