mirror of https://github.com/docker/compose.git
detect network conflict as name is not guaranteed to be unique (#10612)
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
fed8ef6b79
commit
1bd8a773a7
|
@ -484,7 +484,7 @@ func (s *composeService) startContainer(ctx context.Context, container moby.Cont
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) createMobyContainer(ctx context.Context,
|
func (s *composeService) createMobyContainer(ctx context.Context, //nolint:gocyclo
|
||||||
project *types.Project,
|
project *types.Project,
|
||||||
service types.ServiceConfig,
|
service types.ServiceConfig,
|
||||||
name string,
|
name string,
|
||||||
|
@ -513,6 +513,18 @@ func (s *composeService) createMobyContainer(ctx context.Context,
|
||||||
}
|
}
|
||||||
plat = &p
|
plat = &p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
links, err := s.getLinks(ctx, project.Name, service, number)
|
||||||
|
if err != nil {
|
||||||
|
return created, err
|
||||||
|
}
|
||||||
|
if networkingConfig != nil {
|
||||||
|
for k, s := range networkingConfig.EndpointsConfig {
|
||||||
|
s.Links = links
|
||||||
|
networkingConfig.EndpointsConfig[k] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response, err := s.apiClient().ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
|
response, err := s.apiClient().ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
|
@ -536,10 +548,6 @@ func (s *composeService) createMobyContainer(ctx context.Context,
|
||||||
Networks: inspectedContainer.NetworkSettings.Networks,
|
Networks: inspectedContainer.NetworkSettings.Networks,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
links, err := s.getLinks(ctx, project.Name, service, number)
|
|
||||||
if err != nil {
|
|
||||||
return created, err
|
|
||||||
}
|
|
||||||
for _, netName := range service.NetworksByPriority() {
|
for _, netName := range service.NetworksByPriority() {
|
||||||
netwrk := project.Networks[netName]
|
netwrk := project.Networks[netName]
|
||||||
cfg := service.Networks[netName]
|
cfg := service.Networks[netName]
|
||||||
|
|
|
@ -143,11 +143,12 @@ func prepareNetworks(project *types.Project) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error {
|
func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error {
|
||||||
for _, network := range networks {
|
for i, network := range networks {
|
||||||
err := s.ensureNetwork(ctx, network)
|
err := s.ensureNetwork(ctx, &network)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
networks[i] = network
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1009,7 +1010,129 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
|
||||||
return aliases
|
return aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
|
func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
|
||||||
|
if n.External.External {
|
||||||
|
return s.resolveExternalNetwork(ctx, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.resolveOrCreateNetwork(ctx, n)
|
||||||
|
if errdefs.IsConflict(err) {
|
||||||
|
// Maybe another execution of `docker compose up|run` created same network
|
||||||
|
// let's retry once
|
||||||
|
return s.resolveOrCreateNetwork(ctx, n)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.NetworkConfig) error { //nolint:gocyclo
|
||||||
|
expectedNetworkLabel := n.Labels[api.NetworkLabel]
|
||||||
|
expectedProjectLabel := n.Labels[api.ProjectLabel]
|
||||||
|
|
||||||
|
// First, try to find a unique network matching by name or ID
|
||||||
|
inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
|
||||||
|
if err == nil {
|
||||||
|
// NetworkInspect will match on ID prefix, so double check we get the expected one
|
||||||
|
// as looking for network named `db` we could erroneously matched network ID `db9086999caf`
|
||||||
|
if inspect.Name == n.Name || inspect.ID == n.Name {
|
||||||
|
if inspect.Labels[api.ProjectLabel] != expectedProjectLabel {
|
||||||
|
logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
|
||||||
|
"Set `external: true` to use an existing network", n.Name)
|
||||||
|
}
|
||||||
|
if inspect.Labels[api.NetworkLabel] != expectedNetworkLabel {
|
||||||
|
return fmt.Errorf("network %s was found but has incorrect label %s set to %q", n.Name, api.NetworkLabel, inspect.Labels[api.NetworkLabel])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
|
||||||
|
|
||||||
|
// Either not found, or name is ambiguous - use NetworkList to list by name
|
||||||
|
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
|
||||||
|
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkList Matches all or part of a network name, so we have to filter for a strict match
|
||||||
|
networks = utils.Filter(networks, func(net moby.NetworkResource) bool {
|
||||||
|
return net.Name == n.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, net := range networks {
|
||||||
|
if net.Labels[api.ProjectLabel] == expectedProjectLabel &&
|
||||||
|
net.Labels[api.NetworkLabel] == expectedNetworkLabel {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we could have set NetworkList with a projectFilter and networkFilter but not doing so allows to catch this
|
||||||
|
// scenario were a network with same name exists but doesn't have label, and use of `CheckDuplicate: true`
|
||||||
|
// prevents to create another one.
|
||||||
|
if len(networks) > 0 {
|
||||||
|
logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
|
||||||
|
"Set `external: true` to use an existing network", n.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipam *network.IPAM
|
||||||
|
if n.Ipam.Config != nil {
|
||||||
|
var config []network.IPAMConfig
|
||||||
|
for _, pool := range n.Ipam.Config {
|
||||||
|
config = append(config, network.IPAMConfig{
|
||||||
|
Subnet: pool.Subnet,
|
||||||
|
IPRange: pool.IPRange,
|
||||||
|
Gateway: pool.Gateway,
|
||||||
|
AuxAddress: pool.AuxiliaryAddresses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ipam = &network.IPAM{
|
||||||
|
Driver: n.Ipam.Driver,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createOpts := moby.NetworkCreate{
|
||||||
|
CheckDuplicate: true,
|
||||||
|
Labels: n.Labels,
|
||||||
|
Driver: n.Driver,
|
||||||
|
Options: n.DriverOpts,
|
||||||
|
Internal: n.Internal,
|
||||||
|
Attachable: n.Attachable,
|
||||||
|
IPAM: ipam,
|
||||||
|
EnableIPv6: n.EnableIPv6,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
config := network.IPAMConfig{
|
||||||
|
Subnet: ipamConfig.Subnet,
|
||||||
|
IPRange: ipamConfig.IPRange,
|
||||||
|
Gateway: ipamConfig.Gateway,
|
||||||
|
AuxAddress: ipamConfig.AuxiliaryAddresses,
|
||||||
|
}
|
||||||
|
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
|
||||||
|
}
|
||||||
|
networkEventName := fmt.Sprintf("Network %s", n.Name)
|
||||||
|
w := progress.ContextWriter(ctx)
|
||||||
|
w.Event(progress.CreatingEvent(networkEventName))
|
||||||
|
|
||||||
|
_, err = s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
|
||||||
|
if err != nil {
|
||||||
|
w.Event(progress.ErrorEvent(networkEventName))
|
||||||
|
return errors.Wrapf(err, "failed to create network %s", n.Name)
|
||||||
|
}
|
||||||
|
w.Event(progress.CreatedEvent(networkEventName))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) error {
|
||||||
// NetworkInspect will match on ID prefix, so NetworkList with a name
|
// NetworkInspect will match on ID prefix, so NetworkList with a name
|
||||||
// filter is used to look for an exact match to prevent e.g. a network
|
// filter is used to look for an exact match to prevent e.g. a network
|
||||||
// named `db` from getting erroneously matched to a network with an ID
|
// named `db` from getting erroneously matched to a network with an ID
|
||||||
|
@ -1020,87 +1143,35 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
networkNotFound := true
|
|
||||||
for _, net := range networks {
|
|
||||||
if net.Name == n.Name {
|
|
||||||
networkNotFound = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if networkNotFound {
|
|
||||||
if n.External.External {
|
|
||||||
if n.Driver == "overlay" {
|
|
||||||
// Swarm nodes do not register overlay networks that were
|
|
||||||
// created on a different node unless they're in use.
|
|
||||||
// Here we assume `driver` is relevant for a network we don't manage
|
|
||||||
// which is a non-sense, but this is our legacy ¯\(ツ)/¯
|
|
||||||
// networkAttach will later fail anyway if network actually doesn't exists
|
|
||||||
enabled, err := s.isSWarmEnabled(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
|
||||||
}
|
|
||||||
var ipam *network.IPAM
|
|
||||||
if n.Ipam.Config != nil {
|
|
||||||
var config []network.IPAMConfig
|
|
||||||
for _, pool := range n.Ipam.Config {
|
|
||||||
config = append(config, network.IPAMConfig{
|
|
||||||
Subnet: pool.Subnet,
|
|
||||||
IPRange: pool.IPRange,
|
|
||||||
Gateway: pool.Gateway,
|
|
||||||
AuxAddress: pool.AuxiliaryAddresses,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
ipam = &network.IPAM{
|
|
||||||
Driver: n.Ipam.Driver,
|
|
||||||
Config: config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createOpts := moby.NetworkCreate{
|
|
||||||
CheckDuplicate: true,
|
|
||||||
// TODO NameSpace Labels
|
|
||||||
Labels: n.Labels,
|
|
||||||
Driver: n.Driver,
|
|
||||||
Options: n.DriverOpts,
|
|
||||||
Internal: n.Internal,
|
|
||||||
Attachable: n.Attachable,
|
|
||||||
IPAM: ipam,
|
|
||||||
EnableIPv6: n.EnableIPv6,
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
|
// NetworkList API doesn't return the exact name match, so we can retrieve more than one network with a request
|
||||||
createOpts.IPAM = &network.IPAM{}
|
networks = utils.Filter(networks, func(net moby.NetworkResource) bool {
|
||||||
}
|
return net.Name == n.Name
|
||||||
|
})
|
||||||
|
|
||||||
if n.Ipam.Driver != "" {
|
switch len(networks) {
|
||||||
createOpts.IPAM.Driver = n.Ipam.Driver
|
case 1:
|
||||||
}
|
n.Name = networks[0].ID
|
||||||
|
|
||||||
for _, ipamConfig := range n.Ipam.Config {
|
|
||||||
config := network.IPAMConfig{
|
|
||||||
Subnet: ipamConfig.Subnet,
|
|
||||||
IPRange: ipamConfig.IPRange,
|
|
||||||
Gateway: ipamConfig.Gateway,
|
|
||||||
AuxAddress: ipamConfig.AuxiliaryAddresses,
|
|
||||||
}
|
|
||||||
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
|
|
||||||
}
|
|
||||||
networkEventName := fmt.Sprintf("Network %s", n.Name)
|
|
||||||
w := progress.ContextWriter(ctx)
|
|
||||||
w.Event(progress.CreatingEvent(networkEventName))
|
|
||||||
if _, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts); err != nil {
|
|
||||||
w.Event(progress.ErrorEvent(networkEventName))
|
|
||||||
return errors.Wrapf(err, "failed to create network %s", n.Name)
|
|
||||||
}
|
|
||||||
w.Event(progress.CreatedEvent(networkEventName))
|
|
||||||
return nil
|
return nil
|
||||||
|
case 0:
|
||||||
|
if n.Driver == "overlay" {
|
||||||
|
// Swarm nodes do not register overlay networks that were
|
||||||
|
// created on a different node unless they're in use.
|
||||||
|
// Here we assume `driver` is relevant for a network we don't manage
|
||||||
|
// which is a non-sense, but this is our legacy ¯\(ツ)/¯
|
||||||
|
// networkAttach will later fail anyway if network actually doesn't exists
|
||||||
|
enabled, err := s.isSWarmEnabled(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
|
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
|
||||||
|
|
|
@ -171,32 +171,30 @@ func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Pr
|
||||||
|
|
||||||
func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
|
func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
|
||||||
var ops []downOp
|
var ops []downOp
|
||||||
for _, n := range project.Networks {
|
for key, n := range project.Networks {
|
||||||
if n.External.External {
|
if n.External.External {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// loop capture variable for op closure
|
// loop capture variable for op closure
|
||||||
networkName := n.Name
|
networkKey := key
|
||||||
|
idOrName := n.Name
|
||||||
ops = append(ops, func() error {
|
ops = append(ops, func() error {
|
||||||
return s.removeNetwork(ctx, networkName, w)
|
return s.removeNetwork(ctx, networkKey, project.Name, idOrName, w)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return ops
|
return ops
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) removeNetwork(ctx context.Context, name string, w progress.Writer) error {
|
func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName string, projectName string, name string, w progress.Writer) error {
|
||||||
// networks are guaranteed to have unique IDs but NOT names, so it's
|
|
||||||
// possible to get into a situation where a compose down will fail with
|
|
||||||
// an error along the lines of:
|
|
||||||
// failed to remove network test: Error response from daemon: network test is ambiguous (2 matches found based on name)
|
|
||||||
// as a workaround here, the delete is done by ID after doing a list using
|
|
||||||
// the name as a filter (99.9% of the time this will return a single result)
|
|
||||||
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
|
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
|
||||||
Filters: filters.NewArgs(filters.Arg("name", name)),
|
Filters: filters.NewArgs(
|
||||||
|
projectFilter(projectName),
|
||||||
|
networkFilter(composeNetworkName)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, fmt.Sprintf("failed to inspect network %s", name))
|
return errors.Wrapf(err, "failed to list networks")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(networks) == 0 {
|
if len(networks) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,8 +60,8 @@ func TestDown(t *testing.T) {
|
||||||
// cleanup properly if duplicates are inadvertently created
|
// cleanup properly if duplicates are inadvertently created
|
||||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||||
Return([]moby.NetworkResource{
|
Return([]moby.NetworkResource{
|
||||||
{ID: "abc123", Name: "myProject_default"},
|
{ID: "abc123", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
|
||||||
{ID: "def456", Name: "myProject_default"},
|
{ID: "def456", Name: "myProject_default", Labels: map[string]string{compose.NetworkLabel: "default"}},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
stopOptions := containerType.StopOptions{}
|
stopOptions := containerType.StopOptions{}
|
||||||
|
@ -74,7 +74,9 @@ func TestDown(t *testing.T) {
|
||||||
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||||
|
|
||||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
|
||||||
Filters: filters.NewArgs(filters.Arg("name", "myProject_default")),
|
Filters: filters.NewArgs(
|
||||||
|
projectFilter(strings.ToLower(testProject)),
|
||||||
|
networkFilter("default")),
|
||||||
}).Return([]moby.NetworkResource{
|
}).Return([]moby.NetworkResource{
|
||||||
{ID: "abc123", Name: "myProject_default"},
|
{ID: "abc123", Name: "myProject_default"},
|
||||||
{ID: "def456", Name: "myProject_default"},
|
{ID: "def456", Name: "myProject_default"},
|
||||||
|
@ -106,7 +108,11 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||||
Return(volume.ListResponse{}, nil)
|
Return(volume.ListResponse{}, nil)
|
||||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||||
Return([]moby.NetworkResource{{Name: "myProject_default"}}, nil)
|
Return([]moby.NetworkResource{
|
||||||
|
{
|
||||||
|
Name: "myProject_default",
|
||||||
|
Labels: map[string]string{compose.NetworkLabel: "default"},
|
||||||
|
}}, nil)
|
||||||
|
|
||||||
stopOptions := containerType.StopOptions{}
|
stopOptions := containerType.StopOptions{}
|
||||||
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
|
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
|
||||||
|
@ -118,7 +124,10 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||||
api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||||
|
|
||||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
|
||||||
Filters: filters.NewArgs(filters.Arg("name", "myProject_default")),
|
Filters: filters.NewArgs(
|
||||||
|
networkFilter("default"),
|
||||||
|
projectFilter(strings.ToLower(testProject)),
|
||||||
|
),
|
||||||
}).Return([]moby.NetworkResource{{ID: "abc123", Name: "myProject_default"}}, nil)
|
}).Return([]moby.NetworkResource{{ID: "abc123", Name: "myProject_default"}}, nil)
|
||||||
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(moby.NetworkResource{ID: "abc123"}, nil)
|
api.EXPECT().NetworkInspect(gomock.Any(), "abc123", gomock.Any()).Return(moby.NetworkResource{ID: "abc123"}, nil)
|
||||||
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
|
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
|
||||||
|
|
|
@ -31,6 +31,10 @@ func serviceFilter(serviceName string) filters.KeyValuePair {
|
||||||
return filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, serviceName))
|
return filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, serviceName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func networkFilter(name string) filters.KeyValuePair {
|
||||||
|
return filters.Arg("label", fmt.Sprintf("%s=%s", api.NetworkLabel, name))
|
||||||
|
}
|
||||||
|
|
||||||
func oneOffFilter(b bool) filters.KeyValuePair {
|
func oneOffFilter(b bool) filters.KeyValuePair {
|
||||||
v := "False"
|
v := "False"
|
||||||
if b {
|
if b {
|
||||||
|
|
|
@ -39,3 +39,13 @@ func Remove[T any](origin []T, elements ...T) []T {
|
||||||
}
|
}
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Filter[T any](elements []T, predicate func(T) bool) []T {
|
||||||
|
var filtered []T
|
||||||
|
for _, v := range elements {
|
||||||
|
if predicate(v) {
|
||||||
|
filtered = append(filtered, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue