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

Commit

Permalink
introduce compose rm command
Browse files Browse the repository at this point in the history
Signed-off-by: Nicolas De Loof <[email protected]>
  • Loading branch information
ndeloof committed Feb 15, 2021
1 parent a69aa3d commit 20b83aa
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 8 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) error {
return 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) error {
return errdefs.ErrNotImplemented
}
10 changes: 10 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) error
}

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

// RemoveOptions group options of the Remove API
type RemoveOptions struct {
// 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
84 changes: 84 additions & 0 deletions cli/cmd/compose/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
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/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"

"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
}

_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
if opts.stop {
err := c.ComposeService().Stop(ctx, project)
if err != nil {
return "", err
}
}
return "", c.ComposeService().Remove(ctx, project, compose.RemoveOptions{
Volumes: opts.volumes,
Force: opts.force,
})
})
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) 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) error {
return 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) error {
return 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
7 changes: 6 additions & 1 deletion local/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strings"

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

"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
Expand All @@ -34,11 +35,15 @@ import (

// 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,
ui: prompt.User{},
}
}

type composeService struct {
apiClient client.APIClient
ui prompt.UI
}

func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
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
83 changes: 83 additions & 0 deletions local/compose/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
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/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"golang.org/x/sync/errgroup"

"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
status "github.com/docker/compose-cli/local/moby"
)

func (s *composeService) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) error {
containers, err := s.getContainers(ctx, project, oneOffInclude)
if err != nil {
return 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 len(stoppedContainers) == 0 {
fmt.Println("No stopped containers")
return nil
}
msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
if options.Force {
fmt.Println(msg)
} else {
confirm, err := s.ui.Confirm(msg, false)
if err != nil {
return err
}
if !confirm {
return 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 eg.Wait()
}

0 comments on commit 20b83aa

Please sign in to comment.