Repository: compose-spec/compose-go
Pull request #609 (2024-03-22 07:05:34)

checkConsistency must ignore disabled service if required=false
after https://github.com/compose-spec/compose-go/pull/606 `checkConsistency` will fail if a service referred by `depends_on`  is disabled by profile (see https://github.com/docker/compose/blob/main/pkg/e2e/fixtures/dependencies/deps-not-required.yaml) 


Patch:
diff --git a/errdefs/errors.go b/errdefs/errors.go
index a544070..1990ddd 100644
--- a/errdefs/errors.go
+++ b/errdefs/errors.go
@@ -30,6 +30,9 @@ var (
 
 	// ErrIncompatible is returned when a compose project uses an incompatible attribute
 	ErrIncompatible = errors.New("incompatible attribute")
+
+	// ErrDisabled is returned when a resource was found in model but is disabled
+	ErrDisabled = errors.New("disabled")
 )
 
 // IsNotFoundError returns true if the unwrapped error is ErrNotFound
diff --git a/loader/validate.go b/loader/validate.go
index 6f7750e..973e521 100644
--- a/loader/validate.go
+++ b/loader/validate.go
@@ -70,8 +70,11 @@ func checkConsistency(project *types.Project) error {
 			}
 		}
 
-		for dependedService := range s.DependsOn {
+		for dependedService, cfg := range s.DependsOn {
 			if _, err := project.GetService(dependedService); err != nil {
+				if errors.Is(err, errdefs.ErrDisabled) && !cfg.Required {
+					continue
+				}
 				return fmt.Errorf("service %q depends on undefined service %q: %w", s.Name, dependedService, errdefs.ErrInvalid)
 			}
 		}
diff --git a/types/project.go b/types/project.go
index b9d18eb..780dd91 100644
--- a/types/project.go
+++ b/types/project.go
@@ -26,6 +26,7 @@ import (
 	"sort"
 
 	"github.com/compose-spec/compose-go/v2/dotenv"
+	"github.com/compose-spec/compose-go/v2/errdefs"
 	"github.com/compose-spec/compose-go/v2/utils"
 	"github.com/distribution/reference"
 	"github.com/mitchellh/copystructure"
@@ -216,9 +217,9 @@ func (p *Project) GetService(name string) (ServiceConfig, error) {
 	if !ok {
 		_, ok := p.DisabledServices[name]
 		if ok {
-			return ServiceConfig{}, fmt.Errorf("service %s is disabled", name)
+			return ServiceConfig{}, fmt.Errorf("no such service: %s: %w", name, errdefs.ErrDisabled)
 		}
-		return ServiceConfig{}, fmt.Errorf("no such service: %s", name)
+		return ServiceConfig{}, fmt.Errorf("no such service: %s: %w", name, errdefs.ErrNotFound)
 	}
 	return service, nil
 }


Test patch:
diff --git a/loader/validate_test.go b/loader/validate_test.go
index 93ee77f..d4a2de6 100644
--- a/loader/validate_test.go
+++ b/loader/validate_test.go
@@ -366,4 +366,45 @@ func TestValidateWatch(t *testing.T) {
 		err := checkConsistency(&project)
 		assert.NilError(t, err)
 	})
+
+	t.Run("depends on disabled service", func(t *testing.T) {
+		project := types.Project{
+			Services: types.Services{
+				"myservice": {
+					Name:  "myservice",
+					Image: "scratch",
+					DependsOn: map[string]types.ServiceDependency{
+						"other": {
+							Required: false,
+						},
+					},
+				},
+			},
+			DisabledServices: types.Services{
+				"other": {
+					Image: "scratch",
+				},
+			},
+		}
+		err := checkConsistency(&project)
+		assert.NilError(t, err)
+	})
+
+	t.Run("depends on unknown service", func(t *testing.T) {
+		project := types.Project{
+			Services: types.Services{
+				"myservice": {
+					Name:  "myservice",
+					Image: "scratch",
+					DependsOn: map[string]types.ServiceDependency{
+						"other": {
+							Required: false,
+						},
+					},
+				},
+			},
+		}
+		err := checkConsistency(&project)
+		assert.ErrorContains(t, err, "depends on undefined service")
+	})
 }