|  | @@ -35,22 +35,23 @@ import (
 | 
											
												
													
														|  |  const (
 |  |  const (
 | 
											
												
													
														|  |  	extLifecycle  = "x-lifecycle"
 |  |  	extLifecycle  = "x-lifecycle"
 | 
											
												
													
														|  |  	forceRecreate = "force_recreate"
 |  |  	forceRecreate = "force_recreate"
 | 
											
												
													
														|  | -)
 |  | 
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -func (s *composeService) ensureService(ctx context.Context, observedState Containers, project *types.Project, service types.ServiceConfig) error {
 |  | 
 | 
											
												
													
														|  | -	scale := getScale(service)
 |  | 
 | 
											
												
													
														|  | -	actual := observedState.filter(isService(service.Name))
 |  | 
 | 
											
												
													
														|  | 
 |  | +	doubledContainerNameWarning = "WARNING: The %q service is using the custom container name %q. " +
 | 
											
												
													
														|  | 
 |  | +		"Docker requires each container to have a unique name. " +
 | 
											
												
													
														|  | 
 |  | +		"Remove the custom name to scale the service.\n"
 | 
											
												
													
														|  | 
 |  | +)
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +func (s *composeService) ensureScale(ctx context.Context, actual []moby.Container, scale int, project *types.Project, service types.ServiceConfig) (*errgroup.Group, []moby.Container, error) {
 | 
											
												
													
														|  |  	eg, _ := errgroup.WithContext(ctx)
 |  |  	eg, _ := errgroup.WithContext(ctx)
 | 
											
												
													
														|  |  	if len(actual) < scale {
 |  |  	if len(actual) < scale {
 | 
											
												
													
														|  |  		next, err := nextContainerNumber(actual)
 |  |  		next, err := nextContainerNumber(actual)
 | 
											
												
													
														|  |  		if err != nil {
 |  |  		if err != nil {
 | 
											
												
													
														|  | -			return err
 |  | 
 | 
											
												
													
														|  | 
 |  | +			return nil, actual, err
 | 
											
												
													
														|  |  		}
 |  |  		}
 | 
											
												
													
														|  |  		missing := scale - len(actual)
 |  |  		missing := scale - len(actual)
 | 
											
												
													
														|  |  		for i := 0; i < missing; i++ {
 |  |  		for i := 0; i < missing; i++ {
 | 
											
												
													
														|  |  			number := next + i
 |  |  			number := next + i
 | 
											
												
													
														|  | -			name := fmt.Sprintf("%s_%s_%d", project.Name, service.Name, number)
 |  | 
 | 
											
												
													
														|  | 
 |  | +			name := getContainerName(project.Name, service, number)
 | 
											
												
													
														|  |  			eg.Go(func() error {
 |  |  			eg.Go(func() error {
 | 
											
												
													
														|  |  				return s.createContainer(ctx, project, service, name, number, false)
 |  |  				return s.createContainer(ctx, project, service, name, number, false)
 | 
											
												
													
														|  |  			})
 |  |  			})
 | 
											
										
											
												
													
														|  | @@ -70,6 +71,21 @@ func (s *composeService) ensureService(ctx context.Context, observedState Contai
 | 
											
												
													
														|  |  		}
 |  |  		}
 | 
											
												
													
														|  |  		actual = actual[:scale]
 |  |  		actual = actual[:scale]
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  | 
 |  | +	return eg, actual, nil
 | 
											
												
													
														|  | 
 |  | +}
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +func (s *composeService) ensureService(ctx context.Context, observedState Containers, project *types.Project, service types.ServiceConfig) error {
 | 
											
												
													
														|  | 
 |  | +	actual := observedState.filter(isService(service.Name))
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +	scale, err := getScale(service)
 | 
											
												
													
														|  | 
 |  | +	if err != nil {
 | 
											
												
													
														|  | 
 |  | +		return err
 | 
											
												
													
														|  | 
 |  | +	}
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +	eg, actual, err := s.ensureScale(ctx, actual, scale, project, service)
 | 
											
												
													
														|  | 
 |  | +	if err != nil {
 | 
											
												
													
														|  | 
 |  | +		return err
 | 
											
												
													
														|  | 
 |  | +	}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	expected, err := jsonHash(service)
 |  |  	expected, err := jsonHash(service)
 | 
											
												
													
														|  |  	if err != nil {
 |  |  	if err != nil {
 | 
											
										
											
												
													
														|  | @@ -78,7 +94,7 @@ func (s *composeService) ensureService(ctx context.Context, observedState Contai
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  	for _, container := range actual {
 |  |  	for _, container := range actual {
 | 
											
												
													
														|  |  		container := container
 |  |  		container := container
 | 
											
												
													
														|  | -		name := getContainerName(container)
 |  | 
 | 
											
												
													
														|  | 
 |  | +		name := getCanonicalContainerName(container)
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  		diverged := container.Labels[configHashLabel] != expected
 |  |  		diverged := container.Labels[configHashLabel] != expected
 | 
											
												
													
														|  |  		if diverged || service.Extensions[extLifecycle] == forceRecreate {
 |  |  		if diverged || service.Extensions[extLifecycle] == forceRecreate {
 | 
											
										
											
												
													
														|  | @@ -104,6 +120,14 @@ func (s *composeService) ensureService(ctx context.Context, observedState Contai
 | 
											
												
													
														|  |  	return eg.Wait()
 |  |  	return eg.Wait()
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +func getContainerName(projectName string, service types.ServiceConfig, number int) string {
 | 
											
												
													
														|  | 
 |  | +	name := fmt.Sprintf("%s_%s_%d", projectName, service.Name, number)
 | 
											
												
													
														|  | 
 |  | +	if service.ContainerName != "" {
 | 
											
												
													
														|  | 
 |  | +		name = service.ContainerName
 | 
											
												
													
														|  | 
 |  | +	}
 | 
											
												
													
														|  | 
 |  | +	return name
 | 
											
												
													
														|  | 
 |  | +}
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |  func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
 |  |  func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
 | 
											
												
													
														|  |  	eg, _ := errgroup.WithContext(ctx)
 |  |  	eg, _ := errgroup.WithContext(ctx)
 | 
											
												
													
														|  |  	for dep, config := range service.DependsOn {
 |  |  	for dep, config := range service.DependsOn {
 | 
											
										
											
												
													
														|  | @@ -143,14 +167,22 @@ func nextContainerNumber(containers []moby.Container) (int, error) {
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -func getScale(config types.ServiceConfig) int {
 |  | 
 | 
											
												
													
														|  | 
 |  | +func getScale(config types.ServiceConfig) (int, error) {
 | 
											
												
													
														|  | 
 |  | +	scale := 1
 | 
											
												
													
														|  | 
 |  | +	var err error
 | 
											
												
													
														|  |  	if config.Deploy != nil && config.Deploy.Replicas != nil {
 |  |  	if config.Deploy != nil && config.Deploy.Replicas != nil {
 | 
											
												
													
														|  | -		return int(*config.Deploy.Replicas)
 |  | 
 | 
											
												
													
														|  | 
 |  | +		scale = int(*config.Deploy.Replicas)
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  	if config.Scale != 0 {
 |  |  	if config.Scale != 0 {
 | 
											
												
													
														|  | -		return config.Scale
 |  | 
 | 
											
												
													
														|  | 
 |  | +		scale = config.Scale
 | 
											
												
													
														|  | 
 |  | +	}
 | 
											
												
													
														|  | 
 |  | +	if scale > 1 && config.ContainerName != "" {
 | 
											
												
													
														|  | 
 |  | +		scale = -1
 | 
											
												
													
														|  | 
 |  | +		err = fmt.Errorf(doubledContainerNameWarning,
 | 
											
												
													
														|  | 
 |  | +			config.Name,
 | 
											
												
													
														|  | 
 |  | +			config.ContainerName)
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  | -	return 1
 |  | 
 | 
											
												
													
														|  | 
 |  | +	return scale, err
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, autoRemove bool) error {
 |  |  func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, autoRemove bool) error {
 | 
											
										
											
												
													
														|  | @@ -166,12 +198,12 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, container moby.Container) error {
 |  |  func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, container moby.Container) error {
 | 
											
												
													
														|  |  	w := progress.ContextWriter(ctx)
 |  |  	w := progress.ContextWriter(ctx)
 | 
											
												
													
														|  | -	w.Event(progress.NewEvent(getContainerName(container), progress.Working, "Recreate"))
 |  | 
 | 
											
												
													
														|  | 
 |  | +	w.Event(progress.NewEvent(getCanonicalContainerName(container), progress.Working, "Recreate"))
 | 
											
												
													
														|  |  	err := s.apiClient.ContainerStop(ctx, container.ID, nil)
 |  |  	err := s.apiClient.ContainerStop(ctx, container.ID, nil)
 | 
											
												
													
														|  |  	if err != nil {
 |  |  	if err != nil {
 | 
											
												
													
														|  |  		return err
 |  |  		return err
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  | -	name := getContainerName(container)
 |  | 
 | 
											
												
													
														|  | 
 |  | +	name := getCanonicalContainerName(container)
 | 
											
												
													
														|  |  	tmpName := fmt.Sprintf("%s_%s", container.ID[:12], name)
 |  |  	tmpName := fmt.Sprintf("%s_%s", container.ID[:12], name)
 | 
											
												
													
														|  |  	err = s.apiClient.ContainerRename(ctx, container.ID, tmpName)
 |  |  	err = s.apiClient.ContainerRename(ctx, container.ID, tmpName)
 | 
											
												
													
														|  |  	if err != nil {
 |  |  	if err != nil {
 | 
											
										
											
												
													
														|  | @@ -189,7 +221,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 | 
											
												
													
														|  |  	if err != nil {
 |  |  	if err != nil {
 | 
											
												
													
														|  |  		return err
 |  |  		return err
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  | -	w.Event(progress.NewEvent(getContainerName(container), progress.Done, "Recreated"))
 |  | 
 | 
											
												
													
														|  | 
 |  | +	w.Event(progress.NewEvent(getCanonicalContainerName(container), progress.Done, "Recreated"))
 | 
											
												
													
														|  |  	setDependentLifecycle(project, service.Name, forceRecreate)
 |  |  	setDependentLifecycle(project, service.Name, forceRecreate)
 | 
											
												
													
														|  |  	return nil
 |  |  	return nil
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
										
											
												
													
														|  | @@ -209,12 +241,12 @@ func setDependentLifecycle(project *types.Project, service string, strategy stri
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  func (s *composeService) restartContainer(ctx context.Context, container moby.Container) error {
 |  |  func (s *composeService) restartContainer(ctx context.Context, container moby.Container) error {
 | 
											
												
													
														|  |  	w := progress.ContextWriter(ctx)
 |  |  	w := progress.ContextWriter(ctx)
 | 
											
												
													
														|  | -	w.Event(progress.NewEvent(getContainerName(container), progress.Working, "Restart"))
 |  | 
 | 
											
												
													
														|  | 
 |  | +	w.Event(progress.NewEvent(getCanonicalContainerName(container), progress.Working, "Restart"))
 | 
											
												
													
														|  |  	err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
 |  |  	err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
 | 
											
												
													
														|  |  	if err != nil {
 |  |  	if err != nil {
 | 
											
												
													
														|  |  		return err
 |  |  		return err
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  | -	w.Event(progress.NewEvent(getContainerName(container), progress.Done, "Restarted"))
 |  | 
 | 
											
												
													
														|  | 
 |  | +	w.Event(progress.NewEvent(getCanonicalContainerName(container), progress.Done, "Restarted"))
 | 
											
												
													
														|  |  	return nil
 |  |  	return nil
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
										
											
												
													
														|  | @@ -229,8 +261,8 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
 | 
											
												
													
														|  |  	}
 |  |  	}
 | 
											
												
													
														|  |  	id := created.ID
 |  |  	id := created.ID
 | 
											
												
													
														|  |  	for netName := range service.Networks {
 |  |  	for netName := range service.Networks {
 | 
											
												
													
														|  | -		network := project.Networks[netName]
 |  | 
 | 
											
												
													
														|  | -		err = s.connectContainerToNetwork(ctx, id, service.Name, network.Name)
 |  | 
 | 
											
												
													
														|  | 
 |  | +		netwrk := project.Networks[netName]
 | 
											
												
													
														|  | 
 |  | +		err = s.connectContainerToNetwork(ctx, id, netwrk.Name, service.Name, getContainerName(project.Name, service, number))
 | 
											
												
													
														|  |  		if err != nil {
 |  |  		if err != nil {
 | 
											
												
													
														|  |  			return err
 |  |  			return err
 | 
											
												
													
														|  |  		}
 |  |  		}
 | 
											
										
											
												
													
														|  | @@ -238,9 +270,9 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
 | 
											
												
													
														|  |  	return nil
 |  |  	return nil
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -func (s *composeService) connectContainerToNetwork(ctx context.Context, id string, service string, n string) error {
 |  | 
 | 
											
												
													
														|  | -	err := s.apiClient.NetworkConnect(ctx, n, id, &network.EndpointSettings{
 |  | 
 | 
											
												
													
														|  | -		Aliases: []string{service},
 |  | 
 | 
											
												
													
														|  | 
 |  | +func (s *composeService) connectContainerToNetwork(ctx context.Context, id string, netwrk string, aliases ...string) error {
 | 
											
												
													
														|  | 
 |  | +	err := s.apiClient.NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
 | 
											
												
													
														|  | 
 |  | +		Aliases: aliases,
 | 
											
												
													
														|  |  	})
 |  |  	})
 | 
											
												
													
														|  |  	if err != nil {
 |  |  	if err != nil {
 | 
											
												
													
														|  |  		return err
 |  |  		return err
 | 
											
										
											
												
													
														|  | @@ -300,10 +332,10 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
 | 
											
												
													
														|  |  		}
 |  |  		}
 | 
											
												
													
														|  |  		eg.Go(func() error {
 |  |  		eg.Go(func() error {
 | 
											
												
													
														|  |  			w := progress.ContextWriter(ctx)
 |  |  			w := progress.ContextWriter(ctx)
 | 
											
												
													
														|  | -			w.Event(progress.StartingEvent(getContainerName(container)))
 |  | 
 | 
											
												
													
														|  | 
 |  | +			w.Event(progress.StartingEvent(getCanonicalContainerName(container)))
 | 
											
												
													
														|  |  			err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
 |  |  			err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
 | 
											
												
													
														|  |  			if err == nil {
 |  |  			if err == nil {
 | 
											
												
													
														|  | -				w.Event(progress.StartedEvent(getContainerName(container)))
 |  | 
 | 
											
												
													
														|  | 
 |  | +				w.Event(progress.StartedEvent(getCanonicalContainerName(container)))
 | 
											
												
													
														|  |  			}
 |  |  			}
 | 
											
												
													
														|  |  			return err
 |  |  			return err
 | 
											
												
													
														|  |  		})
 |  |  		})
 |