Bladeren bron

Merge branch 'v2' into 8768-avoid-pulling-same-image-multiple-times

Vedant Koditkar 3 jaren geleden
bovenliggende
commit
89dfb9140e

+ 3 - 3
.github/workflows/ci.yml

@@ -28,9 +28,9 @@ jobs:
       - name: Run golangci-lint
         env:
           BUILD_TAGS: e2e
-        run: |
-          curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b /usr/bin/ v1.39.0
-          make -f builder.Makefile lint
+        uses: golangci/golangci-lint-action@v2
+        with:
+          args: --timeout=180s
 
   # only on main branch, costs too much for the gain on every PR
   validate-cross-build:

+ 19 - 18
cmd/compose/compose.go

@@ -120,24 +120,6 @@ func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Co
 			return err
 		}
 
-		if o.EnvFile != "" {
-			var services types.Services
-			for _, s := range project.Services {
-				ef := o.EnvFile
-				if ef != "" {
-					if !filepath.IsAbs(ef) {
-						ef = filepath.Join(project.WorkingDir, o.EnvFile)
-					}
-					if s.Labels == nil {
-						s.Labels = make(map[string]string)
-					}
-					s.Labels[api.EnvironmentFileLabel] = ef
-					services = append(services, s)
-				}
-			}
-			project.Services = services
-		}
-
 		return fn(ctx, project, args)
 	})
 }
@@ -180,6 +162,25 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
 		compose.Separator = "_"
 	}
 
+	ef := o.EnvFile
+	if ef != "" && !filepath.IsAbs(ef) {
+		ef = filepath.Join(project.WorkingDir, o.EnvFile)
+	}
+	for i, s := range project.Services {
+		s.CustomLabels = map[string]string{
+			api.ProjectLabel:     project.Name,
+			api.ServiceLabel:     s.Name,
+			api.VersionLabel:     api.ComposeVersion,
+			api.WorkingDirLabel:  project.WorkingDir,
+			api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
+			api.OneoffLabel:      "False", // default, will be overridden by `run` command
+		}
+		if ef != "" {
+			s.CustomLabels[api.EnvironmentFileLabel] = ef
+		}
+		project.Services[i] = s
+	}
+
 	if len(services) > 0 {
 		s, err := project.GetServices(services...)
 		if err != nil {

+ 14 - 1
cmd/compose/down.go

@@ -19,10 +19,14 @@ package compose
 import (
 	"context"
 	"fmt"
+	"os"
+	"strings"
 	"time"
 
 	"github.com/compose-spec/compose-go/types"
+	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
 
 	"github.com/docker/compose/v2/pkg/api"
 )
@@ -58,10 +62,19 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
 		ValidArgsFunction: noCompletion(),
 	}
 	flags := downCmd.Flags()
-	flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
+	removeOrphans := strings.ToLower(os.Getenv("COMPOSE_REMOVE_ORPHANS ")) == "true"
+	flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
 	flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
 	flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
 	flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
+	flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
+		switch name {
+		case "volume":
+			name = "volumes"
+			logrus.Warn("--volume is deprecated, please use --volumes")
+		}
+		return pflag.NormalizedName(name)
+	})
 	return downCmd
 }
 

+ 8 - 6
cmd/compose/list.go

@@ -92,22 +92,24 @@ func runList(ctx context.Context, backend api.Service, opts lsOptions) error {
 	view := viewFromStackList(stackList)
 	return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) {
 		for _, stack := range view {
-			_, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status)
+			_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
 		}
