mirror of https://github.com/docker/compose.git
Merge pull request #9797 from laurazard/start-only-services
Only attempt to start specified services on `compose start [services]`
This commit is contained in:
commit
88df5ede42
|
@ -51,5 +51,6 @@ func runStart(ctx context.Context, backend api.Service, opts startOptions, servi
|
|||
return backend.Start(ctx, name, api.StartOptions{
|
||||
AttachTo: services,
|
||||
Project: project,
|
||||
Services: services,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -129,6 +129,8 @@ type StartOptions struct {
|
|||
ExitCodeFrom string
|
||||
// Wait won't return until containers reached the running|healthy state
|
||||
Wait bool
|
||||
// Services passed in the command line to be started
|
||||
Services []string
|
||||
}
|
||||
|
||||
// RestartOptions group options of the Restart API
|
||||
|
|
|
@ -63,21 +63,24 @@ var (
|
|||
)
|
||||
|
||||
// InDependencyOrder applies the function to the services of the project taking in account the dependency order
|
||||
func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error) error {
|
||||
return visit(ctx, project, upDirectionTraversalConfig, fn, ServiceStopped)
|
||||
func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error, options ...func(*graphTraversalConfig)) error {
|
||||
graph, err := NewGraph(project.Services, ServiceStopped)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return visit(ctx, graph, upDirectionTraversalConfig, fn)
|
||||
}
|
||||
|
||||
// InReverseDependencyOrder applies the function to the services of the project in reverse order of dependencies
|
||||
func InReverseDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error) error {
|
||||
return visit(ctx, project, downDirectionTraversalConfig, fn, ServiceStarted)
|
||||
}
|
||||
|
||||
func visit(ctx context.Context, project *types.Project, traversalConfig graphTraversalConfig, fn func(context.Context, string) error, initialStatus ServiceStatus) error {
|
||||
g := NewGraph(project.Services, initialStatus)
|
||||
if b, err := g.HasCycles(); b {
|
||||
graph, err := NewGraph(project.Services, ServiceStarted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return visit(ctx, graph, downDirectionTraversalConfig, fn)
|
||||
}
|
||||
|
||||
func visit(ctx context.Context, g *Graph, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
|
||||
nodes := traversalConfig.extremityNodesFn(g)
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
|
@ -155,7 +158,7 @@ func (v *Vertex) GetChildren() []*Vertex {
|
|||
}
|
||||
|
||||
// NewGraph returns the dependency graph of the services
|
||||
func NewGraph(services types.Services, initialStatus ServiceStatus) *Graph {
|
||||
func NewGraph(services types.Services, initialStatus ServiceStatus) (*Graph, error) {
|
||||
graph := &Graph{
|
||||
lock: sync.RWMutex{},
|
||||
Vertices: map[string]*Vertex{},
|
||||
|
@ -171,7 +174,11 @@ func NewGraph(services types.Services, initialStatus ServiceStatus) *Graph {
|
|||
}
|
||||
}
|
||||
|
||||
return graph
|
||||
if b, err := graph.HasCycles(); b {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return graph, nil
|
||||
}
|
||||
|
||||
// NewVertex is the constructor function for the Vertex
|
||||
|
|
|
@ -18,10 +18,12 @@ package compose
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
var project = types.Project{
|
||||
|
@ -69,3 +71,181 @@ func TestInDependencyReverseDownCommandOrder(t *testing.T) {
|
|||
require.NoError(t, err, "Error during iteration")
|
||||
require.Equal(t, []string{"test1", "test2", "test3"}, order)
|
||||
}
|
||||
|
||||
func TestBuildGraph(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
services types.Services
|
||||
expectedVertices map[string]*Vertex
|
||||
}{
|
||||
{
|
||||
desc: "builds graph with single service",
|
||||
services: types.Services{
|
||||
{
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
"test": {
|
||||
Key: "test",
|
||||
Service: "test",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "builds graph with two separate services",
|
||||
services: types.Services{
|
||||
{
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
{
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
"test": {
|
||||
Key: "test",
|
||||
Service: "test",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
"another": {
|
||||
Key: "another",
|
||||
Service: "another",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "builds graph with a service and a dependency",
|
||||
services: types.Services{
|
||||
{
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"another": types.ServiceDependency{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
"test": {
|
||||
Key: "test",
|
||||
Service: "test",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{
|
||||
"another": {},
|
||||
},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
"another": {
|
||||
Key: "another",
|
||||
Service: "another",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{
|
||||
"test": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "builds graph with multiple dependency levels",
|
||||
services: types.Services{
|
||||
{
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"another": types.ServiceDependency{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"another_dep": types.ServiceDependency{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "another_dep",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
"test": {
|
||||
Key: "test",
|
||||
Service: "test",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{
|
||||
"another": {},
|
||||
},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
"another": {
|
||||
Key: "another",
|
||||
Service: "another",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{
|
||||
"another_dep": {},
|
||||
},
|
||||
Parents: map[string]*Vertex{
|
||||
"test": {},
|
||||
},
|
||||
},
|
||||
"another_dep": {
|
||||
Key: "another_dep",
|
||||
Service: "another_dep",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{
|
||||
"another": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tC := range testCases {
|
||||
t.Run(tC.desc, func(t *testing.T) {
|
||||
project := types.Project{
|
||||
Services: tC.services,
|
||||
}
|
||||
|
||||
graph, err := NewGraph(project.Services, ServiceStopped)
|
||||
assert.NilError(t, err, fmt.Sprintf("failed to build graph for: %s", tC.desc))
|
||||
|
||||
for k, vertex := range graph.Vertices {
|
||||
expected, ok := tC.expectedVertices[k]
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, true, isVertexEqual(*expected, *vertex))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func isVertexEqual(a, b Vertex) bool {
|
||||
childrenEquality := true
|
||||
for c := range a.Children {
|
||||
if _, ok := b.Children[c]; !ok {
|
||||
childrenEquality = false
|
||||
}
|
||||
}
|
||||
parentEquality := true
|
||||
for p := range a.Parents {
|
||||
if _, ok := b.Parents[p]; !ok {
|
||||
parentEquality = false
|
||||
}
|
||||
}
|
||||
return a.Key == b.Key &&
|
||||
a.Service == b.Service &&
|
||||
childrenEquality &&
|
||||
parentEquality
|
||||
}
|
||||
|
|
|
@ -50,6 +50,13 @@ func (s *composeService) start(ctx context.Context, projectName string, options
|
|||
}
|
||||
}
|
||||
|
||||
if len(options.Services) > 0 {
|
||||
err := project.ForServices(options.Services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
if listener != nil {
|
||||
attached, err := s.attach(ctx, project, listener, options.AttachTo)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
services:
|
||||
another_2:
|
||||
image: nginx:alpine
|
||||
another:
|
||||
image: nginx:alpine
|
||||
depends_on:
|
||||
- another_2
|
||||
dep_2:
|
||||
image: nginx:alpine
|
||||
dep_1:
|
||||
image: nginx:alpine
|
||||
depends_on:
|
||||
- dep_2
|
||||
desired:
|
||||
image: nginx:alpine
|
||||
depends_on:
|
||||
- dep_1
|
|
@ -247,6 +247,30 @@ func TestStartStopMultipleServices(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStartSingleServiceAndDependency(t *testing.T) {
|
||||
cli := NewParallelCLI(t, WithEnv(
|
||||
"COMPOSE_PROJECT_NAME=e2e-start-single-deps",
|
||||
"COMPOSE_FILE=./fixtures/start-stop/start-stop-deps.yaml"))
|
||||
t.Cleanup(func() {
|
||||
cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
|
||||
})
|
||||
|
||||
cli.RunDockerComposeCmd(t, "create", "desired")
|
||||
|
||||
res := cli.RunDockerComposeCmd(t, "start", "desired")
|
||||
desiredServices := []string{"desired", "dep_1", "dep_2"}
|
||||
for _, s := range desiredServices {
|
||||
startMsg := fmt.Sprintf("Container e2e-start-single-deps-%s-1 Started", s)
|
||||
assert.Assert(t, strings.Contains(res.Combined(), startMsg),
|
||||
fmt.Sprintf("Missing start message for service: %s\n%s", s, res.Combined()))
|
||||
}
|
||||
undesiredServices := []string{"another", "another_2"}
|
||||
for _, s := range undesiredServices {
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), s),
|
||||
fmt.Sprintf("Shouldn't have message for service: %s\n%s", s, res.Combined()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartStopMultipleFiles(t *testing.T) {
|
||||
cli := NewParallelCLI(t, WithEnv("COMPOSE_PROJECT_NAME=e2e-start-stop-svc-multiple-files"))
|
||||
t.Cleanup(func() {
|
||||
|
|
Loading…
Reference in New Issue