mirror of
https://github.com/docker/compose.git
synced 2026-09-21 13:04:36 +00:00
Add docker compose wait
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
This commit is contained in:
parent
c496c23071
commit
edd76bfd70
12 changed files with 310 additions and 1 deletions
|
|
@ -84,6 +84,15 @@ type Service interface {
|
|||
Watch(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
|
||||
// Viz generates a graphviz graph of the project services
|
||||
Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error)
|
||||
// Wait blocks until at least one of the services' container exits
|
||||
Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error)
|
||||
}
|
||||
|
||||
type WaitOptions struct {
|
||||
// Services passed in the command line to be waited
|
||||
Services []string
|
||||
// Executes a down when a container exits
|
||||
DownProjectOnContainerExit bool
|
||||
}
|
||||
|
||||
type VizOptions struct {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ type ServiceProxy struct {
|
|||
MaxConcurrencyFn func(parallel int)
|
||||
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
|
||||
VizFn func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
|
||||
WaitFn func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
|
||||
interceptors []Interceptor
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +96,7 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
|
|||
s.MaxConcurrencyFn = service.MaxConcurrency
|
||||
s.DryRunModeFn = service.DryRunMode
|
||||
s.VizFn = service.Viz
|
||||
s.WaitFn = service.Wait
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
@ -325,7 +327,7 @@ func (s *ServiceProxy) Watch(ctx context.Context, project *types.Project, servic
|
|||
return s.WatchFn(ctx, project, services, options)
|
||||
}
|
||||
|
||||
// Viz implements Viz interface
|
||||
// Viz implements Service interface
|
||||
func (s *ServiceProxy) Viz(ctx context.Context, project *types.Project, options VizOptions) (string, error) {
|
||||
if s.VizFn == nil {
|
||||
return "", ErrNotImplemented
|
||||
|
|
@ -333,6 +335,14 @@ func (s *ServiceProxy) Viz(ctx context.Context, project *types.Project, options
|
|||
return s.VizFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Wait implements Service interface
|
||||
func (s *ServiceProxy) Wait(ctx context.Context, projectName string, options WaitOptions) (int64, error) {
|
||||
if s.WaitFn == nil {
|
||||
return 0, ErrNotImplemented
|
||||
}
|
||||
return s.WaitFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
func (s *ServiceProxy) MaxConcurrency(i int) {
|
||||
s.MaxConcurrencyFn(i)
|
||||
}
|
||||
|
|
|
|||
67
pkg/compose/wait.go
Normal file
67
pkg/compose/wait.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func (s *composeService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false, options.Services...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return 0, fmt.Errorf("no containers for project %q", projectName)
|
||||
}
|
||||
|
||||
eg, waitCtx := errgroup.WithContext(ctx)
|
||||
var statusCode int64
|
||||
for _, c := range containers {
|
||||
c := c
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
resultC, errC := s.dockerCli.Client().ContainerWait(waitCtx, c.ID, "")
|
||||
|
||||
select {
|
||||
case result := <-resultC:
|
||||
fmt.Fprintf(s.dockerCli.Out(), "container %q exited with status code %d\n", c.ID, result.StatusCode)
|
||||
statusCode = result.StatusCode
|
||||
case err = <-errC:
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return 42, err // Ignore abort flag in case of error in wait
|
||||
}
|
||||
|
||||
if options.DownProjectOnContainerExit {
|
||||
return statusCode, s.Down(ctx, projectName, api.DownOptions{
|
||||
RemoveOrphans: true,
|
||||
})
|
||||
}
|
||||
|
||||
return statusCode, err
|
||||
}
|
||||
11
pkg/e2e/fixtures/wait/compose.yaml
Normal file
11
pkg/e2e/fixtures/wait/compose.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
services:
|
||||
faster:
|
||||
image: alpine
|
||||
command: sleep 2
|
||||
slower:
|
||||
image: alpine
|
||||
command: sleep 5
|
||||
infinity:
|
||||
image: alpine
|
||||
command: sleep infinity
|
||||
|
||||
72
pkg/e2e/wait_test.go
Normal file
72
pkg/e2e/wait_test.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestWaitOnFaster(t *testing.T) {
|
||||
const projectName = "e2e-wait-faster"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "faster")
|
||||
}
|
||||
|
||||
func TestWaitOnSlower(t *testing.T) {
|
||||
const projectName = "e2e-wait-slower"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "slower")
|
||||
}
|
||||
|
||||
func TestWaitOnInfinity(t *testing.T) {
|
||||
const projectName = "e2e-wait-infinity"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
|
||||
finished := make(chan struct{})
|
||||
ticker := time.NewTicker(7 * time.Second)
|
||||
go func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "infinity")
|
||||
finished <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-finished:
|
||||
t.Fatal("wait infinity should not finish")
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitAndDrop(t *testing.T) {
|
||||
const projectName = "e2e-wait-and-drop"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "--down-project", "faster")
|
||||
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
|
||||
}
|
||||
|
|
@ -423,6 +423,21 @@ func (mr *MockServiceMockRecorder) Viz(ctx, project, options interface{}) *gomoc
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Viz", reflect.TypeOf((*MockService)(nil).Viz), ctx, project, options)
|
||||
}
|
||||
|
||||
// Wait mocks base method.
|
||||
func (m *MockService) Wait(ctx context.Context, projectName string, options api.WaitOptions) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Wait", ctx, projectName, options)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Wait indicates an expected call of Wait.
|
||||
func (mr *MockServiceMockRecorder) Wait(ctx, projectName, options interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wait", reflect.TypeOf((*MockService)(nil).Wait), ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Watch mocks base method.
|
||||
func (m *MockService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue