Skip to content

Commit 56d3fd9

Browse files
committed
Fix --network-add adding duplicate networks
When adding a network using `docker service update --network-add`, the new network was added by _name_. Existing entries in a service spec are listed by network ID, which resulted in the CLI not detecting duplicate entries for the same network. This patch changes the behavior to always use the network-ID, so that duplicate entries are correctly caught. Before this change; $ docker network create -d overlay foo $ docker service create --name=test --network=foo nginx:alpine $ docker service update --network-add foo test $ docker service inspect --format '{{ json .Spec.TaskTemplate.Networks}}' test [ { "Target": "9ot0ieagg5xv1gxd85m7y33eq" }, { "Target": "9ot0ieagg5xv1gxd85m7y33eq" } ] After this change: $ docker network create -d overlay foo $ docker service create --name=test --network=foo nginx:alpine $ docker service update --network-add foo test service is already attached to network foo Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 16cccc3 commit 56d3fd9

File tree

5 files changed

+140
-16
lines changed

5 files changed

+140
-16
lines changed

cli/command/service/client_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type fakeClient struct {
1616
serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
1717
taskListFunc func(context.Context, types.TaskListOptions) ([]swarm.Task, error)
1818
infoFunc func(ctx context.Context) (types.Info, error)
19+
networkInspectFunc func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error)
1920
}
2021

2122
func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
@@ -60,6 +61,14 @@ func (f *fakeClient) Info(ctx context.Context) (types.Info, error) {
6061
return f.infoFunc(ctx)
6162
}
6263

