Repository: docker/compose (go)

Issue:
Do not `--wait` for containers with `--scale` `0`
**Description**

`docker-compose up --wait` waits for containers scaled to `0`.

**Steps to reproduce the issue:**
1. Create a `docker-compose.yml` with the following content:
```
version: "3.8"
services:
  foo:
    image: alpine
    command: [ "/bin/sh", "-c", "trap : TERM INT; sleep infinity & wait" ]
  bar:
    image: alpine
    command: [ "/bin/sh", "-c", "trap : TERM INT; sleep infinity & wait" ]
```
2. Run `docker-compose up -d --scale=foo=0 --wait`.

**Describe the results you received:**
`docker-compose` will infinitely wait for `foo` to start.

**Describe the results you expected:**
`docker-compose` will wait only for `bar` to start and exit successfully.


**Additional information you deem important (e.g. issue happens only occasionally):**

**Output of `docker compose version`:**

```
Docker Compose version 2.2.3
```

**Output of `docker info`:**

```
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc., v0.7.1)
  compose: Docker Compose (Docker Inc., v2.2.3)
  scan: Docker Scan (Docker Inc., v0.16.0)

Server:
 Containers: 13
  Running: 12
  Paused: 0
  Stopped: 1
 Images: 47
 Server Version: 20.10.12
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 7b11cfaabd73bb80907dd23182b9347b4245eb5d
 runc version: v1.0.2-0-g52b36a2
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
  cgroupns
 Kernel Version: 5.10.76-linuxkit
 Operating System: Docker Desktop
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 3.843GiB
 Name: docker-desktop
 ID: MACJ:FSVR:FQR3:ZQDN:UMFZ:TTR2:KJR4:ITRP:ICW7:J6FI:4DHT:XCYJ
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

```

Pull request description:
Wait and scale 0
**What I did**
* During dependencies wait process, skip services with a scale property equals to 0 to avoid waiting indefinitely.
* Also fixed an early return in the wait process when reaching a dependency with a `service_started` condition, now the service is skipped and we continue with the rest of the dependencies
* Add tests for this 2 use cases

**Related issue**
fix #9149 

**(not mandatory) A picture of a cute animal, if possible in relation with what you did**
![image](https://user-images.githubusercontent.com/705411/154836506-2b6f7bd7-90e6-42ca-a97f-79c2cd368fda.png)


Patch:
diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go
index 24f051ee2..2e6304f7c 100644
--- a/pkg/compose/convergence.go
+++ b/pkg/compose/convergence.go
@@ -276,9 +276,10 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 	eg, _ := errgroup.WithContext(ctx)
 	w := progress.ContextWriter(ctx)
 	for dep, config := range dependencies {
-		if config.Condition == types.ServiceConditionStarted {
-			// already managed by InDependencyOrder
-			return nil
+		if shouldWait, err := shouldWaitForDependency(dep, config, project); err != nil {
+			return err
+		} else if !shouldWait {
+			continue
 		}
 
 		containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, dep)
@@ -334,6 +335,20 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 	return eg.Wait()
 }
 
+func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceDependency, project *types.Project) (bool, error) {
+	if dependencyConfig.Condition == types.ServiceConditionStarted {
+		// already managed by InDependencyOrder
+		return false, nil
+	}
+	if service, err := project.GetService(serviceName); err != nil {
+		return false, err
+	} else if service.Scale == 0 {
+		// don't wait for the dependency which configured to have 0 containers running
+		return false, nil
+	}
+	return true, nil
+}
+
 func nextContainerNumber(containers []moby.Container) (int, error) {
 	max := 0
 	for _, c := range containers {


Test patch:
diff --git a/pkg/compose/convergence_test.go b/pkg/compose/convergence_test.go
index 0b647c6b4..917af7af9 100644
--- a/pkg/compose/convergence_test.go
+++ b/pkg/compose/convergence_test.go
@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/compose-spec/compose-go/types"
@@ -184,3 +185,31 @@ func TestServiceLinks(t *testing.T) {
 		assert.Equal(t, links[2], "testProject-web-1:testProject-web-1")
 	})
 }
+
+func TestWaitDependencies(t *testing.T) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+	api := mocks.NewMockAPIClient(mockCtrl)
+	tested.apiClient = api
+
+	t.Run("should skip dependencies with scale 0", func(t *testing.T) {
+		dbService := types.ServiceConfig{Name: "db", Scale: 0}
+		redisService := types.ServiceConfig{Name: "redis", Scale: 0}
+		project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
+		dependencies := types.DependsOnConfig{
+			"db":    {Condition: ServiceConditionRunningOrHealthy},
+			"redis": {Condition: ServiceConditionRunningOrHealthy},
+		}
+		assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
+	})
+	t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
+		dbService := types.ServiceConfig{Name: "db", Scale: 1}
+		redisService := types.ServiceConfig{Name: "redis", Scale: 1}
+		project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
+		dependencies := types.DependsOnConfig{
+			"db":    {Condition: types.ServiceConditionStarted},
+			"redis": {Condition: types.ServiceConditionStarted},
+		}
+		assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
+	})
+}