Explorar o código

pull OCI remote resource

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof %!s(int64=2) %!d(string=hai) anos
pai
achega
e0f39ebbef

+ 5 - 4
cmd/compose/alpha.go

@@ -15,12 +15,13 @@
 package compose
 
 import (
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/spf13/cobra"
 )
 
 // alphaCommand groups all experimental subcommands
-func alphaCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	cmd := &cobra.Command{
 		Short:  "Experimental commands",
 		Use:    "alpha [COMMAND]",
@@ -30,9 +31,9 @@ func alphaCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		},
 	}
 	cmd.AddCommand(
-		watchCommand(p, backend),
-		vizCommand(p, backend),
-		publishCommand(p, backend),
+		watchCommand(p, dockerCli, backend),
+		vizCommand(p, dockerCli, backend),
+		publishCommand(p, dockerCli, backend),
 	)
 	return cmd
 }

+ 6 - 5
cmd/compose/build.go

@@ -26,6 +26,7 @@ import (
 	"github.com/compose-spec/compose-go/loader"
 	"github.com/compose-spec/compose-go/types"
 	buildx "github.com/docker/buildx/util/progress"
+	"github.com/docker/cli/cli/command"
 	cliopts "github.com/docker/cli/opts"
 	ui "github.com/docker/compose/v2/pkg/progress"
 	"github.com/spf13/cobra"
@@ -72,7 +73,7 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
 	}, nil
 }
 
-func buildCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := buildOptions{
 		ProjectOptions: p,
 	}
@@ -97,9 +98,9 @@ func buildCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 			if cmd.Flags().Changed("progress") && opts.ssh == "" {
 				fmt.Fprint(os.Stderr, "--progress is a global compose flag, better use `docker compose --progress xx build ...")
 			}
-			return runBuild(ctx, backend, opts, args)
+			return runBuild(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	cmd.Flags().BoolVar(&opts.push, "push", false, "Push service images.")
 	cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
@@ -123,8 +124,8 @@ func buildCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return cmd
 }
 
-func runBuild(ctx context.Context, backend api.Service, opts buildOptions, services []string) error {
-	project, err := opts.ToProject(services, cli.WithResolvedPaths(true))
+func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
+	project, err := opts.ToProject(dockerCli, services, cli.WithResolvedPaths(true))
 	if err != nil {
 		return err
 	}

+ 7 - 4
cmd/compose/completion.go

@@ -20,6 +20,7 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/spf13/cobra"
 )
@@ -33,9 +34,10 @@ func noCompletion() validArgsFn {
 	}
 }
 
-func completeServiceNames(p *ProjectOptions) validArgsFn {
+func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
 	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		project, err := p.ToProject(nil)
+		p.Offline = true
+		project, err := p.ToProject(dockerCli, nil)
 		if err != nil {
 			return nil, cobra.ShellCompDirectiveNoFileComp
 		}
@@ -67,9 +69,10 @@ func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []s
 	}
 }
 
-func completeProfileNames(p *ProjectOptions) validArgsFn {
+func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
 	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		project, err := p.ToProject(nil)
+		p.Offline = true
+		project, err := p.ToProject(dockerCli, nil)
 		if err != nil {
 			return nil, cobra.ShellCompDirectiveNoFileComp
 		}

+ 65 - 43
cmd/compose/compose.go

@@ -26,18 +26,16 @@ import (
 	"strings"
 	"syscall"
 
-	buildx "github.com/docker/buildx/util/progress"
-
-	"github.com/compose-spec/compose-go/dotenv"
-	"github.com/docker/cli/cli/command"
-	"github.com/docker/compose/v2/pkg/remote"
-
 	"github.com/compose-spec/compose-go/cli"
+	"github.com/compose-spec/compose-go/dotenv"
 	"github.com/compose-spec/compose-go/types"
 	composegoutils "github.com/compose-spec/compose-go/utils"
 	"github.com/docker/buildx/util/logutil"
+	buildx "github.com/docker/buildx/util/progress"
 	dockercli "github.com/docker/cli/cli"
 	"github.com/docker/cli/cli-plugins/manager"
+	"github.com/docker/cli/cli/command"
+	"github.com/docker/compose/v2/pkg/remote"
 	"github.com/morikuni/aec"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
@@ -119,6 +117,7 @@ type ProjectOptions struct {
 	EnvFiles      []string
 	Compatibility bool
 	Progress      string
+	Offline       bool
 }
 
 // ProjectFunc does stuff within a types.Project
@@ -128,14 +127,14 @@ type ProjectFunc func(ctx context.Context, project *types.Project) error
 type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error
 
 // WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
-func (o *ProjectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
-	return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
+func (o *ProjectOptions) WithProject(fn ProjectFunc, dockerCli command.Cli) func(cmd *cobra.Command, args []string) error {
+	return o.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
 		return fn(ctx, project)
 	})
 }
 
 // WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
-func (o *ProjectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
+func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
 	return Adapt(func(ctx context.Context, args []string) error {
 		options := []cli.ProjectOptionsFn{
 			cli.WithResolvedPaths(true),
@@ -143,19 +142,7 @@ func (o *ProjectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Co
 			cli.WithContext(ctx),
 		}
 
-		enabled, err := remote.GitRemoteLoaderEnabled()
-		if err != nil {
-			return err
-		}
-		if enabled {
-			git, err := remote.NewGitRemoteLoader()
-			if err != nil {
-				return err
-			}
-			options = append(options, cli.WithResourceLoader(git))
-		}
-
-		project, err := o.ToProject(args, options...)
+		project, err := o.ToProject(dockerCli, args, options...)
 		if err != nil {
 			return err
 		}
@@ -176,11 +163,11 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
 	_ = f.MarkHidden("workdir")
 }
 
-func (o *ProjectOptions) projectOrName(services ...string) (*types.Project, string, error) {
+func (o *ProjectOptions) projectOrName(dockerCli command.Cli, services ...string) (*types.Project, string, error) {
 	name := o.ProjectName
 	var project *types.Project
 	if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
-		p, err := o.ToProject(services, cli.WithDiscardEnvFile)
+		p, err := o.ToProject(dockerCli, services, cli.WithDiscardEnvFile)
 		if err != nil {
 			envProjectName := os.Getenv(ComposeProjectName)
 			if envProjectName != "" {
@@ -194,7 +181,7 @@ func (o *ProjectOptions) projectOrName(services ...string) (*types.Project, stri
 	return project, name, nil
 }
 
-func (o *ProjectOptions) toProjectName() (string, error) {
+func (o *ProjectOptions) toProjectName(dockerCli command.Cli) (string, error) {
 	if o.ProjectName != "" {
 		return o.ProjectName, nil
 	}
@@ -204,14 +191,22 @@ func (o *ProjectOptions) toProjectName() (string, error) {
 		return envProjectName, nil
 	}
 
-	project, err := o.ToProject(nil)
+	project, err := o.ToProject(dockerCli, nil)
 	if err != nil {
 		return "", err
 	}
 	return project.Name, nil
 }
 
-func (o *ProjectOptions) ToProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
+func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
+	if !o.Offline {
+		var err error
+		po, err = o.configureRemoteLoaders(dockerCli, po)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	options, err := o.toProjectOptions(po...)
 	if err != nil {
 		return nil, compose.WrapComposeError(err)
@@ -256,6 +251,33 @@ func (o *ProjectOptions) ToProject(services []string, po ...cli.ProjectOptionsFn
 	return project, err
 }
 
+func (o *ProjectOptions) configureRemoteLoaders(dockerCli command.Cli, po []cli.ProjectOptionsFn) ([]cli.ProjectOptionsFn, error) {
+	enabled, err := remote.GitRemoteLoaderEnabled()
+	if err != nil {
+		return nil, err
+	}
+	if enabled {
+		git, err := remote.NewGitRemoteLoader(o.Offline)
+		if err != nil {
+			return nil, err
+		}
+		po = append(po, cli.WithResourceLoader(git))
+	}
+
+	enabled, err = remote.OCIRemoteLoaderEnabled()
+	if err != nil {
+		return nil, err
+	}
+	if enabled {
+		git, err := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
+		if err != nil {
+			return nil, err
+		}
+		po = append(po, cli.WithResourceLoader(git))
+	}
+	return po, nil
+}
+
 func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
 	return cli.NewProjectOptions(o.ConfigPaths,
 		append(po,
@@ -429,32 +451,32 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
 
 	c.AddCommand(
 		upCommand(&opts, dockerCli, backend),
-		downCommand(&opts, backend),
-		startCommand(&opts, backend),
-		restartCommand(&opts, backend),
-		stopCommand(&opts, backend),
+		downCommand(&opts, dockerCli, backend),
+		startCommand(&opts, dockerCli, backend),
+		restartCommand(&opts, dockerCli, backend),
+		stopCommand(&opts, dockerCli, backend),
 		psCommand(&opts, dockerCli, backend),
 		listCommand(dockerCli, backend),
 		logsCommand(&opts, dockerCli, backend),
 		configCommand(&opts, dockerCli, backend),
-		killCommand(&opts, backend),
+		killCommand(&opts, dockerCli, backend),
 		runCommand(&opts, dockerCli, backend),
-		removeCommand(&opts, backend),
+		removeCommand(&opts, dockerCli, backend),
 		execCommand(&opts, dockerCli, backend),
-		pauseCommand(&opts, backend),
-		unpauseCommand(&opts, backend),
+		pauseCommand(&opts, dockerCli, backend),
+		unpauseCommand(&opts, dockerCli, backend),
 		topCommand(&opts, dockerCli, backend),
 		eventsCommand(&opts, dockerCli, backend),
 		portCommand(&opts, dockerCli, backend),
 		imagesCommand(&opts, dockerCli, backend),
 		versionCommand(dockerCli),
-		buildCommand(&opts, backend),
-		pushCommand(&opts, backend),
-		pullCommand(&opts, backend),
-		createCommand(&opts, backend),
-		copyCommand(&opts, backend),
-		waitCommand(&opts, backend),
-		alphaCommand(&opts, backend),
+		buildCommand(&opts, dockerCli, backend),
+		pushCommand(&opts, dockerCli, backend),
+		pullCommand(&opts, dockerCli, backend),
+		createCommand(&opts, dockerCli, backend),
+		copyCommand(&opts, dockerCli, backend),
+		waitCommand(&opts, dockerCli, backend),
+		alphaCommand(&opts, dockerCli, backend),
 	)
 
 	c.Flags().SetInterspersed(false)
@@ -477,7 +499,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
 	)
 	c.RegisterFlagCompletionFunc( //nolint:errcheck
 		"profile",
-		completeProfileNames(&opts),
+		completeProfileNames(dockerCli, &opts),
 	)
 
 	c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)

+ 30 - 36
cmd/compose/config.go

@@ -26,7 +26,7 @@ import (
 
 	"github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/types"
-	"github.com/docker/compose/v2/pkg/remote"
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -50,24 +50,18 @@ type configOptions struct {
 	noConsistency       bool
 }
 
-func (o *configOptions) ToProject(ctx context.Context, services []string) (*types.Project, error) {
-	git, err := remote.NewGitRemoteLoader()
-	if err != nil {
-		return nil, err
-	}
-
-	return o.ProjectOptions.ToProject(services,
+func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string) (*types.Project, error) {
+	return o.ProjectOptions.ToProject(dockerCli, services,
 		cli.WithInterpolation(!o.noInterpolate),
 		cli.WithResolvedPaths(!o.noResolvePath),
 		cli.WithNormalization(!o.noNormalize),
 		cli.WithConsistency(!o.noConsistency),
 		cli.WithDefaultProfiles(o.Profiles...),
 		cli.WithDiscardEnvFile,
-		cli.WithContext(ctx),
-		cli.WithResourceLoader(git))
+		cli.WithContext(ctx))
 }
 
-func configCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := configOptions{
 		ProjectOptions: p,
 	}
@@ -90,24 +84,24 @@ func configCommand(p *ProjectOptions, streams api.Streams, backend api.Service)
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
 			if opts.services {
-				return runServices(ctx, streams, opts)
+				return runServices(ctx, dockerCli, opts)
 			}
 			if opts.volumes {
-				return runVolumes(ctx, streams, opts)
+				return runVolumes(ctx, dockerCli, opts)
 			}
 			if opts.hash != "" {
-				return runHash(ctx, streams, opts)
+				return runHash(ctx, dockerCli, opts)
 			}
 			if opts.profiles {
-				return runProfiles(ctx, streams, opts, args)
+				return runProfiles(ctx, dockerCli, opts, args)
 			}
 			if opts.images {
-				return runConfigImages(ctx, streams, opts, args)
+				return runConfigImages(ctx, dockerCli, opts, args)
 			}
 
-			return runConfig(ctx, streams, backend, opts, args)
+			return runConfig(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := cmd.Flags()
 	flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
@@ -128,9 +122,9 @@ func configCommand(p *ProjectOptions, streams api.Streams, backend api.Service)
 	return cmd
 }
 
-func runConfig(ctx context.Context, streams api.Streams, backend api.Service, opts configOptions, services []string) error {
+func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, opts configOptions, services []string) error {
 	var content []byte
-	project, err := opts.ToProject(ctx, services)
+	project, err := opts.ToProject(ctx, dockerCli, services)
 	if err != nil {
 		return err
 	}
@@ -155,38 +149,38 @@ func runConfig(ctx context.Context, streams api.Streams, backend api.Service, op
 	if opts.Output != "" && len(content) > 0 {
 		return os.WriteFile(opts.Output, content, 0o666)
 	}
-	_, err = fmt.Fprint(streams.Out(), string(content))
+	_, err = fmt.Fprint(dockerCli.Out(), string(content))
 	return err
 }
 
-func runServices(ctx context.Context, streams api.Streams, opts configOptions) error {
-	project, err := opts.ToProject(ctx, nil)
+func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
+	project, err := opts.ToProject(ctx, dockerCli, nil)
 	if err != nil {
 		return err
 	}
 	return project.WithServices(project.ServiceNames(), func(s types.ServiceConfig) error {
-		fmt.Fprintln(streams.Out(), s.Name)
+		fmt.Fprintln(dockerCli.Out(), s.Name)
 		return nil
 	})
 }
 
-func runVolumes(ctx context.Context, streams api.Streams, opts configOptions) error {
-	project, err := opts.ToProject(ctx, nil)
+func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
+	project, err := opts.ToProject(ctx, dockerCli, nil)
 	if err != nil {
 		return err
 	}
 	for n := range project.Volumes {
-		fmt.Fprintln(streams.Out(), n)
+		fmt.Fprintln(dockerCli.Out(), n)
 	}
 	return nil
 }
 
-func runHash(ctx context.Context, streams api.Streams, opts configOptions) error {
+func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
 	var services []string
 	if opts.hash != "*" {
 		services = append(services, strings.Split(opts.hash, ",")...)
 	}
-	project, err := opts.ToProject(ctx, nil)
+	project, err := opts.ToProject(ctx, dockerCli, nil)
 	if err != nil {
 		return err
 	}
@@ -208,14 +202,14 @@ func runHash(ctx context.Context, streams api.Streams, opts configOptions) error
 		if err != nil {
 			return err
 		}
-		fmt.Fprintf(streams.Out(), "%s %s\n", s.Name, hash)
+		fmt.Fprintf(dockerCli.Out(), "%s %s\n", s.Name, hash)
 	}
 	return nil
 }
 
-func runProfiles(ctx context.Context, streams api.Streams, opts configOptions, services []string) error {
+func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
 	set := map[string]struct{}{}
-	project, err := opts.ToProject(ctx, services)
+	project, err := opts.ToProject(ctx, dockerCli, services)
 	if err != nil {
 		return err
 	}
@@ -230,18 +224,18 @@ func runProfiles(ctx context.Context, streams api.Streams, opts configOptions, s
 	}
 	sort.Strings(profiles)
 	for _, p := range profiles {
-		fmt.Fprintln(streams.Out(), p)
+		fmt.Fprintln(dockerCli.Out(), p)
 	}
 	return nil
 }
 
-func runConfigImages(ctx context.Context, streams api.Streams, opts configOptions, services []string) error {
-	project, err := opts.ToProject(ctx, services)
+func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
+	project, err := opts.ToProject(ctx, dockerCli, services)
 	if err != nil {
 		return err
 	}
 	for _, s := range project.Services {
-		fmt.Fprintln(streams.Out(), api.GetImageNameOrDefault(s, project.Name))
+		fmt.Fprintln(dockerCli.Out(), api.GetImageNameOrDefault(s, project.Name))
 	}
 	return nil
 }

+ 6 - 5
cmd/compose/cp.go

@@ -21,6 +21,7 @@ import (
 	"errors"
 
 	"github.com/docker/cli/cli"
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -37,7 +38,7 @@ type copyOptions struct {
 	copyUIDGID  bool
 }
 
-func copyCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := copyOptions{
 		ProjectOptions: p,
 	}
@@ -58,9 +59,9 @@ func copyCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
 			opts.source = args[0]
 			opts.destination = args[1]
-			return runCopy(ctx, backend, opts)
+			return runCopy(ctx, dockerCli, backend, opts)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 
 	flags := copyCmd.Flags()
@@ -74,8 +75,8 @@ func copyCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return copyCmd
 }
 
-func runCopy(ctx context.Context, backend api.Service, opts copyOptions) error {
-	name, err := opts.toProjectName()
+func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Service, opts copyOptions) error {
+	name, err := opts.toProjectName(dockerCli)
 	if err != nil {
 		return err
 	}

+ 4 - 3
cmd/compose/create.go

@@ -24,6 +24,7 @@ import (
 	"time"
 
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -46,7 +47,7 @@ type createOptions struct {
 	scale         []string
 }
 
-func createCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := createOptions{}
 	cmd := &cobra.Command{
 		Use:   "create [OPTIONS] [SERVICE...]",
@@ -74,8 +75,8 @@ func createCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 				Timeout:              opts.GetTimeout(),
 				QuietPull:            false,
 			})
-		}),
-		ValidArgsFunction: completeServiceNames(p),
+		}, dockerCli),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := cmd.Flags()
 	flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")

+ 5 - 4
cmd/compose/down.go

@@ -22,6 +22,7 @@ import (
 	"os"
 	"time"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -39,7 +40,7 @@ type downOptions struct {
 	images        string
 }
 
-func downCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := downOptions{
 		ProjectOptions: p,
 	}
@@ -56,7 +57,7 @@ func downCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 			return nil
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runDown(ctx, backend, opts, args)
+			return runDown(ctx, dockerCli, backend, opts, args)
 		}),
 		ValidArgsFunction: noCompletion(),
 	}
@@ -76,8 +77,8 @@ func downCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return downCmd
 }
 
-func runDown(ctx context.Context, backend api.Service, opts downOptions, services []string) error {
-	project, name, err := opts.projectOrName()
+func runDown(ctx context.Context, dockerCli command.Cli, backend api.Service, opts downOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli)
 	if err != nil {
 		return err
 	}

+ 8 - 7
cmd/compose/events.go

@@ -21,6 +21,7 @@ import (
 	"encoding/json"
 	"fmt"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 
 	"github.com/spf13/cobra"
@@ -31,7 +32,7 @@ type eventsOpts struct {
 	json bool
 }
 
-func eventsCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := eventsOpts{
 		composeOptions: &composeOptions{
 			ProjectOptions: p,
@@ -41,17 +42,17 @@ func eventsCommand(p *ProjectOptions, streams api.Streams, backend api.Service)
 		Use:   "events [OPTIONS] [SERVICE...]",
 		Short: "Receive real time events from containers.",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runEvents(ctx, streams, backend, opts, args)
+			return runEvents(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 
 	cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")
 	return cmd
 }
 
-func runEvents(ctx context.Context, streams api.Streams, backend api.Service, opts eventsOpts, services []string) error {
-	name, err := opts.toProjectName()
+func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service, opts eventsOpts, services []string) error {
+	name, err := opts.toProjectName(dockerCli)
 	if err != nil {
 		return err
 	}
@@ -71,9 +72,9 @@ func runEvents(ctx context.Context, streams api.Streams, backend api.Service, op
 				if err != nil {
 					return err
 				}
-				fmt.Fprintln(streams.Out(), string(marshal))
+				fmt.Fprintln(dockerCli.Out(), string(marshal))
 			} else {
-				fmt.Fprintln(streams.Out(), event)
+				fmt.Fprintln(dockerCli.Out(), event)
 			}
 			return nil
 		},

+ 7 - 6
cmd/compose/exec.go

@@ -21,6 +21,7 @@ import (
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/cli/cli"
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/spf13/cobra"
@@ -42,7 +43,7 @@ type execOpts struct {
 	interactive bool
 }
 
-func execCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := execOpts{
 		composeOptions: &composeOptions{
 			ProjectOptions: p,
@@ -58,9 +59,9 @@ func execCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
 			return nil
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runExec(ctx, backend, opts)
+			return runExec(ctx, dockerCli, backend, opts)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 
 	runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
@@ -68,7 +69,7 @@ func execCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
 	runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
 	runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
 	runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
-	runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
+	runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
 	runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
 
 	runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
@@ -80,8 +81,8 @@ func execCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
 	return runCmd
 }
 
-func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
-	projectName, err := opts.toProjectName()
+func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, opts execOpts) error {
+	projectName, err := opts.toProjectName(dockerCli)
 	if err != nil {
 		return err
 	}

+ 8 - 7
cmd/compose/images.go

@@ -23,6 +23,7 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/go-units"
 	"github.com/spf13/cobra"
@@ -38,7 +39,7 @@ type imageOptions struct {
 	Format string
 }
 
-func imagesCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := imageOptions{
 		ProjectOptions: p,
 	}
@@ -46,17 +47,17 @@ func imagesCommand(p *ProjectOptions, streams api.Streams, backend api.Service)
 		Use:   "images [OPTIONS] [SERVICE...]",
 		Short: "List images used by the created containers",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runImages(ctx, streams, backend, opts, args)
+			return runImages(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json].")
 	imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
 	return imgCmd
 }
 
-func runImages(ctx context.Context, streams api.Streams, backend api.Service, opts imageOptions, services []string) error {
-	projectName, err := opts.toProjectName()
+func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service, opts imageOptions, services []string) error {
+	projectName, err := opts.toProjectName(dockerCli)
 	if err != nil {
 		return err
 	}
@@ -80,7 +81,7 @@ func runImages(ctx context.Context, streams api.Streams, backend api.Service, op
 			}
 		}
 		for _, img := range ids {
-			fmt.Fprintln(streams.Out(), img)
+			fmt.Fprintln(dockerCli.Out(), img)
 		}
 		return nil
 	}
@@ -89,7 +90,7 @@ func runImages(ctx context.Context, streams api.Streams, backend api.Service, op
 		return images[i].ContainerName < images[j].ContainerName
 	})
 
-	return formatter.Print(images, opts.Format, streams.Out(),
+	return formatter.Print(images, opts.Format, dockerCli.Out(),
 		func(w io.Writer) {
 			for _, img := range images {
 				id := stringid.TruncateID(img.ID)

+ 6 - 5
cmd/compose/kill.go

@@ -20,6 +20,7 @@ import (
 	"context"
 	"os"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -32,7 +33,7 @@ type killOptions struct {
 	signal        string
 }
 
-func killCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := killOptions{
 		ProjectOptions: p,
 	}
@@ -40,9 +41,9 @@ func killCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		Use:   "kill [OPTIONS] [SERVICE...]",
 		Short: "Force stop service containers.",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runKill(ctx, backend, opts, args)
+			return runKill(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 
 	flags := cmd.Flags()
@@ -53,8 +54,8 @@ func killCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return cmd
 }
 
-func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
-	project, name, err := opts.projectOrName(services...)
+func runKill(ctx context.Context, dockerCli command.Cli, backend api.Service, opts killOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli, services...)
 	if err != nil {
 		return err
 	}

+ 6 - 5
cmd/compose/list.go

@@ -22,6 +22,7 @@ import (
 	"io"
 	"strings"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/cmd/formatter"
 
 	"github.com/docker/cli/opts"
@@ -37,13 +38,13 @@ type lsOptions struct {
 	Filter opts.FilterOpt
 }
 
-func listCommand(streams api.Streams, backend api.Service) *cobra.Command {
+func listCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
 	lsOpts := lsOptions{Filter: opts.NewFilterOpt()}
 	lsCmd := &cobra.Command{
 		Use:   "ls [OPTIONS]",
 		Short: "List running compose projects",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runList(ctx, streams, backend, lsOpts)
+			return runList(ctx, dockerCli, backend, lsOpts)
 		}),
 		Args:              cobra.NoArgs,
 		ValidArgsFunction: noCompletion(),
@@ -60,7 +61,7 @@ var acceptedListFilters = map[string]bool{
 	"name": true,
 }
 
-func runList(ctx context.Context, streams api.Streams, backend api.Service, lsOpts lsOptions) error {
+func runList(ctx context.Context, dockerCli command.Cli, backend api.Service, lsOpts lsOptions) error {
 	filters := lsOpts.Filter.Value()
 	err := filters.Validate(acceptedListFilters)
 	if err != nil {
@@ -73,7 +74,7 @@ func runList(ctx context.Context, streams api.Streams, backend api.Service, lsOp
 	}
 	if lsOpts.Quiet {
 		for _, s := range stackList {
-			fmt.Fprintln(streams.Out(), s.Name)
+			fmt.Fprintln(dockerCli.Out(), s.Name)
 		}
 		return nil
 	}
@@ -90,7 +91,7 @@ func runList(ctx context.Context, streams api.Streams, backend api.Service, lsOp
 	}
 
 	view := viewFromStackList(stackList)
-	return formatter.Print(view, lsOpts.Format, streams.Out(), func(w io.Writer) {
+	return formatter.Print(view, lsOpts.Format, dockerCli.Out(), func(w io.Writer) {
 		for _, stack := range view {
 			_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
 		}

+ 7 - 6
cmd/compose/logs.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/cmd/formatter"
@@ -37,7 +38,7 @@ type logsOptions struct {
 	timestamps bool
 }
 
-func logsCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := logsOptions{
 		ProjectOptions: p,
 	}
@@ -45,9 +46,9 @@ func logsCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
 		Use:   "logs [OPTIONS] [SERVICE...]",
 		Short: "View output from containers",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runLogs(ctx, streams, backend, opts, args)
+			return runLogs(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := logsCmd.Flags()
 	flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")
@@ -60,12 +61,12 @@ func logsCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
 	return logsCmd
 }
 
-func runLogs(ctx context.Context, streams api.Streams, backend api.Service, opts logsOptions, services []string) error {
-	project, name, err := opts.projectOrName(services...)
+func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Service, opts logsOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli, services...)
 	if err != nil {
 		return err
 	}
-	consumer := formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !opts.noColor, !opts.noPrefix, false)
+	consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !opts.noColor, !opts.noPrefix, false)
 	return backend.Logs(ctx, name, consumer, api.LogOptions{
 		Project:    project,
 		Services:   services,

+ 11 - 10
cmd/compose/pause.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -28,7 +29,7 @@ type pauseOptions struct {
 	*ProjectOptions
 }
 
-func pauseCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := pauseOptions{
 		ProjectOptions: p,
 	}
@@ -36,15 +37,15 @@ func pauseCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		Use:   "pause [SERVICE...]",
 		Short: "Pause services",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runPause(ctx, backend, opts, args)
+			return runPause(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	return cmd
 }
 
-func runPause(ctx context.Context, backend api.Service, opts pauseOptions, services []string) error {
-	project, name, err := opts.projectOrName(services...)
+func runPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pauseOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli, services...)
 	if err != nil {
 		return err
 	}
@@ -59,7 +60,7 @@ type unpauseOptions struct {
 	*ProjectOptions
 }
 
-func unpauseCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := unpauseOptions{
 		ProjectOptions: p,
 	}
@@ -67,15 +68,15 @@ func unpauseCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		Use:   "unpause [SERVICE...]",
 		Short: "Unpause services",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runUnPause(ctx, backend, opts, args)
+			return runUnPause(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	return cmd
 }
 
-func runUnPause(ctx context.Context, backend api.Service, opts unpauseOptions, services []string) error {
-	project, name, err := opts.projectOrName(services...)
+func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts unpauseOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli, services...)
 	if err != nil {
 		return err
 	}

+ 7 - 6
cmd/compose/port.go

@@ -22,6 +22,7 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -34,7 +35,7 @@ type portOptions struct {
 	index    int
 }
 
-func portCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := portOptions{
 		ProjectOptions: p,
 	}
@@ -52,17 +53,17 @@ func portCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *c
 			return nil
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runPort(ctx, streams, backend, opts, args[0])
+			return runPort(ctx, dockerCli, backend, opts, args[0])
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
 	cmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
 	return cmd
 }
 
-func runPort(ctx context.Context, streams api.Streams, backend api.Service, opts portOptions, service string) error {
-	projectName, err := opts.toProjectName()
+func runPort(ctx context.Context, dockerCli command.Cli, backend api.Service, opts portOptions, service string) error {
+	projectName, err := opts.toProjectName(dockerCli)
 	if err != nil {
 		return err
 	}
@@ -74,6 +75,6 @@ func runPort(ctx context.Context, streams api.Streams, backend api.Service, opts
 		return err
 	}
 
-	fmt.Fprintf(streams.Out(), "%s:%d\n", ip, port)
+	fmt.Fprintf(dockerCli.Out(), "%s:%d\n", ip, port)
 	return nil
 }

+ 2 - 2
cmd/compose/ps.go

@@ -75,7 +75,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
 		RunE: Adapt(func(ctx context.Context, args []string) error {
 			return runPs(ctx, dockerCli, backend, args, opts)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := psCmd.Flags()
 	flags.StringVar(&opts.Format, "format", "table", cliflags.FormatHelp)
@@ -88,7 +88,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
 }
 
 func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, opts psOptions) error {
-	project, name, err := opts.projectOrName(services...)
+	project, name, err := opts.projectOrName(dockerCli, services...)
 	if err != nil {
 		return err
 	}

+ 6 - 5
cmd/compose/publish.go

@@ -19,12 +19,13 @@ package compose
 import (
 	"context"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
 )
 
-func publishCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := pushOptions{
 		ProjectOptions: p,
 	}
@@ -32,18 +33,18 @@ func publishCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		Use:   "publish [OPTIONS] [REPOSITORY]",
 		Short: "Publish compose application",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runPublish(ctx, backend, opts, args[0])
+			return runPublish(ctx, dockerCli, backend, opts, args[0])
 		}),
 		Args: cobra.ExactArgs(1),
 	}
 	return publishCmd
 }
 
-func runPublish(ctx context.Context, backend api.Service, opts pushOptions, repository string) error {
-	project, err := opts.ToProject(nil)
+func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, repository string) error {
+	project, err := opts.ToProject(dockerCli, nil)
 	if err != nil {
 		return err
 	}
 
-	return backend.Publish(ctx, project, repository)
+	return backend.Publish(ctx, project, repository, api.PublishOptions{})
 }

+ 6 - 5
cmd/compose/pull.go

@@ -22,6 +22,7 @@ import (
 	"os"
 
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/cli/command"
 	"github.com/morikuni/aec"
 	"github.com/spf13/cobra"
 
@@ -39,7 +40,7 @@ type pullOptions struct {
 	noBuildable        bool
 }
 
-func pullCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := pullOptions{
 		ProjectOptions: p,
 	}
@@ -53,9 +54,9 @@ func pullCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 			return nil
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runPull(ctx, backend, opts, args)
+			return runPull(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := cmd.Flags()
 	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information.")
@@ -69,8 +70,8 @@ func pullCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return cmd
 }
 
-func runPull(ctx context.Context, backend api.Service, opts pullOptions, services []string) error {
-	project, err := opts.ToProject(services)
+func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error {
+	project, err := opts.ToProject(dockerCli, services)
 	if err != nil {
 		return err
 	}

+ 6 - 5
cmd/compose/push.go

@@ -20,6 +20,7 @@ import (
 	"context"
 
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -33,7 +34,7 @@ type pushOptions struct {
 	Quiet          bool
 }
 
-func pushCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := pushOptions{
 		ProjectOptions: p,
 	}
@@ -41,9 +42,9 @@ func pushCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		Use:   "push [OPTIONS] [SERVICE...]",
 		Short: "Push service images",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runPush(ctx, backend, opts, args)
+			return runPush(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
 	pushCmd.Flags().BoolVar(&opts.IncludeDeps, "include-deps", false, "Also push images of services declared as dependencies")
@@ -52,8 +53,8 @@ func pushCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return pushCmd
 }
 
-func runPush(ctx context.Context, backend api.Service, opts pushOptions, services []string) error {
-	project, err := opts.ToProject(services)
+func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, services []string) error {
+	project, err := opts.ToProject(dockerCli, services)
 	if err != nil {
 		return err
 	}

+ 6 - 5
cmd/compose/remove.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/spf13/cobra"
 )
@@ -30,7 +31,7 @@ type removeOptions struct {
 	volumes bool
 }
 
-func removeCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func removeCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := removeOptions{
 		ProjectOptions: p,
 	}
@@ -44,9 +45,9 @@ can override this with -v. To list all volumes, use "docker volume ls".
 
 Any data which is not in a volume will be lost.`,
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runRemove(ctx, backend, opts, args)
+			return runRemove(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	f := cmd.Flags()
 	f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")
@@ -58,8 +59,8 @@ Any data which is not in a volume will be lost.`,
 	return cmd
 }
 
-func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
-	project, name, err := opts.projectOrName(services...)
+func runRemove(ctx context.Context, dockerCli command.Cli, backend api.Service, opts removeOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli, services...)
 	if err != nil {
 		return err
 	}

+ 6 - 5
cmd/compose/restart.go

@@ -20,6 +20,7 @@ import (
 	"context"
 	"time"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -32,7 +33,7 @@ type restartOptions struct {
 	noDeps      bool
 }
 
-func restartCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := restartOptions{
 		ProjectOptions: p,
 	}
@@ -43,9 +44,9 @@ func restartCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 			opts.timeChanged = cmd.Flags().Changed("timeout")
 		},
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runRestart(ctx, backend, opts, args)
+			return runRestart(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := restartCmd.Flags()
 	flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
@@ -54,8 +55,8 @@ func restartCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return restartCmd
 }
 
-func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
-	project, name, err := opts.projectOrName()
+func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts restartOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli)
 	if err != nil {
 		return err
 	}

+ 8 - 7
cmd/compose/run.go

@@ -26,6 +26,7 @@ import (
 	cgo "github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/loader"
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/opts"
 	"github.com/mattn/go-shellwords"
 	"github.com/spf13/cobra"
@@ -111,7 +112,7 @@ func (options runOptions) apply(project *types.Project) error {
 	return nil
 }
 
-func runCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	options := runOptions{
 		composeOptions: &composeOptions{
 			ProjectOptions: p,
@@ -152,7 +153,7 @@ func runCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *co
 			return nil
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			project, err := p.ToProject([]string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
+			project, err := p.ToProject(dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
 			if err != nil {
 				return err
 			}
@@ -162,16 +163,16 @@ func runCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *co
 			}
 
 			options.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
-			return runRun(ctx, backend, project, options, createOpts, buildOpts, streams)
+			return runRun(ctx, backend, project, options, createOpts, buildOpts, dockerCli)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := cmd.Flags()
 	flags.BoolVarP(&options.Detach, "detach", "d", false, "Run container in background and print container ID")
 	flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables")
 	flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label")
 	flags.BoolVar(&options.Remove, "rm", false, "Automatically remove the container when it exits")
-	flags.BoolVarP(&options.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
+	flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
 	flags.StringVar(&options.name, "name", "", "Assign a name to the container")
 	flags.StringVarP(&options.user, "user", "u", "", "Run as specified username or uid")
 	flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
@@ -206,7 +207,7 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
 	return pflag.NormalizedName(name)
 }
 
-func runRun(ctx context.Context, backend api.Service, project *types.Project, options runOptions, createOpts createOptions, buildOpts buildOptions, streams api.Streams) error {
+func runRun(ctx context.Context, backend api.Service, project *types.Project, options runOptions, createOpts createOptions, buildOpts buildOptions, dockerCli command.Cli) error {
 	err := options.apply(project)
 	if err != nil {
 		return err
@@ -228,7 +229,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 			buildForDeps = &bo
 		}
 		return startDependencies(ctx, backend, *project, buildForDeps, options.Service, options.ignoreOrphans)
-	}, streams.Err())
+	}, dockerCli.Err())
 	if err != nil {
 		return err
 	}

+ 6 - 5
cmd/compose/start.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/spf13/cobra"
 )
@@ -27,7 +28,7 @@ type startOptions struct {
 	*ProjectOptions
 }
 
-func startCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := startOptions{
 		ProjectOptions: p,
 	}
@@ -35,15 +36,15 @@ func startCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		Use:   "start [SERVICE...]",
 		Short: "Start services",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runStart(ctx, backend, opts, args)
+			return runStart(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	return startCmd
 }
 
-func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
-	project, name, err := opts.projectOrName(services...)
+func runStart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts startOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli, services...)
 	if err != nil {
 		return err
 	}

+ 6 - 5
cmd/compose/stop.go

@@ -20,6 +20,7 @@ import (
 	"context"
 	"time"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -31,7 +32,7 @@ type stopOptions struct {
 	timeout     int
 }
 
-func stopCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := stopOptions{
 		ProjectOptions: p,
 	}
@@ -42,9 +43,9 @@ func stopCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 			opts.timeChanged = cmd.Flags().Changed("timeout")
 		},
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runStop(ctx, backend, opts, args)
+			return runStop(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := cmd.Flags()
 	flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
@@ -52,8 +53,8 @@ func stopCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return cmd
 }
 
-func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
-	project, name, err := opts.projectOrName(services...)
+func runStop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts stopOptions, services []string) error {
+	project, name, err := opts.projectOrName(dockerCli, services...)
 	if err != nil {
 		return err
 	}

+ 8 - 7
cmd/compose/top.go

@@ -24,6 +24,7 @@ import (
 	"strings"
 	"text/tabwriter"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/spf13/cobra"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -33,7 +34,7 @@ type topOptions struct {
 	*ProjectOptions
 }
 
-func topCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := topOptions{
 		ProjectOptions: p,
 	}
@@ -41,15 +42,15 @@ func topCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *co
 		Use:   "top [SERVICES...]",
 		Short: "Display the running processes",
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runTop(ctx, streams, backend, opts, args)
+			return runTop(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	return topCmd
 }
 
-func runTop(ctx context.Context, streams api.Streams, backend api.Service, opts topOptions, services []string) error {
-	projectName, err := opts.toProjectName()
+func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error {
+	projectName, err := opts.toProjectName(dockerCli)
 	if err != nil {
 		return err
 	}
@@ -63,8 +64,8 @@ func runTop(ctx context.Context, streams api.Streams, backend api.Service, opts
 	})
 
 	for _, container := range containers {
-		fmt.Fprintf(streams.Out(), "%s\n", container.Name)
-		err := psPrinter(streams.Out(), func(w io.Writer) {
+		fmt.Fprintf(dockerCli.Out(), "%s\n", container.Name)
+		err := psPrinter(dockerCli.Out(), func(w io.Writer) {
 			for _, proc := range container.Processes {
 				info := []interface{}{}
 				for _, p := range proc {

+ 7 - 6
cmd/compose/up.go

@@ -26,6 +26,7 @@ import (
 	xprogress "github.com/docker/buildx/util/progress"
 
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/spf13/cobra"
 
@@ -73,7 +74,7 @@ func (opts upOptions) apply(project *types.Project, services []string) error {
 	return nil
 }
 
-func upCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
+func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	up := upOptions{}
 	create := createOptions{}
 	build := buildOptions{ProjectOptions: p}
@@ -85,7 +86,7 @@ func upCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cob
 			create.timeChanged = cmd.Flags().Changed("timeout")
 			return validateFlags(&up, &create)
 		}),
-		RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
+		RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
 			create.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
 			if create.ignoreOrphans && create.removeOrphans {
 				return fmt.Errorf("cannot combine %s and --remove-orphans", ComposeIgnoreOrphans)
@@ -93,9 +94,9 @@ func upCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cob
 			if len(up.attach) != 0 && up.attachDependencies {
 				return errors.New("cannot combine --attach and --attach-dependencies")
 			}
-			return runUp(ctx, streams, backend, create, up, build, project, services)
+			return runUp(ctx, dockerCli, backend, create, up, build, project, services)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 	flags := upCmd.Flags()
 	flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
@@ -153,7 +154,7 @@ func validateFlags(up *upOptions, create *createOptions) error {
 
 func runUp(
 	ctx context.Context,
-	streams api.Streams,
+	dockerCli command.Cli,
 	backend api.Service,
 	createOptions createOptions,
 	upOptions upOptions,
@@ -212,7 +213,7 @@ func runUp(
 	var consumer api.LogConsumer
 	var attach []string
 	if !upOptions.Detach {
-		consumer = formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
+		consumer = formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
 
 		var attachSet utils.Set[string]
 		if len(upOptions.attach) != 0 {

+ 6 - 6
cmd/compose/version.go

@@ -33,14 +33,14 @@ type versionOptions struct {
 	short  bool
 }
 
-func versionCommand(streams command.Cli) *cobra.Command {
+func versionCommand(dockerCli command.Cli) *cobra.Command {
 	opts := versionOptions{}
 	cmd := &cobra.Command{
 		Use:   "version [OPTIONS]",
 		Short: "Show the Docker Compose version information",
 		Args:  cobra.NoArgs,
 		RunE: func(cmd *cobra.Command, _ []string) error {
-			runVersion(opts, streams)
+			runVersion(opts, dockerCli)
 			return nil
 		},
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
@@ -57,14 +57,14 @@ func versionCommand(streams command.Cli) *cobra.Command {
 	return cmd
 }
 
-func runVersion(opts versionOptions, streams command.Cli) {
+func runVersion(opts versionOptions, dockerCli command.Cli) {
 	if opts.short {
-		fmt.Fprintln(streams.Out(), strings.TrimPrefix(internal.Version, "v"))
+		fmt.Fprintln(dockerCli.Out(), strings.TrimPrefix(internal.Version, "v"))
 		return
 	}
 	if opts.format == formatter.JSON {
-		fmt.Fprintf(streams.Out(), "{\"version\":%q}\n", internal.Version)
+		fmt.Fprintf(dockerCli.Out(), "{\"version\":%q}\n", internal.Version)
 		return
 	}
-	fmt.Fprintln(streams.Out(), "Docker Compose version", internal.Version)
+	fmt.Fprintln(dockerCli.Out(), "Docker Compose version", internal.Version)
 }

+ 5 - 4
cmd/compose/viz.go

@@ -22,6 +22,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/spf13/cobra"
 )
@@ -34,7 +35,7 @@ type vizOptions struct {
 	indentationStr   string
 }
 
-func vizCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := vizOptions{
 		ProjectOptions: p,
 	}
@@ -50,7 +51,7 @@ func vizCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 			return err
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runViz(ctx, backend, &opts)
+			return runViz(ctx, dockerCli, backend, &opts)
 		}),
 	}
 
@@ -62,9 +63,9 @@ func vizCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return cmd
 }
 
-func runViz(ctx context.Context, backend api.Service, opts *vizOptions) error {
+func runViz(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *vizOptions) error {
 	_, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL")
-	project, err := opts.ToProject(nil)
+	project, err := opts.ToProject(dockerCli, nil)
 	if err != nil {
 		return err
 	}

+ 5 - 4
cmd/compose/wait.go

@@ -21,6 +21,7 @@ import (
 	"os"
 
 	"github.com/docker/cli/cli"
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/spf13/cobra"
 )
@@ -33,7 +34,7 @@ type waitOptions struct {
 	downProject bool
 }
 
-func waitCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := waitOptions{
 		ProjectOptions: p,
 	}
@@ -46,7 +47,7 @@ func waitCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 		Args:  cli.RequiresMinArgs(1),
 		RunE: Adapt(func(ctx context.Context, services []string) error {
 			opts.services = services
-			statusCode, err = runWait(ctx, backend, &opts)
+			statusCode, err = runWait(ctx, dockerCli, backend, &opts)
 			return err
 		}),
 		PostRun: func(cmd *cobra.Command, args []string) {
@@ -59,8 +60,8 @@ func waitCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 	return cmd
 }
 
-func runWait(ctx context.Context, backend api.Service, opts *waitOptions) (int64, error) {
-	_, name, err := opts.projectOrName()
+func runWait(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *waitOptions) (int64, error) {
+	_, name, err := opts.projectOrName(dockerCli)
 	if err != nil {
 		return 0, err
 	}

+ 6 - 5
cmd/compose/watch.go

@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/internal/locker"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -32,7 +33,7 @@ type watchOptions struct {
 	quiet bool
 }
 
-func watchCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
+func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
 	opts := watchOptions{
 		ProjectOptions: p,
 	}
@@ -43,18 +44,18 @@ func watchCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
 			return nil
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			return runWatch(ctx, backend, opts, args)
+			return runWatch(ctx, dockerCli, backend, opts, args)
 		}),
-		ValidArgsFunction: completeServiceNames(p),
+		ValidArgsFunction: completeServiceNames(dockerCli, p),
 	}
 
 	cmd.Flags().BoolVar(&opts.quiet, "quiet", false, "hide build output")
 	return cmd
 }
 
-func runWatch(ctx context.Context, backend api.Service, opts watchOptions, services []string) error {
+func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, opts watchOptions, services []string) error {
 	fmt.Fprintln(os.Stderr, "watch command is EXPERIMENTAL")
-	project, err := opts.ToProject(nil)
+	project, err := opts.ToProject(dockerCli, nil)
 	if err != nil {
 		return err
 	}

+ 1 - 0
go.mod

@@ -54,6 +54,7 @@ require (
 	github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Masterminds/semver/v3 v3.2.1 // indirect
+	github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
 	github.com/aws/aws-sdk-go-v2 v1.17.6 // indirect
 	github.com/aws/aws-sdk-go-v2/config v1.18.16 // indirect
 	github.com/aws/aws-sdk-go-v2/credentials v1.13.16 // indirect

+ 2 - 1
go.sum

@@ -60,8 +60,9 @@ github.com/Microsoft/hcsshim v0.10.0-rc.8/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCzt
 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d h1:hi6J4K6DKrR4/ljxn6SF6nURyu785wKMuQcjt7H3VCQ=
 github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
+github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
+github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
 github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
 github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=

+ 5 - 1
pkg/api/api.go

@@ -75,7 +75,7 @@ type Service interface {
 	// Port executes the equivalent to a `compose port`
 	Port(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (string, int, error)
 	// Publish executes the equivalent to a `compose publish`
-	Publish(ctx context.Context, project *types.Project, repository string) error
+	Publish(ctx context.Context, project *types.Project, repository string, options PublishOptions) error
 	// Images executes the equivalent of a `compose images`
 	Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
 	// MaxConcurrency defines upper limit for concurrent operations against engine API
@@ -354,6 +354,10 @@ type PortOptions struct {
 	Index    int
 }
 
+// PublishOptions group options of the Publish API
+type PublishOptions struct {
+}
+
 func (e Event) String() string {
 	t := e.Timestamp.Format("2006-01-02 15:04:05.000000")
 	var attr []string

+ 3 - 3
pkg/api/proxy.go

@@ -55,7 +55,7 @@ type ServiceProxy struct {
 	DryRunModeFn         func(ctx context.Context, dryRun bool) (context.Context, error)
 	VizFn                func(ctx context.Context, project *types.Project, options VizOptions) (string, error)
 	WaitFn               func(ctx context.Context, projectName string, options WaitOptions) (int64, error)
-	PublishFn            func(ctx context.Context, project *types.Project, repository string) error
+	PublishFn            func(ctx context.Context, project *types.Project, repository string, options PublishOptions) error
 	interceptors         []Interceptor
 }
 
@@ -313,8 +313,8 @@ func (s *ServiceProxy) Port(ctx context.Context, projectName string, service str
 	return s.PortFn(ctx, projectName, service, port, options)
 }
 
-func (s *ServiceProxy) Publish(ctx context.Context, project *types.Project, repository string) error {
-	return s.PublishFn(ctx, project, repository)
+func (s *ServiceProxy) Publish(ctx context.Context, project *types.Project, repository string, options PublishOptions) error {
+	return s.PublishFn(ctx, project, repository, options)
 }
 
 // Images implements Service interface

+ 2 - 2
pkg/compose/publish.go

@@ -20,11 +20,11 @@ import (
 	"context"
 
 	"github.com/compose-spec/compose-go/types"
-	"github.com/distribution/distribution/v3/reference"
+	"github.com/distribution/reference"
 	"github.com/docker/compose/v2/pkg/api"
 )
 
-func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string) error {
+func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error {
 	err := s.Push(ctx, project, api.PushOptions{})
 	if err != nil {
 		return err

+ 4 - 4
pkg/mocks/mock_docker_compose_api.go

@@ -267,17 +267,17 @@ func (mr *MockServiceMockRecorder) Ps(ctx, projectName, options interface{}) *go
 }
 
 // Publish mocks base method.
-func (m *MockService) Publish(ctx context.Context, project *types.Project, repository string) error {
+func (m *MockService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Publish", ctx, project, repository)
+	ret := m.ctrl.Call(m, "Publish", ctx, project, repository, options)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // Publish indicates an expected call of Publish.
-func (mr *MockServiceMockRecorder) Publish(ctx, project, repository interface{}) *gomock.Call {
+func (mr *MockServiceMockRecorder) Publish(ctx, project, repository, options interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockService)(nil).Publish), ctx, project, repository)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockService)(nil).Publish), ctx, project, repository, options)
 }
 
 // Pull mocks base method.

+ 9 - 12
pkg/remote/git.go

@@ -46,7 +46,7 @@ func GitRemoteLoaderEnabled() (bool, error) {
 	return false, nil
 }
 
-func NewGitRemoteLoader() (loader.ResourceLoader, error) {
+func NewGitRemoteLoader(offline bool) (loader.ResourceLoader, error) {
 	// xdg.CacheFile creates the parent directories for the target file path
 	// and returns the fully qualified path, so use "git" as a filename and
 	// then chop it off after, i.e. no ~/.cache/docker-compose/git file will
@@ -57,12 +57,14 @@ func NewGitRemoteLoader() (loader.ResourceLoader, error) {
 	}
 	cache = filepath.Dir(cache)
 	return gitRemoteLoader{
-		cache: cache,
+		cache:   cache,
+		offline: offline,
 	}, err
 }
 
 type gitRemoteLoader struct {
-	cache string
+	cache   string
+	offline bool
 }
 
 func (g gitRemoteLoader) Accept(path string) bool {
@@ -104,6 +106,9 @@ func (g gitRemoteLoader) Load(ctx context.Context, path string) (string, error)
 
 	local := filepath.Join(g.cache, ref.Commit)
 	if _, err := os.Stat(local); os.IsNotExist(err) {
+		if g.offline {
+			return "", nil
+		}
 		err = g.checkout(ctx, local, ref)
 		if err != nil {
 			return "", err
@@ -167,7 +172,7 @@ func (g gitRemoteLoader) gitCommandEnv() []string {
 		// Disable any ssh connection pooling by Git and do not attempt to prompt the user.
 		env["GIT_SSH_COMMAND"] = "ssh -o ControlMaster=no -o BatchMode=yes"
 	}
-	v := values(env)
+	v := env.Values()
 	return v
 }
 
@@ -182,11 +187,3 @@ func findFile(names []string, pwd string) (string, error) {
 }
 
 var _ loader.ResourceLoader = gitRemoteLoader{}
-
-func values(m types.Mapping) []string {
-	values := make([]string, 0, len(m))
-	for k, v := range m {
-		values = append(values, fmt.Sprintf("%s=%s", k, v))
-	}
-	return values
-}

+ 144 - 0
pkg/remote/oci.go

@@ -0,0 +1,144 @@
+/*
+   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 remote
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	"github.com/adrg/xdg"
+	"github.com/distribution/reference"
+	"github.com/docker/buildx/store/storeutil"
+	"github.com/docker/buildx/util/imagetools"
+	"github.com/docker/cli/cli/command"
+	v1 "github.com/opencontainers/image-spec/specs-go/v1"
+
+	"github.com/compose-spec/compose-go/loader"
+	"github.com/pkg/errors"
+)
+
+func OCIRemoteLoaderEnabled() (bool, error) {
+	if v := os.Getenv("COMPOSE_EXPERIMENTAL_OCI_REMOTE"); v != "" {
+		enabled, err := strconv.ParseBool(v)
+		if err != nil {
+			return false, errors.Wrap(err, "COMPOSE_EXPERIMENTAL_OCI_REMOTE environment variable expects boolean value")
+		}
+		return enabled, err
+	}
+	return false, nil
+}
+
+func NewOCIRemoteLoader(dockerCli command.Cli, offline bool) (loader.ResourceLoader, error) {
+	// xdg.CacheFile creates the parent directories for the target file path
+	// and returns the fully qualified path, so use "git" as a filename and
+	// then chop it off after, i.e. no ~/.cache/docker-compose/git file will
+	// ever be created
+	cache, err := xdg.CacheFile(filepath.Join("docker-compose", "oci"))
+	if err != nil {
+		return nil, fmt.Errorf("initializing git cache: %w", err)
+	}
+	cache = filepath.Dir(cache)
+	return ociRemoteLoader{
+		cache:     cache,
+		dockerCli: dockerCli,
+		offline:   offline,
+	}, err
+}
+
+type ociRemoteLoader struct {
+	cache     string
+	dockerCli command.Cli
+	offline   bool
+}
+
+const prefix = "oci:"
+
+func (g ociRemoteLoader) Accept(path string) bool {
+	return strings.HasPrefix(path, prefix)
+}
+
+func (g ociRemoteLoader) Load(ctx context.Context, path string) (string, error) {
+	if g.offline {
+		return "", nil
+	}
+
+	ref, err := reference.ParseDockerRef(path[len(prefix):])
+	if err != nil {
+		return "", err
+	}
+
+	opt, err := storeutil.GetImageConfig(g.dockerCli, nil)
+	if err != nil {
+		return "", err
+	}
+	resolver := imagetools.New(opt)
+
+	content, descriptor, err := resolver.Get(ctx, ref.String())
+	if err != nil {
+		return "", err
+	}
+
+	local := filepath.Join(g.cache, descriptor.Digest.Hex())
+	composeFile := filepath.Join(local, "compose.yaml")
+	if _, err = os.Stat(local); os.IsNotExist(err) {
+
+		err = os.MkdirAll(local, 0o700)
+		if err != nil {
+			return "", err
+		}
+
+		f, err := os.Create(composeFile)
+		if err != nil {
+			return "", err
+		}
+		defer f.Close() //nolint:errcheck
+
+		var descriptor v1.Manifest
+		err = json.Unmarshal(content, &descriptor)
+		if err != nil {
+			return "", err
+		}
+		for i, layer := range descriptor.Layers {
+			digested, err := reference.WithDigest(ref, layer.Digest)
+			if err != nil {
+				return "", err
+			}
+			content, _, err := resolver.Get(ctx, digested.String())
+			if err != nil {
+				return "", err
+			}
+			if i > 0 {
+				_, err = f.Write([]byte("\n---\n"))
+				if err != nil {
+					return "", err
+				}
+			}
+			_, err = f.Write(content)
+			if err != nil {
+				return "", err
+			}
+		}
+	}
+	return composeFile, nil
+}
+
+var _ loader.ResourceLoader = ociRemoteLoader{}