diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 6d15f28f28..55c6cc4e43 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -1406,7 +1406,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty } var ipam *network.IPAM - if n.Ipam.Config != nil { + if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 || len(n.Ipam.Options) > 0 { var config []network.IPAMConfig for _, pool := range n.Ipam.Config { c, err := parseIPAMPool(pool) @@ -1416,8 +1416,9 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty config = append(config, c) } ipam = &network.IPAM{ - Driver: n.Ipam.Driver, - Config: config, + Driver: n.Ipam.Driver, + Config: config, + Options: n.Ipam.Options, } } hash, err := NetworkHash(n) @@ -1436,22 +1437,6 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty EnableIPv4: n.EnableIPv4, } - if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 { - createOpts.IPAM = &network.IPAM{} - } - - if n.Ipam.Driver != "" { - createOpts.IPAM.Driver = n.Ipam.Driver - } - - for _, ipamConfig := range n.Ipam.Config { - c, err := parseIPAMPool(ipamConfig) - if err != nil { - return "", err - } - createOpts.IPAM.Config = append(createOpts.IPAM.Config, c) - } - networkEventName := fmt.Sprintf("Network %s", n.Name) s.events.On(creatingEvent(networkEventName)) diff --git a/pkg/compose/executor_test.go b/pkg/compose/executor_test.go index 5f4ab88049..a1cd2e0a75 100644 --- a/pkg/compose/executor_test.go +++ b/pkg/compose/executor_test.go @@ -23,6 +23,7 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/client" "go.uber.org/mock/gomock" "gotest.tools/v3/assert" @@ -38,6 +39,17 @@ func (noopEventProcessor) Start(_ context.Context, _ string) {} func (noopEventProcessor) On(_ ...api.Resource) {} func (noopEventProcessor) Done(_ string, _ bool) {} +const ( + executorTestProjectName = "test" + executorTestNetworkKey = "default" + executorTestNetworkName = "test_default" + executorTestNetworkResource = "network:" + executorTestNetworkKey + executorTestNotFoundCause = "not found" + executorTestCreatedNetworkID = "net1" + ipamOptionsKey = "ipam-option" + ipamOptionsValue = "enabled" +) + func newTestService(t *testing.T) (*composeService, *mocks.MockAPIClient) { t.Helper() mockCtrl := gomock.NewController(t) @@ -52,37 +64,80 @@ func newTestService(t *testing.T) (*composeService, *mocks.MockAPIClient) { func TestExecutePlanEmpty(t *testing.T) { svc, _ := newTestService(t) - err := svc.executePlan(t.Context(), &types.Project{Name: "test"}, emptyObservedState("test"), &Plan{}) + err := svc.executePlan(t.Context(), &types.Project{Name: executorTestProjectName}, emptyObservedState(executorTestProjectName), &Plan{}) assert.NilError(t, err) } func TestExecutePlanCreateNetwork(t *testing.T) { svc, apiClient := newTestService(t) - nw := types.NetworkConfig{Name: "test_default"} + nw := types.NetworkConfig{Name: executorTestNetworkName} project := &types.Project{ - Name: "test", - Networks: types.Networks{"default": nw}, + Name: executorTestProjectName, + Networks: types.Networks{executorTestNetworkKey: nw}, } // ensureNetwork: inspect → not found, list → empty, create - apiClient.EXPECT().NetworkInspect(gomock.Any(), "test_default", gomock.Any()). + apiClient.EXPECT().NetworkInspect(gomock.Any(), executorTestNetworkName, gomock.Any()). + Return(client.NetworkInspectResult{}, notFoundError{}) + apiClient.EXPECT().NetworkList(gomock.Any(), gomock.Any()). + Return(client.NetworkListResult{}, nil) + apiClient.EXPECT().NetworkCreate(gomock.Any(), executorTestNetworkName, gomock.Any()). + Return(client.NetworkCreateResult{ID: executorTestCreatedNetworkID}, nil) + + plan := &Plan{} + plan.addNode(Operation{ + Type: OpCreateNetwork, + ResourceID: executorTestNetworkResource, + Cause: executorTestNotFoundCause, + Name: nw.Name, + Network: &nw, + }, "") + + err := svc.executePlan(t.Context(), project, emptyObservedState(executorTestProjectName), plan) + assert.NilError(t, err) +} + +func TestExecutePlanCreateNetworkWithIPAMOptions(t *testing.T) { + svc, apiClient := newTestService(t) + + nw := types.NetworkConfig{ + Name: executorTestNetworkName, + Ipam: types.IPAMConfig{ + Options: types.Options{ + ipamOptionsKey: ipamOptionsValue, + }, + }, + } + project := &types.Project{ + Name: executorTestProjectName, + Networks: types.Networks{executorTestNetworkKey: nw}, + } + + apiClient.EXPECT().NetworkInspect(gomock.Any(), executorTestNetworkName, gomock.Any()). Return(client.NetworkInspectResult{}, notFoundError{}) apiClient.EXPECT().NetworkList(gomock.Any(), gomock.Any()). Return(client.NetworkListResult{}, nil) - apiClient.EXPECT().NetworkCreate(gomock.Any(), "test_default", gomock.Any()). - Return(client.NetworkCreateResult{ID: "net1"}, nil) + apiClient.EXPECT().NetworkCreate(gomock.Any(), executorTestNetworkName, gomock.Any()). + DoAndReturn(func(_ context.Context, _ string, opts client.NetworkCreateOptions) (client.NetworkCreateResult, error) { + assert.DeepEqual(t, opts.IPAM, &network.IPAM{ + Options: map[string]string{ + ipamOptionsKey: ipamOptionsValue, + }, + }) + return client.NetworkCreateResult{ID: executorTestCreatedNetworkID}, nil + }) plan := &Plan{} plan.addNode(Operation{ Type: OpCreateNetwork, - ResourceID: "network:default", - Cause: "not found", + ResourceID: executorTestNetworkResource, + Cause: executorTestNotFoundCause, Name: nw.Name, Network: &nw, }, "") - err := svc.executePlan(t.Context(), project, emptyObservedState("test"), plan) + err := svc.executePlan(t.Context(), project, emptyObservedState(executorTestProjectName), plan) assert.NilError(t, err) }