Skip to content

Commit 4c1837e

Browse files
authored
Merge pull request #432 from glours/filter-non-required-dependencies
filter depends_on services when required is not set
2 parents e4d696a + 27cae8c commit 4c1837e

File tree

6 files changed

+106
-19
lines changed

6 files changed

+106
-19
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
github.com/sirupsen/logrus v1.9.0
1616
github.com/stretchr/testify v1.8.4
1717
github.com/xeipuuv/gojsonschema v1.2.0
18+
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
1819
golang.org/x/sync v0.3.0
1920
gopkg.in/yaml.v3 v3.0.1
2021
gotest.tools/v3 v3.4.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
4141
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4242
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
4343
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
44+
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
45+
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
4446
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
4547
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4648
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

types/config_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func Test_WithServices(t *testing.T) {
3030
DependsOn: map[string]ServiceDependency{
3131
"service_3": {
3232
Condition: ServiceConditionStarted,
33+
Required: true,
3334
},
3435
},
3536
}, ServiceConfig{
@@ -39,6 +40,7 @@ func Test_WithServices(t *testing.T) {
3940
DependsOn: map[string]ServiceDependency{
4041
"service_2": {
4142
Condition: ServiceConditionStarted,
43+
Required: true,
4244
},
4345
},
4446
}),

types/project.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"path/filepath"
2525
"sort"
2626

27+
"github.com/compose-spec/compose-go/utils"
28+
2729
"github.com/compose-spec/compose-go/dotenv"
2830
"github.com/distribution/distribution/v3/reference"
2931
godigest "github.com/opencontainers/go-digest"
@@ -102,10 +104,19 @@ func (p *Project) ConfigNames() []string {
102104

103105
// GetServices retrieve services by names, or return all services if no name specified
104106
func (p *Project) GetServices(names ...string) (Services, error) {
107+
services, servicesNotFound := p.getServicesByNames(names...)
108+
if len(servicesNotFound) > 0 {
109+
return services, fmt.Errorf("no such service: %s", servicesNotFound[0])
110+
}
111+
return services, nil
112+
}
113+
114+
func (p *Project) getServicesByNames(names ...string) (Services, []string) {
105115
if len(names) == 0 {
106116
return p.Services, nil
107117
}
108118
services := Services{}
119+
var servicesNotFound []string
109120
for _, name := range names {
110121
var serviceConfig *ServiceConfig
111122
for _, s := range p.Services {
@@ -115,11 +126,12 @@ func (p *Project) GetServices(names ...string) (Services, error) {
115126
}
116127
}
117128
if serviceConfig == nil {
118-
return services, fmt.Errorf("no such service: %s", name)
129+
servicesNotFound = append(servicesNotFound, name)
130+
continue
119131
}
120132
services = append(services, *serviceConfig)
121133
}
122-
return services, nil
134+
return services, servicesNotFound
123135
}
124136

125137
// GetDisabledService retrieve disabled service by name
@@ -159,34 +171,38 @@ func (p *Project) WithServices(names []string, fn ServiceFunc, options ...Depend
159171
// backward compatibility
160172
options = []DependencyOption{IncludeDependencies}
161173
}
162-
return p.withServices(names, fn, map[string]bool{}, options)
174+
return p.withServices(names, fn, map[string]bool{}, options, map[string]ServiceDependency{})
163175
}
164176

