mirror of https://github.com/docker/compose.git
introduce --remove-orphans option
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
849fbed1ac
commit
4de01936f8
|
@ -56,7 +56,7 @@ func (cs *aciComposeService) Pull(ctx context.Context, project *types.Project) e
|
|||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (cs *aciComposeService) Create(ctx context.Context, project *types.Project) error {
|
||||
func (cs *aciComposeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ func (c *composeService) Pull(ctx context.Context, project *types.Project) error
|
|||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (c *composeService) Create(ctx context.Context, project *types.Project) error {
|
||||
func (c *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ type Service interface {
|
|||
// Pull executes the equivalent of a `compose pull`
|
||||
Pull(ctx context.Context, project *types.Project) error
|
||||
// Create executes the equivalent to a `compose create`
|
||||
Create(ctx context.Context, project *types.Project) error
|
||||
Create(ctx context.Context, project *types.Project, opts CreateOptions) error
|
||||
// Start executes the equivalent to a `compose start`
|
||||
Start(ctx context.Context, project *types.Project, consumer LogConsumer) error
|
||||
// Up executes the equivalent to a `compose up`
|
||||
|
@ -51,6 +51,12 @@ type Service interface {
|
|||
RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) error
|
||||
}
|
||||
|
||||
// CreateOptions group options of the Create API
|
||||
type CreateOptions struct {
|
||||
// Remove legacy containers for services that are not defined in the project
|
||||
RemoveOrphans bool
|
||||
}
|
||||
|
||||
// UpOptions group options of the Up API
|
||||
type UpOptions struct {
|
||||
// Detach will create services and return immediately
|
||||
|
|
|
@ -104,7 +104,7 @@ func startDependencies(ctx context.Context, c *client.Client, project *types.Pro
|
|||
}
|
||||
}
|
||||
project.Services = dependencies
|
||||
if err := c.ComposeService().Create(ctx, project); err != nil {
|
||||
if err := c.ComposeService().Create(ctx, project, compose.CreateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.ComposeService().Start(ctx, project, nil); err != nil {
|
||||
|
|
|
@ -33,8 +33,13 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type upOptions struct {
|
||||
composeOptions
|
||||
removeOrphans bool
|
||||
}
|
||||
|
||||
func upCommand(contextType string) *cobra.Command {
|
||||
opts := composeOptions{}
|
||||
opts := upOptions{}
|
||||
upCmd := &cobra.Command{
|
||||
Use: "up [SERVICE...]",
|
||||
Short: "Create and start containers",
|
||||
|
@ -53,6 +58,7 @@ func upCommand(contextType string) *cobra.Command {
|
|||
upCmd.Flags().StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
|
||||
upCmd.Flags().BoolVarP(&opts.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
||||
upCmd.Flags().BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
|
||||
upCmd.Flags().BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
|
||||
if contextType == store.AciContextType {
|
||||
upCmd.Flags().StringVar(&opts.DomainName, "domainname", "", "Container NIS domain name")
|
||||
|
@ -61,8 +67,8 @@ func upCommand(contextType string) *cobra.Command {
|
|||
return upCmd
|
||||
}
|
||||
|
||||
func runUp(ctx context.Context, opts composeOptions, services []string) error {
|
||||
c, project, err := setup(ctx, opts, services)
|
||||
func runUp(ctx context.Context, opts upOptions, services []string) error {
|
||||
c, project, err := setup(ctx, opts.composeOptions, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -75,14 +81,16 @@ func runUp(ctx context.Context, opts composeOptions, services []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func runCreateStart(ctx context.Context, opts composeOptions, services []string) error {
|
||||
c, project, err := setup(ctx, opts, services)
|
||||
func runCreateStart(ctx context.Context, opts upOptions, services []string) error {
|
||||
c, project, err := setup(ctx, opts.composeOptions, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
|
||||
return "", c.ComposeService().Create(ctx, project)
|
||||
return "", c.ComposeService().Create(ctx, project, compose.CreateOptions{
|
||||
RemoveOrphans: opts.removeOrphans,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -43,13 +43,13 @@ func (e ecsLocalSimulation) Pull(ctx context.Context, project *types.Project) er
|
|||
return e.compose.Pull(ctx, project)
|
||||
}
|
||||
|
||||
func (e ecsLocalSimulation) Create(ctx context.Context, project *types.Project) error {
|
||||
func (e ecsLocalSimulation) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
|
||||
enhanced, err := e.enhanceForLocalSimulation(project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.compose.Create(ctx, enhanced)
|
||||
return e.compose.Create(ctx, enhanced, opts)
|
||||
}
|
||||
|
||||
func (e ecsLocalSimulation) Start(ctx context.Context, project *types.Project, consumer compose.LogConsumer) error {
|
||||
|
|
|
@ -43,7 +43,7 @@ func (b *ecsAPIService) Pull(ctx context.Context, project *types.Project) error
|
|||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (b *ecsAPIService) Create(ctx context.Context, project *types.Project) error {
|
||||
func (b *ecsAPIService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ func (cs *composeService) Pull(ctx context.Context, project *types.Project) erro
|
|||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (cs *composeService) Create(ctx context.Context, project *types.Project) error {
|
||||
func (cs *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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 moby "github.com/docker/docker/api/types"
|
||||
|
||||
// Containers is a set of moby Container
|
||||
type Containers []moby.Container
|
||||
|
||||
// containerPredicate define a predicate we want container to satisfy for filtering operations
|
||||
type containerPredicate func(c moby.Container) bool
|
||||
|
||||
func isService(services ...string) containerPredicate {
|
||||
return func(c moby.Container) bool {
|
||||
service := c.Labels[serviceLabel]
|
||||
return contains(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
func isNotService(services ...string) containerPredicate {
|
||||
return func(c moby.Container) bool {
|
||||
service := c.Labels[serviceLabel]
|
||||
return !contains(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
// filter return Containers with elements to match predicate
|
||||
func (containers Containers) filter(predicate containerPredicate) Containers {
|
||||
var filtered Containers
|
||||
for _, c := range containers {
|
||||
if predicate(c) {
|
||||
filtered = append(filtered, c)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// split return Containers with elements to match and those not to match predicate
|
||||
func (containers Containers) split(predicate containerPredicate) (Containers, Containers) {
|
||||
var right Containers
|
||||
var left Containers
|
||||
for _, c := range containers {
|
||||
if predicate(c) {
|
||||
right = append(right, c)
|
||||
} else {
|
||||
left = append(left, c)
|
||||
}
|
||||
}
|
||||
return right, left
|
||||
}
|
||||
|
||||
func (containers Containers) names() []string {
|
||||
var names []string
|
||||
for _, c := range containers {
|
||||
names = append(names, getContainerName(c))
|
||||
}
|
||||
return names
|
||||
}
|
|
@ -37,19 +37,9 @@ const (
|
|||
forceRecreate = "force_recreate"
|
||||
)
|
||||
|
||||
func (s *composeService) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
actual, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(project.Name),
|
||||
serviceFilter(service.Name),
|
||||
),
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *composeService) ensureService(ctx context.Context, observedState Containers, project *types.Project, service types.ServiceConfig) error {
|
||||
scale := getScale(service)
|
||||
actual := observedState.filter(isService(service.Name))
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
if len(actual) < scale {
|
||||
|
|
|
@ -23,12 +23,10 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
convert "github.com/docker/compose-cli/local/moby"
|
||||
"github.com/docker/compose-cli/progress"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
|
@ -36,9 +34,15 @@ import (
|
|||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
convert "github.com/docker/compose-cli/local/moby"
|
||||
"github.com/docker/compose-cli/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) Create(ctx context.Context, project *types.Project) error {
|
||||
func (s *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
|
||||
err := s.ensureImagesExists(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -52,8 +56,36 @@ func (s *composeService) Create(ctx context.Context, project *types.Project) err
|
|||
return err
|
||||
}
|
||||
|
||||
var observedState Containers
|
||||
observedState, err = s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(project.Name),
|
||||
),
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orphans := observedState.filter(isNotService(project.ServiceNames()...))
|
||||
if len(orphans) > 0 {
|
||||
if opts.RemoveOrphans {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
w := progress.ContextWriter(ctx)
|
||||
s.removeContainers(ctx, w, eg, orphans)
|
||||
if eg.Wait() != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("Found orphan containers (%s) for this project. If "+
|
||||
"you removed or renamed this service in your compose "+
|
||||
"file, you can run this command with the "+
|
||||
"--remove-orphans flag to clean it up.", orphans.names())
|
||||
}
|
||||
}
|
||||
|
||||
return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
|
||||
return s.ensureService(c, project, service)
|
||||
return s.ensureService(c, observedState, project, service)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,8 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
|
|||
return err
|
||||
}
|
||||
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
var containers Containers
|
||||
containers, err = s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(projectFilter(project.Name)),
|
||||
All: true,
|
||||
})
|
||||
|
@ -50,14 +51,14 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
|
|||
}
|
||||
|
||||
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
|
||||
serviceContainers, others := split(containers, isService(service.Name))
|
||||
err := s.removeContainers(ctx, w, eg, serviceContainers)
|
||||
serviceContainers, others := containers.split(isService(service.Name))
|
||||
s.removeContainers(ctx, w, eg, serviceContainers)
|
||||
containers = others
|
||||
return err
|
||||
})
|
||||
|
||||
if options.RemoveOrphans {
|
||||
err := s.removeContainers(ctx, w, eg, containers)
|
||||
s.removeContainers(ctx, w, eg, containers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -89,7 +90,7 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
|
|||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, eg *errgroup.Group, containers []moby.Container) error {
|
||||
func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, eg *errgroup.Group, containers []moby.Container) {
|
||||
for _, container := range containers {
|
||||
toDelete := container
|
||||
eg.Go(func() error {
|
||||
|
@ -110,7 +111,6 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
|||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) projectFromContainerLabels(ctx context.Context, projectName string) (*types.Project, error) {
|
||||
|
@ -160,25 +160,3 @@ func loadProjectOptionsFromLabels(c moby.Container) (*cli.ProjectOptions, error)
|
|||
cli.WithWorkingDirectory(c.Labels[workingDirLabel]),
|
||||
cli.WithName(c.Labels[projectLabel]))
|
||||
}
|
||||
|
||||
type containerPredicate func(c moby.Container) bool
|
||||
|
||||
func isService(service string) containerPredicate {
|
||||
return func(c moby.Container) bool {
|
||||
return c.Labels[serviceLabel] == service
|
||||
}
|
||||
}
|
||||
|
||||
// split return a container slice with elements to match predicate
|
||||
func split(containers []moby.Container, predicate containerPredicate) ([]moby.Container, []moby.Container) {
|
||||
var right []moby.Container
|
||||
var left []moby.Container
|
||||
for _, c := range containers {
|
||||
if predicate(c) {
|
||||
right = append(right, c)
|
||||
} else {
|
||||
left = append(left, c)
|
||||
}
|
||||
}
|
||||
return right, left
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue