@@ -143,11 +143,12 @@ func prepareNetworks(project *types.Project) {
143143}
144144
145145func (s * composeService ) ensureNetworks (ctx context.Context , networks types.Networks ) error {
146- for _ , network := range networks {
147- err := s .ensureNetwork (ctx , network )
146+ for i , network := range networks {
147+ err := s .ensureNetwork (ctx , & network )
148148 if err != nil {
149149 return err
150150 }
151+ networks [i ] = network
151152 }
152153 return nil
153154}
@@ -1009,98 +1010,168 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
10091010 return aliases
10101011}
10111012
1012- func (s * composeService ) ensureNetwork (ctx context.Context , n types.NetworkConfig ) error {
1013- // NetworkInspect will match on ID prefix, so NetworkList with a name
1014- // filter is used to look for an exact match to prevent e.g. a network
1015- // named `db` from getting erroneously matched to a network with an ID
1016- // like `db9086999caf`
1013+ func (s * composeService ) ensureNetwork (ctx context.Context , n * types.NetworkConfig ) error {
1014+ if n .External .External {
1015+ return s .resolveExternalNetwork (ctx , n )
1016+ }
1017+
1018+ err := s .resolveOrCreateNetwork (ctx , n )
1019+ if errdefs .IsConflict (err ) {
1020+ // Maybe another execution of `docker compose up|run` created same network
1021+ // let's retry once
1022+ return s .resolveOrCreateNetwork (ctx , n )
1023+ }
1024+ return err
1025+ }
1026+
1027+ func (s * composeService ) resolveOrCreateNetwork (ctx context.Context , n * types.NetworkConfig ) error { //nolint:gocyclo
1028+ expectedNetworkLabel := n .Labels [api .NetworkLabel ]
1029+ expectedProjectLabel := n .Labels [api .ProjectLabel ]
1030+
1031+ // First, try to find a unique network matching by name or ID
1032+ inspect , err := s .apiClient ().NetworkInspect (ctx , n .Name , moby.NetworkInspectOptions {})
1033+ if err == nil {
1034+ // NetworkInspect will match on ID prefix, so double check we get the expected one
1035+ // as looking for network named `db` we could erroneously matched network ID `db9086999caf`
1036+ if inspect .Name == n .Name || inspect .ID == n .Name {
1037+ if inspect .Labels [api .ProjectLabel ] != expectedProjectLabel {
1038+ logrus .Warnf ("a network with name %s exists but was not created by compose.\n " +
1039+ "Set `external: true` to use an existing network" , n .Name )
1040+ }
1041+ if inspect .Labels [api .NetworkLabel ] != expectedNetworkLabel {
1042+ return fmt .Errorf ("network %s was found but has incorrect label %s set to %q" , n .Name , api .NetworkLabel , inspect .Labels [api .NetworkLabel ])
1043+ }
1044+ return nil
1045+ }
1046+ }
1047+ // ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
1048+
1049+ // Either not found, or name is ambiguous - use NetworkList to list by name
10171050 networks , err := s .apiClient ().NetworkList (ctx , moby.NetworkListOptions {
10181051 Filters : filters .NewArgs (filters .Arg ("name" , n .Name )),
10191052 })
10201053 if err != nil {
10211054 return err
10221055 }
1023- networkNotFound := true
1056+
1057+ // NetworkList Matches all or part of a network name, so we have to filter for a strict match
1058+ networks = utils .Filter (networks , func (net moby.NetworkResource ) bool {
1059+ return net .Name == n .Name
1060+ })
1061+
10241062 for _ , net := range networks {
1025- if net .Name == n . Name {
1026- networkNotFound = false
1027- break
1063+ if net .Labels [ api . ProjectLabel ] == expectedProjectLabel &&
1064+ net . Labels [ api . NetworkLabel ] == expectedNetworkLabel {
1065+ return nil
10281066 }
10291067 }
1030- if networkNotFound {
1031- if n .External .External {
1032- if n .Driver == "overlay" {
1033- // Swarm nodes do not register overlay networks that were
1034- // created on a different node unless they're in use.
1035- // Here we assume `driver` is relevant for a network we don't manage
1036- // which is a non-sense, but this is our legacy ¯\(ツ)/¯
1037- // networkAttach will later fail anyway if network actually doesn't exists
1038- enabled , err := s .isSWarmEnabled (ctx )
1039- if err != nil {
1040- return err
1041- }
1042- if enabled {
1043- return nil
1044- }
1045- }
1046- return fmt .Errorf ("network %s declared as external, but could not be found" , n .Name )
1047- }
1048- var ipam * network.IPAM
1049- if n .Ipam .Config != nil {
1050- var config []network.IPAMConfig
1051- for _ , pool := range n .Ipam .Config {
1052- config = append (config , network.IPAMConfig {
1053- Subnet : pool .Subnet ,
1054- IPRange : pool .IPRange ,
1055- Gateway : pool .Gateway ,
1056- AuxAddress : pool .AuxiliaryAddresses ,
1057- })
1058- }
1059- ipam = & network.IPAM {
1060- Driver : n .Ipam .Driver ,
1061- Config : config ,
1062- }
1068+
1069+ // we could have set NetworkList with a projectFilter and networkFilter but not doing so allows to catch this
1070+ // scenario were a network with same name exists but doesn't have label, and use of `CheckDuplicate: true`
1071+ // prevents to create another one.
1072+ if len (networks ) > 0 {
1073+ logrus .Warnf ("a network with name %s exists but was not created by compose.\n " +
1074+ "Set `external: true` to use an existing network" , n .Name )
1075+ return nil
1076+ }
1077+
1078+ var ipam * network.IPAM
1079+ if n .Ipam .Config != nil {
1080+ var config []network.IPAMConfig
1081+ for _ , pool := range n .Ipam .Config {
1082+ config = append (config , network.IPAMConfig {
1083+ Subnet : pool .Subnet ,
1084+ IPRange : pool .IPRange ,
1085+ Gateway : pool .Gateway ,
1086+ AuxAddress : pool .AuxiliaryAddresses ,
1087+ })
10631088 }
1064- createOpts := moby.NetworkCreate {
1065- CheckDuplicate : true ,
1066- // TODO NameSpace Labels
1067- Labels : n .Labels ,
1068- Driver : n .Driver ,
1069- Options : n .DriverOpts ,
1070- Internal : n .Internal ,
1071- Attachable : n .Attachable ,
1072- IPAM : ipam ,
1073- EnableIPv6 : n .EnableIPv6 ,
1089+ ipam = & network.IPAM {
1090+ Driver : n .Ipam .Driver ,
1091+ Config : config ,
10741092 }
1093+ }
1094+ createOpts := moby.NetworkCreate {
1095+ CheckDuplicate : true ,
1096+ Labels : n .Labels ,
1097+ Driver : n .Driver ,
1098+ Options : n .DriverOpts ,
1099+ Internal : n .Internal ,
1100+ Attachable : n .Attachable ,
1101+ IPAM : ipam ,
1102+ EnableIPv6 : n .EnableIPv6 ,
1103+ }
10751104
1076- if n .Ipam .Driver != "" || len (n .Ipam .Config ) > 0 {
1077- createOpts .IPAM = & network.IPAM {}
1078- }
1105+ if n .Ipam .Driver != "" || len (n .Ipam .Config ) > 0 {
1106+ createOpts .IPAM = & network.IPAM {}
1107+ }
10791108
1080- if n .Ipam .Driver != "" {
1081- createOpts .IPAM .Driver = n .Ipam .Driver
1109+ if n .Ipam .Driver != "" {
1110+ createOpts .IPAM .Driver = n .Ipam .Driver
1111+ }
1112+
1113+ for _ , ipamConfig := range n .Ipam .Config {
1114+ config := network.IPAMConfig {
1115+ Subnet : ipamConfig .Subnet ,
1116+ IPRange : ipamConfig .IPRange ,
1117+ Gateway : ipamConfig .Gateway ,
1118+ AuxAddress : ipamConfig .AuxiliaryAddresses ,
10821119 }
1120+ createOpts .IPAM .Config = append (createOpts .IPAM .Config , config )
1121+ }
1122+ networkEventName := fmt .Sprintf ("Network %s" , n .Name )
1123+ w := progress .ContextWriter (ctx )
1124+ w .Event (progress .CreatingEvent (networkEventName ))
1125+
1126+ _ , err = s .apiClient ().NetworkCreate (ctx , n .Name , createOpts )
1127+ if err != nil {
1128+ w .Event (progress .ErrorEvent (networkEventName ))
1129+ return errors .Wrapf (err , "failed to create network %s" , n .Name )
1130+ }
1131+ w .Event (progress .CreatedEvent (networkEventName ))
1132+ return nil
1133+ }
1134+
1135+ func (s * composeService ) resolveExternalNetwork (ctx context.Context , n * types.NetworkConfig ) error {
1136+ // NetworkInspect will match on ID prefix, so NetworkList with a name
1137+ // filter is used to look for an exact match to prevent e.g. a network
1138+ // named `db` from getting erroneously matched to a network with an ID
1139+ // like `db9086999caf`
1140+ networks , err := s .apiClient ().NetworkList (ctx , moby.NetworkListOptions {
1141+ Filters : filters .NewArgs (filters .Arg ("name" , n .Name )),
1142+ })
1143+ if err != nil {
1144+ return err
1145+ }
1146+
1147+ // NetworkList API doesn't return the exact name match, so we can retrieve more than one network with a request
1148+ networks = utils .Filter (networks , func (net moby.NetworkResource ) bool {
1149+ return net .Name == n .Name
1150+ })
10831151
1084- for _ , ipamConfig := range n .Ipam .Config {
1085- config := network.IPAMConfig {
1086- Subnet : ipamConfig .Subnet ,
1087- IPRange : ipamConfig .IPRange ,
1088- Gateway : ipamConfig .Gateway ,
1089- AuxAddress : ipamConfig .AuxiliaryAddresses ,
1152+ switch len (networks ) {
1153+ case 1 :
1154+ n .Name = networks [0 ].ID
1155+ return nil
1156+ case 0 :
1157+ if n .Driver == "overlay" {
1158+ // Swarm nodes do not register overlay networks that were
1159+ // created on a different node unless they're in use.
1160+ // Here we assume `driver` is relevant for a network we don't manage
1161+ // which is a non-sense, but this is our legacy ¯\(ツ)/¯
1162+ // networkAttach will later fail anyway if network actually doesn't exists
1163+ enabled , err := s .isSWarmEnabled (ctx )
1164+ if err != nil {
1165+ return err
1166+ }
1167+ if enabled {
1168+ return nil
10901169 }
1091- createOpts .IPAM .Config = append (createOpts .IPAM .Config , config )
1092- }
1093- networkEventName := fmt .Sprintf ("Network %s" , n .Name )
1094- w := progress .ContextWriter (ctx )
1095- w .Event (progress .CreatingEvent (networkEventName ))
1096- if _ , err := s .apiClient ().NetworkCreate (ctx , n .Name , createOpts ); err != nil {
1097- w .Event (progress .ErrorEvent (networkEventName ))
1098- return errors .Wrapf (err , "failed to create network %s" , n .Name )
10991170 }
1100- w .Event (progress .CreatedEvent (networkEventName ))
1101- return nil
1171+ return fmt .Errorf ("network %s declared as external, but could not be found" , n .Name )
1172+ default :
1173+ return fmt .Errorf ("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity" , n .Name )
11021174 }
1103- return nil
11041175}
11051176
11061177func (s * composeService ) ensureVolume (ctx context.Context , volume types.VolumeConfig , project string ) error {
0 commit comments