mirror of https://github.com/docker/compose.git
introduce service hooks
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
6c06170eb0
commit
82b41b9ebd
|
@ -61,6 +61,16 @@ func (l *logConsumer) Register(name string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logConsumer) register(name string) *presenter {
|
func (l *logConsumer) register(name string) *presenter {
|
||||||
|
var p *presenter
|
||||||
|
root, _, found := strings.Cut(name, " ")
|
||||||
|
if found {
|
||||||
|
parent := l.getPresenter(root)
|
||||||
|
p = &presenter{
|
||||||
|
colors: parent.colors,
|
||||||
|
name: name,
|
||||||
|
prefix: parent.prefix,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
cf := monochrome
|
cf := monochrome
|
||||||
if l.color {
|
if l.color {
|
||||||
if name == api.WatchLogger {
|
if name == api.WatchLogger {
|
||||||
|
@ -69,13 +79,14 @@ func (l *logConsumer) register(name string) *presenter {
|
||||||
cf = nextColor()
|
cf = nextColor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p := &presenter{
|
p = &presenter{
|
||||||
colors: cf,
|
colors: cf,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
l.presenters.Store(name, p)
|
l.presenters.Store(name, p)
|
||||||
if l.prefix {
|
|
||||||
l.computeWidth()
|
l.computeWidth()
|
||||||
|
if l.prefix {
|
||||||
l.presenters.Range(func(key, value interface{}) bool {
|
l.presenters.Range(func(key, value interface{}) bool {
|
||||||
p := value.(*presenter)
|
p := value.(*presenter)
|
||||||
p.setPrefix(l.width)
|
p.setPrefix(l.width)
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
||||||
github.com/Microsoft/go-winio v0.6.2
|
github.com/Microsoft/go-winio v0.6.2
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||||
github.com/buger/goterm v1.0.4
|
github.com/buger/goterm v1.0.4
|
||||||
github.com/compose-spec/compose-go/v2 v2.2.1-0.20241003145835-48d3a5bbf4ea
|
github.com/compose-spec/compose-go/v2 v2.2.1-0.20241007090213-a59035ad2bf4
|
||||||
github.com/containerd/containerd v1.7.22
|
github.com/containerd/containerd v1.7.22
|
||||||
github.com/containerd/platforms v0.2.1
|
github.com/containerd/platforms v0.2.1
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -85,8 +85,8 @@ github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/P
|
||||||
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
|
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
|
||||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
||||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||||
github.com/compose-spec/compose-go/v2 v2.2.1-0.20241003145835-48d3a5bbf4ea h1:BU/Sx/dAU6f64sDad58igm4OwwI1Z1uvV5E0ZKv4CZ8=
|
github.com/compose-spec/compose-go/v2 v2.2.1-0.20241007090213-a59035ad2bf4 h1:2FWtPQWe/tkeGuwxk5x03luRw5pzPhPCRfzfeVw56vo=
|
||||||
github.com/compose-spec/compose-go/v2 v2.2.1-0.20241003145835-48d3a5bbf4ea/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
github.com/compose-spec/compose-go/v2 v2.2.1-0.20241007090213-a59035ad2bf4/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
|
||||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||||
|
|
|
@ -637,6 +637,8 @@ const (
|
||||||
ContainerEventExit
|
ContainerEventExit
|
||||||
// UserCancel user cancelled compose up, we are stopping containers
|
// UserCancel user cancelled compose up, we are stopping containers
|
||||||
UserCancel
|
UserCancel
|
||||||
|
// HookEventLog is a ContainerEvent of type log on stdout by service hook
|
||||||
|
HookEventLog
|
||||||
)
|
)
|
||||||
|
|
||||||
// Separator is used for naming components
|
// Separator is used for naming components
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||||
container := container
|
container := container
|
||||||
traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(container)...)
|
traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(container)...)
|
||||||
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "service/scale/down", traceOpts, func(ctx context.Context) error {
|
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "service/scale/down", traceOpts, func(ctx context.Context) error {
|
||||||
return c.service.stopAndRemoveContainer(ctx, container, timeout, false)
|
return c.service.stopAndRemoveContainer(ctx, container, &service, timeout, false)
|
||||||
}))
|
}))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,7 @@ func (c *convergence) stopDependentContainers(ctx context.Context, project *type
|
||||||
dependents := project.GetDependentsForService(service)
|
dependents := project.GetDependentsForService(service)
|
||||||
for _, name := range dependents {
|
for _, name := range dependents {
|
||||||
dependents := c.getObservedState(name)
|
dependents := c.getObservedState(name)
|
||||||
err := c.service.stopContainers(ctx, w, dependents, nil)
|
err := c.service.stopContainers(ctx, w, &service, dependents, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -769,7 +769,10 @@ func (s *composeService) isServiceCompleted(ctx context.Context, containers Cont
|
||||||
return false, 0, nil
|
return false, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig, containers Containers, timeout time.Duration) error {
|
func (s *composeService) startService(ctx context.Context,
|
||||||
|
project *types.Project, service types.ServiceConfig,
|
||||||
|
containers Containers, listener api.ContainerEventListener,
|
||||||
|
timeout time.Duration) error {
|
||||||
if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
|
if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -793,10 +796,18 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||||
}
|
}
|
||||||
eventName := getContainerProgressName(container)
|
eventName := getContainerProgressName(container)
|
||||||
w.Event(progress.StartingEvent(eventName))
|
w.Event(progress.StartingEvent(eventName))
|
||||||
err := s.apiClient().ContainerStart(ctx, container.ID, containerType.StartOptions{})
|
err = s.apiClient().ContainerStart(ctx, container.ID, containerType.StartOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, hook := range service.PostStart {
|
||||||
|
err = s.runHook(ctx, container, service, hook, listener)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w.Event(progress.StartedEvent(eventName))
|
w.Event(progress.StartedEvent(eventName))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -104,7 +104,7 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||||
orphans := observedState.filter(isOrphaned(project))
|
orphans := observedState.filter(isOrphaned(project))
|
||||||
if len(orphans) > 0 && !options.IgnoreOrphans {
|
if len(orphans) > 0 && !options.IgnoreOrphans {
|
||||||
if options.RemoveOrphans {
|
if options.RemoveOrphans {
|
||||||
err := s.removeContainers(ctx, orphans, nil, false)
|
err := s.removeContainers(ctx, orphans, nil, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,8 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||||
|
|
||||||
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
||||||
serviceContainers := containers.filter(isService(service))
|
serviceContainers := containers.filter(isService(service))
|
||||||
err := s.removeContainers(ctx, serviceContainers, options.Timeout, options.Volumes)
|
serv := project.Services[service]
|
||||||
|
err := s.removeContainers(ctx, serviceContainers, &serv, options.Timeout, options.Volumes)
|
||||||
return err
|
return err
|
||||||
}, WithRootNodesAndDown(options.Services))
|
}, WithRootNodesAndDown(options.Services))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -94,7 +95,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||||
|
|
||||||
orphans := containers.filter(isOrphaned(project))
|
orphans := containers.filter(isOrphaned(project))
|
||||||
if options.RemoveOrphans && len(orphans) > 0 {
|
if options.RemoveOrphans && len(orphans) > 0 {
|
||||||
err := s.removeContainers(ctx, orphans, options.Timeout, false)
|
err := s.removeContainers(ctx, orphans, nil, options.Timeout, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -296,9 +297,19 @@ func (s *composeService) removeVolume(ctx context.Context, id string, w progress
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, container moby.Container, timeout *time.Duration) error {
|
func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, service *types.ServiceConfig, container moby.Container, timeout *time.Duration) error {
|
||||||
eventName := getContainerProgressName(container)
|
eventName := getContainerProgressName(container)
|
||||||
w.Event(progress.StoppingEvent(eventName))
|
w.Event(progress.StoppingEvent(eventName))
|
||||||
|
|
||||||
|
if service != nil {
|
||||||
|
for _, hook := range service.PreStop {
|
||||||
|
err := s.runHook(ctx, container, *service, hook, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||||
err := s.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{Timeout: timeoutInSecond})
|
err := s.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{Timeout: timeoutInSecond})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -309,32 +320,32 @@ func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, c
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, containers []moby.Container, timeout *time.Duration) error {
|
func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, serv *types.ServiceConfig, containers []moby.Container, timeout *time.Duration) error {
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
container := container
|
container := container
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
return s.stopContainer(ctx, w, container, timeout)
|
return s.stopContainer(ctx, w, serv, container, timeout)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return eg.Wait()
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) removeContainers(ctx context.Context, containers []moby.Container, timeout *time.Duration, volumes bool) error {
|
func (s *composeService) removeContainers(ctx context.Context, containers []moby.Container, service *types.ServiceConfig, timeout *time.Duration, volumes bool) error {
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
container := container
|
container := container
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
return s.stopAndRemoveContainer(ctx, container, timeout, volumes)
|
return s.stopAndRemoveContainer(ctx, container, service, timeout, volumes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return eg.Wait()
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) stopAndRemoveContainer(ctx context.Context, container moby.Container, timeout *time.Duration, volumes bool) error {
|
func (s *composeService) stopAndRemoveContainer(ctx context.Context, container moby.Container, service *types.ServiceConfig, timeout *time.Duration, volumes bool) error {
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
eventName := getContainerProgressName(container)
|
eventName := getContainerProgressName(container)
|
||||||
err := s.stopContainer(ctx, w, container, timeout)
|
err := s.stopContainer(ctx, w, service, container, timeout)
|
||||||
if errdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
w.Event(progress.RemovedEvent(eventName))
|
w.Event(progress.RemovedEvent(eventName))
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
|
moby "github.com/docker/docker/api/types"
|
||||||
|
containerType "github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s composeService) runHook(ctx context.Context, container moby.Container, service types.ServiceConfig, hook types.ServiceHook, listener api.ContainerEventListener) error {
|
||||||
|
wOut := utils.GetWriter(func(line string) {
|
||||||
|
listener(api.ContainerEvent{
|
||||||
|
Type: api.HookEventLog,
|
||||||
|
Container: getContainerNameWithoutProject(container) + " ->",
|
||||||
|
ID: container.ID,
|
||||||
|
Service: service.Name,
|
||||||
|
Line: line,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
defer wOut.Close() //nolint:errcheck
|
||||||
|
|
||||||
|
detached := listener == nil
|
||||||
|
exec, err := s.apiClient().ContainerExecCreate(ctx, container.ID, containerType.ExecOptions{
|
||||||
|
User: hook.User,
|
||||||
|
Privileged: hook.Privileged,
|
||||||
|
Env: ToMobyEnv(hook.Environment),
|
||||||
|
WorkingDir: hook.WorkingDir,
|
||||||
|
Cmd: hook.Command,
|
||||||
|
Detach: detached,
|
||||||
|
AttachStdout: !detached,
|
||||||
|
AttachStderr: !detached,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if detached {
|
||||||
|
return s.runWaitExec(ctx, exec, service, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
height, width := s.stdout().GetTtySize()
|
||||||
|
consoleSize := &[2]uint{height, width}
|
||||||
|
attach, err := s.apiClient().ContainerExecAttach(ctx, exec.ID, containerType.ExecAttachOptions{
|
||||||
|
Tty: service.Tty,
|
||||||
|
ConsoleSize: consoleSize,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer attach.Close()
|
||||||
|
|
||||||
|
if service.Tty {
|
||||||
|
_, err = io.Copy(wOut, attach.Reader)
|
||||||
|
} else {
|
||||||
|
_, err = stdcopy.StdCopy(wOut, wOut, attach.Reader)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
inspected, err := s.apiClient().ContainerExecInspect(ctx, exec.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inspected.ExitCode != 0 {
|
||||||
|
return fmt.Errorf("%s hook exited with status %d", service.Name, inspected.ExitCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s composeService) runWaitExec(ctx context.Context, exec moby.IDResponse, service types.ServiceConfig, listener api.ContainerEventListener) error {
|
||||||
|
err := s.apiClient().ContainerExecStart(ctx, exec.ID, containerType.ExecStartOptions{
|
||||||
|
Detach: listener == nil,
|
||||||
|
Tty: service.Tty,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We miss a ContainerExecWait API
|
||||||
|
tick := time.NewTicker(100 * time.Millisecond)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
case <-tick.C:
|
||||||
|
inspect, err := s.apiClient().ContainerExecInspect(ctx, exec.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !inspect.Running {
|
||||||
|
if inspect.ExitCode != 0 {
|
||||||
|
return fmt.Errorf("%s hook exited with status %d", service.Name, inspect.ExitCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -148,7 +148,7 @@ func (p *printer) Run(cascade api.Cascade, exitCodeFrom string, stopFn func() er
|
||||||
// Last container terminated, done
|
// Last container terminated, done
|
||||||
return exitCode, nil
|
return exitCode, nil
|
||||||
}
|
}
|
||||||
case api.ContainerEventLog:
|
case api.ContainerEventLog, api.HookEventLog:
|
||||||
if !aborting {
|
if !aborting {
|
||||||
p.consumer.Log(container, event.Line)
|
p.consumer.Log(container, event.Line)
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.startService(ctx, project, service, containers, options.WaitTimeout)
|
return s.startService(ctx, project, service, containers, listener, options.WaitTimeout)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -54,6 +54,7 @@ func (s *composeService) stop(ctx context.Context, projectName string, options a
|
||||||
if !utils.StringContains(options.Services, service) {
|
if !utils.StringContains(options.Services, service) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return s.stopContainers(ctx, w, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout)
|
serv := project.Services[service]
|
||||||
|
return s.stopContainers(ctx, w, &serv, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue