Skip to content
This repository has been archived by the owner on Nov 27, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1299 from docker/remove
Browse files Browse the repository at this point in the history
introduce compose rm command
  • Loading branch information
ndeloof authored Feb 15, 2021
2 parents d6c2004 + 5cb2533 commit 9063c13
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 11 deletions.
4 changes: 4 additions & 0 deletions aci/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,7 @@ func (cs *aciComposeService) Kill(ctx context.Context, project *types.Project, o
func (cs *aciComposeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}

func (cs *aciComposeService) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) ([]string, error) {
return nil, errdefs.ErrNotImplemented
}
4 changes: 4 additions & 0 deletions api/client/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@ func (c *composeService) Kill(ctx context.Context, project *types.Project, optio
func (c *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}

func (c *composeService) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) ([]string, error) {
return nil, errdefs.ErrNotImplemented
}
12 changes: 12 additions & 0 deletions api/compose/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ type Service interface {
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) (int, error)
// Remove executes the equivalent to a `compose rm`
Remove(ctx context.Context, project *types.Project, options RemoveOptions) ([]string, error)
}

// CreateOptions group options of the Create API
Expand Down Expand Up @@ -97,6 +99,16 @@ type KillOptions struct {
Signal string
}

// RemoveOptions group options of the Remove API
type RemoveOptions struct {
// DryRun just list removable resources
DryRun bool
// Volumes remove anonymous volumes
Volumes bool
// Force don't ask to confirm removal
Force bool
}

// RunOptions options to execute compose run
type RunOptions struct {
Service string
Expand Down
1 change: 1 addition & 0 deletions cli/cmd/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func Command(contextType string) *cobra.Command {
convertCommand(&opts),
killCommand(&opts),
runCommand(&opts),
removeCommand(&opts),
)

if contextType == store.LocalContextType || contextType == store.DefaultContextType {
Expand Down
116 changes: 116 additions & 0 deletions cli/cmd/compose/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
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"
"strings"

"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/utils/prompt"

"github.com/spf13/cobra"
)

type removeOptions struct {
*projectOptions
force bool
stop bool
volumes bool
}

func removeCommand(p *projectOptions) *cobra.Command {
opts := removeOptions{
projectOptions: p,
}
cmd := &cobra.Command{
Use: "rm [SERVICE...]",
Short: "Removes stopped service containers",
Long: `Removes stopped service containers
By default, anonymous volumes attached to containers will not be removed. You
can override this with -v. To list all volumes, use "docker volume ls".
Any data which is not in a volume will be lost.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(cmd.Context(), opts, args)
},
}
f := cmd.Flags()
f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")
f.BoolVarP(&opts.stop, "stop", "s", false, "Stop the containers, if required, before removing")
f.BoolVarP(&opts.volumes, "volumes", "v", false, "Remove any anonymous volumes attached to containers")
return cmd
}

func runRemove(ctx context.Context, opts removeOptions, services []string) error {
c, err := client.NewWithDefaultLocalBackend(ctx)
if err != nil {
return err
}

project, err := opts.toProject(services)
if err != nil {
return err
}

if opts.stop {
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
err := c.ComposeService().Stop(ctx, project)
return "", err
})
if err != nil {
return err
}
}

reosurces, err := c.ComposeService().Remove(ctx, project, compose.RemoveOptions{
DryRun: true,
})
if err != nil {
return err
}

if len(reosurces) == 0 {
fmt.Println("No stopped containers")
return nil
}
msg := fmt.Sprintf("Going to remove %s", strings.Join(reosurces, ", "))
if opts.force {
fmt.Println(msg)
} else {
confirm, err := prompt.User{}.Confirm(msg, false)
if err != nil {
return err
}
if !confirm {
return nil
}
}

_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
_, err = c.ComposeService().Remove(ctx, project, compose.RemoveOptions{
Volumes: opts.volumes,
Force: opts.force,
})
return "", err
})
return err
}
4 changes: 4 additions & 0 deletions ecs/local/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,7 @@ func (e ecsLocalSimulation) List(ctx context.Context) ([]compose.Stack, error) {
func (e ecsLocalSimulation) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose run")
}

func (e ecsLocalSimulation) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) ([]string, error) {
return e.compose.Remove(ctx, project, options)
}
4 changes: 4 additions & 0 deletions ecs/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ import (
func (b *ecsAPIService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}

func (b *ecsAPIService) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) ([]string, error) {
return nil, errdefs.ErrNotImplemented
}
4 changes: 4 additions & 0 deletions kube/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,7 @@ func (s *composeService) Kill(ctx context.Context, project *types.Project, optio
func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
return 0, errdefs.ErrNotImplemented
}

func (s *composeService) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) ([]string, error) {
return nil, errdefs.ErrNotImplemented
}
2 changes: 1 addition & 1 deletion local/compose/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
)

func (s *composeService) attach(ctx context.Context, project *types.Project, consumer compose.ContainerEventListener) (Containers, error) {
containers, err := s.getContainers(ctx, project)
containers, err := s.getContainers(ctx, project, oneOffExclude)
if err != nil {
return nil, err
}
Expand Down
9 changes: 5 additions & 4 deletions local/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,27 @@ import (
"strings"

"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/errdefs"

"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/sanathkr/go-yaml"

errdefs2 "github.com/docker/compose-cli/api/errdefs"
)

// NewComposeService create a local implementation of the compose.Service API
func NewComposeService(apiClient client.APIClient) compose.Service {
return &composeService{apiClient: apiClient}
return &composeService{
apiClient: apiClient,
}
}

type composeService struct {
apiClient client.APIClient
}

func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
return errdefs2.ErrNotImplemented
return errdefs.ErrNotImplemented
}

func getCanonicalContainerName(c moby.Container) string {
Expand Down
27 changes: 22 additions & 5 deletions local/compose/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package compose

import (
"context"
"fmt"
"sort"

"github.com/compose-spec/compose-go/types"
Expand All @@ -28,13 +29,29 @@ import (
// Containers is a set of moby Container
type Containers []moby.Container

func (s *composeService) getContainers(ctx context.Context, project *types.Project) (Containers, error) {
type oneOff int

const (
oneOffInclude = oneOff(iota)
oneOffExclude
oneOffOnly
)

func (s *composeService) getContainers(ctx context.Context, project *types.Project, oneOff oneOff) (Containers, error) {
var containers Containers
f := filters.NewArgs(
projectFilter(project.Name),
)
switch oneOff {
case oneOffOnly:
f.Add("label", fmt.Sprintf("%s=%s", oneoffLabel, "True"))
case oneOffExclude:
f.Add("label", fmt.Sprintf("%s=%s", oneoffLabel, "False"))
case oneOffInclude:
}
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(project.Name),
),
All: true,
Filters: f,
All: true,
})
if err != nil {
return nil, err
Expand Down
3 changes: 2 additions & 1 deletion local/compose/convergence.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,14 +334,15 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
if err != nil {
return err
}

w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx)
for _, c := range containers {
container := c
if container.State == status.ContainerRunning {
continue
}
eg.Go(func() error {
w := progress.ContextWriter(ctx)
eventName := getContainerProgressName(container)
w.Event(progress.StartingEvent(eventName))
err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
Expand Down
68 changes: 68 additions & 0 deletions local/compose/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
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/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
status "github.com/docker/compose-cli/local/moby"

"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"golang.org/x/sync/errgroup"
)

func (s *composeService) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) ([]string, error) {
containers, err := s.getContainers(ctx, project, oneOffInclude)
if err != nil {
return nil, err
}

stoppedContainers := containers.filter(func(c moby.Container) bool {
return c.State != status.ContainerRunning
})

var names []string
stoppedContainers.forEach(func(c moby.Container) {
names = append(names, getCanonicalContainerName(c))
})

if options.DryRun {
return names, nil
}

w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx)
for _, c := range stoppedContainers {
c := c
eg.Go(func() error {
eventName := getContainerProgressName(c)
w.Event(progress.RemovingEvent(eventName))
err = s.apiClient.ContainerRemove(ctx, c.ID, moby.ContainerRemoveOptions{
RemoveVolumes: options.Volumes,
Force: options.Force,
})
if err == nil {
w.Event(progress.RemovedEvent(eventName))
}
return err
})
}
return nil, eg.Wait()
}

0 comments on commit 9063c13

Please sign in to comment.