-	}, "NAME", "STATUS")
+	}, "NAME", "STATUS", "CONFIG FILES")
 }
 
 type stackView struct {
-	Name   string
-	Status string
+	Name        string
+	Status      string
+	ConfigFiles string
 }
 
 func viewFromStackList(stackList []api.Stack) []stackView {
 	retList := make([]stackView, len(stackList))
 	for i, s := range stackList {
 		retList[i] = stackView{
-			Name:   s.Name,
-			Status: strings.TrimSpace(fmt.Sprintf("%s %s", s.Status, s.Reason)),
+			Name:        s.Name,
+			Status:      strings.TrimSpace(fmt.Sprintf("%s %s", s.Status, s.Reason)),
+			ConfigFiles: s.ConfigFiles,
 		}
 	}
 	return retList

+ 1 - 1
cmd/compose/remove.go

@@ -65,7 +65,7 @@ func runRemove(ctx context.Context, backend api.Service, opts removeOptions, ser
 	}
 
 	if opts.stop {
-		err := backend.Stop(ctx, project, api.StopOptions{
+		err := backend.Stop(ctx, project.Name, api.StopOptions{
 			Services: services,
 		})
 		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 {
-	project, err := opts.toProject(services)
+	projectName, err := opts.toProjectName()
 	if err != nil {
 		return err
 	}
 
 	timeout := time.Duration(opts.timeout) * time.Second
-	return backend.Restart(ctx, project, api.RestartOptions{
+	return backend.Restart(ctx, projectName, api.RestartOptions{
 		Timeout:  &timeout,
 		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 {
 		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 {
-	project, err := opts.toProject(services)
+	projectName, err := opts.toProjectName()
 	if err != nil {
 		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 {
-	project, err := opts.toProject(services)
+	projectName, err := opts.toProjectName()
 	if err != nil {
 		return err
 	}
@@ -63,7 +63,7 @@ func runStop(ctx context.Context, backend api.Service, opts stopOptions, service
 		timeoutValue := time.Duration(opts.timeout) * time.Second
 		timeout = &timeoutValue
 	}
-	return backend.Stop(ctx, project, api.StopOptions{
+	return backend.Stop(ctx, projectName, api.StopOptions{
 		Timeout:  timeout,
 		Services: services,
 	})

+ 10 - 1
docs/yaml/main/generate.go

@@ -32,7 +32,16 @@ func generateCliYaml(opts *options) error {
 	disableFlagsInUseLine(cmd)
 
 	cmd.DisableAutoGenTag = true
-	return clidocstool.GenYamlTree(cmd, opts.target)
+	tool, err := clidocstool.New(clidocstool.Options{
+		Root:      cmd,
+		SourceDir: opts.source,
+		TargetDir: opts.target,
+		Plugin:    true,
+	})
+	if err != nil {
+		return err
+	}
+	return tool.GenYamlTree(cmd)
 }
 
 func disableFlagsInUseLine(cmd *cobra.Command) {

+ 56 - 41
go.mod

@@ -6,13 +6,13 @@ require (
 	github.com/AlecAivazis/survey/v2 v2.3.2
 	github.com/buger/goterm v1.0.4
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
-	github.com/compose-spec/compose-go v1.0.9
+	github.com/compose-spec/compose-go v1.1.0
 	github.com/containerd/console v1.0.3
-	github.com/containerd/containerd v1.5.8
+	github.com/containerd/containerd v1.6.0
 	github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
-	github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc
-	github.com/docker/cli v20.10.7+incompatible
-	github.com/docker/cli-docs-tool v0.1.1
+	github.com/docker/buildx v0.7.1
+	github.com/docker/cli v20.10.12+incompatible
+	github.com/docker/cli-docs-tool v0.2.1
 	github.com/docker/docker v20.10.7+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/docker/go-units v0.4.0
@@ -21,7 +21,7 @@ require (
 	github.com/hashicorp/go-version v1.3.0
 	github.com/mattn/go-isatty v0.0.14
 	github.com/mattn/go-shellwords v1.0.12
-	github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf
+	github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da
 	github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
 	github.com/morikuni/aec v1.0.0
 	github.com/opencontainers/go-digest v1.0.0
@@ -40,41 +40,38 @@ require (
 require (
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Masterminds/semver v1.5.0 // indirect
-	github.com/Microsoft/go-winio v0.4.17 // indirect
-	github.com/Microsoft/hcsshim v0.8.23 // indirect
+	github.com/Microsoft/go-winio v0.5.1 // indirect
 	github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
-	github.com/containerd/cgroups v1.0.1 // indirect
-	github.com/containerd/continuity v0.1.0 // indirect
+	github.com/containerd/continuity v0.2.2 // indirect
 	github.com/containerd/typeurl v1.0.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/docker/distribution v2.7.1+incompatible // indirect
-	github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a // indirect
+	github.com/docker/distribution v2.8.0+incompatible // indirect
+	github.com/docker/docker-credential-helpers v0.6.4 // indirect
 	github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
 	github.com/docker/go-metrics v0.0.1 // indirect
+	github.com/felixge/httpsnoop v1.0.2 // indirect
 	github.com/fvbommel/sortorder v1.0.1 // indirect
-	github.com/go-logr/logr v0.4.0 // indirect
+	github.com/go-logr/logr v1.2.2 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/gofrs/flock v0.8.0 // indirect
 	github.com/gogo/googleapis v1.4.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
-	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-cmp v0.5.6 // indirect
-	github.com/google/gofuzz v1.1.0 // indirect
+	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/gorilla/mux v1.8.0 // indirect
-	github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
-	github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
-	github.com/hashicorp/errwrap v1.0.0 // indirect
+	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
+	github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
+	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/imdario/mergo v0.3.12 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
-	github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
-	github.com/klauspost/compress v1.11.13 // indirect
-	github.com/kr/pty v1.1.8 // indirect
+	github.com/klauspost/compress v1.13.5 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
@@ -82,48 +79,66 @@ require (
 	github.com/mitchellh/mapstructure v1.4.3 // indirect
 	github.com/moby/locker v1.0.1 // indirect
 	github.com/moby/sys/mount v0.2.0 // indirect
-	github.com/moby/sys/mountinfo v0.4.1 // indirect
-	github.com/moby/sys/symlink v0.1.0 // indirect
+	github.com/moby/sys/mountinfo v0.5.0 // indirect
+	github.com/moby/sys/signal v0.6.0 // indirect
+	github.com/moby/sys/symlink v0.2.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/opencontainers/runc v1.0.2 // indirect
-	github.com/opentracing/opentracing-go v1.2.0 // indirect
+	github.com/opencontainers/runc v1.1.0 // indirect
+	github.com/pelletier/go-toml v1.9.4 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_golang v1.7.1 // indirect
+	github.com/prometheus/client_golang v1.11.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
-	github.com/prometheus/common v0.10.0 // indirect
-	github.com/prometheus/procfs v0.6.0 // indirect
+	github.com/prometheus/common v0.30.0 // indirect
+	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/qri-io/jsonpointer v0.1.0 // indirect
 	github.com/qri-io/jsonschema v0.1.1 // indirect
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/theupdateframework/notary v0.6.1 // indirect
-	github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 // indirect
+	github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect
 	github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
+	github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
-	go.opencensus.io v0.23.0 // indirect
+	go.opentelemetry.io/contrib v0.21.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0 // indirect
+	go.opentelemetry.io/otel v1.3.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect
+	go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect
+	go.opentelemetry.io/otel/metric v0.21.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.3.0 // indirect
+	go.opentelemetry.io/otel/trace v1.3.0 // indirect
+	go.opentelemetry.io/proto/otlp v0.11.0 // indirect
 	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
-	golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
+	golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
-	golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
-	golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
+	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
+	golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
 	golang.org/x/text v0.3.7 // indirect
-	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
+	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
-	google.golang.org/grpc v1.42.0 // indirect
+	google.golang.org/grpc v1.43.0 // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
-	k8s.io/apimachinery v0.21.0 // indirect
-	k8s.io/client-go v0.21.0 // indirect
-	k8s.io/klog/v2 v2.8.0 // indirect
-	k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
-	sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect
+	k8s.io/apimachinery v0.22.5 // indirect
+	k8s.io/client-go v0.22.5 // indirect
+	k8s.io/klog/v2 v2.30.0 // indirect
+	k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
 	sigs.k8s.io/yaml v1.2.0 // indirect
 )
 
 // (for buildx)
-replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
+replace (
+	github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible
+	github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939
+	go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/httptrace/otelhttptrace v0.0.0-20210714055410-d010b05b4939
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/otelhttp v0.0.0-20210714055410-d010b05b4939
+)

File diff suppressed because it is too large
+ 337 - 49
go.sum


+ 8 - 7
pkg/api/api.go

@@ -37,11 +37,11 @@ type Service interface {
 	// Create executes the equivalent to a `compose create`
 	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, options StartOptions) error
+	Start(ctx context.Context, projectName string, options StartOptions) error
 	// 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(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(ctx context.Context, project *types.Project, options UpOptions) error
 	// Down executes the equivalent to a `compose down`
@@ -404,10 +404,11 @@ const (
 
 // Stack holds the name and state of a compose application/stack
 type Stack struct {
-	ID     string
-	Name   string
-	Status string
-	Reason string
+	ID          string
+	Name        string
+	Status      string
+	ConfigFiles string
+	Reason      string
 }
 
 // LogConsumer is a callback to process log messages from services

+ 9 - 18
pkg/api/proxy.go

@@ -28,9 +28,9 @@ type ServiceProxy struct {
 	PushFn               func(ctx context.Context, project *types.Project, options PushOptions) error
 	PullFn               func(ctx context.Context, project *types.Project, opts PullOptions) 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
 	DownFn               func(ctx context.Context, projectName string, options DownOptions) 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
-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 {
 		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
-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 {
 		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
-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 {
 		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

+ 1 - 1
pkg/compose/build.go

@@ -141,7 +141,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
 			if project.Services[i].Labels == nil {
 				project.Services[i].Labels = types.Labels{}
 			}
-			project.Services[i].Labels[api.ImageDigestLabel] = digest
+			project.Services[i].CustomLabels[api.ImageDigestLabel] = digest
 			project.Services[i].Image = image
 		}
 	}

+ 3 - 2
pkg/compose/build_buildkit.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 	"os"
+	"path/filepath"
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/buildx/build"
@@ -28,7 +29,7 @@ import (
 
 func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
 	const drivername = "default"
-	d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, "", nil, nil, project.WorkingDir)
+	d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, nil, nil, nil, project.WorkingDir)
 	if err != nil {
 		return nil, err
 	}
@@ -47,7 +48,7 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
 	w := xprogress.NewPrinter(progressCtx, os.Stdout, mode)
 
 	// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
-	response, err := build.Build(ctx, driverInfo, opts, nil, nil, w)
+	response, err := build.Build(ctx, driverInfo, opts, nil, filepath.Dir(s.configFile.Filename), w)
 	errW := w.Wait()
 	if err == nil {
 		err = errW

+ 57 - 0
pkg/compose/compose.go

@@ -24,6 +24,7 @@ import (
 	"strings"
 
 	"github.com/docker/compose/v2/pkg/api"
+	"github.com/pkg/errors"
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/cli/cli/config/configfile"
@@ -92,3 +93,59 @@ func escapeDollarSign(marshal []byte) []byte {
 	escDollar := []byte{'$', '$'}
 	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
+}

+ 18 - 3
pkg/compose/convergence.go

@@ -276,9 +276,10 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 	eg, _ := errgroup.WithContext(ctx)
 	w := progress.ContextWriter(ctx)
 	for dep, config := range dependencies {
-		if config.Condition == types.ServiceConditionStarted {
-			// already managed by InDependencyOrder
-			return nil
+		if shouldWait, err := shouldWaitForDependency(dep, config, project); err != nil {
+			return err
+		} else if !shouldWait {
+			continue
 		}
 
 		containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, dep)
@@ -334,6 +335,20 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 	return eg.Wait()
 }
 
+func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceDependency, project *types.Project) (bool, error) {
+	if dependencyConfig.Condition == types.ServiceConditionStarted {
+		// already managed by InDependencyOrder
+		return false, nil
+	}
+	if service, err := project.GetService(serviceName); err != nil {
+		return false, err
+	} else if service.Scale == 0 {
+		// don't wait for the dependency which configured to have 0 containers running
+		return false, nil
+	}
+	return true, nil
+}
+
 func nextContainerNumber(containers []moby.Container) (int, error) {
 	max := 0
 	for _, c := range containers {

+ 29 - 0
pkg/compose/convergence_test.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/compose-spec/compose-go/types"
@@ -184,3 +185,31 @@ func TestServiceLinks(t *testing.T) {
 		assert.Equal(t, links[2], "testProject-web-1:testProject-web-1")
 	})
 }
+
+func TestWaitDependencies(t *testing.T) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+	api := mocks.NewMockAPIClient(mockCtrl)
+	tested.apiClient = api
+
+	t.Run("should skip dependencies with scale 0", func(t *testing.T) {
+		dbService := types.ServiceConfig{Name: "db", Scale: 0}
+		redisService := types.ServiceConfig{Name: "redis", Scale: 0}
+		project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
+		dependencies := types.DependsOnConfig{
+			"db":    {Condition: ServiceConditionRunningOrHealthy},
+			"redis": {Condition: ServiceConditionRunningOrHealthy},
+		}
+		assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
+	})
+	t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
+		dbService := types.ServiceConfig{Name: "db", Scale: 1}
+		redisService := types.ServiceConfig{Name: "redis", Scale: 1}
+		project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
+		dependencies := types.DependsOnConfig{
+			"db":    {Condition: types.ServiceConditionStarted},
+			"redis": {Condition: types.ServiceConditionStarted},
+		}
+		assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
+	})
+}

+ 12 - 21
pkg/compose/create.go

@@ -229,7 +229,7 @@ func getImageName(service types.ServiceConfig, projectName string) string {
 func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig,
 	number int, inherit *moby.Container, autoRemove bool, attachStdin bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
 
-	labels, err := s.prepareLabels(p, service, number)
+	labels, err := s.prepareLabels(service, number)
 	if err != nil {
 		return nil, nil, nil, err
 	}
@@ -414,31 +414,26 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, error
 	return securityOpts, nil
 }
 
-func (s *composeService) prepareLabels(p *types.Project, service types.ServiceConfig, number int) (map[string]string, error) {
+func (s *composeService) prepareLabels(service types.ServiceConfig, number int) (map[string]string, error) {
 	labels := map[string]string{}
 	for k, v := range service.Labels {
 		labels[k] = v
 	}
-
-	labels[api.ProjectLabel] = p.Name
-	labels[api.ServiceLabel] = service.Name
-	labels[api.VersionLabel] = api.ComposeVersion
-	if _, ok := service.Labels[api.OneoffLabel]; !ok {
-		labels[api.OneoffLabel] = "False"
+	for k, v := range service.CustomLabels {
+		labels[k] = v
 	}
 
 	hash, err := ServiceHash(service)
 	if err != nil {
 		return nil, err
 	}
-
 	labels[api.ConfigHashLabel] = hash
-	labels[api.WorkingDirLabel] = p.WorkingDir
-	labels[api.ConfigFilesLabel] = strings.Join(p.ComposeFiles, ",")
+
 	labels[api.ContainerNumberLabel] = strconv.Itoa(number)
+
 	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, ",")
 	return labels, nil
@@ -643,15 +638,11 @@ func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
 	bindings := nat.PortMap{}
 	for _, port := range s.Ports {
 		p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
-		bind := bindings[p]
 		binding := nat.PortBinding{
-			HostIP: port.HostIP,
-		}
-		if port.Published > 0 {
-			binding.HostPort = fmt.Sprint(port.Published)
+			HostIP:   port.HostIP,
+			HostPort: port.Published,
 		}
-		bind = append(bind, binding)
-		bindings[p] = bind
+		bindings[p] = append(bindings[p], binding)
 	}
 	return bindings
 }
@@ -1107,7 +1098,7 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
 			return err
 		}
 		if volume.External.External {
-			return fmt.Errorf("external volume %q not found", volume.External.Name)
+			return fmt.Errorf("external volume %q not found", volume.Name)
 		}
 		err := s.createVolume(ctx, volume)
 		return err

+ 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 {
+	builtFromResources := options.Project == nil
 	w := progress.ContextWriter(ctx)
 	resourceToRemove := false
 
@@ -50,8 +51,8 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
 		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 {
 			return err
 		}
@@ -232,34 +233,9 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
 	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)))
 	if err != nil {
 		return nil, err
@@ -273,6 +249,5 @@ func (s *composeService) projectFromLabels(ctx context.Context, containers Conta
 			Labels: vol.Labels,
 		}
 	}
-
 	return project, nil
 }

+ 0 - 1
pkg/compose/hash.go

@@ -24,7 +24,6 @@ import (
 )
 
 // ServiceHash compute configuration has for a service
-// TODO move this to compose-go
 func ServiceHash(o types.ServiceConfig) (string, error) {
 	// remove the Build config when generating the service hash
 	o.Build = nil

+ 30 - 3
pkg/compose/ls.go

@@ -20,8 +20,10 @@ import (
 	"context"
 	"fmt"
 	"sort"
+	"strings"
 
 	"github.com/docker/compose/v2/pkg/api"
+	"github.com/docker/compose/v2/pkg/utils"
 
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
@@ -46,15 +48,40 @@ func containersToStacks(containers []moby.Container) ([]api.Stack, error) {
 	}
 	var projects []api.Stack
 	for _, project := range keys {
+		configFiles, err := combinedConfigFiles(containersByLabel[project])
+		if err != nil {
+			return nil, err
+		}
+
 		projects = append(projects, api.Stack{
-			ID:     project,
-			Name:   project,
-			Status: combinedStatus(containerToState(containersByLabel[project])),
+			ID:          project,
+			Name:        project,
+			Status:      combinedStatus(containerToState(containersByLabel[project])),
+			ConfigFiles: configFiles,
 		})
 	}
 	return projects, nil
 }
 
+func combinedConfigFiles(containers []moby.Container) (string, error) {
+	configFiles := []string{}
+
+	for _, c := range containers {
+		files, ok := c.Labels[api.ConfigFilesLabel]
+		if !ok {
+			return "", fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, c.ID)
+		}
+
+		for _, f := range strings.Split(files, ",") {
+			if !utils.StringContains(configFiles, f) {
+				configFiles = append(configFiles, f)
+			}
+		}
+	}
+
+	return strings.Join(configFiles, ","), nil
+}
+
 func containerToState(containers []moby.Container) []string {
 	statuses := []string{}
 	for _, c := range containers {

+ 65 - 9
pkg/compose/ls_test.go

@@ -17,6 +17,7 @@
 package compose
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -30,31 +31,33 @@ func TestContainersToStacks(t *testing.T) {
 		{
 			ID:     "service1",
 			State:  "running",
-			Labels: map[string]string{api.ProjectLabel: "project1"},
+			Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
 		},
 		{
 			ID:     "service2",
 			State:  "running",
-			Labels: map[string]string{api.ProjectLabel: "project1"},
+			Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
 		},
 		{
 			ID:     "service3",
 			State:  "running",
-			Labels: map[string]string{api.ProjectLabel: "project2"},
+			Labels: map[string]string{api.ProjectLabel: "project2", api.ConfigFilesLabel: "/home/project2-docker-compose.yaml"},
 		},
 	}
 	stacks, err := containersToStacks(containers)
 	assert.NilError(t, err)
 	assert.DeepEqual(t, stacks, []api.Stack{
 		{
-			ID:     "project1",
-			Name:   "project1",
-			Status: "running(2)",
+			ID:          "project1",
+			Name:        "project1",
+			Status:      "running(2)",
+			ConfigFiles: "/home/docker-compose.yaml",
 		},
 		{
-			ID:     "project2",
-			Name:   "project2",
-			Status: "running(1)",
+			ID:          "project2",
+			Name:        "project2",
+			Status:      "running(1)",
+			ConfigFiles: "/home/project2-docker-compose.yaml",
 		},
 	})
 }
@@ -64,3 +67,56 @@ func TestStacksMixedStatus(t *testing.T) {
 	assert.Equal(t, combinedStatus([]string{"running", "running", "running"}), "running(3)")
 	assert.Equal(t, combinedStatus([]string{"running", "exited", "running"}), "exited(1), running(2)")
 }
+
+func TestCombinedConfigFiles(t *testing.T) {
+	containersByLabel := map[string][]moby.Container{
+		"project1": {
+			{
+				ID:     "service1",
+				State:  "running",
+				Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
+			},
+			{
+				ID:     "service2",
+				State:  "running",
+				Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
+			},
+		},
+		"project2": {
+			{
+				ID:     "service3",
+				State:  "running",
+				Labels: map[string]string{api.ProjectLabel: "project2", api.ConfigFilesLabel: "/home/project2-docker-compose.yaml"},
+			},
+		},
+		"project3": {
+			{
+				ID:     "service4",
+				State:  "running",
+				Labels: map[string]string{api.ProjectLabel: "project3"},
+			},
+		},
+	}
+
+	testData := map[string]struct {
+		ConfigFiles string
+		Error       error
+	}{
+		"project1": {ConfigFiles: "/home/docker-compose.yaml", Error: nil},
+		"project2": {ConfigFiles: "/home/project2-docker-compose.yaml", Error: nil},
+		"project3": {ConfigFiles: "", Error: fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, "service4")},
+	}
+
+	for project, containers := range containersByLabel {
+		configFiles, err := combinedConfigFiles(containers)
+
+		expected := testData[project]
+
+		if expected.Error != nil {
+			assert.Equal(t, err.Error(), expected.Error.Error())
+		} else {
+			assert.Equal(t, err, expected.Error)
+		}
+		assert.Equal(t, configFiles, expected.ConfigFiles)
+	}
+}

+ 2 - 2
pkg/compose/pause.go

@@ -33,7 +33,7 @@ func (s *composeService) Pause(ctx context.Context, project string, options api.
 }
 
 func (s *composeService) pause(ctx context.Context, project string, options api.PauseOptions) error {
-	containers, err := s.getContainers(ctx, project, oneOffExclude, true, options.Services...)
+	containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...)
 	if err != nil {
 		return err
 	}
@@ -61,7 +61,7 @@ func (s *composeService) UnPause(ctx context.Context, project string, options ap
 }
 
 func (s *composeService) unPause(ctx context.Context, project string, options api.PauseOptions) error {
-	containers, err := s.getContainers(ctx, project, oneOffExclude, true, options.Services...)
+	containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...)
 	if err != nil {
 		return err
 	}

+ 10 - 5
pkg/compose/restart.go

@@ -19,7 +19,6 @@ package compose
 import (
 	"context"
 
-	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"golang.org/x/sync/errgroup"
 
@@ -27,14 +26,20 @@ import (
 	"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 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 {
 		return err
 	}

+ 3 - 2
pkg/compose/run.go

@@ -153,8 +153,9 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
 	if service.Deploy != nil {
 		service.Deploy.RestartPolicy = nil
 	}
-	service.Labels = service.Labels.Add(api.SlugLabel, slug)
-	service.Labels = service.Labels.Add(api.OneoffLabel, "True")
+	service.CustomLabels = service.CustomLabels.
+		Add(api.SlugLabel, slug).
+		Add(api.OneoffLabel, "True")
 
 	if err := s.ensureImagesExists(ctx, project, opts.QuietPull); err != nil { // all dependencies already checked, but might miss service img
 		return "", err

+ 13 - 6
pkg/compose/start.go

@@ -28,15 +28,22 @@ import (
 	"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 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)
@@ -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)
 		if err != nil {
 			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/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 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)
 
 	services := options.Services
 	if len(services) == 0 {
-		services = project.ServiceNames()
+		services = []string{}
 	}
+
 	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 {
 		return err
 	}

+ 1 - 8
pkg/compose/stop_test.go

@@ -25,7 +25,6 @@ import (
 	compose "github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/mocks"
 
-	"github.com/compose-spec/compose-go/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/golang/mock/gomock"
 	"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(), "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,
 	})
 	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
 		}
 		if options.Start.Attach == nil {
-			return s.start(ctx, project, options.Start, nil)
+			return s.start(ctx, project.Name, options.Start, 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,
 			})
 		})
@@ -85,7 +85,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 		return err
 	})
 
-	err = s.start(ctx, project, options.Start, printer.HandleEvent)
+	err = s.start(ctx, project.Name, options.Start, printer.HandleEvent)
 	if err != nil {
 		return err
 	}

File diff suppressed because it is too large
+ 134 - 133
pkg/mocks/mock_docker_api.go


Some files were not shown because too many files changed in this diff