Repository: compose-spec/compose-go
Pull request #73 (2020-09-01 00:45:30)

Update DependsOn type to use v2 signature
Docker Compose v2 accepts both a list of service names, and a map of
services to their required condition. This commit modifies the DependsOn
type to use the latter format, and automatically transforms the v3
signature of []string.

I've had this in my fork for awhile, would love to get it merged. Don't know if an API change like this is acceptable. Thoughts?


Patch:
diff --git a/loader/loader.go b/loader/loader.go
index e1cab0b..c7ef137 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -349,6 +349,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
 		reflect.TypeOf(types.ServiceVolumeConfig{}):              transformServiceVolumeConfig,
 		reflect.TypeOf(types.BuildConfig{}):                      transformBuildConfig,
 		reflect.TypeOf(types.Duration(0)):                        transformStringToDuration,
+		reflect.TypeOf(types.DependsOnConfig{}):                  transformDependsOnConfig,
 	}
 
 	for _, transformer := range additionalTransformers {
@@ -778,6 +779,25 @@ var transformBuildConfig TransformerFunc = func(data interface{}) (interface{},
 	}
 }
 
+var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface{}, error) {
+	switch value := data.(type) {
+	case []interface{}:
+		transformed := map[string]interface{}{}
+		for _, serviceIntf := range value {
+			service, ok := serviceIntf.(string)
+			if !ok {
+				return data, errors.Errorf("invalid type %T for service depends_on element. Expected string.", value)
+			}
+			transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted}
+		}
+		return transformed, nil
+	case map[string]interface{}:
+		return data, nil
+	default:
+		return data, errors.Errorf("invalid type %T for service depends_on", value)
+	}
+}
+
 var transformServiceVolumeConfig TransformerFunc = func(data interface{}) (interface{}, error) {
 	switch value := data.(type) {
 	case string:
diff --git a/types/types.go b/types/types.go
index 8b056c8..a645b1f 100644
--- a/types/types.go
+++ b/types/types.go
@@ -95,7 +95,7 @@ type ServiceConfig struct {
 	Configs         []ServiceConfigObjConfig         `yaml:",omitempty" json:"configs,omitempty"`
 	ContainerName   string                           `mapstructure:"container_name" yaml:"container_name,omitempty" json:"container_name,omitempty"`
 	CredentialSpec  *CredentialSpecConfig            `mapstructure:"credential_spec" yaml:"credential_spec,omitempty" json:"credential_spec,omitempty"`
-	DependsOn       []string                         `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
+	DependsOn       DependsOnConfig                  `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
 	Deploy          *DeployConfig                    `yaml:",omitempty" json:"deploy,omitempty"`
 	Devices         []string                         `yaml:",omitempty" json:"devices,omitempty"`
 	DNS             StringList                       `yaml:",omitempty" json:"dns,omitempty"`
@@ -165,7 +165,9 @@ type ServiceConfig struct {
 // GetDependencies retrieve all services this service depends on
 func (s ServiceConfig) GetDependencies() []string {
 	dependencies := make(set)
-	dependencies.append(s.DependsOn...)
+	for dependency := range s.DependsOn {
+		dependencies.append(dependency)
+	}
 	dependencies.append(s.Links...)
 	if strings.HasPrefix(s.NetworkMode, "service:") {
 		dependencies.append(s.NetworkMode[8:])
@@ -657,6 +659,23 @@ type FileObjectConfig struct {
 	Extensions     map[string]interface{} `yaml:",inline" json:"-"`
 }
 
+const (
+	// TypeServiceConditionHealthy is the type for waiting until a service is
+	// healthy.
+	ServiceConditionHealthy = "service_healthy"
+
+	// TypeServiceConditionHealthy is the type for waiting until a service has
+	// started.
+	ServiceConditionStarted = "service_started"
+)
+
+type DependsOnConfig map[string]ServiceDependency
+
+type ServiceDependency struct {
+	Condition  string                 `yaml:",omitempty" json:"condition,omitempty"`
+	Extensions map[string]interface{} `yaml:",inline" json:"-"`
+}
+
 // SecretConfig for a secret
 type SecretConfig FileObjectConfig
 


Test patch:
diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go
index 2dcdae3..088b933 100644
--- a/loader/full-struct_test.go
+++ b/loader/full-struct_test.go
@@ -74,7 +74,10 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
 				},
 			},
 			ContainerName: "my-web-container",
-			DependsOn:     []string{"db", "redis"},
+			DependsOn: types.DependsOnConfig{
+				"db":    {Condition: types.ServiceConditionStarted},
+				"redis": {Condition: types.ServiceConditionStarted},
+			},
 			Deploy: &types.DeployConfig{
 				Mode:     "replicated",
 				Replicas: uint64Ptr(6),
@@ -587,8 +590,10 @@ func fullExampleYAML(workingDir, homeDir string) string {
       mode: 288
     container_name: my-web-container
     depends_on:
-    - db
-    - redis
+      db:
+        condition: service_started
+      redis:
+        condition: service_started
     deploy:
       mode: replicated
       replicas: 6
@@ -1083,10 +1088,14 @@ func fullExampleJSON(workingDir, homeDir string) string {
         }
       ],
       "container_name": "my-web-container",
-      "depends_on": [
-        "db",
-        "redis"
-      ],
+      "depends_on": {
+        "db": {
+          "condition": "service_started"
+        },
+        "redis": {
+          "condition": "service_started"
+        }
+      },
       "deploy": {
         "mode": "replicated",
         "replicas": 6,
diff --git a/types/config_test.go b/types/config_test.go
index 80b1efb..32ebc74 100644
--- a/types/config_test.go
+++ b/types/config_test.go
@@ -26,13 +26,21 @@ func Test_WithServices(t *testing.T) {
 	p := Project{
 		Services: append(Services{},
 			ServiceConfig{
-				Name:      "service_1",
-				DependsOn: []string{"service_3"},
+				Name: "service_1",
+				DependsOn: map[string]ServiceDependency{
+					"service_3": {
+						Condition: ServiceConditionStarted,
+					},
+				},
 			}, ServiceConfig{
 				Name: "service_2",
 			}, ServiceConfig{
-				Name:  "service_3",
-				Links: []string{"service_2"},
+				Name: "service_3",
+				DependsOn: map[string]ServiceDependency{
+					"service_2": {
+						Condition: ServiceConditionStarted,
+					},
+				},
 			}),
 	}
 	order := []string{}