Przeglądaj źródła

Merge pull request #9148 from arhemd/issue#9147

Using start, stop, restart from outside the working directory using --project-name (#9147)
Guillaume Lours 3 lat temu
rodzic
commit
9d73cc88cc

+ 1 - 1
cmd/compose/remove.go

@@ -65,7 +65,7 @@ func runRemove(ctx context.Context, backend api.Service, opts removeOptions, ser
 	}
 	}
 
 
 	if opts.stop {
 	if opts.stop {
-		err := backend.Stop(ctx, project, api.StopOptions{
+		err := backend.Stop(ctx, project.Name, api.StopOptions{
 			Services: services,
 			Services: services,
 		})
 		})
 		if err != nil {
 		if err != nil {

+ 2 - 2
cmd/compose/restart.go

@@ -49,13 +49,13 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
 }
 }
 
 
 func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
 func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
-	project, err := opts.toProject(services)
+	projectName, err := opts.toProjectName()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	timeout := time.Duration(opts.timeout) * time.Second
 	timeout := time.Duration(opts.timeout) * time.Second
-	return backend.Restart(ctx, project, api.RestartOptions{
+	return backend.Restart(ctx, projectName, api.RestartOptions{
 		Timeout:  &timeout,
 		Timeout:  &timeout,
 		Services: services,
 		Services: services,
 	})
 	})

+ 1 - 1
cmd/compose/run.go

@@ -240,5 +240,5 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
 	if err := backend.Create(ctx, &project, api.CreateOptions{}); err != nil {
 	if err := backend.Create(ctx, &project, api.CreateOptions{}); err != nil {
 		return err
 		return err
 	}
 	}
-	return backend.Start(ctx, &project, api.StartOptions{})
+	return backend.Start(ctx, project.Name, api.StartOptions{})
 }
 }

+ 4 - 2
cmd/compose/start.go

@@ -43,10 +43,12 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
 }
 }
 
 
 func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
 func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
-	project, err := opts.toProject(services)
+	projectName, err := opts.toProjectName()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	return backend.Start(ctx, project, api.StartOptions{})
+	return backend.Start(ctx, projectName, api.StartOptions{
+		AttachTo: services,
+	})
 }
 }

+ 2 - 2
cmd/compose/stop.go

@@ -53,7 +53,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
 }
 }
 
 
 func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
 func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
-	project, err := opts.toProject(services)
+	projectName, err := opts.toProjectName()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -63,7 +63,7 @@ func runStop(ctx context.Context, backend api.Service, opts stopOptions, service
 		timeoutValue := time.Duration(opts.timeout) * time.Second
 		timeoutValue := time.Duration(opts.timeout) * time.Second
 		timeout = &timeoutValue
 		timeout = &timeoutValue
 	}
 	}
-	return backend.Stop(ctx, project, api.StopOptions{
+	return backend.Stop(ctx, projectName, api.StopOptions{
 		Timeout:  timeout,
 		Timeout:  timeout,
 		Services: services,
 		Services: services,
 	})
 	})

+ 3 - 3
pkg/api/api.go

@@ -37,11 +37,11 @@ type Service interface {
 	// Create executes the equivalent to a `compose create`
 	// Create executes the equivalent to a `compose create`
 	Create(ctx context.Context, project *types.Project, opts CreateOptions) 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, options StartOptions) error
+	Start(ctx context.Context, projectName string, options StartOptions) error
 	// Restart restarts containers
 	// Restart restarts containers
-	Restart(ctx context.Context, project *types.Project, options RestartOptions) error
+	Restart(ctx context.Context, projectName string, options RestartOptions) error
 	// Stop executes the equivalent to a `compose stop`
 	// Stop executes the equivalent to a `compose stop`
-	Stop(ctx context.Context, project *types.Project, options StopOptions) error
+	Stop(ctx context.Context, projectName string, options StopOptions) error
 	// Up executes the equivalent to a `compose up`
 	// Up executes the equivalent to a `compose up`
 	Up(ctx context.Context, project *types.Project, options UpOptions) error
 	Up(ctx context.Context, project *types.Project, options UpOptions) error
 	// Down executes the equivalent to a `compose down`
 	// Down executes the equivalent to a `compose down`

+ 9 - 18
pkg/api/proxy.go

