mirror of https://github.com/docker/compose.git
detect network config changes and recreate if needed
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
61f1d4f69b
commit
c21d4cfb40
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
||||||
github.com/Microsoft/go-winio v0.6.2
|
github.com/Microsoft/go-winio v0.6.2
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||||
github.com/buger/goterm v1.0.4
|
github.com/buger/goterm v1.0.4
|
||||||
github.com/compose-spec/compose-go/v2 v2.4.4
|
github.com/compose-spec/compose-go/v2 v2.4.5-0.20241111154218-9d02caaf8465
|
||||||
github.com/containerd/containerd v1.7.23
|
github.com/containerd/containerd v1.7.23
|
||||||
github.com/containerd/platforms v0.2.1
|
github.com/containerd/platforms v0.2.1
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -85,8 +85,8 @@ github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0Tx
|
||||||
github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
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/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||||
github.com/compose-spec/compose-go/v2 v2.4.4 h1:cvHBl5Jf1iNBmRrZCICmHvaoskYc1etTPEMLKVwokAY=
|
github.com/compose-spec/compose-go/v2 v2.4.5-0.20241111154218-9d02caaf8465 h1:1PRX/3a/n4W2DrMJu4CV9OS8Z2eauOBLe0zOuSlrWDY=
|
||||||
github.com/compose-spec/compose-go/v2 v2.4.4/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
github.com/compose-spec/compose-go/v2 v2.4.5-0.20241111154218-9d02caaf8465/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
||||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||||
github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=
|
github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=
|
||||||
github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE=
|
github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE=
|
||||||
|
|
|
@ -57,24 +57,25 @@ const (
|
||||||
// Cross services dependencies are managed by creating services in expected order and updating `service:xx` reference
|
// Cross services dependencies are managed by creating services in expected order and updating `service:xx` reference
|
||||||
// when a service has converged, so dependent ones can be managed with resolved containers references.
|
// when a service has converged, so dependent ones can be managed with resolved containers references.
|
||||||
type convergence struct {
|
type convergence struct {
|
||||||
service *composeService
|
service *composeService
|
||||||
observedState map[string]Containers
|
services map[string]Containers
|
||||||
stateMutex sync.Mutex
|
networks map[string]string
|
||||||
|
stateMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *convergence) getObservedState(serviceName string) Containers {
|
func (c *convergence) getObservedState(serviceName string) Containers {
|
||||||
c.stateMutex.Lock()
|
c.stateMutex.Lock()
|
||||||
defer c.stateMutex.Unlock()
|
defer c.stateMutex.Unlock()
|
||||||
return c.observedState[serviceName]
|
return c.services[serviceName]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *convergence) setObservedState(serviceName string, containers Containers) {
|
func (c *convergence) setObservedState(serviceName string, containers Containers) {
|
||||||
c.stateMutex.Lock()
|
c.stateMutex.Lock()
|
||||||
defer c.stateMutex.Unlock()
|
defer c.stateMutex.Unlock()
|
||||||
c.observedState[serviceName] = containers
|
c.services[serviceName] = containers
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConvergence(services []string, state Containers, s *composeService) *convergence {
|
func newConvergence(services []string, state Containers, networks map[string]string, s *composeService) *convergence {
|
||||||
observedState := map[string]Containers{}
|
observedState := map[string]Containers{}
|
||||||
for _, s := range services {
|
for _, s := range services {
|
||||||
observedState[s] = Containers{}
|
observedState[s] = Containers{}
|
||||||
|
@ -84,8 +85,9 @@ func newConvergence(services []string, state Containers, s *composeService) *con
|
||||||
observedState[service] = append(observedState[service], c)
|
observedState[service] = append(observedState[service], c)
|
||||||
}
|
}
|
||||||
return &convergence{
|
return &convergence{
|
||||||
service: s,
|
service: s,
|
||||||
observedState: observedState,
|
services: observedState,
|
||||||
|
networks: networks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,11 +126,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||||
|
|
||||||
sort.Slice(containers, func(i, j int) bool {
|
sort.Slice(containers, func(i, j int) bool {
|
||||||
// select obsolete containers first, so they get removed as we scale down
|
// select obsolete containers first, so they get removed as we scale down
|
||||||
if obsolete, _ := mustRecreate(service, containers[i], recreate); obsolete {
|
if obsolete, _ := c.mustRecreate(service, containers[i], recreate); obsolete {
|
||||||
// i is obsolete, so must be first in the list
|
// i is obsolete, so must be first in the list
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if obsolete, _ := mustRecreate(service, containers[j], recreate); obsolete {
|
if obsolete, _ := c.mustRecreate(service, containers[j], recreate); obsolete {
|
||||||
// j is obsolete, so must be first in the list
|
// j is obsolete, so must be first in the list
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -157,7 +159,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
mustRecreate, err := mustRecreate(service, container, recreate)
|
mustRecreate, err := c.mustRecreate(service, container, recreate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -315,7 +317,7 @@ func (c *convergence) resolveSharedNamespaces(service *types.ServiceConfig) erro
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
|
func (c *convergence) mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
|
||||||
if policy == api.RecreateNever {
|
if policy == api.RecreateNever {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -328,7 +330,33 @@ func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy st
|
||||||
}
|
}
|
||||||
configChanged := actual.Labels[api.ConfigHashLabel] != configHash
|
configChanged := actual.Labels[api.ConfigHashLabel] != configHash
|
||||||
imageUpdated := actual.Labels[api.ImageDigestLabel] != expected.CustomLabels[api.ImageDigestLabel]
|
imageUpdated := actual.Labels[api.ImageDigestLabel] != expected.CustomLabels[api.ImageDigestLabel]
|
||||||
return configChanged || imageUpdated, nil
|
if configChanged || imageUpdated {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.networks != nil {
|
||||||
|
// check the networks container is connected to are the expected ones
|
||||||
|
for net := range expected.Networks {
|
||||||
|
id := c.networks[net]
|
||||||
|
if id == "swarm" {
|
||||||
|
// corner-case : swarm overlay network isn't visible until a container is attached
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, settings := range actual.NetworkSettings.Networks {
|
||||||
|
if settings.NetworkID == id {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
// config is up-t-date but container is not connected to network - maybe recreated ?
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainerName(projectName string, service types.ServiceConfig, number int) string {
|
func getContainerName(projectName string, service types.ServiceConfig, number int) string {
|
||||||
|
|
|
@ -80,12 +80,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var observedState Containers
|
|
||||||
observedState, err = s.getContainers(ctx, project.Name, oneOffInclude, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.ensureImagesExists(ctx, project, options.Build, options.QuietPull)
|
err = s.ensureImagesExists(ctx, project, options.Build, options.QuietPull)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -93,7 +87,8 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||||
|
|
||||||
prepareNetworks(project)
|
prepareNetworks(project)
|
||||||
|
|
||||||
if err := s.ensureNetworks(ctx, project.Networks); err != nil {
|
networks, err := s.ensureNetworks(ctx, project)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +96,11 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var observedState Containers
|
||||||
|
observedState, err = s.getContainers(ctx, project.Name, oneOffInclude, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
orphans := observedState.filter(isOrphaned(project))
|
orphans := observedState.filter(isOrphaned(project))
|
||||||
if len(orphans) > 0 && !options.IgnoreOrphans {
|
if len(orphans) > 0 && !options.IgnoreOrphans {
|
||||||
if options.RemoveOrphans {
|
if options.RemoveOrphans {
|
||||||
|
@ -115,27 +115,30 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||||
"--remove-orphans flag to clean it up.", orphans.names())
|
"--remove-orphans flag to clean it up.", orphans.names())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
|
return newConvergence(options.Services, observedState, networks, s).apply(ctx, project, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareNetworks(project *types.Project) {
|
func prepareNetworks(project *types.Project) {
|
||||||
for k, nw := range project.Networks {
|
for k, nw := range project.Networks {
|
||||||
nw.Labels = nw.Labels.Add(api.NetworkLabel, k)
|
nw.CustomLabels = nw.CustomLabels.
|
||||||
nw.Labels = nw.Labels.Add(api.ProjectLabel, project.Name)
|
Add(api.NetworkLabel, k).
|
||||||
nw.Labels = nw.Labels.Add(api.VersionLabel, api.ComposeVersion)
|
Add(api.ProjectLabel, project.Name).
|
||||||
|
Add(api.VersionLabel, api.ComposeVersion)
|
||||||
project.Networks[k] = nw
|
project.Networks[k] = nw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error {
|
func (s *composeService) ensureNetworks(ctx context.Context, project *types.Project) (map[string]string, error) {
|
||||||
for i, nw := range networks {
|
networks := map[string]string{}
|
||||||
err := s.ensureNetwork(ctx, &nw)
|
for name, nw := range project.Networks {
|
||||||
|
id, err := s.ensureNetwork(ctx, project, name, &nw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
networks[i] = nw
|
networks[name] = id
|
||||||
|
project.Networks[name] = nw
|
||||||
}
|
}
|
||||||
return nil
|
return networks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
|
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
|
||||||
|
@ -1200,24 +1203,21 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
|
func (s *composeService) ensureNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) {
|
||||||
if n.External {
|
if n.External {
|
||||||
return s.resolveExternalNetwork(ctx, n)
|
return s.resolveExternalNetwork(ctx, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.resolveOrCreateNetwork(ctx, n)
|
id, err := s.resolveOrCreateNetwork(ctx, project, name, n)
|
||||||
if errdefs.IsConflict(err) {
|
if errdefs.IsConflict(err) {
|
||||||
// Maybe another execution of `docker compose up|run` created same network
|
// Maybe another execution of `docker compose up|run` created same network
|
||||||
// let's retry once
|
// let's retry once
|
||||||
return s.resolveOrCreateNetwork(ctx, n)
|
return s.resolveOrCreateNetwork(ctx, project, "", n)
|
||||||
}
|
}
|
||||||
return err
|
return id, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.NetworkConfig) error { //nolint:gocyclo
|
func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, 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
|
// First, try to find a unique network matching by name or ID
|
||||||
inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
|
inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -1228,20 +1228,33 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||||
if !ok {
|
if !ok {
|
||||||
logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
|
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)
|
"Set `external: true` to use an existing network", n.Name)
|
||||||
} else if p != expectedProjectLabel {
|
} else if p != project.Name {
|
||||||
logrus.Warnf("a network with name %s exists but was not created for project %q.\n"+
|
logrus.Warnf("a network with name %s exists but was not created for project %q.\n"+
|
||||||
"Set `external: true` to use an existing network", n.Name, expectedProjectLabel)
|
"Set `external: true` to use an existing network", n.Name, project.Name)
|
||||||
}
|
}
|
||||||
if inspect.Labels[api.NetworkLabel] != expectedNetworkLabel {
|
if inspect.Labels[api.NetworkLabel] != name {
|
||||||
return fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"network %s was found but has incorrect label %s set to %q (expected: %q)",
|
"network %s was found but has incorrect label %s set to %q (expected: %q)",
|
||||||
n.Name,
|
n.Name,
|
||||||
api.NetworkLabel,
|
api.NetworkLabel,
|
||||||
inspect.Labels[api.NetworkLabel],
|
inspect.Labels[api.NetworkLabel],
|
||||||
expectedNetworkLabel,
|
name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
hash := inspect.Labels[api.ConfigHashLabel]
|
||||||
|
expected, err := NetworkHash(n)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if hash == "" || hash == expected {
|
||||||
|
return inspect.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.removeDivergedNetwork(ctx, project, name, n)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
|
// ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
|
||||||
|
@ -1251,7 +1264,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||||
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
|
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkList Matches all or part of a network name, so we have to filter for a strict match
|
// NetworkList Matches all or part of a network name, so we have to filter for a strict match
|
||||||
|
@ -1260,9 +1273,9 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, net := range networks {
|
for _, net := range networks {
|
||||||
if net.Labels[api.ProjectLabel] == expectedProjectLabel &&
|
if net.Labels[api.ProjectLabel] == project.Name &&
|
||||||
net.Labels[api.NetworkLabel] == expectedNetworkLabel {
|
net.Labels[api.NetworkLabel] == name {
|
||||||
return nil
|
return net.ID, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1272,7 +1285,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||||
if len(networks) > 0 {
|
if len(networks) > 0 {
|
||||||
logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
|
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)
|
"Set `external: true` to use an existing network", n.Name)
|
||||||
return nil
|
return networks[0].ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipam *network.IPAM
|
var ipam *network.IPAM
|
||||||
|
@ -1291,8 +1304,13 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||||
Config: config,
|
Config: config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
hash, err := NetworkHash(n)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
n.CustomLabels = n.CustomLabels.Add(api.ConfigHashLabel, hash)
|
||||||
createOpts := network.CreateOptions{
|
createOpts := network.CreateOptions{
|
||||||
Labels: n.Labels,
|
Labels: mergeLabels(n.Labels, n.CustomLabels),
|
||||||
Driver: n.Driver,
|
Driver: n.Driver,
|
||||||
Options: n.DriverOpts,
|
Options: n.DriverOpts,
|
||||||
Internal: n.Internal,
|
Internal: n.Internal,
|
||||||
|
@ -1322,16 +1340,42 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
w.Event(progress.CreatingEvent(networkEventName))
|
w.Event(progress.CreatingEvent(networkEventName))
|
||||||
|
|
||||||
_, err = s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
|
resp, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Event(progress.ErrorEvent(networkEventName))
|
w.Event(progress.ErrorEvent(networkEventName))
|
||||||
return fmt.Errorf("failed to create network %s: %w", n.Name, err)
|
return "", fmt.Errorf("failed to create network %s: %w", n.Name, err)
|
||||||
}
|
}
|
||||||
w.Event(progress.CreatedEvent(networkEventName))
|
w.Event(progress.CreatedEvent(networkEventName))
|
||||||
return nil
|
return resp.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) error {
|
func (s *composeService) removeDivergedNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) error {
|
||||||
|
// Remove services attached to this network to force recreation
|
||||||
|
var services []string
|
||||||
|
for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
|
||||||
|
_, ok := config.Networks[name]
|
||||||
|
return ok
|
||||||
|
}) {
|
||||||
|
services = append(services, service.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop containers so we can remove network
|
||||||
|
// They will be restarted (actually: recreated) with the updated network
|
||||||
|
err := s.stop(ctx, project.Name, api.StopOptions{
|
||||||
|
Services: services,
|
||||||
|
Project: project,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.apiClient().NetworkRemove(ctx, n.Name)
|
||||||
|
eventName := fmt.Sprintf("Network %s", n.Name)
|
||||||
|
progress.ContextWriter(ctx).Event(progress.RemovedEvent(eventName))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) (string, 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
|
||||||
|
@ -1341,14 +1385,14 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(networks) == 0 {
|
if len(networks) == 0 {
|
||||||
// in this instance, n.Name is really an ID
|
// in this instance, n.Name is really an ID
|
||||||
sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
|
sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
|
||||||
if err != nil && !errdefs.IsNotFound(err) {
|
if err != nil && !errdefs.IsNotFound(err) {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
networks = append(networks, sn)
|
networks = append(networks, sn)
|
||||||
}
|
}
|
||||||
|
@ -1363,22 +1407,22 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
|
||||||
|
|
||||||
switch len(networks) {
|
switch len(networks) {
|
||||||
case 1:
|
case 1:
|
||||||
return nil
|
return networks[0].ID, nil
|
||||||
case 0:
|
case 0:
|
||||||
enabled, err := s.isSWarmEnabled(ctx)
|
enabled, err := s.isSWarmEnabled(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
if enabled {
|
if enabled {
|
||||||
// Swarm nodes do not register overlay networks that were
|
// Swarm nodes do not register overlay networks that were
|
||||||
// created on a different node unless they're in use.
|
// created on a different node unless they're in use.
|
||||||
// So we can't preemptively check network exists, but
|
// So we can't preemptively check network exists, but
|
||||||
// networkAttach will later fail anyway if network actually doesn't exist
|
// networkAttach will later fail anyway if network actually doesn't exist
|
||||||
return nil
|
return "swarm", nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
return "", fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
|
return "", fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ func TestPrepareNetworkLabels(t *testing.T) {
|
||||||
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{"skynet": {}}),
|
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{"skynet": {}}),
|
||||||
}
|
}
|
||||||
prepareNetworks(&project)
|
prepareNetworks(&project)
|
||||||
assert.DeepEqual(t, project.Networks["skynet"].Labels, composetypes.Labels(map[string]string{
|
assert.DeepEqual(t, project.Networks["skynet"].CustomLabels, composetypes.Labels(map[string]string{
|
||||||
"com.docker.compose.network": "skynet",
|
"com.docker.compose.network": "skynet",
|
||||||
"com.docker.compose.project": "myProject",
|
"com.docker.compose.project": "myProject",
|
||||||
"com.docker.compose.version": api.ComposeVersion,
|
"com.docker.compose.version": api.ComposeVersion,
|
||||||
|
|
|
@ -41,3 +41,11 @@ func ServiceHash(o types.ServiceConfig) (string, error) {
|
||||||
}
|
}
|
||||||
return digest.SHA256.FromBytes(bytes).Encoded(), nil
|
return digest.SHA256.FromBytes(bytes).Encoded(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NetworkHash(o *types.NetworkConfig) (string, error) {
|
||||||
|
bytes, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return digest.SHA256.FromBytes(bytes).Encoded(), nil
|
||||||
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
|
||||||
_, _ = fmt.Fprintln(s.stdinfo(), "No stopped containers")
|
_, _ = fmt.Fprintln(s.stdinfo(), "No stopped containers")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
|
msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
|
||||||
if options.Force {
|
if options.Force {
|
||||||
_, _ = fmt.Fprintln(s.stdout(), msg)
|
_, _ = fmt.Fprintln(s.stdout(), msg)
|
||||||
|
|
|
@ -104,7 +104,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||||
Labels: mergeLabels(service.Labels, service.CustomLabels),
|
Labels: mergeLabels(service.Labels, service.CustomLabels),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = newConvergence(project.ServiceNames(), observedState, s).resolveServiceReferences(&service)
|
err = newConvergence(project.ServiceNames(), observedState, nil, s).resolveServiceReferences(&service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
image: nginx:alpine
|
||||||
|
networks:
|
||||||
|
- test
|
||||||
|
|
||||||
|
networks:
|
||||||
|
test:
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: ${SUBNET-172.28.0.0/16}
|
||||||
|
|
|
@ -147,3 +147,28 @@ func TestNetworkModes(t *testing.T) {
|
||||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNetworkConfigChanged(t *testing.T) {
|
||||||
|
// fixture is shared with TestNetworks and is not safe to run concurrently
|
||||||
|
c := NewCLI(t)
|
||||||
|
const projectName = "network_config_change"
|
||||||
|
|
||||||
|
c.RunDockerComposeCmd(t, "-f", "./fixtures/network-test/compose.subnet.yaml", "--project-name", projectName, "up", "-d")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||||
|
})
|
||||||
|
|
||||||
|
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "exec", "test", "hostname", "-i")
|
||||||
|
res.Assert(t, icmd.Expected{Out: "172.28.0."})
|
||||||
|
res.Combined()
|
||||||
|
|
||||||
|
cmd := c.NewCmdWithEnv([]string{"SUBNET=192.168.0.0/16"},
|
||||||
|
"docker", "compose", "-f", "./fixtures/network-test/compose.subnet.yaml", "--project-name", projectName, "up", "-d")
|
||||||
|
res = icmd.RunCmd(cmd)
|
||||||
|
res.Assert(t, icmd.Success)
|
||||||
|
out := res.Combined()
|
||||||
|
fmt.Println(out)
|
||||||
|
|
||||||
|
res = c.RunDockerComposeCmd(t, "--project-name", projectName, "exec", "test", "hostname", "-i")
|
||||||
|
res.Assert(t, icmd.Expected{Out: "192.168.0."})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue