Просмотр исходного кода

move progress UI components into cmd

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 1 месяц назад
Родитель
Сommit
aff5c115d6

+ 5 - 5
cmd/compose/build.go

@@ -26,8 +26,8 @@ import (
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
 	cliopts "github.com/docker/cli/opts"
 	cliopts "github.com/docker/cli/opts"
+	"github.com/docker/compose/v2/cmd/display"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/docker/compose/v2/pkg/compose"
-	ui "github.com/docker/compose/v2/pkg/progress"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
@@ -67,8 +67,8 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
 		builderName = os.Getenv("BUILDX_BUILDER")
 		builderName = os.Getenv("BUILDX_BUILDER")
 	}
 	}
 
 
-	uiMode := ui.Mode
-	if uiMode == ui.ModeJSON {
+	uiMode := display.Mode
+	if uiMode == display.ModeJSON {
 		uiMode = "rawjson"
 		uiMode = "rawjson"
 	}
 	}
 
 
@@ -100,7 +100,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
 		Short: "Build or rebuild services",
 		Short: "Build or rebuild services",
 		PreRunE: Adapt(func(ctx context.Context, args []string) error {
 		PreRunE: Adapt(func(ctx context.Context, args []string) error {
 			if opts.quiet {
 			if opts.quiet {
-				ui.Mode = ui.ModeQuiet
+				display.Mode = display.ModeQuiet
 				devnull, err := os.Open(os.DevNull)
 				devnull, err := os.Open(os.DevNull)
 				if err != nil {
 				if err != nil {
 					return err
 					return err
@@ -151,7 +151,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
 
 
 func runBuild(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts buildOptions, services []string) error {
 func runBuild(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts buildOptions, services []string) error {
 	if opts.print {
 	if opts.print {
-		backendOptions.Add(compose.WithEventProcessor(ui.NewQuietWriter()))
+		backendOptions.Add(compose.WithEventProcessor(display.Quiet()))
 	}
 	}
 	backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
 	backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...)
 	if err != nil {
 	if err != nil {

+ 41 - 28
cmd/compose/compose.go

@@ -39,11 +39,11 @@ import (
 	"github.com/docker/cli/cli-plugins/metadata"
 	"github.com/docker/cli/cli-plugins/metadata"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/pkg/kvfile"
 	"github.com/docker/cli/pkg/kvfile"
+	"github.com/docker/compose/v2/cmd/display"
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/docker/compose/v2/pkg/compose"
-	ui "github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/remote"
 	"github.com/docker/compose/v2/pkg/remote"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/morikuni/aec"
 	"github.com/morikuni/aec"
@@ -84,10 +84,16 @@ func rawEnv(r io.Reader, filename string, vars map[string]string, lookup func(ke
 	return nil
 	return nil
 }
 }
 
 
+var stdioToStdout bool
+
 func init() {
 func init() {
 	// compose evaluates env file values for interpolation
 	// compose evaluates env file values for interpolation
 	// `raw` format allows to load env_file with the same parser used by docker run --env-file
 	// `raw` format allows to load env_file with the same parser used by docker run --env-file
 	dotenv.RegisterFormat("raw", rawEnv)
 	dotenv.RegisterFormat("raw", rawEnv)
+
+	if v, ok := os.LookupEnv("COMPOSE_STATUS_STDOUT"); ok {
+		stdioToStdout, _ = strconv.ParseBool(v)
+	}
 }
 }
 
 
 // Command defines a compose CLI command as a func with args
 // Command defines a compose CLI command as a func with args
@@ -116,7 +122,7 @@ func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
 				StatusCode: 130,
 				StatusCode: 130,
 			}
 			}
 		}
 		}
-		if ui.Mode == ui.ModeJSON {
+		if display.Mode == display.ModeJSON {
 			err = makeJSONError(err)
 			err = makeJSONError(err)
 		}
 		}
 		return err
 		return err
@@ -486,49 +492,49 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
 			formatter.SetANSIMode(dockerCli, ansi)
 			formatter.SetANSIMode(dockerCli, ansi)
 
 
 			if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
 			if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
-				ui.NoColor()
+				display.NoColor()
 				formatter.SetANSIMode(dockerCli, formatter.Never)
 				formatter.SetANSIMode(dockerCli, formatter.Never)
 			}
 			}
 
 
 			switch ansi {
 			switch ansi {
 			case "never":
 			case "never":
-				ui.Mode = ui.ModePlain
+				display.Mode = display.ModePlain
 			case "always":
 			case "always":
-				ui.Mode = ui.ModeTTY
+				display.Mode = display.ModeTTY
 			}
 			}
 
 
-			var ep ui.EventProcessor
+			var ep api.EventProcessor
 			switch opts.Progress {
 			switch opts.Progress {
-			case "", ui.ModeAuto:
+			case "", display.ModeAuto:
 				switch {
 				switch {
 				case ansi == "never":
 				case ansi == "never":
-					ui.Mode = ui.ModePlain
-					ep = ui.NewPlainWriter(dockerCli.Err())
+					display.Mode = display.ModePlain
+					ep = display.Plain(dockerCli.Err())
 				case dockerCli.Out().IsTerminal():
 				case dockerCli.Out().IsTerminal():
-					ep = ui.NewTTYWriter(dockerCli.Err())
+					ep = display.Full(dockerCli.Err(), stdinfo(dockerCli))
 				default:
 				default:
-					ep = ui.NewPlainWriter(dockerCli.Err())
+					ep = display.Plain(dockerCli.Err())
 				}
 				}
-			case ui.ModeTTY:
+			case display.ModeTTY:
 				if ansi == "never" {
 				if ansi == "never" {
 					return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
 					return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
 				}
 				}
-				ui.Mode = ui.ModeTTY
-				ep = ui.NewTTYWriter(dockerCli.Err())
+				display.Mode = display.ModeTTY
+				ep = display.Full(dockerCli.Err(), stdinfo(dockerCli))
 
 
-			case ui.ModePlain:
+			case display.ModePlain:
 				if ansi == "always" {
 				if ansi == "always" {
 					return fmt.Errorf("can't use --progress plain while ANSI support is forced")
 					return fmt.Errorf("can't use --progress plain while ANSI support is forced")
 				}
 				}
-				ui.Mode = ui.ModePlain
-				ep = ui.NewPlainWriter(dockerCli.Err())
-			case ui.ModeQuiet, "none":
-				ui.Mode = ui.ModeQuiet
-				ep = ui.NewQuietWriter()
-			case ui.ModeJSON:
-				ui.Mode = ui.ModeJSON
+				display.Mode = display.ModePlain
+				ep = display.Plain(dockerCli.Err())
+			case display.ModeQuiet, "none":
+				display.Mode = display.ModeQuiet
+				ep = display.Quiet()
+			case display.ModeJSON:
+				display.Mode = display.ModeJSON
 				logrus.SetFormatter(&logrus.JSONFormatter{})
 				logrus.SetFormatter(&logrus.JSONFormatter{})
-				ep = ui.NewJSONWriter(dockerCli.Err())
+				ep = display.JSON(dockerCli.Err())
 			default:
 			default:
 				return fmt.Errorf("unsupported --progress value %q", opts.Progress)
 				return fmt.Errorf("unsupported --progress value %q", opts.Progress)
 			}
 			}
@@ -658,6 +664,13 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C
 	return c
 	return c
 }
 }
 
 
+func stdinfo(dockerCli command.Cli) io.Writer {
+	if stdioToStdout {
+		return dockerCli.Out()
+	}
+	return dockerCli.Err()
+}
+
 func setEnvWithDotEnv(opts ProjectOptions) error {
 func setEnvWithDotEnv(opts ProjectOptions) error {
 	options, err := cli.NewProjectOptions(opts.ConfigPaths,
 	options, err := cli.NewProjectOptions(opts.ConfigPaths,
 		cli.WithWorkingDirectory(opts.ProjectDir),
 		cli.WithWorkingDirectory(opts.ProjectDir),
@@ -683,9 +696,9 @@ func setEnvWithDotEnv(opts ProjectOptions) error {
 }
 }
 
 
 var printerModes = []string{
 var printerModes = []string{
-	ui.ModeAuto,
-	ui.ModeTTY,
-	ui.ModePlain,
-	ui.ModeJSON,
-	ui.ModeQuiet,
+	display.ModeAuto,
+	display.ModeTTY,
+	display.ModePlain,
+	display.ModeJSON,
+	display.ModeQuiet,
 }
 }

+ 8 - 1
cmd/compose/kill.go

@@ -18,6 +18,8 @@ package compose
 
 
 import (
 import (
 	"context"
 	"context"
+	"errors"
+	"fmt"
 	"os"
 	"os"
 
 
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
@@ -65,10 +67,15 @@ func runKill(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return backend.Kill(ctx, name, api.KillOptions{
+	err = backend.Kill(ctx, name, api.KillOptions{
 		RemoveOrphans: opts.removeOrphans,
 		RemoveOrphans: opts.removeOrphans,
 		Project:       project,
 		Project:       project,
 		Services:      services,
 		Services:      services,
 		Signal:        opts.signal,
 		Signal:        opts.signal,
 	})
 	})
+	if errors.Is(err, api.ErrNoResources) {
+		_, _ = fmt.Fprintln(stdinfo(dockerCli), "No container to kill")
+		return nil
+	}
+	return err
 }
 }

+ 6 - 7
cmd/compose/logs.go

@@ -22,7 +22,6 @@ import (
 
 
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/docker/compose/v2/pkg/compose"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/docker/compose/v2/cmd/formatter"
@@ -107,28 +106,28 @@ func runLogs(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
 var _ api.LogConsumer = &logConsumer{}
 var _ api.LogConsumer = &logConsumer{}
 
 
 type logConsumer struct {
 type logConsumer struct {
-	events progress.EventProcessor
+	events api.EventProcessor
 }
 }
 
 
 func (l logConsumer) Log(containerName, message string) {
 func (l logConsumer) Log(containerName, message string) {
-	l.events.On(progress.Event{
+	l.events.On(api.Resource{
 		ID:   containerName,
 		ID:   containerName,
 		Text: message,
 		Text: message,
 	})
 	})
 }
 }
 
 
 func (l logConsumer) Err(containerName, message string) {
 func (l logConsumer) Err(containerName, message string) {
-	l.events.On(progress.Event{
+	l.events.On(api.Resource{
 		ID:     containerName,
 		ID:     containerName,
-		Status: progress.Error,
+		Status: api.Error,
 		Text:   message,
 		Text:   message,
 	})
 	})
 }
 }
 
 
 func (l logConsumer) Status(containerName, message string) {
 func (l logConsumer) Status(containerName, message string) {
-	l.events.On(progress.Event{
+	l.events.On(api.Resource{
 		ID:     containerName,
 		ID:     containerName,
-		Status: progress.Error,
+		Status: api.Error,
 		Text:   message,
 		Text:   message,
 	})
 	})
 }
 }

+ 2 - 2
cmd/compose/options.go

@@ -30,9 +30,9 @@ import (
 	"github.com/compose-spec/compose-go/v2/template"
 	"github.com/compose-spec/compose-go/v2/template"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
+	"github.com/docker/compose/v2/cmd/display"
 	"github.com/docker/compose/v2/cmd/prompt"
 	"github.com/docker/compose/v2/cmd/prompt"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/internal/tracing"
-	ui "github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
 func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
@@ -247,7 +247,7 @@ func displayInterpolationVariables(writer io.Writer, varsInfo []varInfo) {
 
 
 func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, options buildOptions) {
 func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, options buildOptions) {
 	mainComposeFile := options.ProjectOptions.ConfigPaths[0] //nolint:staticcheck
 	mainComposeFile := options.ProjectOptions.ConfigPaths[0] //nolint:staticcheck
-	if ui.Mode != ui.ModeQuiet && ui.Mode != ui.ModeJSON {
+	if display.Mode != display.ModeQuiet && display.Mode != display.ModeJSON {
 		_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir)
 		_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir)
 	}
 	}
 }
 }

+ 8 - 1
cmd/compose/remove.go

@@ -18,6 +18,8 @@ package compose
 
 
 import (
 import (
 	"context"
 	"context"
+	"errors"
+	"fmt"
 
 
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
@@ -70,11 +72,16 @@ func runRemove(ctx context.Context, dockerCli command.Cli, backendOptions *Backe
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return backend.Remove(ctx, name, api.RemoveOptions{
+	err = backend.Remove(ctx, name, api.RemoveOptions{
 		Services: services,
 		Services: services,
 		Force:    opts.force,
 		Force:    opts.force,
 		Volumes:  opts.volumes,
 		Volumes:  opts.volumes,
 		Project:  project,
 		Project:  project,
 		Stop:     opts.stop,
 		Stop:     opts.stop,
 	})
 	})
+	if errors.Is(err, api.ErrNoResources) {
+		_, _ = fmt.Fprintln(stdinfo(dockerCli), "No stopped containers")
+		return nil
+	}
+	return err
 }
 }

+ 2 - 2
cmd/compose/run.go

@@ -25,8 +25,8 @@ import (
 	composecli "github.com/compose-spec/compose-go/v2/cli"
 	composecli "github.com/compose-spec/compose-go/v2/cli"
 	"github.com/compose-spec/compose-go/v2/dotenv"
 	"github.com/compose-spec/compose-go/v2/dotenv"
 	"github.com/compose-spec/compose-go/v2/format"
 	"github.com/compose-spec/compose-go/v2/format"
+	"github.com/docker/compose/v2/cmd/display"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/docker/compose/v2/pkg/compose"
-	"github.com/docker/compose/v2/pkg/progress"
 	xprogress "github.com/moby/buildkit/util/progress/progressui"
 	xprogress "github.com/moby/buildkit/util/progress/progressui"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 
 
@@ -193,7 +193,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backen
 			}
 			}
 
 
 			if options.quiet {
 			if options.quiet {
-				progress.Mode = progress.ModeQuiet
+				display.Mode = display.ModeQuiet
 				devnull, err := os.Open(os.DevNull)
 				devnull, err := os.Open(os.DevNull)
 				if err != nil {
 				if err != nil {
 					return err
 					return err

+ 2 - 2
cmd/compose/up.go

@@ -26,8 +26,8 @@ import (
 
 
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
+	"github.com/docker/compose/v2/cmd/display"
 	"github.com/docker/compose/v2/pkg/compose"
 	"github.com/docker/compose/v2/pkg/compose"
-	ui "github.com/docker/compose/v2/pkg/progress"
 	xprogress "github.com/moby/buildkit/util/progress/progressui"
 	xprogress "github.com/moby/buildkit/util/progress/progressui"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
@@ -341,7 +341,7 @@ func runUp(
 			WaitTimeout:    timeout,
 			WaitTimeout:    timeout,
 			Watch:          upOptions.watch,
 			Watch:          upOptions.watch,
 			Services:       services,
 			Services:       services,
-			NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain" && dockerCli.In().IsTerminal(),
+			NavigationMenu: upOptions.navigationMenu && display.Mode != "plain" && dockerCli.In().IsTerminal(),
 		},
 		},
 	})
 	})
 }
 }

+ 1 - 1
pkg/progress/colors.go → cmd/display/colors.go

@@ -14,7 +14,7 @@
    limitations under the License.
    limitations under the License.
 */
 */
 
 
-package progress
+package display
 
 
 import (
 import (
 	"github.com/morikuni/aec"
 	"github.com/morikuni/aec"

+ 6 - 4
pkg/progress/json.go → cmd/display/json.go

@@ -14,16 +14,18 @@
    limitations under the License.
    limitations under the License.
 */
 */
 
 
-package progress
+package display
 
 
 import (
 import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
+
+	"github.com/docker/compose/v2/pkg/api"
 )
 )
 
 
-func NewJSONWriter(out io.Writer) EventProcessor {
+func JSON(out io.Writer) api.EventProcessor {
 	return &jsonWriter{
 	return &jsonWriter{
 		out: out,
 		out: out,
 	}
 	}
@@ -50,7 +52,7 @@ type jsonMessage struct {
 func (p *jsonWriter) Start(ctx context.Context, operation string) {
 func (p *jsonWriter) Start(ctx context.Context, operation string) {
 }
 }
 
 
-func (p *jsonWriter) Event(e Event) {
+func (p *jsonWriter) Event(e api.Resource) {
 	message := &jsonMessage{
 	message := &jsonMessage{
 		DryRun:   p.dryRun,
 		DryRun:   p.dryRun,
 		Tail:     false,
 		Tail:     false,
@@ -69,7 +71,7 @@ func (p *jsonWriter) Event(e Event) {
 	}
 	}
 }
 }
 
 
-func (p *jsonWriter) On(events ...Event) {
+func (p *jsonWriter) On(events ...api.Resource) {
 	for _, e := range events {
 	for _, e := range events {
 		p.Event(e)
 		p.Event(e)
 	}
 	}

+ 6 - 5
pkg/progress/json_test.go → cmd/display/json_test.go

@@ -14,13 +14,14 @@
    limitations under the License.
    limitations under the License.
 */
 */
 
 
-package progress
+package display
 
 
 import (
 import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"testing"
 	"testing"
 
 
+	"github.com/docker/compose/v2/pkg/api"
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/assert"
 )
 )
 
 
@@ -31,11 +32,11 @@ func TestJsonWriter_Event(t *testing.T) {
 		dryRun: true,
 		dryRun: true,
 	}
 	}
 
 
-	event := Event{
+	event := api.Resource{
 		ID:       "service1",
 		ID:       "service1",
 		ParentID: "project",
 		ParentID: "project",
-		Status:   Working,
-		Text:     StatusCreating,
+		Status:   api.Working,
+		Text:     api.StatusCreating,
 		Current:  50,
 		Current:  50,
 		Total:    100,
 		Total:    100,
 		Percent:  50,
 		Percent:  50,
@@ -50,7 +51,7 @@ func TestJsonWriter_Event(t *testing.T) {
 		DryRun:   true,
 		DryRun:   true,
 		ID:       event.ID,
 		ID:       event.ID,
 		ParentID: event.ParentID,
 		ParentID: event.ParentID,
-		Text:     StatusCreating,
+		Text:     api.StatusCreating,
 		Status:   "Working",
 		Status:   "Working",
 		Current:  event.Current,
 		Current:  event.Current,
 		Total:    event.Total,
 		Total:    event.Total,

+ 4 - 17
pkg/progress/progress.go → cmd/display/mode.go

@@ -1,5 +1,5 @@
 /*
 /*
-   Copyright 2020 Docker Compose CLI authors
+   Copyright 2024 Docker Compose CLI authors
 
 
    Licensed under the Apache License, Version 2.0 (the "License");
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    you may not use this file except in compliance with the License.
@@ -14,20 +14,10 @@
    limitations under the License.
    limitations under the License.
 */
 */
 
 
-package progress
+package display
 
 
-import (
-	"context"
-)
-
-type progressFunc func(context.Context) error
-
-func Run(ctx context.Context, pf progressFunc, operation string, bus EventProcessor) error {
-	bus.Start(ctx, operation)
-	err := pf(ctx)
-	bus.Done(operation, err != nil)
-	return err
-}
+// Mode define how progress should be rendered, either as ModePlain or ModeTTY
+var Mode = ModeAuto
 
 
 const (
 const (
 	// ModeAuto detect console capabilities
 	// ModeAuto detect console capabilities
@@ -41,6 +31,3 @@ const (
 	// ModeJSON outputs a machine-readable JSON stream
 	// ModeJSON outputs a machine-readable JSON stream
 	ModeJSON = "json"
 	ModeJSON = "json"
 )
 )
-
-// Mode define how progress should be rendered, either as ModePlain or ModeTTY
-var Mode = ModeAuto

+ 4 - 4
pkg/progress/plain.go → cmd/display/plain.go

@@ -14,7 +14,7 @@
    limitations under the License.
    limitations under the License.
 */
 */
 
 
-package progress
+package display
 
 
 import (
 import (
 	"context"
 	"context"
@@ -24,7 +24,7 @@ import (
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 )
 )
 
 
-func NewPlainWriter(out io.Writer) EventProcessor {
+func Plain(out io.Writer) api.EventProcessor {
 	return &plainWriter{
 	return &plainWriter{
 		out: out,
 		out: out,
 	}
 	}
@@ -38,7 +38,7 @@ type plainWriter struct {
 func (p *plainWriter) Start(ctx context.Context, operation string) {
 func (p *plainWriter) Start(ctx context.Context, operation string) {
 }
 }
 
 
-func (p *plainWriter) Event(e Event) {
+func (p *plainWriter) Event(e api.Resource) {
 	prefix := ""
 	prefix := ""
 	if p.dryRun {
 	if p.dryRun {
 		prefix = api.DRYRUN_PREFIX
 		prefix = api.DRYRUN_PREFIX
@@ -46,7 +46,7 @@ func (p *plainWriter) Event(e Event) {
 	_, _ = fmt.Fprintln(p.out, prefix, e.ID, e.Text, e.Details)
 	_, _ = fmt.Fprintln(p.out, prefix, e.ID, e.Text, e.Details)
 }
 }
 
 
-func (p *plainWriter) On(events ...Event) {
+func (p *plainWriter) On(events ...api.Resource) {
 	for _, e := range events {
 	for _, e := range events {
 		p.Event(e)
 		p.Event(e)
 	}
 	}

+ 8 - 4
pkg/progress/quiet.go → cmd/display/quiet.go

@@ -14,11 +14,15 @@
    limitations under the License.
    limitations under the License.
 */
 */
 
 
-package progress
+package display
 
 
-import "context"
+import (
+	"context"
 
 
-func NewQuietWriter() EventProcessor {
+	"github.com/docker/compose/v2/pkg/api"
+)
+
+func Quiet() api.EventProcessor {
 	return &quiet{}
 	return &quiet{}
 }
 }
 
 
@@ -30,5 +34,5 @@ func (q *quiet) Start(_ context.Context, _ string) {
 func (q *quiet) Done(_ string, _ bool) {
 func (q *quiet) Done(_ string, _ bool) {
 }
 }
 
 
-func (q *quiet) On(_ ...Event) {
+func (q *quiet) On(_ ...api.Resource) {
 }
 }

+ 1 - 1
pkg/progress/spinner.go → cmd/display/spinner.go

@@ -14,7 +14,7 @@
    limitations under the License.
    limitations under the License.
 */
 */
 
 
-package progress
+package display
 
 
 import (
 import (
 	"runtime"
 	"runtime"

+ 34 - 27
pkg/progress/tty.go → cmd/display/tty.go

@@ -14,7 +14,7 @@
    limitations under the License.
    limitations under the License.
 */
 */
 
 
-package progress
+package display
 
 
 import (
 import (
 	"context"
 	"context"
@@ -31,11 +31,12 @@ import (
 	"github.com/morikuni/aec"
 	"github.com/morikuni/aec"
 )
 )
 
 
-// NewTTYWriter creates an EventProcessor that render advanced UI within a terminal.
+// Full creates an EventProcessor that render advanced UI within a terminal.
 // On Start, TUI lists task with a progress timer
 // On Start, TUI lists task with a progress timer
-func NewTTYWriter(out io.Writer) EventProcessor {
+func Full(out io.Writer, info io.Writer) api.EventProcessor {
 	return &ttyWriter{
 	return &ttyWriter{
 		out:   out,
 		out:   out,
+		info:  info,
 		tasks: map[string]task{},
 		tasks: map[string]task{},
 		done:  make(chan bool),
 		done:  make(chan bool),
 		mtx:   &sync.Mutex{},
 		mtx:   &sync.Mutex{},
@@ -55,6 +56,7 @@ type ttyWriter struct {
 	operation       string
 	operation       string
 	ticker          *time.Ticker
 	ticker          *time.Ticker
 	suspended       bool
 	suspended       bool
+	info            io.Writer
 }
 }
 
 
 type task struct {
 type task struct {
@@ -64,7 +66,7 @@ type task struct {
 	endTime   time.Time
 	endTime   time.Time
 	text      string
 	text      string
 	details   string
 	details   string
-	status    EventStatus
+	status    api.EventStatus
 	current   int64
 	current   int64
 	percent   int
 	percent   int
 	total     int64
 	total     int64
@@ -108,11 +110,16 @@ func (w *ttyWriter) Done(operation string, success bool) {
 	w.done <- true
 	w.done <- true
 }
 }
 
 
-func (w *ttyWriter) On(events ...Event) {
+func (w *ttyWriter) On(events ...api.Resource) {
 	w.mtx.Lock()
 	w.mtx.Lock()
 	defer w.mtx.Unlock()
 	defer w.mtx.Unlock()
 	for _, e := range events {
 	for _, e := range events {
-		if w.operation != "start" && (e.Text == StatusStarted || e.Text == StatusStarting) {
+		if e.ID == "Compose" {
+			_, _ = fmt.Fprintln(w.info, ErrorColor(e.Details))
+			continue
+		}
+
+		if w.operation != "start" && (e.Text == api.StatusStarted || e.Text == api.StatusStarting) {
 			// skip those events to avoid mix with container logs
 			// skip those events to avoid mix with container logs
 			continue
 			continue
 		}
 		}
@@ -120,9 +127,9 @@ func (w *ttyWriter) On(events ...Event) {
 	}
 	}
 }
 }
 
 
-func (w *ttyWriter) event(e Event) {
+func (w *ttyWriter) event(e api.Resource) {
 	// Suspend print while a build is in progress, to avoid collision with buildkit Display
 	// Suspend print while a build is in progress, to avoid collision with buildkit Display
-	if e.Text == StatusBuilding {
+	if e.Text == api.StatusBuilding {
 		w.ticker.Stop()
 		w.ticker.Stop()
 		w.suspended = true
 		w.suspended = true
 	} else if w.suspended {
 	} else if w.suspended {
@@ -132,11 +139,11 @@ func (w *ttyWriter) event(e Event) {
 
 
 	if last, ok := w.tasks[e.ID]; ok {
 	if last, ok := w.tasks[e.ID]; ok {
 		switch e.Status {
 		switch e.Status {
-		case Done, Error, Warning:
+		case api.Done, api.Error, api.Warning:
 			if last.status != e.Status {
 			if last.status != e.Status {
 				last.stop()
 				last.stop()
 			}
 			}
-		case Working:
+		case api.Working:
 			last.hasMore()
 			last.hasMore()
 		}
 		}
 		last.status = e.Status
 		last.status = e.Status
@@ -170,7 +177,7 @@ func (w *ttyWriter) event(e Event) {
 			total:     e.Total,
 			total:     e.Total,
 			spinner:   NewSpinner(),
 			spinner:   NewSpinner(),
 		}
 		}
-		if e.Status == Done || e.Status == Error {
+		if e.Status == api.Done || e.Status == api.Error {
 			t.stop()
 			t.stop()
 		}
 		}
 		w.tasks[e.ID] = t
 		w.tasks[e.ID] = t
@@ -179,7 +186,7 @@ func (w *ttyWriter) event(e Event) {
 	w.printEvent(e)
 	w.printEvent(e)
 }
 }
 
 
-func (w *ttyWriter) printEvent(e Event) {
+func (w *ttyWriter) printEvent(e api.Resource) {
 	if w.operation != "" {
 	if w.operation != "" {
 		// event will be displayed by progress UI on ticker's ticks
 		// event will be displayed by progress UI on ticker's ticks
 		return
 		return
@@ -187,13 +194,13 @@ func (w *ttyWriter) printEvent(e Event) {
 
 
 	var color colorFunc
 	var color colorFunc
 	switch e.Status {
 	switch e.Status {
-	case Working:
+	case api.Working:
 		color = SuccessColor
 		color = SuccessColor
-	case Done:
+	case api.Done:
 		color = SuccessColor
 		color = SuccessColor
-	case Warning:
+	case api.Warning:
 		color = WarningColor
 		color = WarningColor
-	case Error:
+	case api.Error:
 		color = ErrorColor
 		color = ErrorColor
 	}
 	}
 	_, _ = fmt.Fprintf(w.out, "%s %s %s\n", e.ID, color(e.Text), e.Details)
 	_, _ = fmt.Fprintf(w.out, "%s %s %s\n", e.ID, color(e.Text), e.Details)
@@ -271,7 +278,7 @@ func (w *ttyWriter) print() {
 
 
 func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding int, dryRun bool) string {
 func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding int, dryRun bool) string {
 	endTime := time.Now()
 	endTime := time.Now()
-	if t.status != Working {
+	if t.status != api.Working {
 		endTime = t.startTime
 		endTime = t.startTime
 		if (t.endTime != time.Time{}) {
 		if (t.endTime != time.Time{}) {
 			endTime = t.endTime
 			endTime = t.endTime
@@ -292,11 +299,11 @@ func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding in
 	)
 	)
 
 
 	// only show the aggregated progress while the root operation is in-progress
 	// only show the aggregated progress while the root operation is in-progress
-	if parent := t; parent.status == Working {
+	if parent := t; parent.status == api.Working {
 		for _, id := range w.ids {
 		for _, id := range w.ids {
 			child := w.tasks[id]
 			child := w.tasks[id]
 			if child.parentID == parent.ID {
 			if child.parentID == parent.ID {
-				if child.status == Working && child.total == 0 {
+				if child.status == api.Working && child.total == 0 {
 					// we don't have totals available for all the child events
 					// we don't have totals available for all the child events
 					// so don't show the total progress yet
 					// so don't show the total progress yet
 					hideDetails = true
 					hideDetails = true
@@ -361,24 +368,24 @@ var (
 
 
 func spinner(t task) string {
 func spinner(t task) string {
 	switch t.status {
 	switch t.status {
-	case Done:
+	case api.Done:
 		return SuccessColor(spinnerDone)
 		return SuccessColor(spinnerDone)
-	case Warning:
+	case api.Warning:
 		return WarningColor(spinnerWarning)
 		return WarningColor(spinnerWarning)
-	case Error:
+	case api.Error:
 		return ErrorColor(spinnerError)
 		return ErrorColor(spinnerError)
 	default:
 	default:
 		return CountColor(t.spinner.String())
 		return CountColor(t.spinner.String())
 	}
 	}
 }
 }
 
 
-func colorFn(s EventStatus) colorFunc {
+func colorFn(s api.EventStatus) colorFunc {
 	switch s {
 	switch s {
-	case Done:
+	case api.Done:
 		return SuccessColor
 		return SuccessColor
-	case Warning:
+	case api.Warning:
 		return WarningColor
 		return WarningColor
-	case Error:
+	case api.Error:
 		return ErrorColor
 		return ErrorColor
 	default:
 	default:
 		return nocolor
 		return nocolor
@@ -388,7 +395,7 @@ func colorFn(s EventStatus) colorFunc {
 func numDone(tasks map[string]task) int {
 func numDone(tasks map[string]task) int {
 	i := 0
 	i := 0
 	for _, t := range tasks {
 	for _, t := range tasks {
-		if t.status != Working {
+		if t.status != api.Working {
 			i++
 			i++
 		}
 		}
 	}
 	}

+ 3 - 9
pkg/api/errors.go

@@ -35,12 +35,7 @@ var (
 	ErrForbidden = errors.New("forbidden")
 	ErrForbidden = errors.New("forbidden")
 	// ErrUnknown is returned when the error type is unmapped
 	// ErrUnknown is returned when the error type is unmapped
 	ErrUnknown = errors.New("unknown")
 	ErrUnknown = errors.New("unknown")
-	// ErrLoginFailed is returned when login failed
-	ErrLoginFailed = errors.New("login failed")
-	// ErrLoginRequired is returned when login is required for a specific action
-	ErrLoginRequired = errors.New("login required")
-	// ErrNotImplemented is returned when a backend doesn't implement
-	// an action
+	// ErrNotImplemented is returned when a backend doesn't implement an action
 	ErrNotImplemented = errors.New("not implemented")
 	ErrNotImplemented = errors.New("not implemented")
 	// ErrUnsupportedFlag is returned when a backend doesn't support a flag
 	// ErrUnsupportedFlag is returned when a backend doesn't support a flag
 	ErrUnsupportedFlag = errors.New("unsupported flag")
 	ErrUnsupportedFlag = errors.New("unsupported flag")
@@ -48,9 +43,8 @@ var (
 	ErrCanceled = errors.New("canceled")
 	ErrCanceled = errors.New("canceled")
 	// ErrParsingFailed is returned when a string cannot be parsed
 	// ErrParsingFailed is returned when a string cannot be parsed
 	ErrParsingFailed = errors.New("parsing failed")
 	ErrParsingFailed = errors.New("parsing failed")
-	// ErrWrongContextType is returned when the caller tries to get a context
-	// with the wrong type
-	ErrWrongContextType = errors.New("wrong context type")
+	// ErrNoResources is returned when operation didn't selected any resource
+	ErrNoResources = errors.New("no resources")
 )
 )
 
 
 // IsNotFoundError returns true if the unwrapped error is ErrNotFound
 // IsNotFoundError returns true if the unwrapped error is ErrNotFound

+ 103 - 0
pkg/api/event.go

@@ -0,0 +1,103 @@
+/*
+   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 api
+
+import (
+	"context"
+)
+
+// EventStatus indicates the status of an action
+type EventStatus int
+
+const (
+	// Working means that the current task is working
+	Working EventStatus = iota
+	// Done means that the current task is done
+	Done
+	// Warning means that the current task has warning
+	Warning
+	// Error means that the current task has errored
+	Error
+)
+
+// ResourceCompose is a special resource ID used when event applies to all resources in the application
+const ResourceCompose = "Compose"
+
+const (
+	StatusError      = "Error"
+	StatusCreating   = "Creating"
+	StatusStarting   = "Starting"
+	StatusStarted    = "Started"
+	StatusWaiting    = "Waiting"
+	StatusHealthy    = "Healthy"
+	StatusExited     = "Exited"
+	StatusRestarting = "Restarting"
+	StatusRestarted  = "Restarted"
+	StatusRunning    = "Running"
+	StatusCreated    = "Created"
+	StatusStopping   = "Stopping"
+	StatusStopped    = "Stopped"
+	StatusKilling    = "Killing"
+	StatusKilled     = "Killed"
+	StatusRemoving   = "Removing"
+	StatusRemoved    = "Removed"
+	StatusBuilding   = "Building"
+	StatusBuilt      = "Built"
+	StatusPulling    = "Pulling"
+	StatusPulled     = "Pulled"
+	StatusCommitting = "Committing"
+	StatusCommitted  = "Committed"
+	StatusCopying    = "Copying"
+	StatusCopied     = "Copied"
+	StatusExporting  = "Exporting"
+	StatusExported   = "Exported"
+)
+
+// Resource represents status change and progress for a compose resource.
+type Resource struct {
+	ID       string
+	ParentID string
+	Text     string
+	Details  string
+	Status   EventStatus
+	Current  int64
+	Percent  int
+	Total    int64
+}
+
+func (e *Resource) StatusText() string {
+	switch e.Status {
+	case Working:
+		return "Working"
+	case Warning:
+		return "Warning"
+	case Done:
+		return "Done"
+	default:
+		return "Error"
+	}
+}
+
+// EventProcessor is notified about Compose operations and tasks
+type EventProcessor interface {
+	// Start is triggered as a Compose operation is starting with context
+	Start(ctx context.Context, operation string)
+	// On notify about (sub)task and progress processing operation
+	On(events ...Resource)
+	// Done is triggered as a Compose operation completed
+	Done(operation string, success bool)
+}

+ 1 - 2
pkg/compose/build.go

@@ -26,7 +26,6 @@ import (
 	"github.com/containerd/platforms"
 	"github.com/containerd/platforms"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
@@ -37,7 +36,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return tracing.SpanWrapFunc("project/build", tracing.ProjectOptions(ctx, project),
 		return tracing.SpanWrapFunc("project/build", tracing.ProjectOptions(ctx, project),
 			func(ctx context.Context) error {
 			func(ctx context.Context) error {
 				_, err := s.build(ctx, project, options, nil)
 				_, err := s.build(ctx, project, options, nil)

+ 9 - 10
pkg/compose/build_bake.go

@@ -40,7 +40,6 @@ import (
 	"github.com/docker/cli/cli/command/image/build"
 	"github.com/docker/cli/cli/command/image/build"
 	"github.com/docker/cli/cli/streams"
 	"github.com/docker/cli/cli/streams"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/moby/buildkit/client"
 	"github.com/moby/buildkit/client"
@@ -118,10 +117,10 @@ type buildStatus struct {
 func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
 func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
 	eg := errgroup.Group{}
 	eg := errgroup.Group{}
 	ch := make(chan *client.SolveStatus)
 	ch := make(chan *client.SolveStatus)
-	if options.Progress == progress.ModeAuto {
+	displayMode := progressui.DisplayMode(options.Progress)
+	if displayMode == progressui.AutoMode {
 		options.Progress = os.Getenv("BUILDKIT_PROGRESS")
 		options.Progress = os.Getenv("BUILDKIT_PROGRESS")
 	}
 	}
-	displayMode := progressui.DisplayMode(options.Progress)
 	out := options.Out
 	out := options.Out
 	if out == nil {
 	if out == nil {
 		out = s.stdout()
 		out = s.stdout()
@@ -206,7 +205,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
 		}
 		}
 
 
 		image := api.GetImageNameOrDefault(service, project.Name)
 		image := api.GetImageNameOrDefault(service, project.Name)
-		s.events.On(progress.BuildingEvent(image))
+		s.events.On(buildingEvent(image))
 
 
 		expectedImages[serviceName] = image
 		expectedImages[serviceName] = image
 
 
@@ -408,7 +407,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
 			return nil, fmt.Errorf("build result not found in Bake metadata for service %s", name)
 			return nil, fmt.Errorf("build result not found in Bake metadata for service %s", name)
 		}
 		}
 		results[image] = built.Digest
 		results[image] = built.Digest
-		s.events.On(progress.BuiltEvent(image))
+		s.events.On(builtEvent(image))
 	}
 	}
 	return results, nil
 	return results, nil
 }
 }
@@ -554,20 +553,20 @@ func (s composeService) dryRunBake(cfg bakeConfig) map[string]string {
 		bakeResponse[name] = dryRunUUID
 		bakeResponse[name] = dryRunUUID
 	}
 	}
 	for name := range bakeResponse {
 	for name := range bakeResponse {
-		s.events.On(progress.BuiltEvent(name))
+		s.events.On(builtEvent(name))
 	}
 	}
 	return bakeResponse
 	return bakeResponse
 }
 }
 
 
 func (s composeService) displayDryRunBuildEvent(name, dryRunUUID, tag string) {
 func (s composeService) displayDryRunBuildEvent(name, dryRunUUID, tag string) {
-	s.events.On(progress.Event{
+	s.events.On(api.Resource{
 		ID:     name + " ==>",
 		ID:     name + " ==>",
-		Status: progress.Done,
+		Status: api.Done,
 		Text:   fmt.Sprintf("==> writing image %s", dryRunUUID),
 		Text:   fmt.Sprintf("==> writing image %s", dryRunUUID),
 	})
 	})
-	s.events.On(progress.Event{
+	s.events.On(api.Resource{
 		ID:     name + " ==> ==>",
 		ID:     name + " ==> ==>",
-		Status: progress.Done,
+		Status: api.Done,
 		Text:   fmt.Sprintf(`naming to %s`, tag),
 		Text:   fmt.Sprintf(`naming to %s`, tag),
 	})
 	})
 }
 }

+ 4 - 5
pkg/compose/build_classic.go

@@ -30,7 +30,6 @@ import (
 	"github.com/docker/cli/cli"
 	"github.com/docker/cli/cli"
 	"github.com/docker/cli/cli/command/image/build"
 	"github.com/docker/cli/cli/command/image/build"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	progress2 "github.com/docker/compose/v2/pkg/progress"
 	buildtypes "github.com/docker/docker/api/types/build"
 	buildtypes "github.com/docker/docker/api/types/build"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/api/types/registry"
@@ -86,12 +85,12 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
 		}
 		}
 
 
 		image := api.GetImageNameOrDefault(service, project.Name)
 		image := api.GetImageNameOrDefault(service, project.Name)
-		s.events.On(progress2.BuildingEvent(image))
+		s.events.On(buildingEvent(image))
 		id, err := s.doBuildImage(ctx, project, service, options)
 		id, err := s.doBuildImage(ctx, project, service, options)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		s.events.On(progress2.BuiltEvent(image))
+		s.events.On(builtEvent(image))
 		builtDigests[getServiceIndex(name)] = id
 		builtDigests[getServiceIndex(name)] = id
 
 
 		if options.Push {
 		if options.Push {
@@ -258,7 +257,7 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
 
 
 	ctx, cancel := context.WithCancel(ctx)
 	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 	defer cancel()
-	s.events.On(progress2.BuildingEvent(imageName))
+	s.events.On(buildingEvent(imageName))
 	response, err := s.apiClient().ImageBuild(ctx, body, buildOpts)
 	response, err := s.apiClient().ImageBuild(ctx, body, buildOpts)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
@@ -287,7 +286,7 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
 		}
 		}
 		return "", err
 		return "", err
 	}
 	}
-	s.events.On(progress2.BuiltEvent(imageName))
+	s.events.On(builtEvent(imageName))
 	return imageID, nil
 	return imageID, nil
 }
 }
 
 

+ 9 - 10
pkg/compose/commit.go

@@ -22,12 +22,11 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 )
 )
 
 
 func (s *composeService) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
 func (s *composeService) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.commit(ctx, projectName, options)
 		return s.commit(ctx, projectName, options)
 	}, "commit", s.events)
 	}, "commit", s.events)
 }
 }
@@ -42,17 +41,17 @@ func (s *composeService) commit(ctx context.Context, projectName string, options
 
 
 	name := getCanonicalContainerName(ctr)
 	name := getCanonicalContainerName(ctr)
 
 
-	s.events.On(progress.Event{
+	s.events.On(api.Resource{
 		ID:     name,
 		ID:     name,
-		Status: progress.Working,
-		Text:   progress.StatusCommitting,
+		Status: api.Working,
+		Text:   api.StatusCommitting,
 	})
 	})
 
 
 	if s.dryRun {
 	if s.dryRun {
-		s.events.On(progress.Event{
+		s.events.On(api.Resource{
 			ID:     name,
 			ID:     name,
-			Status: progress.Done,
-			Text:   progress.StatusCommitted,
+			Status: api.Done,
+			Text:   api.StatusCommitted,
 		})
 		})
 
 
 		return nil
 		return nil
@@ -69,10 +68,10 @@ func (s *composeService) commit(ctx context.Context, projectName string, options
 		return err
 		return err
 	}
 	}
 
 
-	s.events.On(progress.Event{
+	s.events.On(api.Resource{
 		ID:     name,
 		ID:     name,
 		Text:   fmt.Sprintf("Committed as %s", response.ID),
 		Text:   fmt.Sprintf("Committed as %s", response.ID),
-		Status: progress.Done,
+		Status: api.Done,
 	})
 	})
 
 
 	return nil
 	return nil

+ 3 - 21
pkg/compose/compose.go

@@ -21,7 +21,6 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"os"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
@@ -32,7 +31,6 @@ import (
 	"github.com/docker/cli/cli/config/configfile"
 	"github.com/docker/cli/cli/config/configfile"
 	"github.com/docker/cli/cli/flags"
 	"github.com/docker/cli/cli/flags"
 	"github.com/docker/cli/cli/streams"
 	"github.com/docker/cli/cli/streams"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
@@ -45,15 +43,6 @@ import (
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 )
 )
 
 
-var stdioToStdout bool
-
-func init() {
-	out, ok := os.LookupEnv("COMPOSE_STATUS_STDOUT")
-	if ok {
-		stdioToStdout, _ = strconv.ParseBool(out)
-	}
-}
-
 type Option func(service *composeService) error
 type Option func(service *composeService) error
 
 
 // NewComposeService creates a Compose service using Docker CLI.
 // NewComposeService creates a Compose service using Docker CLI.
@@ -96,7 +85,7 @@ func NewComposeService(dockerCli command.Cli, options ...Option) (api.Compose, e
 		}
 		}
 	}
 	}
 	if s.events == nil {
 	if s.events == nil {
-		s.events = progress.NewQuietWriter()
+		s.events = &ignore{}
 	}
 	}
 
 
 	// If custom streams were provided, wrap the Docker CLI to use them
 	// If custom streams were provided, wrap the Docker CLI to use them
@@ -204,7 +193,7 @@ func AlwaysOkPrompt() Prompt {
 
 
 // WithEventProcessor configure component to get notified on Compose operation and progress events.
 // WithEventProcessor configure component to get notified on Compose operation and progress events.
 // Typically used to configure a progress UI
 // Typically used to configure a progress UI
-func WithEventProcessor(bus progress.EventProcessor) Option {
+func WithEventProcessor(bus api.EventProcessor) Option {
 	return func(s *composeService) error {
 	return func(s *composeService) error {
 		s.events = bus
 		s.events = bus
 		return nil
 		return nil
@@ -216,7 +205,7 @@ type composeService struct {
 	// prompt is used to interact with user and confirm actions
 	// prompt is used to interact with user and confirm actions
 	prompt Prompt
 	prompt Prompt
 	// eventBus collects tasks execution events
 	// eventBus collects tasks execution events
-	events progress.EventProcessor
+	events api.EventProcessor
 
 
 	// Optional overrides for specific components (for SDK users)
 	// Optional overrides for specific components (for SDK users)
 	outStream   io.Writer
 	outStream   io.Writer
@@ -278,13 +267,6 @@ func (s *composeService) stderr() *streams.Out {
 	return s.dockerCli.Err()
 	return s.dockerCli.Err()
 }
 }
 
 
-func (s *composeService) stdinfo() *streams.Out {
-	if stdioToStdout {
-		return s.stdout()
-	}
-	return s.stderr()
-}
-
 // readCloserAdapter adapts io.Reader to io.ReadCloser
 // readCloserAdapter adapts io.Reader to io.ReadCloser
 type readCloserAdapter struct {
 type readCloserAdapter struct {
 	r io.Reader
 	r io.Reader

+ 36 - 37
pkg/compose/convergence.go

@@ -41,7 +41,6 @@ import (
 
 
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
 )
 )
 
 
@@ -187,7 +186,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
 		name := getContainerProgressName(ctr)
 		name := getContainerProgressName(ctr)
 		switch ctr.State {
 		switch ctr.State {
 		case container.StateRunning:
 		case container.StateRunning:
-			c.compose.events.On(progress.RunningEvent(name))
+			c.compose.events.On(runningEvent(name))
 		case container.StateCreated:
 		case container.StateCreated:
 		case container.StateRestarting:
 		case container.StateRestarting:
 		case container.StateExited:
 		case container.StateExited:
@@ -426,16 +425,16 @@ func getContainerProgressName(ctr container.Summary) string {
 	return "Container " + getCanonicalContainerName(ctr)
 	return "Container " + getCanonicalContainerName(ctr)
 }
 }
 
 
-func containerEvents(containers Containers, eventFunc func(string) progress.Event) []progress.Event {
-	events := []progress.Event{}
+func containerEvents(containers Containers, eventFunc func(string) api.Resource) []api.Resource {
+	events := []api.Resource{}
 	for _, ctr := range containers {
 	for _, ctr := range containers {
 		events = append(events, eventFunc(getContainerProgressName(ctr)))
 		events = append(events, eventFunc(getContainerProgressName(ctr)))
 	}
 	}
 	return events
 	return events
 }
 }
 
 
-func containerReasonEvents(containers Containers, eventFunc func(string, string) progress.Event, reason string) []progress.Event {
-	events := []progress.Event{}
+func containerReasonEvents(containers Containers, eventFunc func(string, string) api.Resource, reason string) []api.Resource {
+	events := []api.Resource{}
 	for _, ctr := range containers {
 	for _, ctr := range containers {
 		events = append(events, eventFunc(getContainerProgressName(ctr), reason))
 		events = append(events, eventFunc(getContainerProgressName(ctr), reason))
 	}
 	}
@@ -461,7 +460,7 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 		}
 		}
 
 
 		waitingFor := containers.filter(isService(dep), isNotOneOff)
 		waitingFor := containers.filter(isService(dep), isNotOneOff)
-		s.events.On(containerEvents(waitingFor, progress.Waiting)...)
+		s.events.On(containerEvents(waitingFor, waiting)...)
 		if len(waitingFor) == 0 {
 		if len(waitingFor) == 0 {
 			if config.Required {
 			if config.Required {
 				return fmt.Errorf("%s is missing dependency %s", dependant, dep)
 				return fmt.Errorf("%s is missing dependency %s", dependant, dep)
@@ -481,61 +480,61 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 				}
 				}
 				switch config.Condition {
 				switch config.Condition {
 				case ServiceConditionRunningOrHealthy:
 				case ServiceConditionRunningOrHealthy:
-					healthy, err := s.isServiceHealthy(ctx, waitingFor, true)
+					isHealthy, err := s.isServiceHealthy(ctx, waitingFor, true)
 					if err != nil {
 					if err != nil {
 						if !config.Required {
 						if !config.Required {
-							s.events.On(containerReasonEvents(waitingFor, progress.SkippedEvent,
+							s.events.On(containerReasonEvents(waitingFor, skippedEvent,
 								fmt.Sprintf("optional dependency %q is not running or is unhealthy", dep))...)
 								fmt.Sprintf("optional dependency %q is not running or is unhealthy", dep))...)
 							logrus.Warnf("optional dependency %q is not running or is unhealthy: %s", dep, err.Error())
 							logrus.Warnf("optional dependency %q is not running or is unhealthy: %s", dep, err.Error())
 							return nil
 							return nil
 						}
 						}
 						return err
 						return err
 					}
 					}
-					if healthy {
-						s.events.On(containerEvents(waitingFor, progress.Healthy)...)
+					if isHealthy {
+						s.events.On(containerEvents(waitingFor, healthy)...)
 						return nil
 						return nil
 					}
 					}
 				case types.ServiceConditionHealthy:
 				case types.ServiceConditionHealthy:
-					healthy, err := s.isServiceHealthy(ctx, waitingFor, false)
+					isHealthy, err := s.isServiceHealthy(ctx, waitingFor, false)
 					if err != nil {
 					if err != nil {
 						if !config.Required {
 						if !config.Required {
-							s.events.On(containerReasonEvents(waitingFor, progress.SkippedEvent,
+							s.events.On(containerReasonEvents(waitingFor, skippedEvent,
 								fmt.Sprintf("optional dependency %q failed to start", dep))...)
 								fmt.Sprintf("optional dependency %q failed to start", dep))...)
 							logrus.Warnf("optional dependency %q failed to start: %s", dep, err.Error())
 							logrus.Warnf("optional dependency %q failed to start: %s", dep, err.Error())
 							return nil
 							return nil
 						}
 						}
-						s.events.On(containerEvents(waitingFor, func(s string) progress.Event {
-							return progress.ErrorEventf(s, "dependency %s failed to start", dep)
+						s.events.On(containerEvents(waitingFor, func(s string) api.Resource {
+							return errorEventf(s, "dependency %s failed to start", dep)
 						})...)
 						})...)
 						return fmt.Errorf("dependency failed to start: %w", err)
 						return fmt.Errorf("dependency failed to start: %w", err)
 					}
 					}
-					if healthy {
-						s.events.On(containerEvents(waitingFor, progress.Healthy)...)
+					if isHealthy {
+						s.events.On(containerEvents(waitingFor, healthy)...)
 						return nil
 						return nil
 					}
 					}
 				case types.ServiceConditionCompletedSuccessfully:
 				case types.ServiceConditionCompletedSuccessfully:
-					exited, code, err := s.isServiceCompleted(ctx, waitingFor)
+					isExited, code, err := s.isServiceCompleted(ctx, waitingFor)
 					if err != nil {
 					if err != nil {
 						return err
 						return err
 					}
 					}
-					if exited {
+					if isExited {
 						if code == 0 {
 						if code == 0 {
-							s.events.On(containerEvents(waitingFor, progress.Exited)...)
+							s.events.On(containerEvents(waitingFor, exited)...)
 							return nil
 							return nil
 						}
 						}
 
 
 						messageSuffix := fmt.Sprintf("%q didn't complete successfully: exit %d", dep, code)
 						messageSuffix := fmt.Sprintf("%q didn't complete successfully: exit %d", dep, code)
 						if !config.Required {
 						if !config.Required {
 							// optional -> mark as skipped & don't propagate error
 							// optional -> mark as skipped & don't propagate error
-							s.events.On(containerReasonEvents(waitingFor, progress.SkippedEvent,
+							s.events.On(containerReasonEvents(waitingFor, skippedEvent,
 								fmt.Sprintf("optional dependency %s", messageSuffix))...)
 								fmt.Sprintf("optional dependency %s", messageSuffix))...)
 							logrus.Warnf("optional dependency %s", messageSuffix)
 							logrus.Warnf("optional dependency %s", messageSuffix)
 							return nil
 							return nil
 						}
 						}
 
 
 						msg := fmt.Sprintf("service %s", messageSuffix)
 						msg := fmt.Sprintf("service %s", messageSuffix)
-						s.events.On(containerEvents(waitingFor, func(s string) progress.Event {
-							return progress.ErrorEventf(s, "service %s", messageSuffix)
+						s.events.On(containerEvents(waitingFor, func(s string) api.Resource {
+							return errorEventf(s, "service %s", messageSuffix)
 						})...)
 						})...)
 						return errors.New(msg)
 						return errors.New(msg)
 					}
 					}
@@ -599,19 +598,19 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro
 	name string, number int, opts createOptions,
 	name string, number int, opts createOptions,
 ) (ctr container.Summary, err error) {
 ) (ctr container.Summary, err error) {
 	eventName := "Container " + name
 	eventName := "Container " + name
-	s.events.On(progress.CreatingEvent(eventName))
+	s.events.On(creatingEvent(eventName))
 	ctr, err = s.createMobyContainer(ctx, project, service, name, number, nil, opts)
 	ctr, err = s.createMobyContainer(ctx, project, service, name, number, nil, opts)
 	if err != nil {
 	if err != nil {
 		if ctx.Err() == nil {
 		if ctx.Err() == nil {
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:     eventName,
 				ID:     eventName,
-				Status: progress.Error,
+				Status: api.Error,
 				Text:   err.Error(),
 				Text:   err.Error(),
 			})
 			})
 		}
 		}
 		return ctr, err
 		return ctr, err
 	}
 	}
-	s.events.On(progress.CreatedEvent(eventName))
+	s.events.On(createdEvent(eventName))
 	return ctr, nil
 	return ctr, nil
 }
 }
 
 
@@ -619,12 +618,12 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 	replaced container.Summary, inherit bool, timeout *time.Duration,
 	replaced container.Summary, inherit bool, timeout *time.Duration,
 ) (created container.Summary, err error) {
 ) (created container.Summary, err error) {
 	eventName := getContainerProgressName(replaced)
 	eventName := getContainerProgressName(replaced)
-	s.events.On(progress.NewEvent(eventName, progress.Working, "Recreate"))
+	s.events.On(newEvent(eventName, api.Working, "Recreate"))
 	defer func() {
 	defer func() {
 		if err != nil && ctx.Err() == nil {
 		if err != nil && ctx.Err() == nil {
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:     eventName,
 				ID:     eventName,
-				Status: progress.Error,
+				Status: api.Error,
 				Text:   err.Error(),
 				Text:   err.Error(),
 			})
 			})
 		}
 		}
@@ -673,7 +672,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 		return created, err
 		return created, err
 	}
 	}
 
 
-	s.events.On(progress.NewEvent(eventName, progress.Done, "Recreated"))
+	s.events.On(newEvent(eventName, api.Done, "Recreated"))
 	return created, err
 	return created, err
 }
 }
 
 
@@ -681,14 +680,14 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 var startMx sync.Mutex
 var startMx sync.Mutex
 
 
 func (s *composeService) startContainer(ctx context.Context, ctr container.Summary) error {
 func (s *composeService) startContainer(ctx context.Context, ctr container.Summary) error {
-	s.events.On(progress.NewEvent(getContainerProgressName(ctr), progress.Working, "Restart"))
+	s.events.On(newEvent(getContainerProgressName(ctr), api.Working, "Restart"))
 	startMx.Lock()
 	startMx.Lock()
 	defer startMx.Unlock()
 	defer startMx.Unlock()
 	err := s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{})
 	err := s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{})
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	s.events.On(progress.NewEvent(getContainerProgressName(ctr), progress.Done, "Restarted"))
+	s.events.On(newEvent(getContainerProgressName(ctr), api.Done, "Restarted"))
 	return nil
 	return nil
 }
 }
 
 
@@ -719,9 +718,9 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
 		return created, err
 		return created, err
 	}
 	}
 	for _, warning := range response.Warnings {
 	for _, warning := range response.Warnings {
-		s.events.On(progress.Event{
+		s.events.On(api.Resource{
 			ID:     service.Name,
 			ID:     service.Name,
-			Status: progress.Warning,
+			Status: api.Warning,
 			Text:   warning,
 			Text:   warning,
 		})
 		})
 	}
 	}
@@ -906,7 +905,7 @@ func (s *composeService) startService(ctx context.Context,
 		}
 		}
 
 
 		eventName := getContainerProgressName(ctr)
 		eventName := getContainerProgressName(ctr)
-		s.events.On(progress.StartingEvent(eventName))
+		s.events.On(startingEvent(eventName))
 		err = s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{})
 		err = s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{})
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -919,7 +918,7 @@ func (s *composeService) startService(ctx context.Context,
 			}
 			}
 		}
 		}
 
 
-		s.events.On(progress.StartedEvent(eventName))
+		s.events.On(startedEvent(eventName))
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 7 - 8
pkg/compose/cp.go

@@ -25,7 +25,6 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
 
 
-	"github.com/docker/compose/v2/pkg/progress"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 
 
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
@@ -43,7 +42,7 @@ const (
 )
 )
 
 
 func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
 func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.copy(ctx, projectName, options)
 		return s.copy(ctx, projectName, options)
 	}, "copy", s.events)
 	}, "copy", s.events)
 }
 }
@@ -90,20 +89,20 @@ func (s *composeService) copy(ctx context.Context, projectName string, options a
 			} else {
 			} else {
 				msg = fmt.Sprintf("%s to %s:%s", srcPath, name, dstPath)
 				msg = fmt.Sprintf("%s to %s:%s", srcPath, name, dstPath)
 			}
 			}
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:      name,
 				ID:      name,
-				Text:    progress.StatusCopying,
+				Text:    api.StatusCopying,
 				Details: msg,
 				Details: msg,
-				Status:  progress.Working,
+				Status:  api.Working,
 			})
 			})
 			if err := copyFunc(ctx, ctr.ID, srcPath, dstPath, options); err != nil {
 			if err := copyFunc(ctx, ctr.ID, srcPath, dstPath, options); err != nil {
 				return err
 				return err
 			}
 			}
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:      name,
 				ID:      name,
-				Text:    progress.StatusCopied,
+				Text:    api.StatusCopied,
 				Details: msg,
 				Details: msg,
-				Status:  progress.Done,
+				Status:  api.Done,
 			})
 			})
 			return nil
 			return nil
 		})
 		})

+ 8 - 9
pkg/compose/create.go

@@ -43,7 +43,6 @@ import (
 	cdi "tags.cncf.io/container-device-interface/pkg/parser"
 	cdi "tags.cncf.io/container-device-interface/pkg/parser"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 type createOptions struct {
 type createOptions struct {
@@ -61,7 +60,7 @@ type createConfigs struct {
 }
 }
 
 
 func (s *composeService) Create(ctx context.Context, project *types.Project, createOpts api.CreateOptions) error {
 func (s *composeService) Create(ctx context.Context, project *types.Project, createOpts api.CreateOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.create(ctx, project, createOpts)
 		return s.create(ctx, project, createOpts)
 	}, "create", s.events)
 	}, "create", s.events)
 }
 }
@@ -1394,14 +1393,14 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *ty
 	}
 	}
 
 
 	networkEventName := fmt.Sprintf("Network %s", n.Name)
 	networkEventName := fmt.Sprintf("Network %s", n.Name)
-	s.events.On(progress.CreatingEvent(networkEventName))
+	s.events.On(creatingEvent(networkEventName))
 
 
 	resp, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
 	resp, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
 	if err != nil {
 	if err != nil {
-		s.events.On(progress.ErrorEvent(networkEventName, err.Error()))
+		s.events.On(errorEvent(networkEventName, err.Error()))
 		return "", fmt.Errorf("failed to create network %s: %w", n.Name, err)
 		return "", fmt.Errorf("failed to create network %s: %w", n.Name, err)
 	}
 	}
-	s.events.On(progress.CreatedEvent(networkEventName))
+	s.events.On(createdEvent(networkEventName))
 
 
 	err = s.connectNetwork(ctx, n.Name, dangledContainers, nil)
 	err = s.connectNetwork(ctx, n.Name, dangledContainers, nil)
 	if err != nil {
 	if err != nil {
@@ -1443,7 +1442,7 @@ func (s *composeService) removeDivergedNetwork(ctx context.Context, project *typ
 
 
 	err = s.apiClient().NetworkRemove(ctx, n.Name)
 	err = s.apiClient().NetworkRemove(ctx, n.Name)
 	eventName := fmt.Sprintf("Network %s", n.Name)
 	eventName := fmt.Sprintf("Network %s", n.Name)
-	s.events.On(progress.RemovedEvent(eventName))
+	s.events.On(removedEvent(eventName))
 	return containers, err
 	return containers, err
 }
 }
 
 
@@ -1619,7 +1618,7 @@ func (s *composeService) removeDivergedVolume(ctx context.Context, name string,
 
 
 func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
 func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
 	eventName := fmt.Sprintf("Volume %s", volume.Name)
 	eventName := fmt.Sprintf("Volume %s", volume.Name)
-	s.events.On(progress.CreatingEvent(eventName))
+	s.events.On(creatingEvent(eventName))
 	hash, err := VolumeHash(volume)
 	hash, err := VolumeHash(volume)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -1632,9 +1631,9 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
 		DriverOpts: volume.DriverOpts,
 		DriverOpts: volume.DriverOpts,
 	})
 	})
 	if err != nil {
 	if err != nil {
-		s.events.On(progress.ErrorEvent(eventName, err.Error()))
+		s.events.On(errorEvent(eventName, err.Error()))
 		return err
 		return err
 	}
 	}
-	s.events.On(progress.CreatedEvent(eventName))
+	s.events.On(createdEvent(eventName))
 	return nil
 	return nil
 }
 }

+ 22 - 23
pkg/compose/down.go

@@ -25,7 +25,6 @@ import (
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/containerd/errdefs"
 	"github.com/containerd/errdefs"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
 	containerType "github.com/docker/docker/api/types/container"
 	containerType "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
@@ -38,7 +37,7 @@ import (
 type downOp func() error
 type downOp func() error
 
 
 func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error {
 func (s *composeService) Down(ctx context.Context, projectName string, options api.DownOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.down(ctx, strings.ToLower(projectName), options)
 		return s.down(ctx, strings.ToLower(projectName), options)
 	}, "down", s.events)
 	}, "down", s.events)
 }
 }
@@ -210,7 +209,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
 	}
 	}
 
 
 	eventName := fmt.Sprintf("Network %s", name)
 	eventName := fmt.Sprintf("Network %s", name)
-	s.events.On(progress.RemovingEvent(eventName))
+	s.events.On(removingEvent(eventName))
 
 
 	var found int
 	var found int
 	for _, net := range networks {
 	for _, net := range networks {
@@ -219,14 +218,14 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
 		}
 		}
 		nw, err := s.apiClient().NetworkInspect(ctx, net.ID, network.InspectOptions{})
 		nw, err := s.apiClient().NetworkInspect(ctx, net.ID, network.InspectOptions{})
 		if errdefs.IsNotFound(err) {
 		if errdefs.IsNotFound(err) {
-			s.events.On(progress.NewEvent(eventName, progress.Warning, "No resource found to remove"))
+			s.events.On(newEvent(eventName, api.Warning, "No resource found to remove"))
 			return nil
 			return nil
 		}
 		}
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 		if len(nw.Containers) > 0 {
 		if len(nw.Containers) > 0 {
-			s.events.On(progress.NewEvent(eventName, progress.Warning, "Resource is still in use"))
+			s.events.On(newEvent(eventName, api.Warning, "Resource is still in use"))
 			found++
 			found++
 			continue
 			continue
 		}
 		}
@@ -235,10 +234,10 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
 			if errdefs.IsNotFound(err) {
 			if errdefs.IsNotFound(err) {
 				continue
 				continue
 			}
 			}
-			s.events.On(progress.ErrorEvent(eventName, err.Error()))
+			s.events.On(errorEvent(eventName, err.Error()))
 			return fmt.Errorf("failed to remove network %s: %w", name, err)
 			return fmt.Errorf("failed to remove network %s: %w", name, err)
 		}
 		}
-		s.events.On(progress.RemovedEvent(eventName))
+		s.events.On(removedEvent(eventName))
 		found++
 		found++
 	}
 	}
 
 
@@ -246,7 +245,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
 		// in practice, it's extremely unlikely for this to ever occur, as it'd
 		// in practice, it's extremely unlikely for this to ever occur, as it'd
 		// mean the network was present when we queried at the start of this
 		// mean the network was present when we queried at the start of this
 		// method but was then deleted by something else in the interim
 		// method but was then deleted by something else in the interim
-		s.events.On(progress.NewEvent(eventName, progress.Warning, "No resource found to remove"))
+		s.events.On(newEvent(eventName, api.Warning, "No resource found to remove"))
 		return nil
 		return nil
 	}
 	}
 	return nil
 	return nil
@@ -254,18 +253,18 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
 
 
 func (s *composeService) removeImage(ctx context.Context, image string) error {
 func (s *composeService) removeImage(ctx context.Context, image string) error {
 	id := fmt.Sprintf("Image %s", image)
 	id := fmt.Sprintf("Image %s", image)
-	s.events.On(progress.NewEvent(id, progress.Working, "Removing"))
+	s.events.On(newEvent(id, api.Working, "Removing"))
 	_, err := s.apiClient().ImageRemove(ctx, image, imageapi.RemoveOptions{})
 	_, err := s.apiClient().ImageRemove(ctx, image, imageapi.RemoveOptions{})
 	if err == nil {
 	if err == nil {
-		s.events.On(progress.NewEvent(id, progress.Done, "Removed"))
+		s.events.On(newEvent(id, api.Done, "Removed"))
 		return nil
 		return nil
 	}
 	}
 	if errdefs.IsConflict(err) {
 	if errdefs.IsConflict(err) {
-		s.events.On(progress.NewEvent(id, progress.Warning, "Resource is still in use"))
+		s.events.On(newEvent(id, api.Warning, "Resource is still in use"))
 		return nil
 		return nil
 	}
 	}
 	if errdefs.IsNotFound(err) {
 	if errdefs.IsNotFound(err) {
-		s.events.On(progress.NewEvent(id, progress.Done, "Warning: No resource found to remove"))
+		s.events.On(newEvent(id, api.Done, "Warning: No resource found to remove"))
 		return nil
 		return nil
 	}
 	}
 	return err
 	return err
@@ -280,18 +279,18 @@ func (s *composeService) removeVolume(ctx context.Context, id string) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	s.events.On(progress.NewEvent(resource, progress.Working, "Removing"))
+	s.events.On(newEvent(resource, api.Working, "Removing"))
 	err = s.apiClient().VolumeRemove(ctx, id, true)
 	err = s.apiClient().VolumeRemove(ctx, id, true)
 	if err == nil {
 	if err == nil {
-		s.events.On(progress.NewEvent(resource, progress.Done, "Removed"))
+		s.events.On(newEvent(resource, api.Done, "Removed"))
 		return nil
 		return nil
 	}
 	}
 	if errdefs.IsConflict(err) {
 	if errdefs.IsConflict(err) {
-		s.events.On(progress.NewEvent(resource, progress.Warning, "Resource is still in use"))
+		s.events.On(newEvent(resource, api.Warning, "Resource is still in use"))
 		return nil
 		return nil
 	}
 	}
 	if errdefs.IsNotFound(err) {
 	if errdefs.IsNotFound(err) {
-		s.events.On(progress.NewEvent(resource, progress.Done, "Warning: No resource found to remove"))
+		s.events.On(newEvent(resource, api.Done, "Warning: No resource found to remove"))
 		return nil
 		return nil
 	}
 	}
 	return err
 	return err
@@ -299,7 +298,7 @@ func (s *composeService) removeVolume(ctx context.Context, id string) error {
 
 
 func (s *composeService) stopContainer(ctx context.Context, service *types.ServiceConfig, ctr containerType.Summary, timeout *time.Duration, listener api.ContainerEventListener) error {
 func (s *composeService) stopContainer(ctx context.Context, service *types.ServiceConfig, ctr containerType.Summary, timeout *time.Duration, listener api.ContainerEventListener) error {
 	eventName := getContainerProgressName(ctr)
 	eventName := getContainerProgressName(ctr)
-	s.events.On(progress.StoppingEvent(eventName))
+	s.events.On(stoppingEvent(eventName))
 
 
 	if service != nil {
 	if service != nil {
 		for _, hook := range service.PreStop {
 		for _, hook := range service.PreStop {
@@ -317,10 +316,10 @@ func (s *composeService) stopContainer(ctx context.Context, service *types.Servi
 	timeoutInSecond := utils.DurationSecondToInt(timeout)
 	timeoutInSecond := utils.DurationSecondToInt(timeout)
 	err := s.apiClient().ContainerStop(ctx, ctr.ID, containerType.StopOptions{Timeout: timeoutInSecond})
 	err := s.apiClient().ContainerStop(ctx, ctr.ID, containerType.StopOptions{Timeout: timeoutInSecond})
 	if err != nil {
 	if err != nil {
-		s.events.On(progress.ErrorEvent(eventName, "Error while Stopping"))
+		s.events.On(errorEvent(eventName, "Error while Stopping"))
 		return err
 		return err
 	}
 	}
-	s.events.On(progress.StoppedEvent(eventName))
+	s.events.On(stoppedEvent(eventName))
 	return nil
 	return nil
 }
 }
 
 
@@ -348,22 +347,22 @@ func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr contain
 	eventName := getContainerProgressName(ctr)
 	eventName := getContainerProgressName(ctr)
 	err := s.stopContainer(ctx, service, ctr, timeout, nil)
 	err := s.stopContainer(ctx, service, ctr, timeout, nil)
 	if errdefs.IsNotFound(err) {
 	if errdefs.IsNotFound(err) {
-		s.events.On(progress.RemovedEvent(eventName))
+		s.events.On(removedEvent(eventName))
 		return nil
 		return nil
 	}
 	}
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	s.events.On(progress.RemovingEvent(eventName))
+	s.events.On(removingEvent(eventName))
 	err = s.apiClient().ContainerRemove(ctx, ctr.ID, containerType.RemoveOptions{
 	err = s.apiClient().ContainerRemove(ctx, ctr.ID, containerType.RemoveOptions{
 		Force:         true,
 		Force:         true,
 		RemoveVolumes: volumes,
 		RemoveVolumes: volumes,
 	})
 	})
 	if err != nil && !errdefs.IsNotFound(err) && !errdefs.IsConflict(err) {
 	if err != nil && !errdefs.IsNotFound(err) && !errdefs.IsConflict(err) {
-		s.events.On(progress.ErrorEvent(eventName, "Error while Removing"))
+		s.events.On(errorEvent(eventName, "Error while Removing"))
 		return err
 		return err
 	}
 	}
-	s.events.On(progress.RemovedEvent(eventName))
+	s.events.On(removedEvent(eventName))
 	return nil
 	return nil
 }
 }
 
 

+ 8 - 9
pkg/compose/export.go

@@ -24,12 +24,11 @@ import (
 
 
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/moby/sys/atomicwriter"
 	"github.com/moby/sys/atomicwriter"
 )
 )
 
 
 func (s *composeService) Export(ctx context.Context, projectName string, options api.ExportOptions) error {
 func (s *composeService) Export(ctx context.Context, projectName string, options api.ExportOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.export(ctx, projectName, options)
 		return s.export(ctx, projectName, options)
 	}, "export", s.events)
 	}, "export", s.events)
 }
 }
@@ -51,10 +50,10 @@ func (s *composeService) export(ctx context.Context, projectName string, options
 	}
 	}
 
 
 	name := getCanonicalContainerName(container)
 	name := getCanonicalContainerName(container)
-	s.events.On(progress.Event{
+	s.events.On(api.Resource{
 		ID:     name,
 		ID:     name,
-		Text:   progress.StatusExporting,
-		Status: progress.Working,
+		Text:   api.StatusExporting,
+		Status: api.Working,
 	})
 	})
 
 
 	responseBody, err := s.apiClient().ContainerExport(ctx, container.ID)
 	responseBody, err := s.apiClient().ContainerExport(ctx, container.ID)
@@ -64,7 +63,7 @@ func (s *composeService) export(ctx context.Context, projectName string, options
 
 
 	defer func() {
 	defer func() {
 		if err := responseBody.Close(); err != nil {
 		if err := responseBody.Close(); err != nil {
-			s.events.On(progress.ErrorEventf(name, "Failed to close response body: %s", err.Error()))
+			s.events.On(errorEventf(name, "Failed to close response body: %s", err.Error()))
 		}
 		}
 	}()
 	}()
 
 
@@ -84,10 +83,10 @@ func (s *composeService) export(ctx context.Context, projectName string, options
 		}
 		}
 	}
 	}
 
 
-	s.events.On(progress.Event{
+	s.events.On(api.Resource{
 		ID:     name,
 		ID:     name,
-		Text:   progress.StatusExported,
-		Status: progress.Done,
+		Text:   api.StatusExported,
+		Status: api.Done,
 	})
 	})
 
 
 	return nil
 	return nil

+ 5 - 8
pkg/compose/kill.go

@@ -18,18 +18,16 @@ package compose
 
 
 import (
 import (
 	"context"
 	"context"
-	"fmt"
 	"strings"
 	"strings"
 
 
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 func (s *composeService) Kill(ctx context.Context, projectName string, options api.KillOptions) error {
 func (s *composeService) Kill(ctx context.Context, projectName string, options api.KillOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.kill(ctx, strings.ToLower(projectName), options)
 		return s.kill(ctx, strings.ToLower(projectName), options)
 	}, "kill", s.events)
 	}, "kill", s.events)
 }
 }
@@ -55,21 +53,20 @@ func (s *composeService) kill(ctx context.Context, projectName string, options a
 		containers = containers.filter(isService(project.ServiceNames()...))
 		containers = containers.filter(isService(project.ServiceNames()...))
 	}
 	}
 	if len(containers) == 0 {
 	if len(containers) == 0 {
-		_, _ = fmt.Fprintf(s.stdinfo(), "no container to kill")
-		return nil
+		return api.ErrNoResources
 	}
 	}
 
 
 	eg, ctx := errgroup.WithContext(ctx)
 	eg, ctx := errgroup.WithContext(ctx)
 	containers.forEach(func(ctr container.Summary) {
 	containers.forEach(func(ctr container.Summary) {
 		eg.Go(func() error {
 		eg.Go(func() error {
 			eventName := getContainerProgressName(ctr)
 			eventName := getContainerProgressName(ctr)
-			s.events.On(progress.KillingEvent(eventName))
+			s.events.On(killingEvent(eventName))
 			err := s.apiClient().ContainerKill(ctx, ctr.ID, options.Signal)
 			err := s.apiClient().ContainerKill(ctx, ctr.ID, options.Signal)
 			if err != nil {
 			if err != nil {
-				s.events.On(progress.ErrorEvent(eventName, "Error while Killing"))
+				s.events.On(errorEvent(eventName, "Error while Killing"))
 				return err
 				return err
 			}
 			}
-			s.events.On(progress.KilledEvent(eventName))
+			s.events.On(killedEvent(eventName))
 			return nil
 			return nil
 		})
 		})
 	})
 	})

+ 14 - 14
pkg/compose/model.go

@@ -29,7 +29,7 @@ import (
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/containerd/errdefs"
 	"github.com/containerd/errdefs"
 	"github.com/docker/cli/cli-plugins/manager"
 	"github.com/docker/cli/cli-plugins/manager"
-	"github.com/docker/compose/v2/pkg/progress"
+	"github.com/docker/compose/v2/pkg/api"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 )
 )
@@ -101,10 +101,10 @@ func (m *modelAPI) Close() {
 	m.cleanup()
 	m.cleanup()
 }
 }
 
 
-func (m *modelAPI) PullModel(ctx context.Context, model types.ModelConfig, quietPull bool, events progress.EventProcessor) error {
-	events.On(progress.Event{
+func (m *modelAPI) PullModel(ctx context.Context, model types.ModelConfig, quietPull bool, events api.EventProcessor) error {
+	events.On(api.Resource{
 		ID:     model.Name,
 		ID:     model.Name,
-		Status: progress.Working,
+		Status: api.Working,
 		Text:   "Pulling",
 		Text:   "Pulling",
 	})
 	})
 
 
@@ -131,30 +131,30 @@ func (m *modelAPI) PullModel(ctx context.Context, model types.ModelConfig, quiet
 		}
 		}
 
 
 		if !quietPull {
 		if !quietPull {
-			events.On(progress.Event{
+			events.On(api.Resource{
 				ID:     model.Name,
 				ID:     model.Name,
-				Status: progress.Working,
-				Text:   progress.StatusPulling,
+				Status: api.Working,
+				Text:   api.StatusPulling,
 			})
 			})
 		}
 		}
 	}
 	}
 
 
 	err = cmd.Wait()
 	err = cmd.Wait()
 	if err != nil {
 	if err != nil {
-		events.On(progress.ErrorEvent(model.Name, err.Error()))
+		events.On(errorEvent(model.Name, err.Error()))
 	}
 	}
-	events.On(progress.Event{
+	events.On(api.Resource{
 		ID:     model.Name,
 		ID:     model.Name,
-		Status: progress.Working,
-		Text:   progress.StatusPulled,
+		Status: api.Working,
+		Text:   api.StatusPulled,
 	})
 	})
 	return err
 	return err
 }
 }
 
 
-func (m *modelAPI) ConfigureModel(ctx context.Context, config types.ModelConfig, events progress.EventProcessor) error {
-	events.On(progress.Event{
+func (m *modelAPI) ConfigureModel(ctx context.Context, config types.ModelConfig, events api.EventProcessor) error {
+	events.On(api.Resource{
 		ID:     config.Name,
 		ID:     config.Name,
-		Status: progress.Working,
+		Status: api.Working,
 		Text:   "Configuring",
 		Text:   "Configuring",
 	})
 	})
 	// configure [--context-size=<n>] MODEL [-- <runtime-flags...>]
 	// configure [--context-size=<n>] MODEL [-- <runtime-flags...>]

+ 4 - 5
pkg/compose/pause.go

@@ -24,11 +24,10 @@ import (
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 func (s *composeService) Pause(ctx context.Context, projectName string, options api.PauseOptions) error {
 func (s *composeService) Pause(ctx context.Context, projectName string, options api.PauseOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.pause(ctx, strings.ToLower(projectName), options)
 		return s.pause(ctx, strings.ToLower(projectName), options)
 	}, "pause", s.events)
 	}, "pause", s.events)
 }
 }
@@ -49,7 +48,7 @@ func (s *composeService) pause(ctx context.Context, projectName string, options
 			err := s.apiClient().ContainerPause(ctx, container.ID)
 			err := s.apiClient().ContainerPause(ctx, container.ID)
 			if err == nil {
 			if err == nil {
 				eventName := getContainerProgressName(container)
 				eventName := getContainerProgressName(container)
-				s.events.On(progress.NewEvent(eventName, progress.Done, "Paused"))
+				s.events.On(newEvent(eventName, api.Done, "Paused"))
 			}
 			}
 			return err
 			return err
 		})
 		})
@@ -58,7 +57,7 @@ func (s *composeService) pause(ctx context.Context, projectName string, options
 }
 }
 
 
 func (s *composeService) UnPause(ctx context.Context, projectName string, options api.PauseOptions) error {
 func (s *composeService) UnPause(ctx context.Context, projectName string, options api.PauseOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.unPause(ctx, strings.ToLower(projectName), options)
 		return s.unPause(ctx, strings.ToLower(projectName), options)
 	}, "unpause", s.events)
 	}, "unpause", s.events)
 }
 }
@@ -79,7 +78,7 @@ func (s *composeService) unPause(ctx context.Context, projectName string, option
 			err = s.apiClient().ContainerUnpause(ctx, ctr.ID)
 			err = s.apiClient().ContainerUnpause(ctx, ctr.ID)
 			if err == nil {
 			if err == nil {
 				eventName := getContainerProgressName(ctr)
 				eventName := getContainerProgressName(ctr)
-				s.events.On(progress.NewEvent(eventName, progress.Done, "Unpaused"))
+				s.events.On(newEvent(eventName, api.Done, "Unpaused"))
 			}
 			}
 			return err
 			return err
 		})
 		})

+ 8 - 8
pkg/compose/plugins.go

@@ -33,7 +33,7 @@ import (
 	"github.com/containerd/errdefs"
 	"github.com/containerd/errdefs"
 	"github.com/docker/cli/cli-plugins/manager"
 	"github.com/docker/cli/cli-plugins/manager"
 	"github.com/docker/cli/cli/config"
 	"github.com/docker/cli/cli/config"
-	"github.com/docker/compose/v2/pkg/progress"
+	"github.com/docker/compose/v2/pkg/api"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
@@ -89,10 +89,10 @@ func (s *composeService) executePlugin(cmd *exec.Cmd, command string, service ty
 	var action string
 	var action string
 	switch command {
 	switch command {
 	case "up":
 	case "up":
-		s.events.On(progress.CreatingEvent(service.Name))
+		s.events.On(creatingEvent(service.Name))
 		action = "create"
 		action = "create"
 	case "down":
 	case "down":
-		s.events.On(progress.RemovingEvent(service.Name))
+		s.events.On(removingEvent(service.Name))
 		action = "remove"
 		action = "remove"
 	default:
 	default:
 		return nil, fmt.Errorf("unsupported plugin command: %s", command)
 		return nil, fmt.Errorf("unsupported plugin command: %s", command)
@@ -124,10 +124,10 @@ func (s *composeService) executePlugin(cmd *exec.Cmd, command string, service ty
 		}
 		}
 		switch msg.Type {
 		switch msg.Type {
 		case ErrorType:
 		case ErrorType:
-			s.events.On(progress.NewEvent(service.Name, progress.Error, msg.Message))
+			s.events.On(newEvent(service.Name, api.Error, msg.Message))
 			return nil, errors.New(msg.Message)
 			return nil, errors.New(msg.Message)
 		case InfoType:
 		case InfoType:
-			s.events.On(progress.NewEvent(service.Name, progress.Working, msg.Message))
+			s.events.On(newEvent(service.Name, api.Working, msg.Message))
 		case SetEnvType:
 		case SetEnvType:
 			key, val, found := strings.Cut(msg.Message, "=")
 			key, val, found := strings.Cut(msg.Message, "=")
 			if !found {
 			if !found {
@@ -143,14 +143,14 @@ func (s *composeService) executePlugin(cmd *exec.Cmd, command string, service ty
 
 
 	err = cmd.Wait()
 	err = cmd.Wait()
 	if err != nil {
 	if err != nil {
-		s.events.On(progress.ErrorEvent(service.Name, err.Error()))
+		s.events.On(errorEvent(service.Name, err.Error()))
 		return nil, fmt.Errorf("failed to %s service provider: %s", action, err.Error())
 		return nil, fmt.Errorf("failed to %s service provider: %s", action, err.Error())
 	}
 	}
 	switch command {
 	switch command {
 	case "up":
 	case "up":
-		s.events.On(progress.CreatedEvent(service.Name))
+		s.events.On(createdEvent(service.Name))
 	case "down":
 	case "down":
-		s.events.On(progress.RemovedEvent(service.Name))
+		s.events.On(removedEvent(service.Name))
 	}
 	}
 	return variables, nil
 	return variables, nil
 }
 }

+ 176 - 0
pkg/compose/progress.go

@@ -0,0 +1,176 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package compose
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/docker/compose/v2/pkg/api"
+)
+
+type progressFunc func(context.Context) error
+
+func Run(ctx context.Context, pf progressFunc, operation string, bus api.EventProcessor) error {
+	bus.Start(ctx, operation)
+	err := pf(ctx)
+	bus.Done(operation, err != nil)
+	return err
+}
+
+// errorEvent creates a new Error Resource with message
+func errorEvent(id string, msg string) api.Resource {
+	return api.Resource{
+		ID:      id,
+		Status:  api.Error,
+		Text:    api.StatusError,
+		Details: msg,
+	}
+}
+
+// errorEventf creates a new Error Resource with format message
+func errorEventf(id string, msg string, args ...any) api.Resource {
+	return errorEvent(id, fmt.Sprintf(msg, args...))
+}
+
+// creatingEvent creates a new Create in progress Resource
+func creatingEvent(id string) api.Resource {
+	return newEvent(id, api.Working, api.StatusCreating)
+}
+
+// startingEvent creates a new Starting in progress Resource
+func startingEvent(id string) api.Resource {
+	return newEvent(id, api.Working, api.StatusStarting)
+}
+
+// startedEvent creates a new Started in progress Resource
+func startedEvent(id string) api.Resource {
+	return newEvent(id, api.Done, api.StatusStarted)
+}
+
+// waiting creates a new waiting event
+func waiting(id string) api.Resource {
+	return newEvent(id, api.Working, api.StatusWaiting)
+}
+
+// healthy creates a new healthy event
+func healthy(id string) api.Resource {
+	return newEvent(id, api.Done, api.StatusHealthy)
+}
+
+// exited creates a new exited event
+func exited(id string) api.Resource {
+	return newEvent(id, api.Done, api.StatusExited)
+}
+
+// restartingEvent creates a new Restarting in progress Resource
+func restartingEvent(id string) api.Resource {
+	return newEvent(id, api.Working, api.StatusRestarting)
+}
+
+// runningEvent creates a new Running in progress Resource
+func runningEvent(id string) api.Resource {
+	return newEvent(id, api.Done, api.StatusRunning)
+}
+
+// createdEvent creates a new Created (done) Resource
+func createdEvent(id string) api.Resource {
+	return newEvent(id, api.Done, api.StatusCreated)
+}
+
+// stoppingEvent creates a new Stopping in progress Resource
+func stoppingEvent(id string) api.Resource {
+	return newEvent(id, api.Working, api.StatusStopping)
+}
+
+// stoppedEvent creates a new Stopping in progress Resource
+func stoppedEvent(id string) api.Resource {
+	return newEvent(id, api.Done, api.StatusStopped)
+}
+
+// killingEvent creates a new Killing in progress Resource
+func killingEvent(id string) api.Resource {
+	return newEvent(id, api.Working, api.StatusKilling)
+}
+
+// killedEvent creates a new Killed in progress Resource
+func killedEvent(id string) api.Resource {
+	return newEvent(id, api.Done, api.StatusKilled)
+}
+
+// removingEvent creates a new Removing in progress Resource
+func removingEvent(id string) api.Resource {
+	return newEvent(id, api.Working, api.StatusRemoving)
+}
+
+// removedEvent creates a new removed (done) Resource
+func removedEvent(id string) api.Resource {
+	return newEvent(id, api.Done, api.StatusRemoved)
+}
+
+// buildingEvent creates a new Building in progress Resource
+func buildingEvent(id string) api.Resource {
+	return newEvent("Image "+id, api.Working, api.StatusBuilding)
+}
+
+// builtEvent creates a new built (done) Resource
+func builtEvent(id string) api.Resource {
+	return newEvent("Image "+id, api.Done, api.StatusBuilt)
+}
+
+// pullingEvent creates a new pulling (in progress) Resource
+func pullingEvent(id string) api.Resource {
+	return newEvent("Image "+id, api.Working, api.StatusPulling)
+}
+
+// pulledEvent creates a new pulled (done) Resource
+func pulledEvent(id string) api.Resource {
+	return newEvent("Image "+id, api.Done, api.StatusPulled)
+}
+
+// skippedEvent creates a new Skipped Resource
+func skippedEvent(id string, reason string) api.Resource {
+	return api.Resource{
+		ID:     id,
+		Status: api.Warning,
+		Text:   "Skipped: " + reason,
+	}
+}
+
+// newEvent new event
+func newEvent(id string, status api.EventStatus, text string, reason ...string) api.Resource {
+	r := api.Resource{
+		ID:     id,
+		Status: status,
+		Text:   text,
+	}
+	if len(reason) > 0 {
+		r.Details = reason[0]
+	}
+	return r
+}
+
+type ignore struct{}
+
+func (q *ignore) Start(_ context.Context, _ string) {
+}
+
+func (q *ignore) Done(_ string, _ bool) {
+}
+
+func (q *ignore) On(_ ...api.Resource) {
+}

+ 7 - 8
pkg/compose/publish.go

@@ -35,7 +35,6 @@ import (
 	"github.com/docker/compose/v2/internal/oci"
 	"github.com/docker/compose/v2/internal/oci"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/compose/transform"
 	"github.com/docker/compose/v2/pkg/compose/transform"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/image-spec/specs-go"
 	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
@@ -43,7 +42,7 @@ import (
 )
 )
 
 
 func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error {
 func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.publish(ctx, project, repository, options)
 		return s.publish(ctx, project, repository, options)
 	}, "publish", s.events)
 	}, "publish", s.events)
 }
 }
@@ -71,10 +70,10 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
 		return err
 		return err
 	}
 	}
 
 
-	s.events.On(progress.Event{
+	s.events.On(api.Resource{
 		ID:     repository,
 		ID:     repository,
 		Text:   "publishing",
 		Text:   "publishing",
-		Status: progress.Working,
+		Status: api.Working,
 	})
 	})
 	if logrus.IsLevelEnabled(logrus.DebugLevel) {
 	if logrus.IsLevelEnabled(logrus.DebugLevel) {
 		logrus.Debug("publishing layers")
 		logrus.Debug("publishing layers")
@@ -98,10 +97,10 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
 
 
 		descriptor, err := oci.PushManifest(ctx, resolver, named, layers, options.OCIVersion)
 		descriptor, err := oci.PushManifest(ctx, resolver, named, layers, options.OCIVersion)
 		if err != nil {
 		if err != nil {
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:     repository,
 				ID:     repository,
 				Text:   "publishing",
 				Text:   "publishing",
-				Status: progress.Error,
+				Status: api.Error,
 			})
 			})
 			return err
 			return err
 		}
 		}
@@ -150,10 +149,10 @@ func (s *composeService) publish(ctx context.Context, project *types.Project, re
 			}
 			}
 		}
 		}
 	}
 	}
-	s.events.On(progress.Event{
+	s.events.On(api.Resource{
 		ID:     repository,
 		ID:     repository,
 		Text:   "published",
 		Text:   "published",
-		Status: progress.Done,
+		Status: api.Done,
 	})
 	})
 	return nil
 	return nil
 }
 }

+ 24 - 25
pkg/compose/pull.go

@@ -40,11 +40,10 @@ import (
 
 
 	"github.com/docker/compose/v2/internal/registry"
 	"github.com/docker/compose/v2/internal/registry"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 func (s *composeService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error {
 func (s *composeService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error {
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.pull(ctx, project, options)
 		return s.pull(ctx, project, options)
 	}, "pull", s.events)
 	}, "pull", s.events)
 }
 }
@@ -67,9 +66,9 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 	i := 0
 	i := 0
 	for name, service := range project.Services {
 	for name, service := range project.Services {
 		if service.Image == "" {
 		if service.Image == "" {
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:      name,
 				ID:      name,
-				Status:  progress.Done,
+				Status:  api.Done,
 				Text:    "Skipped",
 				Text:    "Skipped",
 				Details: "No image to be pulled",
 				Details: "No image to be pulled",
 			})
 			})
@@ -78,17 +77,17 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 
 
 		switch service.PullPolicy {
 		switch service.PullPolicy {
 		case types.PullPolicyNever, types.PullPolicyBuild:
 		case types.PullPolicyNever, types.PullPolicyBuild:
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:     "Image " + service.Image,
 				ID:     "Image " + service.Image,
-				Status: progress.Done,
+				Status: api.Done,
 				Text:   "Skipped",
 				Text:   "Skipped",
 			})
 			})
 			continue
 			continue
 		case types.PullPolicyMissing, types.PullPolicyIfNotPresent:
 		case types.PullPolicyMissing, types.PullPolicyIfNotPresent:
 			if imageAlreadyPresent(service.Image, images) {
 			if imageAlreadyPresent(service.Image, images) {
-				s.events.On(progress.Event{
+				s.events.On(api.Resource{
 					ID:      "Image " + service.Image,
 					ID:      "Image " + service.Image,
-					Status:  progress.Done,
+					Status:  api.Done,
 					Text:    "Skipped",
 					Text:    "Skipped",
 					Details: "Image is already present locally",
 					Details: "Image is already present locally",
 				})
 				})
@@ -97,9 +96,9 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 		}
 		}
 
 
 		if service.Build != nil && opts.IgnoreBuildable {
 		if service.Build != nil && opts.IgnoreBuildable {
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:      "Image " + service.Image,
 				ID:      "Image " + service.Image,
-				Status:  progress.Done,
+				Status:  api.Done,
 				Text:    "Skipped",
 				Text:    "Skipped",
 				Details: "Image can be built",
 				Details: "Image can be built",
 			})
 			})
@@ -122,7 +121,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
 				}
 				}
 				if !opts.IgnoreFailures && service.Build == nil {
 				if !opts.IgnoreFailures && service.Build == nil {
 					if s.dryRun {
 					if s.dryRun {
-						s.events.On(progress.ErrorEventf("Image "+service.Image,
+						s.events.On(errorEventf("Image "+service.Image,
 							"error pulling image: %s", service.Image))
 							"error pulling image: %s", service.Image))
 					}
 					}
 					// fail fast if image can't be pulled nor built
 					// fail fast if image can't be pulled nor built
@@ -174,7 +173,7 @@ func getUnwrappedErrorMessage(err error) string {
 
 
 func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, quietPull bool, defaultPlatform string) (string, error) {
 func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, quietPull bool, defaultPlatform string) (string, error) {
 	resource := "Image " + service.Image
 	resource := "Image " + service.Image
-	s.events.On(progress.PullingEvent(service.Image))
+	s.events.On(pullingEvent(service.Image))
 	ref, err := reference.ParseNormalizedNamed(service.Image)
 	ref, err := reference.ParseNormalizedNamed(service.Image)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
@@ -196,9 +195,9 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
 	})
 	})
 
 
 	if ctx.Err() != nil {
 	if ctx.Err() != nil {
-		s.events.On(progress.Event{
+		s.events.On(api.Resource{
 			ID:     resource,
 			ID:     resource,
-			Status: progress.Warning,
+			Status: api.Warning,
 			Text:   "Interrupted",
 			Text:   "Interrupted",
 		})
 		})
 		return "", nil
 		return "", nil
@@ -207,16 +206,16 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
 	// check if has error and the service has a build section
 	// check if has error and the service has a build section
 	// then the status should be warning instead of error
 	// then the status should be warning instead of error
 	if err != nil && service.Build != nil {
 	if err != nil && service.Build != nil {
-		s.events.On(progress.Event{
+		s.events.On(api.Resource{
 			ID:     resource,
 			ID:     resource,
-			Status: progress.Warning,
+			Status: api.Warning,
 			Text:   getUnwrappedErrorMessage(err),
 			Text:   getUnwrappedErrorMessage(err),
 		})
 		})
 		return "", err
 		return "", err
 	}
 	}
 
 
 	if err != nil {
 	if err != nil {
-		s.events.On(progress.ErrorEvent(resource, getUnwrappedErrorMessage(err)))
+		s.events.On(errorEvent(resource, getUnwrappedErrorMessage(err)))
 		return "", err
 		return "", err
 	}
 	}
 
 
@@ -236,7 +235,7 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
 			toPullProgressEvent(resource, jm, s.events)
 			toPullProgressEvent(resource, jm, s.events)
 		}
 		}
 	}
 	}
-	s.events.On(progress.PulledEvent(service.Image))
+	s.events.On(pulledEvent(service.Image))
 
 
 	inspected, err := s.apiClient().ImageInspect(ctx, service.Image)
 	inspected, err := s.apiClient().ImageInspect(ctx, service.Image)
 	if err != nil {
 	if err != nil {
@@ -383,7 +382,7 @@ func isServiceImageToBuild(service types.ServiceConfig, services types.Services)
 
 
 const (
 const (
 	PreparingPhase         = "Preparing"
 	PreparingPhase         = "Preparing"
-	WaitingPhase           = "Waiting"
+	WaitingPhase           = "waiting"
 	PullingFsPhase         = "Pulling fs layer"
 	PullingFsPhase         = "Pulling fs layer"
 	DownloadingPhase       = "Downloading"
 	DownloadingPhase       = "Downloading"
 	DownloadCompletePhase  = "Download complete"
 	DownloadCompletePhase  = "Download complete"
@@ -393,7 +392,7 @@ const (
 	PullCompletePhase      = "Pull complete"
 	PullCompletePhase      = "Pull complete"
 )
 )
 
 
-func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events progress.EventProcessor) {
+func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events api.EventProcessor) {
 	if jm.ID == "" || jm.Progress == nil {
 	if jm.ID == "" || jm.Progress == nil {
 		return
 		return
 	}
 	}
@@ -403,7 +402,7 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events progr
 		total   int64
 		total   int64
 		percent int
 		percent int
 		current int64
 		current int64
-		status  = progress.Working
+		status  = api.Working
 	)
 	)
 
 
 	text = jm.Progress.String()
 	text = jm.Progress.String()
@@ -420,22 +419,22 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, events progr
 			}
 			}
 		}
 		}
 	case DownloadCompletePhase, AlreadyExistsPhase, PullCompletePhase:
 	case DownloadCompletePhase, AlreadyExistsPhase, PullCompletePhase:
-		status = progress.Done
+		status = api.Done
 		percent = 100
 		percent = 100
 	}
 	}
 
 
 	if strings.Contains(jm.Status, "Image is up to date") ||
 	if strings.Contains(jm.Status, "Image is up to date") ||
 		strings.Contains(jm.Status, "Downloaded newer image") {
 		strings.Contains(jm.Status, "Downloaded newer image") {
-		status = progress.Done
+		status = api.Done
 		percent = 100
 		percent = 100
 	}
 	}
 
 
 	if jm.Error != nil {
 	if jm.Error != nil {
-		status = progress.Error
+		status = api.Error
 		text = jm.Error.Message
 		text = jm.Error.Message
 	}
 	}
 
 
-	events.On(progress.Event{
+	events.On(api.Resource{
 		ID:       jm.ID,
 		ID:       jm.ID,
 		ParentID: parent,
 		ParentID: parent,
 		Current:  current,
 		Current:  current,

+ 12 - 13
pkg/compose/push.go

@@ -33,14 +33,13 @@ import (
 
 
 	"github.com/docker/compose/v2/internal/registry"
 	"github.com/docker/compose/v2/internal/registry"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 func (s *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error {
 func (s *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error {
 	if options.Quiet {
 	if options.Quiet {
 		return s.push(ctx, project, options)
 		return s.push(ctx, project, options)
 	}
 	}
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.push(ctx, project, options)
 		return s.push(ctx, project, options)
 	}, "push", s.events)
 	}, "push", s.events)
 }
 }
@@ -54,9 +53,9 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
 			if options.ImageMandatory && service.Image == "" && service.Provider == nil {
 			if options.ImageMandatory && service.Image == "" && service.Provider == nil {
 				return fmt.Errorf("%q attribute is mandatory to push an image for service %q", "service.image", service.Name)
 				return fmt.Errorf("%q attribute is mandatory to push an image for service %q", "service.image", service.Name)
 			}
 			}
-			s.events.On(progress.Event{
+			s.events.On(api.Resource{
 				ID:     service.Name,
 				ID:     service.Name,
-				Status: progress.Done,
+				Status: api.Done,
 				Text:   "Skipped",
 				Text:   "Skipped",
 			})
 			})
 			continue
 			continue
@@ -68,16 +67,16 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
 
 
 		for _, tag := range tags {
 		for _, tag := range tags {
 			eg.Go(func() error {
 			eg.Go(func() error {
-				s.events.On(progress.NewEvent(tag, progress.Working, "Pushing"))
+				s.events.On(newEvent(tag, api.Working, "Pushing"))
 				err := s.pushServiceImage(ctx, tag, options.Quiet)
 				err := s.pushServiceImage(ctx, tag, options.Quiet)
 				if err != nil {
 				if err != nil {
 					if !options.IgnoreFailures {
 					if !options.IgnoreFailures {
-						s.events.On(progress.NewEvent(tag, progress.Error, err.Error()))
+						s.events.On(newEvent(tag, api.Error, err.Error()))
 						return err
 						return err
 					}
 					}
-					s.events.On(progress.NewEvent(tag, progress.Warning, err.Error()))
+					s.events.On(newEvent(tag, api.Warning, err.Error()))
 				} else {
 				} else {
-					s.events.On(progress.NewEvent(tag, progress.Done, "Pushed"))
+					s.events.On(newEvent(tag, api.Done, "Pushed"))
 				}
 				}
 				return nil
 				return nil
 			})
 			})
@@ -129,24 +128,24 @@ func (s *composeService) pushServiceImage(ctx context.Context, tag string, quiet
 	return nil
 	return nil
 }
 }
 
 
-func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, events progress.EventProcessor) {
+func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, events api.EventProcessor) {
 	if jm.ID == "" {
 	if jm.ID == "" {
 		// skipped
 		// skipped
 		return
 		return
 	}
 	}
 	var (
 	var (
 		text    string
 		text    string
-		status  = progress.Working
+		status  = api.Working
 		total   int64
 		total   int64
 		current int64
 		current int64
 		percent int
 		percent int
 	)
 	)
 	if isDone(jm) {
 	if isDone(jm) {
-		status = progress.Done
+		status = api.Done
 		percent = 100
 		percent = 100
 	}
 	}
 	if jm.Error != nil {
 	if jm.Error != nil {
-		status = progress.Error
+		status = api.Error
 		text = jm.Error.Message
 		text = jm.Error.Message
 	}
 	}
 	if jm.Progress != nil {
 	if jm.Progress != nil {
@@ -160,7 +159,7 @@ func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, events progr
 		}
 		}
 	}
 	}
 
 
-	events.On(progress.Event{
+	events.On(api.Resource{
 		ParentID: prefix,
 		ParentID: prefix,
 		ID:       jm.ID,
 		ID:       jm.ID,
 		Text:     text,
 		Text:     text,

+ 4 - 7
pkg/compose/remove.go

@@ -24,8 +24,6 @@ import (
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
-
-	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error {
 func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error {
@@ -76,8 +74,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
 	})
 	})
 
 
 	if len(names) == 0 {
 	if len(names) == 0 {
-		_, _ = fmt.Fprintln(s.stdinfo(), "No stopped containers")
-		return nil
+		return api.ErrNoResources
 	}
 	}
 
 
 	msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
 	msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
@@ -92,7 +89,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
 			return nil
 			return nil
 		}
 		}
 	}
 	}
-	return progress.Run(ctx, func(ctx context.Context) error {
+	return Run(ctx, func(ctx context.Context) error {
 		return s.remove(ctx, stoppedContainers, options)
 		return s.remove(ctx, stoppedContainers, options)
 	}, "remove", s.events)
 	}, "remove", s.events)
 }
 }
@@ -102,13 +99,13 @@ func (s *composeService) remove(ctx context.Context, containers Containers, opti
 	for _, ctr := range containers {
 	for _, ctr := range containers {
 		eg.Go(func() error {
 		eg.Go(func() error {
 			eventName := getContainerProgressName(ctr)
 			eventName := getContainerProgressName(ctr)
-			s.events.On(progress.RemovingEvent(eventName))
+			s.events.On(removingEvent(eventName))
 			err := s.apiClient().ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
 			err := s.apiClient().ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
 				RemoveVolumes: options.Volumes,
 				RemoveVolumes: options.Volumes,
 				Force:         options.Force,
 				Force:         options.Force,
 			})
 			})
 			if err == nil {
 			if err == nil {
-				s.events.On(progress.RemovedEvent(eventName))
+				s.events.On(removedEvent(eventName))
 			}
 			}
 			return err
 			return err
 		})
 		})

+ 3 - 4
pkg/compose/restart.go

@@ -22,14 +22,13 @@ import (
 
 
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 )
 )
 
 
 func (s *composeService) Restart(ctx context.Context, projectName string, 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 Run(ctx, func(ctx context.Context) error {
 		return s.restart(ctx, strings.ToLower(projectName), options)
 		return s.restart(ctx, strings.ToLower(projectName), options)
 	}, "restart", s.events)
 	}, "restart", s.events)
 }
 }
@@ -93,13 +92,13 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
 					}
 					}
 				}
 				}
 				eventName := getContainerProgressName(ctr)
 				eventName := getContainerProgressName(ctr)
-				s.events.On(progress.RestartingEvent(eventName))
+				s.events.On(restartingEvent(eventName))
 				timeout := utils.DurationSecondToInt(options.Timeout)
 				timeout := utils.DurationSecondToInt(options.Timeout)
 				err = s.apiClient().ContainerRestart(ctx, ctr.ID, container.StopOptions{Timeout: timeout})
 				err = s.apiClient().ContainerRestart(ctx, ctr.ID, container.StopOptions{Timeout: timeout})
 				if err != nil {
 				if err != nil {
 					return err
 					return err
 				}
 				}
-				s.events.On(progress.StartedEvent(eventName))
+				s.events.On(startedEvent(eventName))
 				for _, hook := range def.PostStart {
 				for _, hook := range def.PostStart {
 					err = s.runHook(ctx, ctr, def, hook, nil)
 					err = s.runHook(ctx, ctr, def, hook, nil)
 					if err != nil {
 					if err != nil {

+ 1 - 2
pkg/compose/run.go

@@ -28,7 +28,6 @@ import (
 	"github.com/docker/cli/cli"
 	"github.com/docker/cli/cli"
 	cmd "github.com/docker/cli/cli/command/container"
 	cmd "github.com/docker/cli/cli/command/container"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
 )
 )
 
 
@@ -65,7 +64,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
 		return "", err
 		return "", err
 	}
 	}
 
 
-	err = progress.Run(ctx, func(ctx context.Context) error {
+	err = Run(ctx, func(ctx context.Context) error {
 		return s.startDependencies(ctx, project, opts)
 		return s.startDependencies(ctx, project, opts)
 	}, "run", s.events)
 	}, "run", s.events)
 	if err != nil {
 	if err != nil {

+ 1 - 2
pkg/compose/scale.go

@@ -21,11 +21,10 @@ import (
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 func (s *composeService) Scale(ctx context.Context, project *types.Project, options api.ScaleOptions) error {
 func (s *composeService) Scale(ctx context.Context, project *types.Project, options api.ScaleOptions) error {
-	return progress.Run(ctx, tracing.SpanWrapFunc("project/scale", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error {
+	return Run(ctx, tracing.SpanWrapFunc("project/scale", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error {
 		err := s.create(ctx, project, api.CreateOptions{Services: options.Services})
 		err := s.create(ctx, project, api.CreateOptions{Services: options.Services})
 		if err != nil {
 		if err != nil {
 			return err
 			return err

+ 1 - 2
pkg/compose/start.go

@@ -23,7 +23,6 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	containerType "github.com/docker/docker/api/types/container"
 	containerType "github.com/docker/docker/api/types/container"
 
 
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
@@ -31,7 +30,7 @@ import (
 )
 )
 
 
 func (s *composeService) Start(ctx context.Context, projectName string, 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 Run(ctx, func(ctx context.Context) error {
 		return s.start(ctx, strings.ToLower(projectName), options, nil)
 		return s.start(ctx, strings.ToLower(projectName), options, nil)
 	}, "start", s.events)
 	}, "start", s.events)
 }
 }

+ 1 - 2
pkg/compose/stop.go

@@ -22,11 +22,10 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 )
 )
 
 
 func (s *composeService) Stop(ctx context.Context, projectName string, 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 Run(ctx, func(ctx context.Context) error {
 		return s.stop(ctx, strings.ToLower(projectName), options, nil)
 		return s.stop(ctx, strings.ToLower(projectName), options, nil)
 	}, "stop", s.events)
 	}, "stop", s.events)
 }
 }

+ 4 - 5
pkg/compose/up.go

@@ -33,14 +33,13 @@ import (
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/docker/compose/v2/cmd/formatter"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	"github.com/eiannone/keyboard"
 	"github.com/eiannone/keyboard"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 )
 )
 
 
 func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { //nolint:gocyclo
 func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { //nolint:gocyclo
-	err := progress.Run(ctx, tracing.SpanWrapFunc("project/up", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error {
+	err := Run(ctx, tracing.SpanWrapFunc("project/up", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error {
 		err := s.create(ctx, project, options.Create)
 		err := s.create(ctx, project, options.Create)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -126,7 +125,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 		first := true
 		first := true
 		gracefulTeardown := func() {
 		gracefulTeardown := func() {
 			first = false
 			first = false
-			fmt.Println("Gracefully Stopping... press Ctrl+C again to force")
+			s.events.On(newEvent(api.ResourceCompose, api.Working, api.StatusStopping, "Gracefully Stopping... press Ctrl+C again to force"))
 			eg.Go(func() error {
 			eg.Go(func() error {
 				err = s.stop(context.WithoutCancel(globalCtx), project.Name, api.StopOptions{
 				err = s.stop(context.WithoutCancel(globalCtx), project.Name, api.StopOptions{
 					Services: options.Create.Services,
 					Services: options.Create.Services,
@@ -162,7 +161,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 						All:      true,
 						All:      true,
 					})
 					})
 					// Ignore errors indicating that some of the containers were already stopped or removed.
 					// Ignore errors indicating that some of the containers were already stopped or removed.
-					if errdefs.IsNotFound(err) || errdefs.IsConflict(err) {
+					if errdefs.IsNotFound(err) || errdefs.IsConflict(err) || errors.Is(err, api.ErrNoResources) {
 						return nil
 						return nil
 					}
 					}
 
 
@@ -205,7 +204,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 				}
 				}
 				once = false
 				once = false
 				exitCode = event.ExitCode
 				exitCode = event.ExitCode
-				_, _ = fmt.Fprintln(s.stdinfo(), progress.ErrorColor("Aborting on container exit..."))
+				s.events.On(newEvent(api.ResourceCompose, api.Working, api.StatusStopping, "Aborting on container exit..."))
 				eg.Go(func() error {
 				eg.Go(func() error {
 					err = s.stop(context.WithoutCancel(globalCtx), project.Name, api.StopOptions{
 					err = s.stop(context.WithoutCancel(globalCtx), project.Name, api.StopOptions{
 						Services: options.Create.Services,
 						Services: options.Create.Services,

+ 3 - 3
pkg/compose/watch.go

@@ -33,9 +33,9 @@ import (
 	"github.com/docker/compose/v2/internal/sync"
 	"github.com/docker/compose/v2/internal/sync"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/internal/tracing"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/docker/compose/v2/pkg/progress"
 	cutils "github.com/docker/compose/v2/pkg/utils"
 	cutils "github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/watch"
 	"github.com/docker/compose/v2/pkg/watch"
+	"github.com/moby/buildkit/util/progress/progressui"
 
 
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/types"
 	"github.com/compose-spec/compose-go/v2/utils"
 	"github.com/compose-spec/compose-go/v2/utils"
@@ -472,7 +472,7 @@ func (t tarDockerClient) Exec(ctx context.Context, containerID string, cmd []str
 		})
 		})
 	}
 	}
 	eg.Go(func() error {
 	eg.Go(func() error {
-		_, err := io.Copy(t.s.stdinfo(), conn.Reader)
+		_, err := io.Copy(t.s.stdout(), conn.Reader)
 		return err
 		return err
 	})
 	})
 
 
@@ -613,7 +613,7 @@ func (s *composeService) rebuild(ctx context.Context, project *types.Project, se
 	options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service(s) %q after changes were detected...", services))
 	options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service(s) %q after changes were detected...", services))
 	// restrict the build to ONLY this service, not any of its dependencies
 	// restrict the build to ONLY this service, not any of its dependencies
 	options.Build.Services = services
 	options.Build.Services = services
-	options.Build.Progress = progress.ModePlain
+	options.Build.Progress = string(progressui.PlainMode)
 	options.Build.Out = cutils.GetWriter(func(line string) {
 	options.Build.Out = cutils.GetWriter(func(line string) {
 		options.LogTo.Log(api.WatchLogger, line)
 		options.LogTo.Log(api.WatchLogger, line)
 	})
 	})

+ 0 - 234
pkg/progress/event.go

@@ -1,234 +0,0 @@
-/*
-   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 progress
-
-import (
-	"context"
-	"fmt"
-)
-
-// EventStatus indicates the status of an action
-type EventStatus int
-
-const (
-	// Working means that the current task is working
-	Working EventStatus = iota
-	// Done means that the current task is done
-	Done
-	// Warning means that the current task has warning
-	Warning
-	// Error means that the current task has errored
-	Error
-)
-
-const (
-	StatusError      = "Error"
-	StatusCreating   = "Creating"
-	StatusStarting   = "Starting"
-	StatusStarted    = "Started"
-	StatusWaiting    = "Waiting"
-	StatusHealthy    = "Healthy"
-	StatusExited     = "Exited"
-	StatusRestarting = "Restarting"
-	StatusRestarted  = "Restarted"
-	StatusRunning    = "Running"
-	StatusCreated    = "Created"
-	StatusStopping   = "Stopping"
-	StatusStopped    = "Stopped"
-	StatusKilling    = "Killing"
-	StatusKilled     = "Killed"
-	StatusRemoving   = "Removing"
-	StatusRemoved    = "Removed"
-	StatusBuilding   = "Building"
-	StatusBuilt      = "Built"
-	StatusPulling    = "Pulling"
-	StatusPulled     = "Pulled"
-	StatusCommitting = "Committing"
-	StatusCommitted  = "Committed"
-	StatusCopying    = "Copying"
-	StatusCopied     = "Copied"
-	StatusExporting  = "Exporting"
-	StatusExported   = "Exported"
-)
-
-// Event represents a progress event.
-type Event struct {
-	ID       string
-	ParentID string
-	Text     string
-	Details  string
-	Status   EventStatus
-	Current  int64
-	Percent  int
-	Total    int64
-}
-
-func (e *Event) StatusText() string {
-	switch e.Status {
-	case Working:
-		return "Working"
-	case Warning:
-		return "Warning"
-	case Done:
-		return "Done"
-	default:
-		return "Error"
-	}
-}
-
-// ErrorEvent creates a new Error Event with message
-func ErrorEvent(id string, msg string) Event {
-	return Event{
-		ID:      id,
-		Status:  Error,
-		Text:    StatusError,
-		Details: msg,
-	}
-}
-
-// ErrorEventf creates a new Error Event with format message
-func ErrorEventf(id string, msg string, args ...any) Event {
-	return ErrorEvent(id, fmt.Sprintf(msg, args...))
-}
-
-// CreatingEvent creates a new Create in progress Event
-func CreatingEvent(id string) Event {
-	return NewEvent(id, Working, StatusCreating)
-}
-
-// StartingEvent creates a new Starting in progress Event
-func StartingEvent(id string) Event {
-	return NewEvent(id, Working, StatusStarting)
-}
-
-// StartedEvent creates a new Started in progress Event
-func StartedEvent(id string) Event {
-	return NewEvent(id, Done, StatusStarted)
-}
-
-// Waiting creates a new waiting event
-func Waiting(id string) Event {
-	return NewEvent(id, Working, StatusWaiting)
-}
-
-// Healthy creates a new healthy event
-func Healthy(id string) Event {
-	return NewEvent(id, Done, StatusHealthy)
-}
-
-// Exited creates a new exited event
-func Exited(id string) Event {
-	return NewEvent(id, Done, StatusExited)
-}
-
-// RestartingEvent creates a new Restarting in progress Event
-func RestartingEvent(id string) Event {
-	return NewEvent(id, Working, StatusRestarting)
-}
-
-// RestartedEvent creates a new Restarted in progress Event
-func RestartedEvent(id string) Event {
-	return NewEvent(id, Done, StatusRestarted)
-}
-
-// RunningEvent creates a new Running in progress Event
-func RunningEvent(id string) Event {
-	return NewEvent(id, Done, StatusRunning)
-}
-
-// CreatedEvent creates a new Created (done) Event
-func CreatedEvent(id string) Event {
-	return NewEvent(id, Done, StatusCreated)
-}
-
-// StoppingEvent creates a new Stopping in progress Event
-func StoppingEvent(id string) Event {
-	return NewEvent(id, Working, StatusStopping)
-}
-
-// StoppedEvent creates a new Stopping in progress Event
-func StoppedEvent(id string) Event {
-	return NewEvent(id, Done, StatusStopped)
-}
-
-// KillingEvent creates a new Killing in progress Event
-func KillingEvent(id string) Event {
-	return NewEvent(id, Working, StatusKilling)
-}
-
-// KilledEvent creates a new Killed in progress Event
-func KilledEvent(id string) Event {
-	return NewEvent(id, Done, StatusKilled)
-}
-
-// RemovingEvent creates a new Removing in progress Event
-func RemovingEvent(id string) Event {
-	return NewEvent(id, Working, StatusRemoving)
-}
-
-// RemovedEvent creates a new removed (done) Event
-func RemovedEvent(id string) Event {
-	return NewEvent(id, Done, StatusRemoved)
-}
-
-// BuildingEvent creates a new Building in progress Event
-func BuildingEvent(id string) Event {
-	return NewEvent("Image "+id, Working, StatusBuilding)
-}
-
-// BuiltEvent creates a new built (done) Event
-func BuiltEvent(id string) Event {
-	return NewEvent("Image "+id, Done, StatusBuilt)
-}
-
-// PullingEvent creates a new pulling (in progress) Event
-func PullingEvent(id string) Event {
-	return NewEvent("Image "+id, Working, StatusPulling)
-}
-
-// PulledEvent creates a new pulled (done) Event
-func PulledEvent(id string) Event {
-	return NewEvent("Image "+id, Done, StatusPulled)
-}
-
-// SkippedEvent creates a new Skipped Event
-func SkippedEvent(id string, reason string) Event {
-	return Event{
-		ID:     id,
-		Status: Warning,
-		Text:   "Skipped: " + reason,
-	}
-}
-
-// NewEvent new event
-func NewEvent(id string, status EventStatus, text string) Event {
-	return Event{
-		ID:     id,
-		Status: status,
-		Text:   text,
-	}
-}
-
-// EventProcessor is notified about Compose operations and tasks
-type EventProcessor interface {
-	// Start is triggered as a Compose operation is starting with context
-	Start(ctx context.Context, operation string)
-	// On notify about (sub)task and progress processing operation
-	On(events ...Event)
-	// Done is triggered as a Compose operation completed
-	Done(operation string, success bool)
-}