@@ -28,9 +28,9 @@ type ServiceProxy struct {
 	PushFn               func(ctx context.Context, project *types.Project, options PushOptions) error
 	PushFn               func(ctx context.Context, project *types.Project, options PushOptions) error
 	PullFn               func(ctx context.Context, project *types.Project, opts PullOptions) error
 	PullFn               func(ctx context.Context, project *types.Project, opts PullOptions) error
 	CreateFn             func(ctx context.Context, project *types.Project, opts CreateOptions) error
 	CreateFn             func(ctx context.Context, project *types.Project, opts CreateOptions) error
-	StartFn              func(ctx context.Context, project *types.Project, options StartOptions) error
-	RestartFn            func(ctx context.Context, project *types.Project, options RestartOptions) error
-	StopFn               func(ctx context.Context, project *types.Project, options StopOptions) error
+	StartFn              func(ctx context.Context, projectName string, options StartOptions) error
+	RestartFn            func(ctx context.Context, projectName string, options RestartOptions) error
+	StopFn               func(ctx context.Context, projectName string, options StopOptions) error
 	UpFn                 func(ctx context.Context, project *types.Project, options UpOptions) error
 	UpFn                 func(ctx context.Context, project *types.Project, options UpOptions) error
 	DownFn               func(ctx context.Context, projectName string, options DownOptions) error
 	DownFn               func(ctx context.Context, projectName string, options DownOptions) error
 	LogsFn               func(ctx context.Context, projectName string, consumer LogConsumer, options LogOptions) error
 	LogsFn               func(ctx context.Context, projectName string, consumer LogConsumer, options LogOptions) error
@@ -141,36 +141,27 @@ func (s *ServiceProxy) Create(ctx context.Context, project *types.Project, optio
 }
 }
 
 
 // Start implements Service interface
 // Start implements Service interface
-func (s *ServiceProxy) Start(ctx context.Context, project *types.Project, options StartOptions) error {
+func (s *ServiceProxy) Start(ctx context.Context, projectName string, options StartOptions) error {
 	if s.StartFn == nil {
 	if s.StartFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	for _, i := range s.interceptors {
-		i(ctx, project)
-	}
-	return s.StartFn(ctx, project, options)
+	return s.StartFn(ctx, projectName, options)
 }
 }
 
 
 // Restart implements Service interface
 // Restart implements Service interface
-func (s *ServiceProxy) Restart(ctx context.Context, project *types.Project, options RestartOptions) error {
+func (s *ServiceProxy) Restart(ctx context.Context, projectName string, options RestartOptions) error {
 	if s.RestartFn == nil {
 	if s.RestartFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	for _, i := range s.interceptors {
-		i(ctx, project)
-	}
-	return s.RestartFn(ctx, project, options)
+	return s.RestartFn(ctx, projectName, options)
 }
 }
 
 
 // Stop implements Service interface
 // Stop implements Service interface
-func (s *ServiceProxy) Stop(ctx context.Context, project *types.Project, options StopOptions) error {
+func (s *ServiceProxy) Stop(ctx context.Context, projectName string, options StopOptions) error {
 	if s.StopFn == nil {
 	if s.StopFn == nil {
 		return ErrNotImplemented
 		return ErrNotImplemented
 	}
 	}
-	for _, i := range s.interceptors {
-		i(ctx, project)
-	}
-	return s.StopFn(ctx, project, options)
+	return s.StopFn(ctx, projectName, options)
 }
 }
 
 
 // Up implements Service interface
 // Up implements Service interface

+ 57 - 0
pkg/compose/compose.go

@@ -24,6 +24,7 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
+	"github.com/pkg/errors"
 
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/cli/cli/config/configfile"
 	"github.com/docker/cli/cli/config/configfile"
@@ -92,3 +93,59 @@ func escapeDollarSign(marshal []byte) []byte {
 	escDollar := []byte{'$', '$'}
 	escDollar := []byte{'$', '$'}
 	return bytes.ReplaceAll(marshal, dollar, escDollar)
 	return bytes.ReplaceAll(marshal, dollar, escDollar)
 }
 }
+
+// projectFromName builds a types.Project based on actual resources with compose labels set
+func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
+	project := &types.Project{
+		Name: projectName,
+	}
+	if len(containers) == 0 {
+		return project, errors.New("no such project: " + projectName)
+	}
+	set := map[string]*types.ServiceConfig{}
+	for _, c := range containers {
+		serviceLabel := c.Labels[api.ServiceLabel]
+		_, ok := set[serviceLabel]
+		if !ok {
+			set[serviceLabel] = &types.ServiceConfig{
+				Name:   serviceLabel,
+				Image:  c.Image,
+				Labels: c.Labels,
+			}
+		}
+		set[serviceLabel].Scale++
+	}
+	for _, service := range set {
+		dependencies := service.Labels[api.DependenciesLabel]
+		if len(dependencies) > 0 {
+			service.DependsOn = types.DependsOnConfig{}
+			for _, dc := range strings.Split(dependencies, ",") {
+				dcArr := strings.Split(dc, ":")
+				condition := ServiceConditionRunningOrHealthy
+				dependency := dcArr[0]
+
+				// backward compatibility
+				if len(dcArr) > 1 {
+					condition = dcArr[1]
+				}
+				service.DependsOn[dependency] = types.ServiceDependency{Condition: condition}
+			}
+		}
+		project.Services = append(project.Services, *service)
+	}
+SERVICES:
+	for _, qs := range services {
+		for _, es := range project.Services {
+			if es.Name == qs {
+				continue SERVICES
+			}
+		}
+		return project, errors.New("no such service: " + qs)
+	}
+	err := project.ForServices(services)
+	if err != nil {
+		return project, err
+	}
+
+	return project, nil
+}

+ 2 - 2
pkg/compose/create.go

@@ -432,8 +432,8 @@ func (s *composeService) prepareLabels(service types.ServiceConfig, number int)
 	labels[api.ContainerNumberLabel] = strconv.Itoa(number)
 	labels[api.ContainerNumberLabel] = strconv.Itoa(number)
 
 
 	var dependencies []string
 	var dependencies []string
-	for s := range service.DependsOn {
-		dependencies = append(dependencies, s)
+	for s, d := range service.DependsOn {
+		dependencies = append(dependencies, s+":"+d.Condition)
 	}
 	}
 	labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
 	labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
 	return labels, nil
 	return labels, nil

+ 6 - 31
pkg/compose/down.go

@@ -41,6 +41,7 @@ func (s *composeService) Down(ctx context.Context, projectName string, options a
 }
 }
 
 
 func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
 func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
+	builtFromResources := options.Project == nil
 	w := progress.ContextWriter(ctx)
 	w := progress.ContextWriter(ctx)
 	resourceToRemove := false
 	resourceToRemove := false
 
 
@@ -50,8 +51,8 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
 		return err
 		return err
 	}
 	}
 
 
-	if options.Project == nil {
-		options.Project, err = s.projectFromLabels(ctx, containers.filter(isNotOneOff), projectName)
+	if builtFromResources {
+		options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -232,34 +233,9 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
 	return eg.Wait()
 	return eg.Wait()
 }
 }
 
 
-// projectFromLabels builds a types.Project based on actual resources with compose labels set
-func (s *composeService) projectFromLabels(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
-	project := &types.Project{
-		Name: projectName,
-	}
-	if len(containers) == 0 {
-		return project, nil
-	}
-	set := map[string]moby.Container{}
-	for _, c := range containers {
-		set[c.Labels[api.ServiceLabel]] = c
-	}
-	for s, c := range set {
-		service := types.ServiceConfig{
-			Name:   s,
-			Image:  c.Image,
-			Labels: c.Labels,
-		}
-		dependencies := c.Labels[api.DependenciesLabel]
-		if len(dependencies) > 0 {
-			service.DependsOn = types.DependsOnConfig{}
-			for _, d := range strings.Split(dependencies, ",") {
-				service.DependsOn[d] = types.ServiceDependency{}
-			}
-		}
-		project.Services = append(project.Services, service)
-	}
-
+func (s *composeService) getProjectWithVolumes(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
+	containers = containers.filter(isNotOneOff)
+	project, _ := s.projectFromName(containers, projectName)
 	volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
 	volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -273,6 +249,5 @@ func (s *composeService) projectFromLabels(ctx context.Context, containers Conta
 			Labels: vol.Labels,
 			Labels: vol.Labels,
 		}
 		}
 	}
 	}
-
 	return project, nil
 	return project, nil
 }
 }

+ 10 - 5
pkg/compose/restart.go

