From 0aa3f4a18980e0fe3a7514481d8661923b5cfaed Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Sun, 31 Jan 2021 14:42:13 -0300 Subject: [PATCH] Add kill command Signed-off-by: Nicolas De Loof --- aci/compose.go | 4 +++ api/client/compose.go | 4 +++ api/compose/api.go | 8 +++++ api/progress/event.go | 14 +++++++-- cli/cmd/compose/compose.go | 1 + cli/cmd/compose/kill.go | 63 +++++++++++++++++++++++++++++++++++++ ecs/cloudformation.go | 5 +++ ecs/local/compose.go | 4 +++ kube/compose.go | 4 +++ local/compose/containers.go | 6 ++++ local/compose/kill.go | 60 +++++++++++++++++++++++++++++++++++ local/compose/stop.go | 2 ++ 12 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 cli/cmd/compose/kill.go create mode 100644 local/compose/kill.go diff --git a/aci/compose.go b/aci/compose.go index 43c765cbb..079acb983 100644 --- a/aci/compose.go +++ b/aci/compose.go @@ -203,6 +203,10 @@ func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project return nil, errdefs.ErrNotImplemented } +func (cs *aciComposeService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error { + return errdefs.ErrNotImplemented +} + func (cs *aciComposeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error { return errdefs.ErrNotImplemented } diff --git a/api/client/compose.go b/api/client/compose.go index 16198c47f..e201068f1 100644 --- a/api/client/compose.go +++ b/api/client/compose.go @@ -76,6 +76,10 @@ func (c *composeService) Convert(context.Context, *types.Project, compose.Conver return nil, errdefs.ErrNotImplemented } +func (c *composeService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error { + return errdefs.ErrNotImplemented +} + func (c *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error { return errdefs.ErrNotImplemented } diff --git a/api/compose/api.go b/api/compose/api.go index 465b20b70..f73ead38c 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -49,6 +49,8 @@ type Service interface { List(ctx context.Context) ([]Stack, error) // Convert translate compose model into backend's native format Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error) + // Kill executes the equivalent to a `compose kill` + Kill(ctx context.Context, project *types.Project, options KillOptions) error // RunOneOffContainer creates a service oneoff container and starts its dependencies RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) error } @@ -81,6 +83,12 @@ type ConvertOptions struct { Format string } +// KillOptions group options of the Kill API +type KillOptions struct { + // Signal to send to containers + Signal string +} + // RunOptions options to execute compose run type RunOptions struct { Service string diff --git a/api/progress/event.go b/api/progress/event.go index c8473decc..2898a038c 100644 --- a/api/progress/event.go +++ b/api/progress/event.go @@ -78,16 +78,26 @@ func CreatedEvent(ID string) Event { return NewEvent(ID, Done, "Created") } -// StoppingEvent stops a new Stopping in progress Event +// StoppingEvent creates a new Stopping in progress Event func StoppingEvent(ID string) Event { return NewEvent(ID, Working, "Stopping") } -// StoppedEvent stops a new Stopping in progress Event +// StoppedEvent creates a new Stopping in progress Event func StoppedEvent(ID string) Event { return NewEvent(ID, Done, "Stopped") } +// KillingEvent creates a new Killing in progress Event +func KillingEvent(ID string) Event { + return NewEvent(ID, Working, "Killing") +} + +// KilledEvent creates a new Killed in progress Event +func KilledEvent(ID string) Event { + return NewEvent(ID, Done, "Killed") +} + // RemovingEvent creates a new Removing in progress Event func RemovingEvent(ID string) Event { return NewEvent(ID, Working, "Removing") diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go index c10d81ee7..1ed1d85a7 100644 --- a/cli/cmd/compose/compose.go +++ b/cli/cmd/compose/compose.go @@ -113,6 +113,7 @@ func Command(contextType string) *cobra.Command { listCommand(), logsCommand(&opts, contextType), convertCommand(&opts), + killCommand(&opts), runCommand(&opts), ) diff --git a/cli/cmd/compose/kill.go b/cli/cmd/compose/kill.go new file mode 100644 index 000000000..7dbf08ff4 --- /dev/null +++ b/cli/cmd/compose/kill.go @@ -0,0 +1,63 @@ +/* + 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" + + "github.com/spf13/cobra" + + "github.com/docker/compose-cli/api/client" + "github.com/docker/compose-cli/api/compose" +) + +type killOptions struct { + *projectOptions + Signal string +} + +func killCommand(p *projectOptions) *cobra.Command { + opts := killOptions{ + projectOptions: p, + } + cmd := &cobra.Command{ + Use: "kill [options] [SERVICE...]", + Short: "Force stop service containers.", + RunE: func(cmd *cobra.Command, args []string) error { + return runKill(cmd.Context(), opts, args) + }, + } + + flags := cmd.Flags() + flags.StringVarP(&opts.Signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.") + + return cmd +} + +func runKill(ctx context.Context, opts killOptions, services []string) error { + c, err := client.NewWithDefaultLocalBackend(ctx) + if err != nil { + return err + } + project, err := opts.toProject(services) + if err != nil { + return err + } + return c.ComposeService().Kill(ctx, project, compose.KillOptions{ + Signal: opts.Signal, + }) +} diff --git a/ecs/cloudformation.go b/ecs/cloudformation.go index afb503583..d162db0f9 100644 --- a/ecs/cloudformation.go +++ b/ecs/cloudformation.go @@ -25,6 +25,7 @@ import ( "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/config" + "github.com/docker/compose-cli/api/errdefs" ecsapi "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/service/elbv2" @@ -46,6 +47,10 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml/merge2" ) +func (b *ecsAPIService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error { + return errdefs.ErrNotImplemented +} + func (b *ecsAPIService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) { err := b.resolveServiceImagesDigests(ctx, project) if err != nil { diff --git a/ecs/local/compose.go b/ecs/local/compose.go index a7bfd747e..f110960c6 100644 --- a/ecs/local/compose.go +++ b/ecs/local/compose.go @@ -65,6 +65,10 @@ func (e ecsLocalSimulation) Up(ctx context.Context, project *types.Project, opti return errdefs.ErrNotImplemented } +func (e ecsLocalSimulation) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error { + return e.compose.Kill(ctx, project, options) +} + func (e ecsLocalSimulation) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) { enhanced, err := e.enhanceForLocalSimulation(project) if err != nil { diff --git a/kube/compose.go b/kube/compose.go index 22b80ada9..c8611f354 100644 --- a/kube/compose.go +++ b/kube/compose.go @@ -103,6 +103,10 @@ func (s *composeService) Convert(ctx context.Context, project *types.Project, op return nil, errdefs.ErrNotImplemented } +func (s *composeService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error { + return errdefs.ErrNotImplemented +} + // RunOneOffContainer creates a service oneoff container and starts its dependencies func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error { return errdefs.ErrNotImplemented diff --git a/local/compose/containers.go b/local/compose/containers.go index 300700e23..ee13f9d23 100644 --- a/local/compose/containers.go +++ b/local/compose/containers.go @@ -70,3 +70,9 @@ func (containers Containers) names() []string { } return names } + +func (containers Containers) forEach(fn func(moby.Container)) { + for _, c := range containers { + fn(c) + } +} diff --git a/local/compose/kill.go b/local/compose/kill.go new file mode 100644 index 000000000..1745a1210 --- /dev/null +++ b/local/compose/kill.go @@ -0,0 +1,60 @@ +/* + 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" + + "github.com/compose-spec/compose-go/types" + moby "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/sync/errgroup" + + "github.com/docker/compose-cli/api/compose" + "github.com/docker/compose-cli/api/progress" +) + +func (s *composeService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error { + w := progress.ContextWriter(ctx) + + var containers Containers + containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{ + Filters: filters.NewArgs(projectFilter(project.Name)), + All: true, + }) + if err != nil { + return err + } + + eg, ctx := errgroup.WithContext(ctx) + containers. + filter(isService(project.ServiceNames()...)). + forEach(func(container moby.Container) { + eg.Go(func() error { + eventName := getContainerProgressName(container) + w.Event(progress.KillingEvent(eventName)) + err := s.apiClient.ContainerKill(ctx, container.ID, options.Signal) + if err != nil { + w.Event(progress.ErrorMessageEvent(eventName, "Error while Killing")) + return err + } + w.Event(progress.KilledEvent(eventName)) + return nil + }) + }) + return eg.Wait() +} diff --git a/local/compose/stop.go b/local/compose/stop.go index 188aabf7b..b1436f33e 100644 --- a/local/compose/stop.go +++ b/local/compose/stop.go @@ -38,6 +38,8 @@ func (s *composeService) Stop(ctx context.Context, project *types.Project) error return err } + 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))) })