165-
func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption) error {
166-
services, err := p.GetServices(names...)
167-
if err != nil {
168-
return err
177+
func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption, dependencies map[string]ServiceDependency) error {
178+
services, servicesNotFound := p.getServicesByNames(names...)
179+
if len(servicesNotFound) > 0 {
180+
for _, serviceNotFound := range servicesNotFound {
181+
if dependency, ok := dependencies[serviceNotFound]; !ok || dependency.Required {
182+
return fmt.Errorf("no such service: %s", serviceNotFound)
183+
}
184+
}
169185
}
170186
for _, service := range services {
171187
if seen[service.Name] {
172188
continue
173189
}
174190
seen[service.Name] = true
175-
var dependencies []string
191+
var dependencies map[string]ServiceDependency
176192
for _, policy := range options {
177193
switch policy {
178194
case IncludeDependents:
179-
dependencies = append(dependencies, p.GetDependentsForService(service)...)
195+
dependencies = utils.MapsAppend(dependencies, p.dependentsForService(service))
180196
case IncludeDependencies:
181-
dependencies = append(dependencies, service.GetDependencies()...)
197+
dependencies = utils.MapsAppend(dependencies, service.DependsOn)
182198
case IgnoreDependencies:
183199
// Noop
184200
default:
185201
return fmt.Errorf("unsupported dependency policy %d", policy)
186202
}
187203
}
188204
if len(dependencies) > 0 {
189-
err := p.withServices(dependencies, fn, seen, options)
205+
err := p.withServices(utils.MapKeys(dependencies), fn, seen, options, dependencies)
190206
if err != nil {
191207
return err
192208
}
@@ -199,11 +215,15 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b
199215
}
200216

201217
func (p *Project) GetDependentsForService(s ServiceConfig) []string {
202-
var dependent []string
218+
return utils.MapKeys(p.dependentsForService(s))
219+
}
220+
221+
func (p *Project) dependentsForService(s ServiceConfig) map[string]ServiceDependency {
222+
dependent := make(map[string]ServiceDependency)
203223
for _, service := range p.Services {
204-
for name := range service.DependsOn {
224+
for name, dependency := range service.DependsOn {
205225
if name == s.Name {
206-
dependent = append(dependent, service.Name)
226+
dependent[service.Name] = dependency
207227
}
208228
}
209229
}

types/project_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
_ "crypto/sha256"
2121
"testing"
2222

23+
"github.com/compose-spec/compose-go/utils"
2324
"github.com/distribution/distribution/v3/reference"
2425
"github.com/opencontainers/go-digest"
2526
"gotest.tools/v3/assert"
@@ -114,14 +115,15 @@ func makeProject() Project {
114115
}, ServiceConfig{
115116
Name: "service_2",
116117
Profiles: []string{"foo"},
117-
DependsOn: map[string]ServiceDependency{"service_1": {}},
118+
DependsOn: map[string]ServiceDependency{"service_1": {Required: true}},
118119
}, ServiceConfig{
119120
Name: "service_3",
120121
Profiles: []string{"bar"},
121-
DependsOn: map[string]ServiceDependency{"service_2": {}},
122+
DependsOn: map[string]ServiceDependency{"service_2": {Required: true}},
122123
}, ServiceConfig{
123-
Name: "service_4",
124-
Profiles: []string{"zot"},
124+
Name: "service_4",
125+
Profiles: []string{"zot"},
126+
DependsOn: map[string]ServiceDependency{"service_2": {Required: false}},
125127
}, ServiceConfig{
126128
Name: "service_5",
127129
Profiles: []string{"zot"},
@@ -189,7 +191,8 @@ func TestWithServices(t *testing.T) {
189191
return nil
190192
}, IncludeDependents)
191193
assert.NilError(t, err)
192-
assert.DeepEqual(t, seen, []string{"service_3", "service_2", "service_1"})
194+
// Order of service_3 and service_4 may change because there both depending on service_2
195+
assert.Check(t, utils.ArrayContains(seen, []string{"service_3", "service_4", "service_2", "service_1"}))
193196

194197
seen = []string{}
195198
err = p.WithServices([]string{"service_1"}, func(service ServiceConfig) error {
@@ -198,4 +201,12 @@ func TestWithServices(t *testing.T) {
198201
}, IgnoreDependencies)
199202
assert.NilError(t, err)
200203
assert.DeepEqual(t, seen, []string{"service_1"})
204+
205+
seen = []string{}
206+
err = p.WithServices([]string{"service_4"}, func(service ServiceConfig) error {
207+
seen = append(seen, service.Name)
208+
return nil
209+
}, IncludeDependencies)
210+
assert.NilError(t, err)
211+
assert.DeepEqual(t, seen, []string{"service_1", "service_2", "service_4"})
201212
}

utils/collectionutils.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2020 The Compose Specification Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package utils
18+
19+
import "golang.org/x/exp/slices"
20+
21+
func MapKeys[T comparable, U any](theMap map[T]U) []T {
22+
var result []T
23+
for key := range theMap {
24+
result = append(result, key)
25+
}
26+
return result
27+
}
28+
29+
func MapsAppend[T comparable, U any](target map[T]U, source map[T]U) map[T]U {
30+
if target == nil {
31+
return source
32+
}
33+
if source == nil {
34+
return target
35+
}
36+
for key, value := range source {
37+
if _, ok := target[key]; !ok {
38+
target[key] = value
39+
}
40+
}
41+
return target
42+
}
43+
44+
func ArrayContains[T comparable](source []T, toCheck []T) bool {
45+
for _, value := range toCheck {
46+
if !slices.Contains(source, value) {
47+
return false
48+
}
49+
}
50+
return true
51+
}

0 commit comments

Comments
 (0)