64+
func (f *fakeClient) NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
65+
if f.networkInspectFunc != nil {
66+
return f.networkInspectFunc(ctx, networkID, options)
67+
}
68+
69+
return types.NetworkResource{}, nil
70+
}
71+
6372
func newService(id string, name string) swarm.Service {
6473
return swarm.Service{
6574
ID: id,

cli/command/service/opts.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -353,22 +353,24 @@ func (c *credentialSpecOpt) Value() *swarm.CredentialSpec {
353353
return c.value
354354
}
355355

356-
func convertNetworks(ctx context.Context, apiClient client.NetworkAPIClient, networks opts.NetworkOpt) ([]swarm.NetworkAttachmentConfig, error) {
356+
func resolveNetworkID(ctx context.Context, apiClient client.NetworkAPIClient, networkIDOrName string) (string, error) {
357+
nw, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"})
358+
if err != nil {
359+
return "", err
360+
}
361+
return nw.ID, nil
362+
}
363+
364+
func convertNetworks(networks opts.NetworkOpt) []swarm.NetworkAttachmentConfig {
357365
var netAttach []swarm.NetworkAttachmentConfig
358366
for _, net := range networks.Value() {
359-
networkIDOrName := net.Target
360-
_, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"})
361-
if err != nil {
362-
return nil, err
363-
}
364367
netAttach = append(netAttach, swarm.NetworkAttachmentConfig{
365368
Target: net.Target,
366369
Aliases: net.Aliases,
367370
DriverOpts: net.DriverOpts,
368371
})
369372
}
370-
sort.Sort(byNetworkTarget(netAttach))
371-
return netAttach, nil
373+
return netAttach
372374
}
373375

374376
type endpointOptions struct {
@@ -590,10 +592,15 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
590592
return service, err
591593
}
592594

593-
networks, err := convertNetworks(ctx, apiClient, options.networks)
594-
if err != nil {
595-
return service, err
595+
networks := convertNetworks(options.networks)
596+
for i, net := range networks {
597+
nwID, err := resolveNetworkID(ctx, apiClient, net.Target)
598+
if err != nil {
599+
return service, err
600+
}
601+
networks[i].Target = nwID
596602
}
603+
sort.Sort(byNetworkTarget(networks))
597604

598605
resources, err := options.resources.ToResourceRequirements()
599606
if err != nil {

cli/command/service/opts_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package service
22

33
import (
4+
"context"
5+
"fmt"
46
"testing"
57
"time"
68

79
"github.com/docker/cli/opts"
10+
"github.com/docker/docker/api/types"
811
"github.com/docker/docker/api/types/container"
12+
"github.com/docker/docker/api/types/swarm"
913
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
1015
)
1116

1217
func TestMemBytesString(t *testing.T) {
@@ -123,3 +128,37 @@ func TestResourceOptionsToResourceRequirements(t *testing.T) {
123128
}
124129

125130
}
131+
132+
func TestToServiceNetwork(t *testing.T) {
133+
nws := []types.NetworkResource{
134+
{Name: "aaa-network", ID: "id555"},
135+
{Name: "mmm-network", ID: "id999"},
136+
{Name: "zzz-network", ID: "id111"},
137+
}
138+
139+
client := &fakeClient{
140+
networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
141+
for _, network := range nws {
142+
if network.ID == networkID || network.Name == networkID {
143+
return network, nil
144+
}
145+
}
146+
return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
147+
},
148+
}
149+
150+
nwo := opts.NetworkOpt{}
151+
nwo.Set("zzz-network")
152+
nwo.Set("mmm-network")
153+
nwo.Set("aaa-network")
154+
155+
o := newServiceOptions()
156+
o.mode = "replicated"
157+
o.networks = nwo
158+
159+
ctx := context.Background()
160+
flags := newCreateCommand(nil).Flags()
161+
service, err := o.ToService(ctx, client, flags)
162+
require.NoError(t, err)
163+
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks)
164+
}

cli/command/service/update.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,14 +1119,16 @@ func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flag
11191119

11201120
if flags.Changed(flagNetworkAdd) {
11211121
values := flags.Lookup(flagNetworkAdd).Value.(*opts.NetworkOpt)
1122-
networks, err := convertNetworks(ctx, apiClient, *values)
1123-
if err != nil {
1124-
return err
1125-
}
1122+
networks := convertNetworks(*values)
11261123
for _, network := range networks {
1127-
if _, exists := existingNetworks[network.Target]; exists {
1124+
nwID, err := resolveNetworkID(ctx, apiClient, network.Target)
1125+
if err != nil {
1126+
return err
1127+
}
1128+
if _, exists := existingNetworks[nwID]; exists {
11281129
return errors.Errorf("service is already attached to network %s", network.Target)
11291130
}
1131+
network.Target = nwID
11301132
newNetworks = append(newNetworks, network)
11311133
existingNetworks[network.Target] = struct{}{}
11321134
}

cli/command/service/update_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package service
22

33
import (
4+
"fmt"
45
"reflect"
56
"sort"
67
"testing"
@@ -586,3 +587,69 @@ func TestRemoveGenericResources(t *testing.T) {
586587
assert.NoError(t, removeGenericResources(flags, task))
587588
assert.Len(t, task.Resources.Reservations.GenericResources, 1)
588589
}
590+
591+
func TestUpdateNetworks(t *testing.T) {
592+
ctx := context.Background()
593+
nws := []types.NetworkResource{
594+
{Name: "aaa-network", ID: "id555"},
595+
{Name: "mmm-network", ID: "id999"},
596+
{Name: "zzz-network", ID: "id111"},
597+
}
598+
599+
client := &fakeClient{
600+
networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
601+
for _, network := range nws {
602+
if network.ID == networkID || network.Name == networkID {
603+
return network, nil
604+
}
605+
}
606+
return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
607+
},
608+
}
609+
610+
svc := swarm.ServiceSpec{
611+
TaskTemplate: swarm.TaskSpec{
612+
ContainerSpec: &swarm.ContainerSpec{},
613+
Networks: []swarm.NetworkAttachmentConfig{
614+
{Target: "id999"},
615+
},
616+
},
617+
}
618+
619+
flags := newUpdateCommand(nil).Flags()
620+
err := flags.Set(flagNetworkAdd, "aaa-network")
621+
require.NoError(t, err)
622+
err = updateService(ctx, client, flags, &svc)
623+
require.NoError(t, err)
624+
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)
625+
626+
flags = newUpdateCommand(nil).Flags()
627+
err = flags.Set(flagNetworkAdd, "aaa-network")
628+
require.NoError(t, err)
629+
err = updateService(ctx, client, flags, &svc)
630+
assert.EqualError(t, err, "service is already attached to network aaa-network")
631+
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)
632+
633+
flags = newUpdateCommand(nil).Flags()
634+
err = flags.Set(flagNetworkAdd, "id555")
635+
require.NoError(t, err)
636+
err = updateService(ctx, client, flags, &svc)
637+
assert.EqualError(t, err, "service is already attached to network id555")
638+
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)
639+
640+
flags = newUpdateCommand(nil).Flags()
641+
err = flags.Set(flagNetworkRemove, "id999")
642+
require.NoError(t, err)
643+
err = updateService(ctx, client, flags, &svc)
644+
assert.NoError(t, err)
645+
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id555"}}, svc.TaskTemplate.Networks)
646+
647+
flags = newUpdateCommand(nil).Flags()
648+
err = flags.Set(flagNetworkAdd, "mmm-network")
649+
require.NoError(t, err)
650+
err = flags.Set(flagNetworkRemove, "aaa-network")
651+
require.NoError(t, err)
652+
err = updateService(ctx, client, flags, &svc)
653+
assert.NoError(t, err)
654+
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id999"}}, svc.TaskTemplate.Networks)
655+
}

0 commit comments

Comments
 (0)