@@ -19,7 +19,6 @@ package compose
 import (
 import (
 	"context"
 	"context"
 
 
-	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 
 
@@ -27,14 +26,20 @@ import (
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
 )
 )
 
 
-func (s *composeService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error {
+func (s *composeService) Restart(ctx context.Context, projectName string, options api.RestartOptions) error {
 	return progress.Run(ctx, func(ctx context.Context) error {
 	return progress.Run(ctx, func(ctx context.Context) error {
-		return s.restart(ctx, project, options)
+		return s.restart(ctx, projectName, options)
 	})
 	})
 }
 }
 
 
-func (s *composeService) restart(ctx context.Context, project *types.Project, options api.RestartOptions) error {
-	observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
+func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error {
+
+	observedState, err := s.getContainers(ctx, projectName, oneOffInclude, true)
+	if err != nil {
+		return err
+	}
+
+	project, err := s.projectFromName(observedState, projectName, options.Services...)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 13 - 6
pkg/compose/start.go

@@ -28,15 +28,22 @@ import (
 	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
-func (s *composeService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error {
+func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error {
 	return progress.Run(ctx, func(ctx context.Context) error {
 	return progress.Run(ctx, func(ctx context.Context) error {
-		return s.start(ctx, project, options, nil)
+		return s.start(ctx, projectName, options, nil)
 	})
 	})
 }
 }
 
 
-func (s *composeService) start(ctx context.Context, project *types.Project, options api.StartOptions, listener api.ContainerEventListener) error {
-	if len(options.AttachTo) == 0 {
-		options.AttachTo = project.ServiceNames()
+func (s *composeService) start(ctx context.Context, projectName string, options api.StartOptions, listener api.ContainerEventListener) error {
+	var containers Containers
+	containers, err := s.getContainers(ctx, projectName, oneOffExclude, true)
+	if err != nil {
+		return err
+	}
+
+	project, err := s.projectFromName(containers, projectName, options.AttachTo...)
+	if err != nil {
+		return err
 	}
 	}
 
 
 	eg, ctx := errgroup.WithContext(ctx)
 	eg, ctx := errgroup.WithContext(ctx)
@@ -53,7 +60,7 @@ func (s *composeService) start(ctx context.Context, project *types.Project, opti
 		})
 		})
 	}
 	}
 
 
-	err := InDependencyOrder(ctx, project, func(c context.Context, name string) error {
+	err = InDependencyOrder(ctx, project, func(c context.Context, name string) error {
 		service, err := project.GetService(name)
 		service, err := project.GetService(name)
 		if err != nil {
 		if err != nil {
 			return err
 			return err

+ 11 - 7
pkg/compose/stop.go

@@ -21,25 +21,29 @@ import (
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/progress"
-
-	"github.com/compose-spec/compose-go/types"
 )
 )
 
 
-func (s *composeService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error {
+func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
 	return progress.Run(ctx, func(ctx context.Context) error {
 	return progress.Run(ctx, func(ctx context.Context) error {
-		return s.stop(ctx, project, options)
+		return s.stop(ctx, projectName, options)
 	})
 	})
 }
 }
 
 
-func (s *composeService) stop(ctx context.Context, project *types.Project, options api.StopOptions) error {
+func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {
 	w := progress.ContextWriter(ctx)
 	w := progress.ContextWriter(ctx)
 
 
 	services := options.Services
 	services := options.Services
 	if len(services) == 0 {
 	if len(services) == 0 {
-		services = project.ServiceNames()
+		services = []string{}
 	}
 	}
+
 	var containers Containers
 	var containers Containers
-	containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true, services...)
+	containers, err := s.getContainers(ctx, projectName, oneOffInclude, true, services...)
+	if err != nil {
+		return err
+	}
+
+	project, err := s.projectFromName(containers, projectName, services...)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 1 - 8
pkg/compose/stop_test.go

@@ -25,7 +25,6 @@ import (
 	compose "github.com/docker/compose/v2/pkg/api"
 	compose "github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/mocks"
 	"github.com/docker/compose/v2/pkg/mocks"
 
 
-	"github.com/compose-spec/compose-go/types"
 	moby "github.com/docker/docker/api/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/golang/mock/gomock"
 	"github.com/golang/mock/gomock"
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/assert"
@@ -50,13 +49,7 @@ func TestStopTimeout(t *testing.T) {
 	api.EXPECT().ContainerStop(gomock.Any(), "456", &timeout).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "456", &timeout).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "789", &timeout).Return(nil)
 	api.EXPECT().ContainerStop(gomock.Any(), "789", &timeout).Return(nil)
 
 
-	err := tested.Stop(ctx, &types.Project{
-		Name: strings.ToLower(testProject),
-		Services: []types.ServiceConfig{
-			{Name: "service1"},
-			{Name: "service2"},
-		},
-	}, compose.StopOptions{
+	err := tested.Stop(ctx, strings.ToLower(testProject), compose.StopOptions{
 		Timeout: &timeout,
 		Timeout: &timeout,
 	})
 	})
 	assert.NilError(t, err)
 	assert.NilError(t, err)

+ 3 - 3
pkg/compose/up.go

@@ -38,7 +38,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 			return err
 			return err
 		}
 		}
 		if options.Start.Attach == nil {
 		if options.Start.Attach == nil {
-			return s.start(ctx, project, options.Start, nil)
+			return s.start(ctx, project.Name, options.Start, nil)
 		}
 		}
 		return nil
 		return nil
 	})
 	})
@@ -65,7 +65,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 				})
 				})
 			}()
 			}()
 
 
-			return s.Stop(ctx, project, api.StopOptions{
+			return s.Stop(ctx, project.Name, api.StopOptions{
 				Services: options.Create.Services,
 				Services: options.Create.Services,
 			})
 			})
 		})
 		})
@@ -85,7 +85,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 		return err
 		return err
 	})
 	})
 
 
-	err = s.start(ctx, project, options.Start, printer.HandleEvent)
+	err = s.start(ctx, project.Name, options.Start, printer.HandleEvent)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}