mirror of
https://github.com/docker/compose.git
synced 2025-07-23 05:34:36 +02:00
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
|
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
|
return errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ func (c *composeService) Pull(ctx context.Context, project *types.Project) error
|
|||||||
return errdefs.ErrNotImplemented
|
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
|
return errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ type Service interface {
|
|||||||
// Pull executes the equivalent of a `compose pull`
|
// Pull executes the equivalent of a `compose pull`
|
||||||
Pull(ctx context.Context, project *types.Project) error
|
Pull(ctx context.Context, project *types.Project) error
|
||||||
// Create executes the equivalent to a `compose create`
|
// 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 executes the equivalent to a `compose start`
|
||||||
Start(ctx context.Context, project *types.Project, consumer LogConsumer) error
|
Start(ctx context.Context, project *types.Project, consumer LogConsumer) error
|
||||||
// Up executes the equivalent to a `compose up`
|
// 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
|
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
|
// UpOptions group options of the Up API
|
||||||
type UpOptions struct {
|
type UpOptions struct {
|
||||||
// Detach will create services and return immediately
|
// 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
|
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
|
return err
|
||||||
}
|
}
|
||||||
if err := c.ComposeService().Start(ctx, project, nil); err != nil {
|
if err := c.ComposeService().Start(ctx, project, nil); err != nil {
|
||||||
|
@ -33,8 +33,13 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type upOptions struct {
|
||||||
|
composeOptions
|
||||||
|
removeOrphans bool
|
||||||
|
}
|
||||||
|
|
||||||
func upCommand(contextType string) *cobra.Command {
|
func upCommand(contextType string) *cobra.Command {
|
||||||
opts := composeOptions{}
|
opts := upOptions{}
|
||||||
upCmd := &cobra.Command{
|
upCmd := &cobra.Command{
|
||||||
Use: "up [SERVICE...]",
|
Use: "up [SERVICE...]",
|
||||||
Short: "Create and start containers",
|
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().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().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.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 {
|
if contextType == store.AciContextType {
|
||||||
upCmd.Flags().StringVar(&opts.DomainName, "domainname", "", "Container NIS domain name")
|
upCmd.Flags().StringVar(&opts.DomainName, "domainname", "", "Container NIS domain name")
|
||||||
@ -61,8 +67,8 @@ func upCommand(contextType string) *cobra.Command {
|
|||||||
return upCmd
|
return upCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUp(ctx context.Context, opts composeOptions, services []string) error {
|
func runUp(ctx context.Context, opts upOptions, services []string) error {
|
||||||
c, project, err := setup(ctx, opts, services)
|
c, project, err := setup(ctx, opts.composeOptions, services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -75,14 +81,16 @@ func runUp(ctx context.Context, opts composeOptions, services []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreateStart(ctx context.Context, opts composeOptions, services []string) error {
|
func runCreateStart(ctx context.Context, opts upOptions, services []string) error {
|
||||||
c, project, err := setup(ctx, opts, services)
|
c, project, err := setup(ctx, opts.composeOptions, services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
|
_, 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -43,13 +43,13 @@ func (e ecsLocalSimulation) Pull(ctx context.Context, project *types.Project) er
|
|||||||
return e.compose.Pull(ctx, project)
|
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)
|
enhanced, err := e.enhanceForLocalSimulation(project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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
|
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
|
return errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ func (cs *composeService) Pull(ctx context.Context, project *types.Project) erro
|
|||||||
return errdefs.ErrNotImplemented
|
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
|
return errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
72
local/compose/containers.go
Normal file
72
local/compose/containers.go
Normal file
@ -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"
|
forceRecreate = "force_recreate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *composeService) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
func (s *composeService) ensureService(ctx context.Context, observedState Containers, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
scale := getScale(service)
|
scale := getScale(service)
|
||||||
|
actual := observedState.filter(isService(service.Name))
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
if len(actual) < scale {
|
if len(actual) < scale {
|
||||||
|
@ -23,12 +23,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
convert "github.com/docker/compose-cli/local/moby"
|
|
||||||
"github.com/docker/compose-cli/progress"
|
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
moby "github.com/docker/docker/api/types"
|
moby "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"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/mount"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/strslice"
|
"github.com/docker/docker/api/types/strslice"
|
||||||
@ -36,9 +34,15 @@ import (
|
|||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/pkg/errors"
|
"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)
|
err := s.ensureImagesExists(ctx, project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -52,8 +56,36 @@ func (s *composeService) Create(ctx context.Context, project *types.Project) err
|
|||||||
return 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 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
|
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)),
|
Filters: filters.NewArgs(projectFilter(project.Name)),
|
||||||
All: true,
|
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 {
|
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
|
||||||
serviceContainers, others := split(containers, isService(service.Name))
|
serviceContainers, others := containers.split(isService(service.Name))
|
||||||
err := s.removeContainers(ctx, w, eg, serviceContainers)
|
s.removeContainers(ctx, w, eg, serviceContainers)
|
||||||
containers = others
|
containers = others
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
if options.RemoveOrphans {
|
if options.RemoveOrphans {
|
||||||
err := s.removeContainers(ctx, w, eg, containers)
|
s.removeContainers(ctx, w, eg, containers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -89,7 +90,7 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
|
|||||||
return eg.Wait()
|
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 {
|
for _, container := range containers {
|
||||||
toDelete := container
|
toDelete := container
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
@ -110,7 +111,6 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) projectFromContainerLabels(ctx context.Context, projectName string) (*types.Project, error) {
|
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.WithWorkingDirectory(c.Labels[workingDirLabel]),
|
||||||
cli.WithName(c.Labels[projectLabel]))
|
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…
x
Reference in New Issue
Block a user