diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go index aa7a41c7..513e2450 100644 --- a/loader/full-struct_test.go +++ b/loader/full-struct_test.go @@ -95,8 +95,8 @@ func services(workingDir, homeDir string) []types.ServiceConfig { }, ContainerName: "my-web-container", DependsOn: types.DependsOnConfig{ - "db": {Condition: types.ServiceConditionStarted}, - "redis": {Condition: types.ServiceConditionStarted}, + "db": {Condition: types.ServiceConditionStarted, Required: true}, + "redis": {Condition: types.ServiceConditionStarted, Required: true}, }, Deploy: &types.DeployConfig{ Mode: "replicated", @@ -657,8 +657,10 @@ services: depends_on: db: condition: service_started + required: true redis: condition: service_started + required: true deploy: mode: replicated replicas: 6 @@ -1228,10 +1230,12 @@ func fullExampleJSON(workingDir, homeDir string) string { "container_name": "my-web-container", "depends_on": { "db": { - "condition": "service_started" + "condition": "service_started", + "required": true }, "redis": { - "condition": "service_started" + "condition": "service_started", + "required": true } }, "deploy": { diff --git a/loader/loader.go b/loader/loader.go index 907d6331..76dbe2ff 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -1088,13 +1088,24 @@ var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface for _, serviceIntf := range value { service, ok := serviceIntf.(string) if !ok { - return data, errors.Errorf("invalid type %T for service depends_on elementn, expected string", value) + return data, errors.Errorf("invalid type %T for service depends_on element, expected string", value) } - transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted} + transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted, "required": true} } return transformed, nil case map[string]interface{}: - return groupXFieldsIntoExtensions(data.(map[string]interface{})), nil + transformed := map[string]interface{}{} + for service, val := range value { + dependsConfigIntf, ok := val.(map[string]interface{}) + if !ok { + return data, errors.Errorf("invalid type %T for service depends_on element", value) + } + if _, ok := dependsConfigIntf["required"]; !ok { + dependsConfigIntf["required"] = true + } + transformed[service] = dependsConfigIntf + } + return groupXFieldsIntoExtensions(transformed), nil default: return data, errors.Errorf("invalid type %T for service depends_on", value) } diff --git a/loader/loader_test.go b/loader/loader_test.go index 8d15202a..b8b889ab 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -2428,7 +2428,7 @@ services: Image: "busybox", Environment: types.MappingWithEquals{}, Scale: 1, - DependsOn: types.DependsOnConfig{"imported": {Condition: "service_started"}}, + DependsOn: types.DependsOnConfig{"imported": {Condition: "service_started", Required: true}}, }, { Name: "imported", @@ -2465,3 +2465,35 @@ func TestLoadWithIncludeCycle(t *testing.T) { }) assert.Check(t, strings.HasPrefix(err.Error(), "include cycle detected")) } + +func TestLoadWithDependsOn(t *testing.T) { + p, err := loadYAML(` +name: test-depends-on +services: + foo: + image: nginx + depends_on: + bar: + condition: service_started + baz: + condition: service_healthy + required: false + qux: + condition: service_completed_successfully + required: true +`) + assert.NilError(t, err) + assert.DeepEqual(t, p.Services, types.Services{ + { + Name: "foo", + Image: "nginx", + Environment: types.MappingWithEquals{}, + Scale: 1, + DependsOn: types.DependsOnConfig{ + "bar": {Condition: types.ServiceConditionStarted, Required: true}, + "baz": {Condition: types.ServiceConditionHealthy, Required: false}, + "qux": {Condition: types.ServiceConditionCompletedSuccessfully, Required: true}, + }, + }, + }) +} diff --git a/loader/normalize.go b/loader/normalize.go index 1fe9fbad..df7812a6 100644 --- a/loader/normalize.go +++ b/loader/normalize.go @@ -83,6 +83,7 @@ func Normalize(project *types.Project) error { s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{ Condition: types.ServiceConditionStarted, Restart: true, + Required: true, }) } @@ -92,6 +93,7 @@ func Normalize(project *types.Project) error { s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{ Condition: types.ServiceConditionStarted, Restart: true, + Required: true, }) } } @@ -102,6 +104,7 @@ func Normalize(project *types.Project) error { s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{ Condition: types.ServiceConditionStarted, Restart: false, + Required: true, }) } } @@ -194,6 +197,7 @@ func inferImplicitDependencies(service *types.ServiceConfig) { if _, ok := service.DependsOn[d]; !ok { service.DependsOn[d] = types.ServiceDependency{ Condition: types.ServiceConditionStarted, + Required: true, } } } diff --git a/loader/normalize_test.go b/loader/normalize_test.go index e68f13c6..db8eca38 100644 --- a/loader/normalize_test.go +++ b/loader/normalize_test.go @@ -136,6 +136,7 @@ func TestNormalizeDependsOn(t *testing.T) { "bar": { // explicit depends_on never should be overridden Condition: types.ServiceConditionHealthy, Restart: false, + Required: true, }, }, NetworkMode: "service:zot", @@ -159,6 +160,7 @@ services: depends_on: zot: condition: service_started + required: true networks: default: null volumes_from: @@ -168,9 +170,11 @@ services: depends_on: bar: condition: service_healthy + required: true zot: condition: service_started restart: true + required: true network_mode: service:zot zot: networks: @@ -200,19 +204,19 @@ func TestNormalizeImplicitDependencies(t *testing.T) { Links: []string{"corge"}, DependsOn: map[string]types.ServiceDependency{ // explicit dependency MUST not be overridden - "foo": {Condition: types.ServiceConditionHealthy, Restart: false}, + "foo": {Condition: types.ServiceConditionHealthy, Restart: false, Required: true}, }, }, }, } expected := types.DependsOnConfig{ - "foo": {Condition: types.ServiceConditionHealthy, Restart: false}, - "bar": {Condition: types.ServiceConditionStarted, Restart: true}, - "baz": {Condition: types.ServiceConditionStarted, Restart: true}, - "qux": {Condition: types.ServiceConditionStarted, Restart: true}, - "quux": {Condition: types.ServiceConditionStarted}, - "corge": {Condition: types.ServiceConditionStarted, Restart: true}, + "foo": {Condition: types.ServiceConditionHealthy, Restart: false, Required: true}, + "bar": {Condition: types.ServiceConditionStarted, Restart: true, Required: true}, + "baz": {Condition: types.ServiceConditionStarted, Restart: true, Required: true}, + "qux": {Condition: types.ServiceConditionStarted, Restart: true, Required: true}, + "quux": {Condition: types.ServiceConditionStarted, Required: true}, + "corge": {Condition: types.ServiceConditionStarted, Restart: true, Required: true}, } err := Normalize(&project) assert.NilError(t, err) diff --git a/types/types.go b/types/types.go index 18d8fe78..f904ce85 100644 --- a/types/types.go +++ b/types/types.go @@ -1025,6 +1025,7 @@ type ServiceDependency struct { Condition string `yaml:"condition,omitempty" json:"condition,omitempty"` Restart bool `yaml:"restart,omitempty" json:"restart,omitempty"` Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Required bool `yaml:"required" json:"required"` } type ExtendsConfig struct {