mirror of https://github.com/docker/compose.git
Merge pull request #1303 from docker/down_timeout
introduce timeout flag on down and stop
This commit is contained in:
commit
bd9248d074
|
@ -64,7 +64,7 @@ func (cs *aciComposeService) Start(ctx context.Context, project *types.Project,
|
|||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (cs *aciComposeService) Stop(ctx context.Context, project *types.Project) error {
|
||||
func (cs *aciComposeService) Stop(ctx context.Context, project *types.Project, options compose.StopOptions) error {
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ func (c *composeService) Start(ctx context.Context, project *types.Project, opti
|
|||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (c *composeService) Stop(ctx context.Context, project *types.Project) error {
|
||||
func (c *composeService) Stop(ctx context.Context, project *types.Project, options compose.StopOptions) error {
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package compose
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
@ -36,7 +37,7 @@ type Service interface {
|
|||
// Start executes the equivalent to a `compose start`
|
||||
Start(ctx context.Context, project *types.Project, options StartOptions) error
|
||||
// Stop executes the equivalent to a `compose stop`
|
||||
Stop(ctx context.Context, project *types.Project) error
|
||||
Stop(ctx context.Context, project *types.Project, options StopOptions) error
|
||||
// Up executes the equivalent to a `compose up`
|
||||
Up(ctx context.Context, project *types.Project, options UpOptions) error
|
||||
// Down executes the equivalent to a `compose down`
|
||||
|
@ -71,6 +72,12 @@ type StartOptions struct {
|
|||
Attach ContainerEventListener
|
||||
}
|
||||
|
||||
// StopOptions group options of the Stop API
|
||||
type StopOptions struct {
|
||||
// Timeout override container stop timeout
|
||||
Timeout *time.Duration
|
||||
}
|
||||
|
||||
// UpOptions group options of the Up API
|
||||
type UpOptions struct {
|
||||
// Detach will create services and return immediately
|
||||
|
@ -83,6 +90,8 @@ type DownOptions struct {
|
|||
RemoveOrphans bool
|
||||
// Project is the compose project used to define this app. Might be nil if user ran `down` just with project name
|
||||
Project *types.Project
|
||||
// Timeout override container stop timeout
|
||||
Timeout *time.Duration
|
||||
}
|
||||
|
||||
// ConvertOptions group options of the Convert API
|
||||
|
|
|
@ -18,6 +18,7 @@ package compose
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
|
||||
|
@ -32,6 +33,8 @@ import (
|
|||
type downOptions struct {
|
||||
*projectOptions
|
||||
removeOrphans bool
|
||||
timeChanged bool
|
||||
timeout int
|
||||
}
|
||||
|
||||
func downCommand(p *projectOptions) *cobra.Command {
|
||||
|
@ -42,11 +45,13 @@ func downCommand(p *projectOptions) *cobra.Command {
|
|||
Use: "down",
|
||||
Short: "Stop and remove containers, networks",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
return runDown(cmd.Context(), opts)
|
||||
},
|
||||
}
|
||||
flags := downCmd.Flags()
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
|
||||
return downCmd
|
||||
}
|
||||
|
@ -69,9 +74,15 @@ func runDown(ctx context.Context, opts downOptions) error {
|
|||
name = p.Name
|
||||
}
|
||||
|
||||
var timeout *time.Duration
|
||||
if opts.timeChanged {
|
||||
timeoutValue := time.Duration(opts.timeout) * time.Second
|
||||
timeout = &timeoutValue
|
||||
}
|
||||
return name, c.ComposeService().Down(ctx, name, compose.DownOptions{
|
||||
RemoveOrphans: opts.removeOrphans,
|
||||
Project: project,
|
||||
Timeout: timeout,
|
||||
})
|
||||
})
|
||||
return err
|
||||
|
|
|
@ -73,7 +73,7 @@ func runRemove(ctx context.Context, opts removeOptions, services []string) error
|
|||
|
||||
if opts.stop {
|
||||
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
|
||||
err := c.ComposeService().Stop(ctx, project)
|
||||
err := c.ComposeService().Stop(ctx, project, compose.StopOptions{})
|
||||
return "", err
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -18,29 +18,37 @@ package compose
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose-cli/api/client"
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/api/progress"
|
||||
)
|
||||
|
||||
type stopOptions struct {
|
||||
*projectOptions
|
||||
timeChanged bool
|
||||
timeout int
|
||||
}
|
||||
|
||||
func stopCommand(p *projectOptions) *cobra.Command {
|
||||
opts := stopOptions{
|
||||
projectOptions: p,
|
||||
}
|
||||
stopCmd := &cobra.Command{
|
||||
cmd := &cobra.Command{
|
||||
Use: "stop [SERVICE...]",
|
||||
Short: "Stop services",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
return runStop(cmd.Context(), opts, args)
|
||||
},
|
||||
}
|
||||
return stopCmd
|
||||
flags := cmd.Flags()
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runStop(ctx context.Context, opts stopOptions, services []string) error {
|
||||
|
@ -54,8 +62,15 @@ func runStop(ctx context.Context, opts stopOptions, services []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var timeout *time.Duration
|
||||
if opts.timeChanged {
|
||||
timeoutValue := time.Duration(opts.timeout) * time.Second
|
||||
timeout = &timeoutValue
|
||||
}
|
||||
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
|
||||
return "", c.ComposeService().Stop(ctx, project)
|
||||
return "", c.ComposeService().Stop(ctx, project, compose.StopOptions{
|
||||
Timeout: timeout,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
|
|||
stopFunc := func() error {
|
||||
ctx := context.Background()
|
||||
_, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
|
||||
return "", c.ComposeService().Stop(ctx, project)
|
||||
return "", c.ComposeService().Stop(ctx, project, compose.StopOptions{})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -57,8 +57,8 @@ func (e ecsLocalSimulation) Start(ctx context.Context, project *types.Project, o
|
|||
return e.compose.Start(ctx, project, options)
|
||||
}
|
||||
|
||||
func (e ecsLocalSimulation) Stop(ctx context.Context, project *types.Project) error {
|
||||
return e.compose.Stop(ctx, project)
|
||||
func (e ecsLocalSimulation) Stop(ctx context.Context, project *types.Project, options compose.StopOptions) error {
|
||||
return e.compose.Stop(ctx, project, options)
|
||||
}
|
||||
|
||||
func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
|
||||
|
|
|
@ -51,7 +51,7 @@ func (b *ecsAPIService) Start(ctx context.Context, project *types.Project, optio
|
|||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Stop(ctx context.Context, project *types.Project) error {
|
||||
func (b *ecsAPIService) Stop(ctx context.Context, project *types.Project, options compose.StopOptions) error {
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ func (s *composeService) Start(ctx context.Context, project *types.Project, opti
|
|||
}
|
||||
|
||||
// Stop executes the equivalent to a `compose stop`
|
||||
func (s *composeService) Stop(ctx context.Context, project *types.Project) error {
|
||||
func (s *composeService) Stop(ctx context.Context, project *types.Project, options compose.StopOptions) error {
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
|
|||
if len(orphans) > 0 {
|
||||
if opts.RemoveOrphans {
|
||||
w := progress.ContextWriter(ctx)
|
||||
err := s.removeContainers(ctx, w, orphans)
|
||||
err := s.removeContainers(ctx, w, orphans, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
|
||||
|
@ -58,7 +59,7 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
|
|||
|
||||
err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service types.ServiceConfig) error {
|
||||
serviceContainers := containers.filter(isService(service.Name))
|
||||
err := s.removeContainers(ctx, w, serviceContainers)
|
||||
err := s.removeContainers(ctx, w, serviceContainers, options.Timeout)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -67,7 +68,7 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
|
|||
|
||||
orphans := containers.filter(isNotService(options.Project.ServiceNames()...))
|
||||
if options.RemoveOrphans && len(orphans) > 0 {
|
||||
err := s.removeContainers(ctx, w, orphans)
|
||||
err := s.removeContainers(ctx, w, orphans, options.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -93,12 +94,12 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
|
|||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, containers []moby.Container) error {
|
||||
func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration) error {
|
||||
for _, container := range containers {
|
||||
toStop := container
|
||||
eventName := getContainerProgressName(toStop)
|
||||
w.Event(progress.StoppingEvent(eventName))
|
||||
err := s.apiClient.ContainerStop(ctx, toStop.ID, nil)
|
||||
err := s.apiClient.ContainerStop(ctx, toStop.ID, timeout)
|
||||
if err != nil {
|
||||
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
|
||||
return err
|
||||
|
@ -108,14 +109,14 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, containers []moby.Container) error {
|
||||
func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration) error {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for _, container := range containers {
|
||||
toDelete := container
|
||||
eg.Go(func() error {
|
||||
eventName := getContainerProgressName(toDelete)
|
||||
w.Event(progress.StoppingEvent(eventName))
|
||||
err := s.stopContainers(ctx, w, []moby.Container{toDelete})
|
||||
err := s.stopContainers(ctx, w, []moby.Container{toDelete}, timeout)
|
||||
if err != nil {
|
||||
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
|
||||
return err
|
||||
|
|
|
@ -20,14 +20,13 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/local/mocks"
|
||||
|
||||
apitypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/local/mocks"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
|
|
|
@ -19,16 +19,16 @@ package compose
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/api/progress"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/docker/compose-cli/api/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) Stop(ctx context.Context, project *types.Project) error {
|
||||
func (s *composeService) Stop(ctx context.Context, project *types.Project, options compose.StopOptions) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
var containers Containers
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(projectFilter(project.Name)),
|
||||
|
@ -41,6 +41,6 @@ func (s *composeService) Stop(ctx context.Context, project *types.Project) error
|
|||
containers = containers.filter(isService(project.ServiceNames()...))
|
||||
|
||||
return InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
|
||||
return s.stopContainers(ctx, w, containers.filter(isService(service.Name)))
|
||||
return s.stopContainers(ctx, w, containers.filter(isService(service.Name)), options.Timeout)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/local/mocks"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestStopTimeout(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = api
|
||||
|
||||
ctx := context.Background()
|
||||
api.EXPECT().ContainerList(ctx, projectFilterListOpt(testProject)).Return(
|
||||
[]moby.Container{
|
||||
testContainer("service1", "123"),
|
||||
testContainer("service1", "456"),
|
||||
testContainer("service2", "789"),
|
||||
}, nil)
|
||||
|
||||
timeout := time.Duration(2) * time.Second
|
||||
api.EXPECT().ContainerStop(ctx, "123", &timeout).Return(nil)
|
||||
api.EXPECT().ContainerStop(ctx, "456", &timeout).Return(nil)
|
||||
api.EXPECT().ContainerStop(ctx, "789", &timeout).Return(nil)
|
||||
|
||||
err := tested.Stop(ctx, &types.Project{
|
||||
Name: testProject,
|
||||
Services: []types.ServiceConfig{
|
||||
{Name: "service1"},
|
||||
{Name: "service2"},
|
||||
},
|
||||
}, compose.StopOptions{
|
||||
Timeout: &timeout,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
}
|
Loading…
Reference in New Issue